diff --git a/.github/workflows/deploy-stage.yaml b/.github/workflows/deploy-stage.yaml
new file mode 100644
index 0000000..6819f88
--- /dev/null
+++ b/.github/workflows/deploy-stage.yaml
@@ -0,0 +1,212 @@
+name: Deploy connector to dockerhub, release cli on github
+on:
+  push:
+    branches:
+      - main
+    tags:
+      - 'v*'
+
+# Defines two custom environment variables for the workflow. These are used for the Container registry domain, and a name for the Docker image that this workflow builds.
+env:
+  REGISTRY: ghcr.io
+  IMAGE_NAME: ${{ github.repository }}
+
+jobs:
+  build-and-push-image:
+    runs-on: ubuntu-latest
+    # Sets the permissions granted to the `GITHUB_TOKEN` for the actions in this job.
+    permissions:
+      contents: read
+      packages: write
+    steps:
+      - name: Checkout repository
+        uses: actions/checkout@v4
+      # Uses the `docker/login-action` action to log in to the Container registry registry using the account and password that will publish the packages. Once published, the packages are scoped to the account defined here.
+      - name: Log in to the Container registry
+        uses: docker/login-action@v3
+        with:
+          registry: ${{ env.REGISTRY }}
+          username: ${{ github.actor }}
+          password: ${{ secrets.GITHUB_TOKEN }}
+      # This step uses [docker/metadata-action](https://github.com/docker/metadata-action#about) to extract tags and labels that will be applied to the specified image. The `id` "meta" allows the output of this step to be referenced in a subsequent step. The `images` value provides the base name for the tags and labels.
+      - name: Extract metadata (tags, labels) for Docker
+        id: meta
+        uses: docker/metadata-action@v5
+        with:
+          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
+      # This step uses the `docker/build-push-action` action to build the image, based on your repository's `Dockerfile`. If the build succeeds, it pushes the image to GitHub Packages.
+      # It uses the `context` parameter to define the build's context as the set of files located in the specified path. For more information, see "[Usage](https://github.com/docker/build-push-action#usage)" in the README of the `docker/build-push-action` repository.
+      # It uses the `tags` and `labels` parameters to tag and label the image with the output from the "meta" step.
+      - name: Build and push Docker image
+        uses: docker/build-push-action@v5
+        with:
+          context: .
+          push: true
+          tags: ${{ steps.meta.outputs.tags }}
+          labels: ${{ steps.meta.outputs.labels }}
+
+  build-cli-binaries:
+    name: build the CLI binaries
+    strategy:
+      matrix:
+        include:
+          - runner: ubuntu-latest
+            target: x86_64-unknown-linux-musl
+            rustflags: -C target-feature=+crt-static
+            linux-packages: musl-tools
+          - runner: ubuntu-latest
+            target: aarch64-unknown-linux-musl
+            rustflags: -C target-feature=+crt-static
+            linux-packages: gcc-aarch64-linux-gnu musl-tools
+            linker: /usr/bin/aarch64-linux-gnu-gcc
+          - runner: macos-latest
+            target: x86_64-apple-darwin
+          - runner: macos-latest
+            target: aarch64-apple-darwin
+          - runner: windows-latest
+            target: x86_64-pc-windows-msvc
+            rustflags: -C target-feature=+crt-static
+            extension: .exe
+    runs-on: ${{ matrix.runner }}
+    env:
+      CARGO_BUILD_TARGET: ${{ matrix.target }}
+      CARGO_NET_GIT_FETCH_WITH_CLI: "true"
+      RUSTFLAGS: "-D warnings ${{ matrix.rustflags }}"
+    defaults:
+      run:
+        shell: bash
+    steps:
+      - uses: actions/checkout@v4
+
+      - name: install protoc
+        uses: arduino/setup-protoc@v3
+        with:
+          version: "25.x"
+          repo-token: ${{ secrets.GITHUB_TOKEN }}
+
+      - name: install tools
+        run: |
+          rustup show
+          rustup target add ${{ matrix.target }}
+
+      - name: install other packages required
+        if: matrix.linux-packages
+        run: |
+          sudo apt-get update
+          sudo apt-get install -y ${{ matrix.linux-packages }}
+
+      - uses: Swatinem/rust-cache@v2
+        with:
+          shared-key: "build" # share the cache across jobs
+
+      - name: build the CLI
+        run: |
+          # If we're on a tag, use the tag name as the release version.
+          if [[ "$GITHUB_REF_TYPE" == 'tag' ]]; then
+            # Ensure that the version specified in Cargo.toml is the same as the tag (with a 'v' prefix).
+            CARGO_VERSION="$(cargo metadata --format-version=1 | jq -r '.packages | .[] | select(.name == "ndc-clickhouse-cli") | .version')"
+            echo "Git tag: ${GITHUB_REF_NAME}"
+            echo "Cargo version: ${CARGO_VERSION}"
+
+            if [[ "${GITHUB_REF_NAME}" != "v${CARGO_VERSION}" ]]; then
+              echo >&2 "The Git tag is \"${GITHUB_REF_NAME}\", but the version in Cargo.toml is \"${CARGO_VERSION}\"."
+              echo >&2 'These must be the same, with a "v" prefix for the tag. Aborting.'
+              exit 1
+            fi
+            export RELEASE_VERSION="$GITHUB_REF_NAME"
+            echo "RELEASE_VERSION = ${RELEASE_VERSION}"
+          fi
+
+          if [[ -n '${{ matrix.linker }}' ]]; then
+            TARGET_SCREAMING="$(echo '${{ matrix.target }}' | tr '[:lower:]' '[:upper:]' | tr '-' '_')"
+            echo "CARGO_TARGET_${TARGET_SCREAMING}_LINKER"='${{ matrix.linker }}'
+            declare "CARGO_TARGET_${TARGET_SCREAMING}_LINKER"='${{ matrix.linker }}'
+            export "CARGO_TARGET_${TARGET_SCREAMING}_LINKER"
+          fi
+
+          echo "Building for target: ${CARGO_BUILD_TARGET}"
+          cargo build --release --package ndc-clickhouse-cli
+
+          mkdir -p release
+          mv -v target/${{ matrix.target }}/release/ndc-clickhouse-cli release/ndc-clickhouse-cli-${{ matrix.target }}${{ matrix.extension }}
+
+      - uses: actions/upload-artifact@v4
+        with:
+          name: ndc-clickhouse-cli-${{ matrix.target }}${{ matrix.extension }}
+          path: release
+          if-no-files-found: error
+
+  release:
+    name: release to GitHub
+    needs:
+      - build-and-push-image
+      - build-cli-binaries
+    runs-on: ubuntu-latest
+    if: ${{ startsWith(github.ref, 'refs/tags/v') }}
+    steps:
+      - uses: actions/checkout@v4
+
+      - uses: actions/download-artifact@v4
+        with:
+          path: release/artifacts
+          merge-multiple: true
+
+      - name: generate CLI manifest
+        run: |
+          set -evo pipefail
+          ROOT="$(pwd)"
+
+          export CLI_VERSION="$GITHUB_REF_NAME"
+
+          export LINUX_AMD64_SHA256=$(sha256sum ${ROOT}/release/artifacts/ndc-clickhouse-cli-x86_64-unknown-linux-musl     | cut -f1 -d' ')
+          export MACOS_AMD64_SHA256=$(sha256sum ${ROOT}/release/artifacts/ndc-clickhouse-cli-x86_64-apple-darwin           | cut -f1 -d' ')
+          export WINDOWS_AMD64_SHA256=$(sha256sum ${ROOT}/release/artifacts/ndc-clickhouse-cli-x86_64-pc-windows-msvc.exe  | cut -f1 -d' ')
+          export LINUX_ARM64_SHA256=$(sha256sum ${ROOT}/release/artifacts/ndc-clickhouse-cli-aarch64-unknown-linux-musl    | cut -f1 -d' ')
+          export MACOS_ARM64_SHA256=$(sha256sum ${ROOT}/release/artifacts/ndc-clickhouse-cli-aarch64-apple-darwin          | cut -f1 -d' ')
+
+          mkdir -p "${ROOT}/release/"
+          cat "${ROOT}/ci/templates/manifest.yaml" | envsubst > "${ROOT}/release/manifest.yaml"
+      
+      - uses: actions/upload-artifact@v4
+        with:
+          name: manifest.yaml
+          path: release/manifest.yaml
+          if-no-files-found: error
+
+      - name: Build connector definition
+        run: |
+          set -evo pipefail
+          ROOT="$(pwd)"
+          
+          export DOCKER_IMAGE="ghcr.io/hasura/ndc-clickhouse:$GITHUB_REF_NAME"
+          export CLI_VERSION=$GITHUB_REF_NAME
+
+          mkdir -p "${ROOT}/release/connector-definition/.hasura-connector/"
+          cat "${ROOT}/ci/templates/connector-metadata.yaml" | envsubst > "${ROOT}/release/connector-definition/.hasura-connector/connector-metadata.yaml"
+          tar -czvf "${ROOT}/release/artifacts/connector-definition.tgz" "${ROOT}/release/connector-definition/"
+
+      - uses: actions/upload-artifact@v4
+        with:
+          name: connector-definition.tgz
+          path: ./release/artifacts/connector-definition.tgz
+          compression-level: 0 # Already compressed
+
+      - name: Get version from tag
+        id: get-version
+        run: |
+          echo "tagged_version=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT
+        shell: bash
+
+      - uses: mindsers/changelog-reader-action@v2
+        id: changelog-reader
+        with:
+          version: ${{ steps.get-version.outputs.tagged_version }}
+          path: ./CHANGELOG.md
+
+      - name: create a draft release
+        uses: ncipollo/release-action@v1
+        with:
+          draft: true
+          tag: v${{ steps.get-version.outputs.tagged_version }}
+          body: ${{ steps.changelog-reader.outputs.changes }}
+          artifacts: release/artifacts/*
\ No newline at end of file
diff --git a/.github/workflows/push-docker-image.yaml b/.github/workflows/push-docker-image.yaml
deleted file mode 100644
index 47a6cf0..0000000
--- a/.github/workflows/push-docker-image.yaml
+++ /dev/null
@@ -1,41 +0,0 @@
-name: build and push docker image
-
-# Build and push a Docker image used by `v3-e2e-testing`.
-# This is pushed to the internal Github Container Registry.
-
-on:
-  push:
-    branches:
-      - main
-
-jobs:
-  docker:
-    runs-on: ubuntu-latest
-    steps:
-      - name: Checkout
-        uses: actions/checkout@v4
-
-      - name: Set up QEMU
-        uses: docker/setup-qemu-action@v3
-
-      - name: Set up Docker Buildx
-        uses: docker/setup-buildx-action@v3
-
-      - name: Login to GitHub Container Registry 📦
-        uses: docker/login-action@v3
-        with:
-          registry: ghcr.io
-          username: ${{ github.actor }}
-          password: ${{ secrets.GITHUB_TOKEN }}
-
-      - uses: satackey/action-docker-layer-caching@v0.0.11
-        continue-on-error: true
-
-      - name: Build and push
-        uses: docker/build-push-action@v5
-        with:
-          context: .
-          platforms: linux/amd64, linux/arm64
-          push: true
-          tags: ghcr.io/hasura/ndc-clickhouse:latest, ghcr.io/hasura/ndc-clickhouse:${{ github.sha }},
-
diff --git a/.gitignore b/.gitignore
index 40d9aca..a8ae2fd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,4 @@
 /target
-/.idea
\ No newline at end of file
+/.idea
+.env
+config
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..239961a
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,18 @@
+# Changelog
+
+All notable changes to this project will be documented in this file.
+
+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
+and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+
+## [Unreleased]
+
+## [0.2.0]
+
+- DDN Beta release
+- add cli plugin
+- remove configuration server mode
+
+## [0.1.1]
+
+- DDN Alpha Release
diff --git a/Cargo.lock b/Cargo.lock
index dcda308..d176145 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -43,9 +43,9 @@ dependencies = [
 
 [[package]]
 name = "anstream"
-version = "0.6.4"
+version = "0.6.13"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44"
+checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb"
 dependencies = [
  "anstyle",
  "anstyle-parse",
@@ -57,53 +57,53 @@ dependencies = [
 
 [[package]]
 name = "anstyle"
-version = "1.0.4"
+version = "1.0.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87"
+checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc"
 
 [[package]]
 name = "anstyle-parse"
-version = "0.2.2"
+version = "0.2.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "317b9a89c1868f5ea6ff1d9539a69f45dffc21ce321ac1fd1160dfa48c8e2140"
+checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c"
 dependencies = [
  "utf8parse",
 ]
 
 [[package]]
 name = "anstyle-query"
-version = "1.0.0"
+version = "1.0.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b"
+checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648"
 dependencies = [
- "windows-sys",
+ "windows-sys 0.52.0",
 ]
 
 [[package]]
 name = "anstyle-wincon"
-version = "3.0.1"
+version = "3.0.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628"
+checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7"
 dependencies = [
  "anstyle",
- "windows-sys",
+ "windows-sys 0.52.0",
 ]
 
 [[package]]
 name = "anyhow"
-version = "1.0.75"
+version = "1.0.81"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
+checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247"
 
 [[package]]
 name = "async-trait"
-version = "0.1.74"
+version = "0.1.78"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9"
+checksum = "461abc97219de0eaaf81fe3ef974a540158f3d079c2ab200f891f1a2ef201e85"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.39",
+ "syn 2.0.53",
 ]
 
 [[package]]
@@ -205,24 +205,9 @@ checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
 
 [[package]]
 name = "base64"
-version = "0.21.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9"
-
-[[package]]
-name = "bit-set"
-version = "0.5.3"
+version = "0.21.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1"
-dependencies = [
- "bit-vec",
-]
-
-[[package]]
-name = "bit-vec"
-version = "0.6.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb"
+checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
 
 [[package]]
 name = "bitflags"
@@ -232,15 +217,15 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
 
 [[package]]
 name = "bitflags"
-version = "2.4.1"
+version = "2.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07"
+checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1"
 
 [[package]]
 name = "bumpalo"
-version = "3.14.0"
+version = "3.15.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec"
+checksum = "7ff69b9dd49fd426c69a0db9fc04dd934cdb6645ff000864d98f7e2af8830eaa"
 
 [[package]]
 name = "bytes"
@@ -250,12 +235,9 @@ checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223"
 
 [[package]]
 name = "cc"
-version = "1.0.83"
+version = "1.0.90"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0"
-dependencies = [
- "libc",
-]
+checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5"
 
 [[package]]
 name = "cfg-if"
@@ -265,22 +247,22 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
 
 [[package]]
 name = "chrono"
-version = "0.4.31"
+version = "0.4.35"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38"
+checksum = "8eaf5903dcbc0a39312feb77df2ff4c76387d591b9fc7b04a238dcf8bb62639a"
 dependencies = [
  "android-tzdata",
  "iana-time-zone",
  "num-traits",
  "serde",
- "windows-targets",
+ "windows-targets 0.52.4",
 ]
 
 [[package]]
 name = "clap"
-version = "4.4.8"
+version = "4.5.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2275f18819641850fa26c89acc84d465c1bf91ce57bc2748b28c420473352f64"
+checksum = "949626d00e063efc93b6dca932419ceb5432f99769911c0b995f7e884c778813"
 dependencies = [
  "clap_builder",
  "clap_derive",
@@ -288,33 +270,43 @@ dependencies = [
 
 [[package]]
 name = "clap_builder"
-version = "4.4.8"
+version = "4.5.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "07cdf1b148b25c1e1f7a42225e30a0d99a615cd4637eae7365548dd4529b95bc"
+checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4"
 dependencies = [
  "anstream",
  "anstyle",
  "clap_lex",
- "strsim",
+ "strsim 0.11.0",
 ]
 
 [[package]]
 name = "clap_derive"
-version = "4.4.7"
+version = "4.5.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442"
+checksum = "90239a040c80f5e14809ca132ddc4176ab33d5e17e49691793296e3fcb34d72f"
 dependencies = [
- "heck",
+ "heck 0.5.0",
  "proc-macro2",
  "quote",
- "syn 2.0.39",
+ "syn 2.0.53",
 ]
 
 [[package]]
 name = "clap_lex"
-version = "0.6.0"
+version = "0.7.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1"
+checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce"
+
+[[package]]
+name = "client"
+version = "0.2.0"
+dependencies = [
+ "config",
+ "reqwest",
+ "serde",
+ "serde_json",
+]
 
 [[package]]
 name = "colorchoice"
@@ -324,20 +316,27 @@ checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
 
 [[package]]
 name = "colored"
-version = "2.0.4"
+version = "2.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2674ec482fbc38012cf31e6c42ba0177b431a0cb6f15fe40efa5aab1bda516f6"
+checksum = "cbf2150cce219b664a8a70df7a1f933836724b503f8a413af9365b4dcc4d90b8"
 dependencies = [
- "is-terminal",
  "lazy_static",
- "windows-sys",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "config"
+version = "0.2.0"
+dependencies = [
+ "schemars",
+ "serde",
 ]
 
 [[package]]
 name = "core-foundation"
-version = "0.9.3"
+version = "0.9.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146"
+checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
 dependencies = [
  "core-foundation-sys",
  "libc",
@@ -345,28 +344,24 @@ dependencies = [
 
 [[package]]
 name = "core-foundation-sys"
-version = "0.8.4"
+version = "0.8.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa"
+checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f"
 
 [[package]]
 name = "crossbeam-channel"
-version = "0.5.8"
+version = "0.5.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200"
+checksum = "ab3db02a9c5b5121e1e42fbdb1aeb65f5e02624cc58c43f2884c6ccac0b82f95"
 dependencies = [
- "cfg-if",
  "crossbeam-utils",
 ]
 
 [[package]]
 name = "crossbeam-utils"
-version = "0.8.16"
+version = "0.8.19"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294"
-dependencies = [
- "cfg-if",
-]
+checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345"
 
 [[package]]
 name = "darling"
@@ -380,12 +375,12 @@ dependencies = [
 
 [[package]]
 name = "darling"
-version = "0.20.3"
+version = "0.20.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0209d94da627ab5605dcccf08bb18afa5009cfbef48d8a8b7d7bdbc79be25c5e"
+checksum = "54e36fcd13ed84ffdfda6f5be89b31287cbb80c439841fe69e04841435464391"
 dependencies = [
- "darling_core 0.20.3",
- "darling_macro 0.20.3",
+ "darling_core 0.20.8",
+ "darling_macro 0.20.8",
 ]
 
 [[package]]
@@ -403,16 +398,16 @@ dependencies = [
 
 [[package]]
 name = "darling_core"
-version = "0.20.3"
+version = "0.20.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "177e3443818124b357d8e76f53be906d60937f0d3a90773a664fa63fa253e621"
+checksum = "9c2cf1c23a687a1feeb728783b993c4e1ad83d99f351801977dd809b48d0a70f"
 dependencies = [
  "fnv",
  "ident_case",
  "proc-macro2",
  "quote",
- "strsim",
- "syn 2.0.39",
+ "strsim 0.10.0",
+ "syn 2.0.53",
 ]
 
 [[package]]
@@ -428,20 +423,20 @@ dependencies = [
 
 [[package]]
 name = "darling_macro"
-version = "0.20.3"
+version = "0.20.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5"
+checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f"
 dependencies = [
- "darling_core 0.20.3",
+ "darling_core 0.20.8",
  "quote",
- "syn 2.0.39",
+ "syn 2.0.53",
 ]
 
 [[package]]
 name = "deranged"
-version = "0.3.9"
+version = "0.3.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0f32d04922c60427da6f9fef14d042d9edddef64cb9d4ce0d64d0685fbeb1fd3"
+checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4"
 dependencies = [
  "powerfmt",
  "serde",
@@ -449,15 +444,15 @@ dependencies = [
 
 [[package]]
 name = "dyn-clone"
-version = "1.0.16"
+version = "1.0.17"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "545b22097d44f8a9581187cdf93de7a71e4722bf51200cfaba810865b49a495d"
+checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125"
 
 [[package]]
 name = "either"
-version = "1.9.0"
+version = "1.10.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07"
+checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a"
 
 [[package]]
 name = "encoding_rs"
@@ -476,12 +471,12 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
 
 [[package]]
 name = "errno"
-version = "0.3.7"
+version = "0.3.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f258a7194e7f7c2a7837a8913aeab7fd8c383457034fa20ce4dd3dcb813e8eb8"
+checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245"
 dependencies = [
  "libc",
- "windows-sys",
+ "windows-sys 0.52.0",
 ]
 
 [[package]]
@@ -522,24 +517,24 @@ dependencies = [
 
 [[package]]
 name = "futures-channel"
-version = "0.3.29"
+version = "0.3.30"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb"
+checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78"
 dependencies = [
  "futures-core",
 ]
 
 [[package]]
 name = "futures-core"
-version = "0.3.29"
+version = "0.3.30"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c"
+checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d"
 
 [[package]]
 name = "futures-executor"
-version = "0.3.29"
+version = "0.3.30"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0f4fb8693db0cf099eadcca0efe2a5a22e4550f98ed16aba6c48700da29597bc"
+checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d"
 dependencies = [
  "futures-core",
  "futures-task",
@@ -548,38 +543,38 @@ dependencies = [
 
 [[package]]
 name = "futures-io"
-version = "0.3.29"
+version = "0.3.30"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8bf34a163b5c4c52d0478a4d757da8fb65cabef42ba90515efee0f6f9fa45aaa"
+checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1"
 
 [[package]]
 name = "futures-macro"
-version = "0.3.29"
+version = "0.3.30"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb"
+checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.39",
+ "syn 2.0.53",
 ]
 
 [[package]]
 name = "futures-sink"
-version = "0.3.29"
+version = "0.3.30"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817"
+checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5"
 
 [[package]]
 name = "futures-task"
-version = "0.3.29"
+version = "0.3.30"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2"
+checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004"
 
 [[package]]
 name = "futures-util"
-version = "0.3.29"
+version = "0.3.30"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104"
+checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48"
 dependencies = [
  "futures-core",
  "futures-io",
@@ -597,19 +592,19 @@ name = "gdc_rust_types"
 version = "1.0.2"
 source = "git+https://github.com/hasura/gdc_rust_types.git?rev=3273434#3273434068400f836cf12ea08c514505446821cb"
 dependencies = [
- "indexmap 2.1.0",
+ "indexmap 2.2.5",
  "openapiv3",
  "serde",
  "serde-enum-str",
  "serde_json",
- "serde_with 3.4.0",
+ "serde_with 3.7.0",
 ]
 
 [[package]]
 name = "getrandom"
-version = "0.2.11"
+version = "0.2.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f"
+checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5"
 dependencies = [
  "cfg-if",
  "libc",
@@ -624,9 +619,9 @@ checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253"
 
 [[package]]
 name = "h2"
-version = "0.3.22"
+version = "0.3.25"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4d6250322ef6e60f93f9a2162799302cd6f68f79f6e5d85c8c16f14d1d958178"
+checksum = "4fbd2820c5e49886948654ab546d0688ff24530286bdcf8fca3cefb16d4618eb"
 dependencies = [
  "bytes",
  "fnv",
@@ -634,7 +629,7 @@ dependencies = [
  "futures-sink",
  "futures-util",
  "http",
- "indexmap 2.1.0",
+ "indexmap 2.2.5",
  "slab",
  "tokio",
  "tokio-util",
@@ -659,11 +654,17 @@ version = "0.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
 
+[[package]]
+name = "heck"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
+
 [[package]]
 name = "hermit-abi"
-version = "0.3.3"
+version = "0.3.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7"
+checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
 
 [[package]]
 name = "hex"
@@ -673,9 +674,9 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
 
 [[package]]
 name = "http"
-version = "0.2.11"
+version = "0.2.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb"
+checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1"
 dependencies = [
  "bytes",
  "fnv",
@@ -684,9 +685,9 @@ dependencies = [
 
 [[package]]
 name = "http-body"
-version = "0.4.5"
+version = "0.4.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1"
+checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2"
 dependencies = [
  "bytes",
  "http",
@@ -713,9 +714,9 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
 
 [[package]]
 name = "hyper"
-version = "0.14.27"
+version = "0.14.28"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468"
+checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80"
 dependencies = [
  "bytes",
  "futures-channel",
@@ -728,13 +729,27 @@ dependencies = [
  "httpdate",
  "itoa",
  "pin-project-lite",
- "socket2 0.4.10",
+ "socket2",
  "tokio",
  "tower-service",
  "tracing",
  "want",
 ]
 
+[[package]]
+name = "hyper-rustls"
+version = "0.24.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590"
+dependencies = [
+ "futures-util",
+ "http",
+ "hyper",
+ "rustls",
+ "tokio",
+ "tokio-rustls",
+]
+
 [[package]]
 name = "hyper-timeout"
 version = "0.4.1"
@@ -762,9 +777,9 @@ dependencies = [
 
 [[package]]
 name = "iana-time-zone"
-version = "0.1.58"
+version = "0.1.60"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8326b86b6cff230b97d0d312a6c40a60726df3332e721f72a1b035f451663b20"
+checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141"
 dependencies = [
  "android_system_properties",
  "core-foundation-sys",
@@ -812,9 +827,9 @@ dependencies = [
 
 [[package]]
 name = "indexmap"
-version = "2.1.0"
+version = "2.2.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f"
+checksum = "7b0b929d511467233429c45a44ac1dcaa21ba0f5ba11e4879e6ed28ddb4f9df4"
 dependencies = [
  "equivalent",
  "hashbrown 0.14.3",
@@ -827,17 +842,6 @@ version = "2.9.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3"
 
-[[package]]
-name = "is-terminal"
-version = "0.4.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b"
-dependencies = [
- "hermit-abi",
- "rustix",
- "windows-sys",
-]
-
 [[package]]
 name = "itertools"
 version = "0.10.5"
@@ -849,15 +853,15 @@ dependencies = [
 
 [[package]]
 name = "itoa"
-version = "1.0.9"
+version = "1.0.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38"
+checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c"
 
 [[package]]
 name = "js-sys"
-version = "0.3.66"
+version = "0.3.69"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cee9c64da59eae3b50095c18d3e74f8b73c0b86d2792824ff01bbce68ba229ca"
+checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d"
 dependencies = [
  "wasm-bindgen",
 ]
@@ -870,21 +874,15 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
 
 [[package]]
 name = "libc"
-version = "0.2.150"
+version = "0.2.153"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c"
-
-[[package]]
-name = "libm"
-version = "0.2.8"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058"
+checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
 
 [[package]]
 name = "linux-raw-sys"
-version = "0.4.11"
+version = "0.4.13"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829"
+checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c"
 
 [[package]]
 name = "lock_api"
@@ -898,9 +896,9 @@ dependencies = [
 
 [[package]]
 name = "log"
-version = "0.4.20"
+version = "0.4.21"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
+checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
 
 [[package]]
 name = "matchers"
@@ -919,9 +917,9 @@ checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94"
 
 [[package]]
 name = "memchr"
-version = "2.6.4"
+version = "2.7.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167"
+checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149"
 
 [[package]]
 name = "mime"
@@ -941,22 +939,22 @@ dependencies = [
 
 [[package]]
 name = "miniz_oxide"
-version = "0.7.1"
+version = "0.7.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7"
+checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7"
 dependencies = [
  "adler",
 ]
 
 [[package]]
 name = "mio"
-version = "0.8.9"
+version = "0.8.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0"
+checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c"
 dependencies = [
  "libc",
  "wasi",
- "windows-sys",
+ "windows-sys 0.48.0",
 ]
 
 [[package]]
@@ -979,28 +977,41 @@ dependencies = [
 
 [[package]]
 name = "ndc-clickhouse"
-version = "0.1.1"
+version = "0.2.0"
 dependencies = [
  "async-trait",
- "indexmap 2.1.0",
+ "client",
+ "config",
+ "indexmap 2.2.5",
  "ndc-sdk",
  "peg",
  "prometheus",
  "reqwest",
- "schemars",
  "serde",
  "serde_json",
  "strum",
  "tokio",
 ]
 
+[[package]]
+name = "ndc-clickhouse-cli"
+version = "0.2.0"
+dependencies = [
+ "clap",
+ "client",
+ "config",
+ "serde",
+ "serde_json",
+ "tokio",
+]
+
 [[package]]
 name = "ndc-client"
 version = "0.1.0"
-source = "git+http://github.com/hasura/ndc-spec.git?tag=v0.1.0-rc.13#1f9b2a996ad74ac4bc97a783c4d014a3fd46b08e"
+source = "git+http://github.com/hasura/ndc-spec.git?tag=v0.1.0#8892f0524affd37e94097c2ce43da8740fc57aca"
 dependencies = [
  "async-trait",
- "indexmap 2.1.0",
+ "indexmap 2.2.5",
  "opentelemetry",
  "reqwest",
  "schemars",
@@ -1014,17 +1025,16 @@ dependencies = [
 [[package]]
 name = "ndc-sdk"
 version = "0.1.0"
-source = "git+https://github.com/hasura/ndc-hub.git?rev=1887363#188736368e37994cf135ea6f07f85bbd28221f01"
+source = "git+https://github.com/hasura/ndc-hub.git?rev=4cb500e#4cb500e2bcab6f003c3d548388037ff2b0f30478"
 dependencies = [
  "async-trait",
  "axum",
  "axum-extra",
- "base64 0.21.5",
  "bytes",
  "clap",
  "gdc_rust_types",
  "http",
- "indexmap 2.1.0",
+ "indexmap 2.2.5",
  "mime",
  "ndc-client",
  "ndc-test",
@@ -1036,7 +1046,6 @@ dependencies = [
  "opentelemetry_sdk",
  "prometheus",
  "reqwest",
- "schemars",
  "serde",
  "serde_json",
  "thiserror",
@@ -1051,14 +1060,14 @@ dependencies = [
 [[package]]
 name = "ndc-test"
 version = "0.1.0"
-source = "git+http://github.com/hasura/ndc-spec.git?tag=v0.1.0-rc.13#1f9b2a996ad74ac4bc97a783c4d014a3fd46b08e"
+source = "git+http://github.com/hasura/ndc-spec.git?tag=v0.1.0#8892f0524affd37e94097c2ce43da8740fc57aca"
 dependencies = [
  "async-trait",
  "clap",
  "colored",
- "indexmap 2.1.0",
+ "indexmap 2.2.5",
  "ndc-client",
- "proptest",
+ "rand",
  "reqwest",
  "semver",
  "serde",
@@ -1077,14 +1086,19 @@ dependencies = [
  "winapi",
 ]
 
+[[package]]
+name = "num-conv"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
+
 [[package]]
 name = "num-traits"
-version = "0.2.17"
+version = "0.2.18"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c"
+checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a"
 dependencies = [
  "autocfg",
- "libm",
 ]
 
 [[package]]
@@ -1099,37 +1113,37 @@ dependencies = [
 
 [[package]]
 name = "object"
-version = "0.32.1"
+version = "0.32.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0"
+checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441"
 dependencies = [
  "memchr",
 ]
 
 [[package]]
 name = "once_cell"
-version = "1.18.0"
+version = "1.19.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
+checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
 
 [[package]]
 name = "openapiv3"
-version = "1.0.3"
+version = "1.0.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "75e56d5c441965b6425165b7e3223cc933ca469834f4a8b4786817a1f9dc4f13"
+checksum = "33b83630305ecc3355e998ddd2f926f98aae8e105eb42652174a58757851ba47"
 dependencies = [
- "indexmap 2.1.0",
+ "indexmap 1.9.3",
  "serde",
  "serde_json",
 ]
 
 [[package]]
 name = "openssl"
-version = "0.10.60"
+version = "0.10.64"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "79a4c6c3a2b158f7f8f2a2fc5a969fa3a068df6fc9dbb4a43845436e3af7c800"
+checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f"
 dependencies = [
- "bitflags 2.4.1",
+ "bitflags 2.5.0",
  "cfg-if",
  "foreign-types",
  "libc",
@@ -1146,7 +1160,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.39",
+ "syn 2.0.53",
 ]
 
 [[package]]
@@ -1157,9 +1171,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
 
 [[package]]
 name = "openssl-sys"
-version = "0.9.96"
+version = "0.9.101"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3812c071ba60da8b5677cc12bcb1d42989a65553772897a7e0355545a819838f"
+checksum = "dda2b0f344e78efc2facf7d195d098df0dd72151b26ab98da807afc26c198dff"
 dependencies = [
  "cc",
  "libc",
@@ -1306,7 +1320,7 @@ dependencies = [
  "libc",
  "redox_syscall",
  "smallvec",
- "windows-targets",
+ "windows-targets 0.48.5",
 ]
 
 [[package]]
@@ -1344,22 +1358,22 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
 
 [[package]]
 name = "pin-project"
-version = "1.1.3"
+version = "1.1.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422"
+checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3"
 dependencies = [
  "pin-project-internal",
 ]
 
 [[package]]
 name = "pin-project-internal"
-version = "1.1.3"
+version = "1.1.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405"
+checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.39",
+ "syn 2.0.53",
 ]
 
 [[package]]
@@ -1376,9 +1390,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
 
 [[package]]
 name = "pkg-config"
-version = "0.3.27"
+version = "0.3.30"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964"
+checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec"
 
 [[package]]
 name = "powerfmt"
@@ -1394,9 +1408,9 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
 
 [[package]]
 name = "proc-macro2"
-version = "1.0.70"
+version = "1.0.79"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b"
+checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e"
 dependencies = [
  "unicode-ident",
 ]
@@ -1416,26 +1430,6 @@ dependencies = [
  "thiserror",
 ]
 
-[[package]]
-name = "proptest"
-version = "1.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "31b476131c3c86cb68032fdc5cb6d5a1045e3e42d96b69fa599fd77701e1f5bf"
-dependencies = [
- "bit-set",
- "bit-vec",
- "bitflags 2.4.1",
- "lazy_static",
- "num-traits",
- "rand",
- "rand_chacha",
- "rand_xorshift",
- "regex-syntax 0.8.2",
- "rusty-fork",
- "tempfile",
- "unarray",
-]
-
 [[package]]
 name = "prost"
 version = "0.11.9"
@@ -1465,17 +1459,11 @@ version = "2.28.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "106dd99e98437432fed6519dedecfade6a06a73bb7b2a1e019fdd2bee5778d94"
 
-[[package]]
-name = "quick-error"
-version = "1.2.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
-
 [[package]]
 name = "quote"
-version = "1.0.33"
+version = "1.0.35"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
+checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
 dependencies = [
  "proc-macro2",
 ]
@@ -1510,15 +1498,6 @@ dependencies = [
  "getrandom",
 ]
 
-[[package]]
-name = "rand_xorshift"
-version = "0.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f"
-dependencies = [
- "rand_core",
-]
-
 [[package]]
 name = "redox_syscall"
 version = "0.4.1"
@@ -1530,13 +1509,13 @@ dependencies = [
 
 [[package]]
 name = "regex"
-version = "1.10.2"
+version = "1.10.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343"
+checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15"
 dependencies = [
  "aho-corasick",
  "memchr",
- "regex-automata 0.4.3",
+ "regex-automata 0.4.6",
  "regex-syntax 0.8.2",
 ]
 
@@ -1551,9 +1530,9 @@ dependencies = [
 
 [[package]]
 name = "regex-automata"
-version = "0.4.3"
+version = "0.4.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f"
+checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea"
 dependencies = [
  "aho-corasick",
  "memchr",
@@ -1574,11 +1553,11 @@ checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
 
 [[package]]
 name = "reqwest"
-version = "0.11.22"
+version = "0.11.27"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "046cd98826c46c2ac8ddecae268eb5c2e58628688a5fc7a2643704a73faba95b"
+checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62"
 dependencies = [
- "base64 0.21.5",
+ "base64 0.21.7",
  "bytes",
  "encoding_rs",
  "futures-core",
@@ -1587,6 +1566,7 @@ dependencies = [
  "http",
  "http-body",
  "hyper",
+ "hyper-rustls",
  "hyper-tls",
  "ipnet",
  "js-sys",
@@ -1597,20 +1577,40 @@ dependencies = [
  "once_cell",
  "percent-encoding",
  "pin-project-lite",
+ "rustls",
+ "rustls-pemfile",
  "serde",
  "serde_json",
  "serde_urlencoded",
+ "sync_wrapper",
  "system-configuration",
  "tokio",
  "tokio-native-tls",
+ "tokio-rustls",
  "tower-service",
  "url",
  "wasm-bindgen",
  "wasm-bindgen-futures",
  "web-sys",
+ "webpki-roots",
  "winreg",
 ]
 
+[[package]]
+name = "ring"
+version = "0.17.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d"
+dependencies = [
+ "cc",
+ "cfg-if",
+ "getrandom",
+ "libc",
+ "spin",
+ "untrusted",
+ "windows-sys 0.52.0",
+]
+
 [[package]]
 name = "rustc-demangle"
 version = "0.1.23"
@@ -1619,48 +1619,67 @@ checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
 
 [[package]]
 name = "rustix"
-version = "0.38.25"
+version = "0.38.32"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dc99bc2d4f1fed22595588a013687477aedf3cdcfb26558c559edb67b4d9b22e"
+checksum = "65e04861e65f21776e67888bfbea442b3642beaa0138fdb1dd7a84a52dffdb89"
 dependencies = [
- "bitflags 2.4.1",
+ "bitflags 2.5.0",
  "errno",
  "libc",
  "linux-raw-sys",
- "windows-sys",
+ "windows-sys 0.52.0",
 ]
 
 [[package]]
-name = "rustversion"
-version = "1.0.14"
+name = "rustls"
+version = "0.21.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4"
+checksum = "f9d5a6813c0759e4609cd494e8e725babae6a2ca7b62a5536a13daaec6fcb7ba"
+dependencies = [
+ "log",
+ "ring",
+ "rustls-webpki",
+ "sct",
+]
 
 [[package]]
-name = "rusty-fork"
-version = "0.3.0"
+name = "rustls-pemfile"
+version = "1.0.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f"
+checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c"
 dependencies = [
- "fnv",
- "quick-error",
- "tempfile",
- "wait-timeout",
+ "base64 0.21.7",
 ]
 
+[[package]]
+name = "rustls-webpki"
+version = "0.101.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765"
+dependencies = [
+ "ring",
+ "untrusted",
+]
+
+[[package]]
+name = "rustversion"
+version = "1.0.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4"
+
 [[package]]
 name = "ryu"
-version = "1.0.15"
+version = "1.0.17"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741"
+checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1"
 
 [[package]]
 name = "schannel"
-version = "0.1.22"
+version = "0.1.23"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88"
+checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534"
 dependencies = [
- "windows-sys",
+ "windows-sys 0.52.0",
 ]
 
 [[package]]
@@ -1671,7 +1690,7 @@ checksum = "45a28f4c49489add4ce10783f7911893516f15afe45d015608d41faca6bc4d29"
 dependencies = [
  "dyn-clone",
  "indexmap 1.9.3",
- "indexmap 2.1.0",
+ "indexmap 2.2.5",
  "schemars_derive",
  "serde",
  "serde_json",
@@ -1696,6 +1715,16 @@ version = "1.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
 
+[[package]]
+name = "sct"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414"
+dependencies = [
+ "ring",
+ "untrusted",
+]
+
 [[package]]
 name = "security-framework"
 version = "2.9.2"
@@ -1721,15 +1750,15 @@ dependencies = [
 
 [[package]]
 name = "semver"
-version = "1.0.20"
+version = "1.0.22"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090"
+checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca"
 
 [[package]]
 name = "serde"
-version = "1.0.193"
+version = "1.0.197"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89"
+checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2"
 dependencies = [
  "serde_derive",
 ]
@@ -1766,13 +1795,13 @@ checksum = "794e44574226fc701e3be5c651feb7939038fc67fb73f6f4dd5c4ba90fd3be70"
 
 [[package]]
 name = "serde_derive"
-version = "1.0.193"
+version = "1.0.197"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3"
+checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.39",
+ "syn 2.0.53",
 ]
 
 [[package]]
@@ -1788,11 +1817,11 @@ dependencies = [
 
 [[package]]
 name = "serde_json"
-version = "1.0.108"
+version = "1.0.114"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b"
+checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0"
 dependencies = [
- "indexmap 2.1.0",
+ "indexmap 2.2.5",
  "itoa",
  "ryu",
  "serde",
@@ -1800,9 +1829,9 @@ dependencies = [
 
 [[package]]
 name = "serde_path_to_error"
-version = "0.1.14"
+version = "0.1.16"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4beec8bce849d58d06238cb50db2e1c417cfeafa4c63f692b15c82b7c80f8335"
+checksum = "af99884400da37c88f5e9146b7f1fd0fbcae8f6eec4e9da38b67d05486f814a6"
 dependencies = [
  "itoa",
  "serde",
@@ -1838,18 +1867,19 @@ dependencies = [
 
 [[package]]
 name = "serde_with"
-version = "3.4.0"
+version = "3.7.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "64cd236ccc1b7a29e7e2739f27c0b2dd199804abc4290e32f59f3b68d6405c23"
+checksum = "ee80b0e361bbf88fd2f6e242ccd19cfda072cb0faa6ae694ecee08199938569a"
 dependencies = [
- "base64 0.21.5",
+ "base64 0.21.7",
  "chrono",
  "hex",
  "indexmap 1.9.3",
- "indexmap 2.1.0",
+ "indexmap 2.2.5",
  "serde",
+ "serde_derive",
  "serde_json",
- "serde_with_macros 3.4.0",
+ "serde_with_macros 3.7.0",
  "time",
 ]
 
@@ -1859,22 +1889,22 @@ version = "2.3.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "881b6f881b17d13214e5d494c939ebab463d01264ce1811e9d4ac3a882e7695f"
 dependencies = [
- "darling 0.20.3",
+ "darling 0.20.8",
  "proc-macro2",
  "quote",
- "syn 2.0.39",
+ "syn 2.0.53",
 ]
 
 [[package]]
 name = "serde_with_macros"
-version = "3.4.0"
+version = "3.7.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "93634eb5f75a2323b16de4748022ac4297f9e76b6dced2be287a099f41b5e788"
+checksum = "6561dc161a9224638a31d876ccdfefbc1df91d3f3a8342eddb35f055d48c7655"
 dependencies = [
- "darling 0.20.3",
+ "darling 0.20.8",
  "proc-macro2",
  "quote",
- "syn 2.0.39",
+ "syn 2.0.53",
 ]
 
 [[package]]
@@ -1906,9 +1936,9 @@ dependencies = [
 
 [[package]]
 name = "smallvec"
-version = "1.11.2"
+version = "1.13.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970"
+checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7"
 
 [[package]]
 name = "smol_str"
@@ -1921,23 +1951,19 @@ dependencies = [
 
 [[package]]
 name = "socket2"
-version = "0.4.10"
+version = "0.5.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d"
+checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871"
 dependencies = [
  "libc",
- "winapi",
+ "windows-sys 0.52.0",
 ]
 
 [[package]]
-name = "socket2"
-version = "0.5.5"
+name = "spin"
+version = "0.9.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9"
-dependencies = [
- "libc",
- "windows-sys",
-]
+checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
 
 [[package]]
 name = "strsim"
@@ -1945,26 +1971,32 @@ version = "0.10.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
 
+[[package]]
+name = "strsim"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01"
+
 [[package]]
 name = "strum"
-version = "0.25.0"
+version = "0.26.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125"
+checksum = "5d8cec3501a5194c432b2b7976db6b7d10ec95c253208b45f83f7136aa985e29"
 dependencies = [
  "strum_macros",
 ]
 
 [[package]]
 name = "strum_macros"
-version = "0.25.3"
+version = "0.26.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0"
+checksum = "c6cf59daf282c0a494ba14fd21610a0325f9f90ec9d1231dea26bcb1d696c946"
 dependencies = [
- "heck",
+ "heck 0.4.1",
  "proc-macro2",
  "quote",
  "rustversion",
- "syn 2.0.39",
+ "syn 2.0.53",
 ]
 
 [[package]]
@@ -1980,9 +2012,9 @@ dependencies = [
 
 [[package]]
 name = "syn"
-version = "2.0.39"
+version = "2.0.53"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a"
+checksum = "7383cd0e49fff4b6b90ca5670bfd3e9d6a733b3f90c686605aa7eec8c4996032"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -2018,42 +2050,41 @@ dependencies = [
 
 [[package]]
 name = "tempfile"
-version = "3.8.1"
+version = "3.10.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5"
+checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1"
 dependencies = [
  "cfg-if",
  "fastrand",
- "redox_syscall",
  "rustix",
- "windows-sys",
+ "windows-sys 0.52.0",
 ]
 
 [[package]]
 name = "thiserror"
-version = "1.0.50"
+version = "1.0.58"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2"
+checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297"
 dependencies = [
  "thiserror-impl",
 ]
 
 [[package]]
 name = "thiserror-impl"
-version = "1.0.50"
+version = "1.0.58"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8"
+checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.39",
+ "syn 2.0.53",
 ]
 
 [[package]]
 name = "thread_local"
-version = "1.1.7"
+version = "1.1.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152"
+checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c"
 dependencies = [
  "cfg-if",
  "once_cell",
@@ -2061,12 +2092,13 @@ dependencies = [
 
 [[package]]
 name = "time"
-version = "0.3.30"
+version = "0.3.34"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5"
+checksum = "c8248b6521bb14bc45b4067159b9b6ad792e2d6d754d6c41fb50e29fefe38749"
 dependencies = [
  "deranged",
  "itoa",
+ "num-conv",
  "powerfmt",
  "serde",
  "time-core",
@@ -2081,10 +2113,11 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
 
 [[package]]
 name = "time-macros"
-version = "0.2.15"
+version = "0.2.17"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4ad70d68dba9e1f8aceda7aa6711965dfec1cac869f311a51bd08b3a2ccbce20"
+checksum = "7ba3a3ef41e6672a2f0f001392bb5dcd3ff0a9992d618ca761a11c3121547774"
 dependencies = [
+ "num-conv",
  "time-core",
 ]
 
@@ -2105,9 +2138,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
 
 [[package]]
 name = "tokio"
-version = "1.34.0"
+version = "1.36.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d0c014766411e834f7af5b8f4cf46257aab4036ca95e9d2c144a10f59ad6f5b9"
+checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931"
 dependencies = [
  "backtrace",
  "bytes",
@@ -2117,9 +2150,9 @@ dependencies = [
  "parking_lot",
  "pin-project-lite",
  "signal-hook-registry",
- "socket2 0.5.5",
+ "socket2",
  "tokio-macros",
- "windows-sys",
+ "windows-sys 0.48.0",
 ]
 
 [[package]]
@@ -2140,7 +2173,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.39",
+ "syn 2.0.53",
 ]
 
 [[package]]
@@ -2153,11 +2186,21 @@ dependencies = [
  "tokio",
 ]
 
+[[package]]
+name = "tokio-rustls"
+version = "0.24.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081"
+dependencies = [
+ "rustls",
+ "tokio",
+]
+
 [[package]]
 name = "tokio-stream"
-version = "0.1.14"
+version = "0.1.15"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842"
+checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af"
 dependencies = [
  "futures-core",
  "pin-project-lite",
@@ -2186,7 +2229,7 @@ checksum = "3082666a3a6433f7f511c7192923fa1fe07c69332d3c6a2e6bb040b569199d5a"
 dependencies = [
  "async-trait",
  "axum",
- "base64 0.21.5",
+ "base64 0.21.7",
  "bytes",
  "futures-core",
  "futures-util",
@@ -2232,7 +2275,7 @@ version = "0.4.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "61c5bb1d698276a2443e5ecfabc1008bf15a36c12e6a7176e7bf089ea9131140"
 dependencies = [
- "bitflags 2.4.1",
+ "bitflags 2.5.0",
  "bytes",
  "futures-core",
  "futures-util",
@@ -2278,7 +2321,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.39",
+ "syn 2.0.53",
 ]
 
 [[package]]
@@ -2347,15 +2390,9 @@ dependencies = [
 
 [[package]]
 name = "try-lock"
-version = "0.2.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed"
-
-[[package]]
-name = "unarray"
-version = "0.1.4"
+version = "0.2.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94"
+checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
 
 [[package]]
 name = "unicase"
@@ -2368,9 +2405,9 @@ dependencies = [
 
 [[package]]
 name = "unicode-bidi"
-version = "0.3.13"
+version = "0.3.15"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460"
+checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75"
 
 [[package]]
 name = "unicode-ident"
@@ -2380,13 +2417,19 @@ checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
 
 [[package]]
 name = "unicode-normalization"
-version = "0.1.22"
+version = "0.1.23"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921"
+checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5"
 dependencies = [
  "tinyvec",
 ]
 
+[[package]]
+name = "untrusted"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
+
 [[package]]
 name = "url"
 version = "2.5.0"
@@ -2428,15 +2471,6 @@ version = "0.9.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
 
-[[package]]
-name = "wait-timeout"
-version = "0.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6"
-dependencies = [
- "libc",
-]
-
 [[package]]
 name = "want"
 version = "0.3.1"
@@ -2454,9 +2488,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
 
 [[package]]
 name = "wasm-bindgen"
-version = "0.2.89"
+version = "0.2.92"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0ed0d4f68a3015cc185aff4db9506a015f4b96f95303897bfa23f846db54064e"
+checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8"
 dependencies = [
  "cfg-if",
  "wasm-bindgen-macro",
@@ -2464,24 +2498,24 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-backend"
-version = "0.2.89"
+version = "0.2.92"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1b56f625e64f3a1084ded111c4d5f477df9f8c92df113852fa5a374dbda78826"
+checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da"
 dependencies = [
  "bumpalo",
  "log",
  "once_cell",
  "proc-macro2",
  "quote",
- "syn 2.0.39",
+ "syn 2.0.53",
  "wasm-bindgen-shared",
 ]
 
 [[package]]
 name = "wasm-bindgen-futures"
-version = "0.4.39"
+version = "0.4.42"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ac36a15a220124ac510204aec1c3e5db8a22ab06fd6706d881dc6149f8ed9a12"
+checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0"
 dependencies = [
  "cfg-if",
  "js-sys",
@@ -2491,9 +2525,9 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-macro"
-version = "0.2.89"
+version = "0.2.92"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0162dbf37223cd2afce98f3d0785506dcb8d266223983e4b5b525859e6e182b2"
+checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726"
 dependencies = [
  "quote",
  "wasm-bindgen-macro-support",
@@ -2501,33 +2535,39 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-macro-support"
-version = "0.2.89"
+version = "0.2.92"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283"
+checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.39",
+ "syn 2.0.53",
  "wasm-bindgen-backend",
  "wasm-bindgen-shared",
 ]
 
 [[package]]
 name = "wasm-bindgen-shared"
-version = "0.2.89"
+version = "0.2.92"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f"
+checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96"
 
 [[package]]
 name = "web-sys"
-version = "0.3.65"
+version = "0.3.69"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5db499c5f66323272151db0e666cd34f78617522fb0c1604d31a27c50c206a85"
+checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef"
 dependencies = [
  "js-sys",
  "wasm-bindgen",
 ]
 
+[[package]]
+name = "webpki-roots"
+version = "0.25.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1"
+
 [[package]]
 name = "winapi"
 version = "0.3.9"
@@ -2552,11 +2592,11 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
 
 [[package]]
 name = "windows-core"
-version = "0.51.1"
+version = "0.52.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64"
+checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
 dependencies = [
- "windows-targets",
+ "windows-targets 0.52.4",
 ]
 
 [[package]]
@@ -2565,7 +2605,16 @@ version = "0.48.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
 dependencies = [
- "windows-targets",
+ "windows-targets 0.48.5",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
+dependencies = [
+ "windows-targets 0.52.4",
 ]
 
 [[package]]
@@ -2574,13 +2623,28 @@ version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
 dependencies = [
- "windows_aarch64_gnullvm",
- "windows_aarch64_msvc",
- "windows_i686_gnu",
- "windows_i686_msvc",
- "windows_x86_64_gnu",
- "windows_x86_64_gnullvm",
- "windows_x86_64_msvc",
+ "windows_aarch64_gnullvm 0.48.5",
+ "windows_aarch64_msvc 0.48.5",
+ "windows_i686_gnu 0.48.5",
+ "windows_i686_msvc 0.48.5",
+ "windows_x86_64_gnu 0.48.5",
+ "windows_x86_64_gnullvm 0.48.5",
+ "windows_x86_64_msvc 0.48.5",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.52.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b"
+dependencies = [
+ "windows_aarch64_gnullvm 0.52.4",
+ "windows_aarch64_msvc 0.52.4",
+ "windows_i686_gnu 0.52.4",
+ "windows_i686_msvc 0.52.4",
+ "windows_x86_64_gnu 0.52.4",
+ "windows_x86_64_gnullvm 0.52.4",
+ "windows_x86_64_msvc 0.52.4",
 ]
 
 [[package]]
@@ -2589,42 +2653,84 @@ version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
 
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.52.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9"
+
 [[package]]
 name = "windows_aarch64_msvc"
 version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
 
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.52.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675"
+
 [[package]]
 name = "windows_i686_gnu"
 version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
 
+[[package]]
+name = "windows_i686_gnu"
+version = "0.52.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3"
+
 [[package]]
 name = "windows_i686_msvc"
 version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
 
+[[package]]
+name = "windows_i686_msvc"
+version = "0.52.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02"
+
 [[package]]
 name = "windows_x86_64_gnu"
 version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
 
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.52.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03"
+
 [[package]]
 name = "windows_x86_64_gnullvm"
 version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
 
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.52.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177"
+
 [[package]]
 name = "windows_x86_64_msvc"
 version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
 
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.52.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8"
+
 [[package]]
 name = "winreg"
 version = "0.50.0"
@@ -2632,5 +2738,5 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1"
 dependencies = [
  "cfg-if",
- "windows-sys",
+ "windows-sys 0.48.0",
 ]
diff --git a/Cargo.toml b/Cargo.toml
index 7d4797e..31cf400 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,23 +1,11 @@
-[package]
-name = "ndc-clickhouse"
-version = "0.1.1"
-edition = "2021"
+[workspace]
+members = [
+  "crates/ndc-clickhouse",
+  "crates/ndc-clickhouse-cli",
+  "crates/config",
+  "crates/client",
+]
+resolver = "2"
 
-# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
-
-[dependencies]
-tokio = { version = "1.27.0", features = [
-  "macros",
-  "rt-multi-thread",
-  "signal",
-] }
-ndc-sdk = { git = "https://github.com/hasura/ndc-hub.git", rev = "1887363", package = "ndc-sdk" }
-peg = "0.8.1"
-async-trait = "0.1.72"
-prometheus = "0.13.3"
-serde = { version = "1.0.183", features = ["derive"] }
-serde_json = "1.0.104"
-schemars = "0.8.12"
-indexmap = "2.1.0"
-reqwest = { version = "0.11.20", features = ["json"] }
-strum = { version = "0.25.0", features = ["derive"] }
+package.version = "0.2.0"
+package.edition = "2021"
diff --git a/Dockerfile b/Dockerfile
index 4718b9a..cb5f094 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,14 +1,24 @@
-FROM rust:1.71.0 as builder
-WORKDIR /tmp
+# https://github.com/LukeMathWalker/cargo-chef
+FROM rust:1.75.0 as chef
+RUN cargo install cargo-chef
+WORKDIR /app
+
+FROM chef AS planner
 COPY Cargo.toml ./
 COPY Cargo.lock ./
-COPY src src
-RUN cargo build --locked --profile release --package ndc-clickhouse
+COPY crates crates
+RUN cargo chef prepare --recipe-path recipe.json
 
-# todo: figure out how to get rid of dependency libssl.so.1.1
-# so we can use multistage builds for a smaller image
-# unable to determine where the dependency comes from,
-# this may be somewhere upstream?
+FROM chef AS builder
+COPY --from=planner /app/recipe.json recipe.json
+RUN cargo chef cook --release --recipe-path recipe.json
+COPY Cargo.toml ./
+COPY Cargo.lock ./
+COPY crates crates 
+RUN cargo build --locked --profile release --package ndc-clickhouse
 
-ENTRYPOINT ["/tmp/target/release/ndc-clickhouse"]
-CMD ["serve", "--configuration", "/etc/connector/config.json"]
+FROM ubuntu:latest AS runtime
+RUN apt-get update && apt-get install -y ca-certificates
+WORKDIR /app
+COPY --from=builder /app/target/release/ndc-clickhouse /usr/local/bin
+ENTRYPOINT [ "/usr/local/bin/ndc-clickhouse" ]
\ No newline at end of file
diff --git a/README.md b/README.md
index 7e5ab88..5bac9d4 100644
--- a/README.md
+++ b/README.md
@@ -18,105 +18,78 @@ In order to use this connector you will need to:
 
 ## Features
 
-- Basic queries
-- Relationships
-- Filtering, including filtering across relationships
-- Remote joins
+This native data connector implements the following Hasura Data Domain Specification features:
+
+| Feature                                                                                                                             |     |
+| ----------------------------------------------------------------------------------------------------------------------------------- | --- |
+| [Simple Queries](https://hasura.io/docs/3.0/graphql-api/queries/simple-queries/)                                                    | ✅  |
+| [Nested Queries](https://hasura.io/docs/3.0/graphql-api/queries/nested-queries/)                                                    | ✅  |
+| [Query Result Sorting](https://hasura.io/docs/3.0/graphql-api/queries/sorting/)                                                     | ✅  |
+| [Query Result Pagination](https://hasura.io/docs/3.0/graphql-api/queries/pagination/)                                               | ✅  |
+| [Multiple Query Arguments](https://hasura.io/docs/3.0/graphql-api/queries/multiple-arguments/)                                      | ✅  |
+| [Multiple Queries in a Request](https://hasura.io/docs/3.0/graphql-api/queries/multiple-queries/)                                   | ✅  |
+| [Variables, Aliases, Fragments, Directives](https://hasura.io/docs/3.0/graphql-api/queries/variables-aliases-fragments-directives/) | ✅  |
+| [Query Filter: Value Comparison](https://hasura.io/docs/3.0/graphql-api/queries/filters/comparison-operators/)                      | ✅  |
+| [Query Filter: Boolean Expressions](https://hasura.io/docs/3.0/graphql-api/queries/filters/boolean-operators/)                      | ✅  |
+| [Query Filter: Text](https://hasura.io/docs/3.0/graphql-api/queries/filters/text-search-operators/)                                 | ✅  |
 
 ## For Hasura Users
 
-If you wish to use this connector with your Hasura projects, the best instructions can be found on the Hasura Hub
-(TODO: Link to hub page for Clickhouse Connector).
-
-The following steps will allow you to deploy the connector and use it in a Hasura V3 project:
-
-- Create a Hasura V3 Project (or use an existing project)
-- Ensure that you have a metadata definition
-- Create a configuration for the Clickhouse Connector referencing your credentials:
-  `clickhouse.connector.configuration.json`
-  You have 2 options for the config:
-  1.  The easiest option is to is to run the connector locally in config mode:
-  ```
-  cargo run -- configuration serve --port 5000
-  curl -X POST -H 'Content-Type: application/json' -d '{"connection": {"username": "your_username", "password": "your_password", "url": "your_clickhouse_url"}, "tables": []}' http://localhost:5000 > clickhouse.connector.configuration.json
-  ```
-  2.  The other option is to manually write your config that follows this pattern:
-  ```
-  {
-     "connection": {
-       "username": "your_username",
-       "password": "your_password",
-       "url": "your_clickhouse_url"
-     },
-     "tables": [
-       {
-         "name": "TableName",
-         "schema": "SchemaName",
-         "alias": "TableAlias",
-         "primary_key": { "name": "TableId", "columns": ["TableId"] },
-         "columns": [
-           { "name": "TableId", "alias": "TableId", "data_type": "Int32" },
-         ]
-       }
-     ]
-   }
-  ```
-- Run the following command to deploy the connector
-- Ensure you are logged in to Hasura CLI
-  ```
-  hasura3 cloud login --pat 'YOUR-HASURA-TOKEN'
-  ```
-- Deploy the connector
-  ```
-  hasura3 connector create clickhouse:v1 \
-  --github-repo-url https://github.com/hasura/ndc-clickhouse/tree/main \
-  --config-file ./clickhouse.connector.configuration.json
-  ```
-- Ensure that your deployed connector is referenced from your metadata with the service token. This can be done by adding a new file under `subgraphs/<YOUR_SUBGRAPH_DIR>/dataconnectors` with the following:
-
-```
-kind: DataConnector
-version: v1
-definition:
-  name: clickhouse
-  url:
-    singleUrl: <DEPLOYED_CONNECTOR_URL>
-```
-
-- Edit your metadata using the [LSP](https://marketplace.visualstudio.com/items?itemName=HasuraHQ.hasura) support to import the defined schema by running the comamnd `Hasura: Refresh Data Connector`. You can also track functions and procedures using the LSP command `Hasura: Track All`.
-- Deploy or update your Hasura cloud project
-  ```
-  hasura3 cloud build create --project-id my-project-id \
-  --metadata-file metadata.json my-build-id
-  ```
-- View in your cloud console, access via the graphql API
+This connector should be used via the hasura ddn cli
 
 ## For Developers
 
 The following instructions are for developers who wish to contribute to the Clickhouse Connector.
 
-### Build
-
-Prerequisites:
+### Prerequisites:
 
 1. Install [rustup](https://www.rust-lang.org/tools/install).
+2. Install [docker](https://docs.docker.com/get-docker/).
+
+### Use the CLI to create/update a configuration directory
+
+View CLI help:
+
+```sh
+cargo run --package ndc-clickhouse-cli -- --help
+```
 
-Commands:
+Create a configuration directory in the `./config` directory:
 
+```sh
+cargo run --package ndc-clickhouse-cli -- init --context-path ./config --clickhouse-url "url" --clickhouse-username "user" --clickhouse-password "pass"
 ```
-cargo build
-cargo run -- configuration serve --port 5000
-curl -X POST -d '{"connection": {"username": "your_username", "password": "your_password", "url": "your_clickhouse_url"}, "tables": []}' http://localhost:5000 > clickhouse.connector.configuration.json
-cargo run -- serve --configuration clickhouse.connector.congifuration.json
+
+Update an existing directory. Will create the directory and files if not present.
+
+This is required whenever the database schema changes
+
+```sh
+cargo run --package ndc-clickhouse-cli -- update --context-path ./config --clickhouse-url "url" --clickhouse-username "user" --clickhouse-password "pass"
 ```
 
-### Docker
+### Run the connector server in docker
 
-The `Dockerfile` is used by the `connector create` command and can be tested as follows:
+Create a `.env` file in the project root, replacing the placeholders with the actual values:
 
+```env
+CLICKHOUSE_URL=<value>
+CLICKHOUSE_USERNAME=<value>
+CLICKHOUSE_PASSWORD=<value>
 ```
-docker build . --tag ndc-clickhouse
-docker run -it --v ./clickhouse.connector.configuration.json:/config.json ndc-clickhouse
+
+Run the connector container. Check `docker-compose.yaml` for configuration details:
+
+```sh
+docker compose up -d
+```
+
+The connector should now be running and accepting requests.
+
+To re-build the connector:
+
+```sh
+docker compose up -d --build
 ```
 
 ## Documentation
diff --git a/ci/templates/connector-metadata.yaml b/ci/templates/connector-metadata.yaml
new file mode 100644
index 0000000..be47aab
--- /dev/null
+++ b/ci/templates/connector-metadata.yaml
@@ -0,0 +1,21 @@
+packagingDefinition:
+  type: PrebuiltDockerImage
+  dockerImage: "${DOCKER_IMAGE}"
+supportedEnvironmentVariables:
+  - name: CLICKHOUSE_URL
+    description: The ClickHouse connection URL
+  - name: CLICKHOUSE_USERNAME
+    description: The ClickHouse connection username
+  - name: CLICKHOUSE_PASSWORD
+    description: The ClickHouse connection password
+commands:
+  update: hasura-clickhouse update
+cliPlugin:
+  name: clickhouse
+  version: "${CLI_VERSION}"
+dockerComposeWatch:
+  - path: ./
+    target: /etc/connector
+    action: sync+restart
+
+
diff --git a/ci/templates/manifest.yaml b/ci/templates/manifest.yaml
new file mode 100644
index 0000000..9cfd1f5
--- /dev/null
+++ b/ci/templates/manifest.yaml
@@ -0,0 +1,40 @@
+name: clickhouse
+version: "${CLI_VERSION}"
+shortDescription: "CLI plugin for Hasura ndc-clickhouse"
+homepage: https://hasura.io/connectors/clickhouse
+platforms:
+  - selector: darwin-arm64
+    uri: "https://github.com/hasura/ndc-clickhouse/releases/download/${CLI_VERSION}/ndc-clickhouse-cli-aarch64-apple-darwin"
+    sha256: "${MACOS_ARM64_SHA256}"
+    bin: "hasura-clickhouse"
+    files:
+      - from: "./ndc-clickhouse-cli-aarch64-apple-darwin"
+        to: "hasura-clickhouse"
+  - selector: linux-arm64
+    uri: "https://github.com/hasura/ndc-clickhouse/releases/download/${CLI_VERSION}/ndc-clickhouse-cli-aarch64-unknown-linux-musl"
+    sha256: "${LINUX_ARM64_SHA256}"
+    bin: "hasura-clickhouse"
+    files:
+      - from: "./ndc-clickhouse-cli-aarch64-unknown-linux-musl"
+        to: "hasura-clickhouse"
+  - selector: darwin-amd64
+    uri: "https://github.com/hasura/ndc-clickhouse/releases/download/${CLI_VERSION}/ndc-clickhouse-cli-x86_64-apple-darwin"
+    sha256: "${MACOS_AMD64_SHA256}"
+    bin: "hasura-clickhouse"
+    files:
+      - from: "./ndc-clickhouse-cli-x86_64-apple-darwin"
+        to: "hasura-clickhouse"
+  - selector: windows-amd64
+    uri: "https://github.com/hasura/ndc-clickhouse/releases/download/${CLI_VERSION}/ndc-clickhouse-cli-x86_64-pc-windows-msvc.exe"
+    sha256: "${WINDOWS_AMD64_SHA256}"
+    bin: "hasura-clickhouse.exe"
+    files:
+      - from: "./ndc-clickhouse-cli-x86_64-pc-windows-msvc.exe"
+        to: "hasura-clickhouse.exe"
+  - selector: linux-amd64
+    uri: "https://github.com/hasura/ndc-clickhouse/releases/download/${CLI_VERSION}/ndc-clickhouse-cli-x86_64-unknown-linux-musl"
+    sha256: "${LINUX_AMD64_SHA256}"
+    bin: "hasura-clickhouse"
+    files:
+      - from: "./ndc-clickhouse-cli-x86_64-unknown-linux-musl"
+        to: "hasura-clickhouse"
\ No newline at end of file
diff --git a/crates/client/Cargo.toml b/crates/client/Cargo.toml
new file mode 100644
index 0000000..903e4b4
--- /dev/null
+++ b/crates/client/Cargo.toml
@@ -0,0 +1,13 @@
+[package]
+name = "client"
+version.workspace = true
+edition.workspace = true
+
+[dependencies]
+config = { path = "../config" }
+reqwest = { version = "0.11.27", features = [
+  "json",
+  "rustls-tls",
+], default-features = false }
+serde = { version = "1.0.197", features = ["derive"] }
+serde_json = "1.0.114"
diff --git a/src/connector/client.rs b/crates/client/src/lib.rs
similarity index 98%
rename from src/connector/client.rs
rename to crates/client/src/lib.rs
index 1934b60..5404486 100644
--- a/src/connector/client.rs
+++ b/crates/client/src/lib.rs
@@ -1,9 +1,8 @@
 use std::error::Error;
 
+use config::ConnectionConfig;
 use serde::{de::DeserializeOwned, Deserialize};
 
-use super::config::ConnectionConfig;
-
 pub fn get_http_client(
     _connection_config: &ConnectionConfig,
 ) -> Result<reqwest::Client, Box<dyn Error>> {
diff --git a/crates/config/Cargo.toml b/crates/config/Cargo.toml
new file mode 100644
index 0000000..12f0f56
--- /dev/null
+++ b/crates/config/Cargo.toml
@@ -0,0 +1,8 @@
+[package]
+name = "config"
+version.workspace = true
+edition.workspace = true
+
+[dependencies]
+schemars = "0.8.16"
+serde = { version = "1.0.197", features = ["derive"] }
diff --git a/crates/config/src/lib.rs b/crates/config/src/lib.rs
new file mode 100644
index 0000000..cd5be14
--- /dev/null
+++ b/crates/config/src/lib.rs
@@ -0,0 +1,45 @@
+use schemars::JsonSchema;
+use serde::{Deserialize, Serialize};
+
+#[derive(Debug, Default, Clone, Serialize, Deserialize, JsonSchema)]
+pub struct ServerConfigFile {
+    pub tables: Vec<TableConfig>,
+}
+
+#[derive(Debug, Default, Clone, Serialize, Deserialize, JsonSchema)]
+pub struct ServerConfig {
+    /// the connection part of the config is not part of the config file
+    pub connection: ConnectionConfig,
+    pub tables: Vec<TableConfig>,
+}
+
+#[derive(Debug, Default, Clone, Serialize, Deserialize, JsonSchema)]
+pub struct ConnectionConfig {
+    pub username: String,
+    pub password: String,
+    pub url: String,
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
+pub struct TableConfig {
+    pub name: String,
+    pub schema: String,
+    pub alias: String,
+    pub primary_key: Option<PrimaryKey>,
+    pub columns: Vec<ColumnConfig>,
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
+pub struct PrimaryKey {
+    pub name: String,
+    pub columns: Vec<String>,
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
+pub struct ColumnConfig {
+    pub name: String,
+    pub alias: String,
+    pub data_type: String,
+}
+
+pub const CONFIG_FILE_NAME: &str = "configuration.json";
diff --git a/crates/ndc-clickhouse-cli/Cargo.toml b/crates/ndc-clickhouse-cli/Cargo.toml
new file mode 100644
index 0000000..6c636e1
--- /dev/null
+++ b/crates/ndc-clickhouse-cli/Cargo.toml
@@ -0,0 +1,12 @@
+[package]
+name = "ndc-clickhouse-cli"
+version.workspace = true
+edition.workspace = true
+
+[dependencies]
+clap = { version = "4.5.3", features = ["derive", "env"] }
+client = { path = "../client" }
+config = { path = "../config" }
+serde = { version = "1.0.197", features = ["derive"] }
+serde_json = "1.0.114"
+tokio = { version = "1.36.0", features = ["macros", "rt-multi-thread", "fs"] }
diff --git a/src/connector/config/database_introspection.rs b/crates/ndc-clickhouse-cli/src/database_introspection.rs
similarity index 94%
rename from src/connector/config/database_introspection.rs
rename to crates/ndc-clickhouse-cli/src/database_introspection.rs
index ec7817d..9e2ba28 100644
--- a/src/connector/config/database_introspection.rs
+++ b/crates/ndc-clickhouse-cli/src/database_introspection.rs
@@ -2,7 +2,7 @@ use std::error::Error;
 
 use serde::{Deserialize, Serialize};
 
-use crate::connector::client::{execute_query, get_http_client};
+use client::{execute_query, get_http_client};
 
 use super::ConnectionConfig;
 
diff --git a/src/connector/config/database_introspection.sql b/crates/ndc-clickhouse-cli/src/database_introspection.sql
similarity index 100%
rename from src/connector/config/database_introspection.sql
rename to crates/ndc-clickhouse-cli/src/database_introspection.sql
diff --git a/crates/ndc-clickhouse-cli/src/main.rs b/crates/ndc-clickhouse-cli/src/main.rs
new file mode 100644
index 0000000..3fab7fe
--- /dev/null
+++ b/crates/ndc-clickhouse-cli/src/main.rs
@@ -0,0 +1,232 @@
+use std::{
+    env,
+    error::Error,
+    path::{Path, PathBuf},
+};
+
+use clap::{Parser, Subcommand, ValueEnum};
+use config::{
+    ColumnConfig, ConnectionConfig, PrimaryKey, ServerConfigFile, TableConfig, CONFIG_FILE_NAME,
+};
+use database_introspection::{introspect_database, ColumnInfo, TableInfo};
+use tokio::fs;
+mod database_introspection;
+
+#[derive(Parser)]
+struct CliArgs {
+    /// The PAT token which can be used to make authenticated calls to Hasura Cloud
+    #[arg(long = "ddn-pat", value_name = "PAT", env = "HASURA_PLUGIN_DDN_PAT")]
+    ddn_pat: Option<String>,
+    /// If the plugins are sending any sort of telemetry back to Hasura, it should be disabled if this is true.
+    #[arg(long = "disable-telemetry", env = "HASURA_PLUGIN_DISABLE_TELEMETRY")]
+    disable_telemetry: bool,
+    /// A UUID for every unique user. Can be used in telemetry
+    #[arg(
+        long = "instance-id",
+        value_name = "ID",
+        env = "HASURA_PLUGIN_INSTANCE_ID"
+    )]
+    instance_id: Option<String>,
+    /// A UUID unique to every invocation of Hasura CLI
+    #[arg(
+        long = "execution-id",
+        value_name = "ID",
+        env = "HASURA_PLUGIN_EXECUTION_ID"
+    )]
+    execution_id: Option<String>,
+    #[arg(
+        long = "log-level",
+        value_name = "LEVEL",
+        env = "HASURA_PLUGIN_LOG_LEVEL",
+        default_value = "info"
+    )]
+    log_level: LogLevel,
+    /// Fully qualified path to the context directory of the connector
+    #[arg(
+        long = "connector-context-path",
+        value_name = "PATH",
+        env = "HASURA_PLUGIN_CONNECTOR_CONTEXT_PATH"
+    )]
+    context_path: Option<PathBuf>,
+    #[arg(long = "clickhouse-url", value_name = "URL", env = "CLICKHOUSE_URL")]
+    clickhouse_url: String,
+    #[arg(long = "clickhouse-username", value_name = "USERNAME", env = "CLICKHOUSE_USERNAME", default_value_t = String::from("default"))]
+    clickhouse_username: String,
+    #[arg(
+        long = "clickhouse-password",
+        value_name = "PASSWORD",
+        env = "CLICKHOUSE_PASSWORD"
+    )]
+    clickhouse_password: String,
+    #[command(subcommand)]
+    command: Command,
+}
+
+#[derive(Clone, Subcommand)]
+enum Command {
+    Init {},
+    Update {},
+    Validate {},
+    Watch {},
+}
+
+#[derive(Clone, ValueEnum)]
+enum LogLevel {
+    Panic,
+    Fatal,
+    Error,
+    Warn,
+    Info,
+    Debug,
+    Trace,
+}
+
+struct Context {
+    context_path: PathBuf,
+    connection: ConnectionConfig,
+}
+
+#[tokio::main]
+async fn main() -> Result<(), Box<dyn Error>> {
+    let args = CliArgs::parse();
+
+    let context_path = match args.context_path {
+        None => env::current_dir()?,
+        Some(path) => path,
+    };
+
+    let connection = ConnectionConfig {
+        url: args.clickhouse_url,
+        username: args.clickhouse_username,
+        password: args.clickhouse_password,
+    };
+
+    let context = Context {
+        context_path,
+        connection,
+    };
+
+    match args.command {
+        Command::Init {} => {
+            update_tables_config(&context.context_path, &context.connection).await?;
+        }
+        Command::Update {} => {
+            update_tables_config(&context.context_path, &context.connection).await?;
+        }
+        Command::Validate {} => {
+            todo!("implement validate command")
+        }
+        Command::Watch {} => {
+            todo!("implement watch command")
+        }
+    }
+
+    Ok(())
+}
+
+pub async fn update_tables_config(
+    configuration_dir: impl AsRef<Path> + Send,
+    connection_config: &ConnectionConfig,
+) -> Result<(), Box<dyn Error>> {
+    let table_infos = introspect_database(connection_config).await?;
+
+    let file_path = configuration_dir.as_ref().join(CONFIG_FILE_NAME);
+
+    let old_config = match fs::read_to_string(&file_path).await {
+        Ok(file) => serde_json::from_str(&file)
+            .map_err(|err| format!("Error parsing {CONFIG_FILE_NAME}: {err}")),
+        Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(ServerConfigFile::default()),
+        Err(_) => Err(format!("Error reading {CONFIG_FILE_NAME}")),
+    }?;
+
+    let tables = table_infos
+        .iter()
+        .map(|table| {
+            let old_table_config = get_old_table_config(table, &old_config.tables);
+
+            TableConfig {
+                name: table.table_name.to_owned(),
+                schema: table.table_schema.to_owned(),
+                alias: get_table_alias(table, &old_table_config),
+                primary_key: table.primary_key.as_ref().map(|primary_key| PrimaryKey {
+                    name: primary_key.to_owned(),
+                    columns: table
+                        .columns
+                        .iter()
+                        .filter_map(|column| {
+                            if column.is_in_primary_key {
+                                Some(get_column_alias(
+                                    column,
+                                    &get_old_column_config(column, &old_table_config),
+                                ))
+                            } else {
+                                None
+                            }
+                        })
+                        .collect(),
+                }),
+                columns: table
+                    .columns
+                    .iter()
+                    .map(|column| ColumnConfig {
+                        name: column.column_name.to_owned(),
+                        alias: get_column_alias(
+                            column,
+                            &get_old_column_config(column, &old_table_config),
+                        ),
+                        data_type: column.data_type.to_owned(),
+                    })
+                    .collect(),
+            }
+        })
+        .collect();
+
+    let config = ServerConfigFile { tables };
+
+    fs::write(&file_path, serde_json::to_string(&config)?).await?;
+
+    Ok(())
+}
+
+fn get_old_table_config<'a>(
+    table: &TableInfo,
+    old_tables: &'a [TableConfig],
+) -> Option<&'a TableConfig> {
+    old_tables.iter().find(|old_table| {
+        old_table.name == table.table_name && old_table.schema == table.table_schema
+    })
+}
+
+fn get_old_column_config<'a>(
+    column: &ColumnInfo,
+    old_table: &Option<&'a TableConfig>,
+) -> Option<&'a ColumnConfig> {
+    old_table
+        .map(|old_table| {
+            old_table
+                .columns
+                .iter()
+                .find(|old_column| old_column.name == column.column_name)
+        })
+        .flatten()
+}
+
+fn get_table_alias(table: &TableInfo, old_table: &Option<&TableConfig>) -> String {
+    // to preserve any customization, aliases are kept throught updates
+    if let Some(old_table) = old_table {
+        old_table.alias.to_owned()
+    } else if table.table_schema == "default" {
+        table.table_name.to_owned()
+    } else {
+        format!("{}_{}", table.table_schema, table.table_name)
+    }
+}
+
+fn get_column_alias(column: &ColumnInfo, old_column: &Option<&ColumnConfig>) -> String {
+    // to preserve any customization, aliases are kept throught updates
+    if let Some(old_column) = old_column {
+        old_column.alias.to_owned()
+    } else {
+        column.column_name.to_owned()
+    }
+}
diff --git a/crates/ndc-clickhouse/Cargo.toml b/crates/ndc-clickhouse/Cargo.toml
new file mode 100644
index 0000000..320c90a
--- /dev/null
+++ b/crates/ndc-clickhouse/Cargo.toml
@@ -0,0 +1,21 @@
+[package]
+name = "ndc-clickhouse"
+version.workspace = true
+edition.workspace = true
+
+[dependencies]
+async-trait = "0.1.78"
+client = { path = "../client" }
+config = { path = "../config" }
+indexmap = "2.1.0"
+ndc-sdk = { git = "https://github.com/hasura/ndc-hub.git", rev = "4cb500e", package = "ndc-sdk" }
+peg = "0.8.2"
+prometheus = "0.13.3"
+reqwest = { version = "0.11.27", features = [
+  "json",
+  "rustls-tls",
+], default-features = false }
+serde = { version = "1.0.197", features = ["derive"] }
+serde_json = "1.0.114"
+strum = { version = "0.26.2", features = ["derive"] }
+tokio = "1.36.0"
diff --git a/crates/ndc-clickhouse/src/connector.rs b/crates/ndc-clickhouse/src/connector.rs
new file mode 100644
index 0000000..8bb3ac7
--- /dev/null
+++ b/crates/ndc-clickhouse/src/connector.rs
@@ -0,0 +1,171 @@
+pub mod handler;
+pub mod state;
+
+use std::{env, path::Path};
+use tokio::fs;
+
+use async_trait::async_trait;
+use ndc_sdk::{
+    connector::{
+        Connector, ConnectorSetup, ExplainError, FetchMetricsError, HealthError,
+        InitializationError, LocatedError, MutationError, ParseError, QueryError, SchemaError,
+    },
+    json_response::JsonResponse,
+    models,
+};
+
+use self::state::ServerState;
+use config::{ConnectionConfig, ServerConfig, ServerConfigFile, CONFIG_FILE_NAME};
+
+#[derive(Debug, Clone, Default)]
+pub struct ClickhouseConnector;
+
+#[async_trait]
+impl ConnectorSetup for ClickhouseConnector {
+    type Connector = Self;
+
+    async fn parse_configuration(
+        &self,
+        configuration_dir: impl AsRef<Path> + Send,
+    ) -> Result<<Self as Connector>::Configuration, ParseError> {
+        read_server_config(configuration_dir).await
+    }
+
+    async fn try_init_state(
+        &self,
+        configuration: &<Self as Connector>::Configuration,
+        _metrics: &mut prometheus::Registry,
+    ) -> Result<<Self as Connector>::State, InitializationError> {
+        Ok(ServerState::new(configuration))
+    }
+}
+
+#[async_trait]
+impl Connector for ClickhouseConnector {
+    type Configuration = ServerConfig;
+    type State = ServerState;
+
+    fn fetch_metrics(
+        _configuration: &Self::Configuration,
+        _state: &Self::State,
+    ) -> Result<(), FetchMetricsError> {
+        Ok(())
+    }
+
+    async fn health_check(
+        configuration: &Self::Configuration,
+        state: &Self::State,
+    ) -> Result<(), HealthError> {
+        let client = state
+            .client(configuration)
+            .await
+            .map_err(|err| HealthError::Other(err.to_string().into()))?;
+
+        client::ping(&client, &configuration.connection)
+            .await
+            .map_err(|err| HealthError::Other(err.to_string().into()))?;
+
+        Ok(())
+    }
+
+    async fn get_capabilities() -> JsonResponse<models::CapabilitiesResponse> {
+        JsonResponse::Value(handler::capabilities())
+    }
+
+    async fn get_schema(
+        configuration: &Self::Configuration,
+    ) -> Result<JsonResponse<models::SchemaResponse>, SchemaError> {
+        handler::schema(configuration)
+            .await
+            .map(JsonResponse::Value)
+    }
+
+    async fn query_explain(
+        configuration: &Self::Configuration,
+        state: &Self::State,
+        request: models::QueryRequest,
+    ) -> Result<JsonResponse<models::ExplainResponse>, ExplainError> {
+        handler::explain(configuration, state, request)
+            .await
+            .map(JsonResponse::Value)
+            .map_err(|err| ExplainError::Other(err.to_string().into()))
+    }
+
+    async fn mutation_explain(
+        _configuration: &Self::Configuration,
+        _state: &Self::State,
+        _request: models::MutationRequest,
+    ) -> Result<JsonResponse<models::ExplainResponse>, ExplainError> {
+        Err(ExplainError::UnsupportedOperation(
+            "mutation explain not supported".to_string(),
+        ))
+    }
+
+    async fn mutation(
+        _configuration: &Self::Configuration,
+        _state: &Self::State,
+        _request: models::MutationRequest,
+    ) -> Result<JsonResponse<models::MutationResponse>, MutationError> {
+        Err(MutationError::UnsupportedOperation(
+            "mutation not supported".to_string(),
+        ))
+    }
+
+    async fn query(
+        configuration: &Self::Configuration,
+        state: &Self::State,
+        request: models::QueryRequest,
+    ) -> Result<JsonResponse<models::QueryResponse>, QueryError> {
+        handler::query(configuration, state, request)
+            .await
+            .map(JsonResponse::Value)
+            .map_err(|err| QueryError::Other(err.to_string().into()))
+    }
+}
+
+/// read server configuration from env var
+pub async fn read_server_config(
+    configuration_dir: impl AsRef<Path> + Send,
+) -> Result<ServerConfig, ParseError> {
+    let connection = get_connection_config()?;
+
+    let file_path = configuration_dir.as_ref().join(CONFIG_FILE_NAME);
+
+    let config_file = fs::read_to_string(&file_path)
+        .await
+        .map_err(|err| match err.kind() {
+            std::io::ErrorKind::NotFound => {
+                ParseError::CouldNotFindConfiguration(file_path.to_owned())
+            }
+            _ => ParseError::IoError(err),
+        })?;
+
+    let ServerConfigFile { tables } = serde_json::from_str::<ServerConfigFile>(&config_file)
+        .map_err(|err| {
+            ParseError::ParseError(LocatedError {
+                file_path,
+                line: err.line(),
+                column: err.column(),
+                message: err.to_string(),
+            })
+        })?;
+
+    Ok(ServerConfig { connection, tables })
+}
+
+fn get_connection_config() -> Result<ConnectionConfig, ParseError> {
+    // define what the new configuration will look like
+    // assemble config from env vars and reading files in config directory
+    let url = env::var("CLICKHOUSE_URL")
+        .map_err(|_err| ParseError::Other("CLICKHOUSE_URL env var must be set".into()))?;
+    let username = env::var("CLICKHOUSE_USERNAME")
+        .map_err(|_err| ParseError::Other("CLICKHOUSE_USERNAME env var must be set".into()))?;
+    let password = env::var("CLICKHOUSE_PASSWORD")
+        .map_err(|_err| ParseError::Other("CLICKHOUSE_PASSWORD env var must be set".into()))?;
+
+    Ok(ConnectionConfig {
+        url,
+        username,
+        password,
+    })
+}
diff --git a/crates/ndc-clickhouse/src/connector/config.rs b/crates/ndc-clickhouse/src/connector/config.rs
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/crates/ndc-clickhouse/src/connector/config.rs
@@ -0,0 +1 @@
+
diff --git a/src/connector/handler.rs b/crates/ndc-clickhouse/src/connector/handler.rs
similarity index 69%
rename from src/connector/handler.rs
rename to crates/ndc-clickhouse/src/connector/handler.rs
index 955080c..16c3abd 100644
--- a/src/connector/handler.rs
+++ b/crates/ndc-clickhouse/src/connector/handler.rs
@@ -2,10 +2,8 @@ mod capabilities;
 mod explain;
 mod query;
 mod schema;
-mod update_configuration;
 
 pub use capabilities::*;
 pub use explain::*;
 pub use query::*;
 pub use schema::*;
-pub use update_configuration::*;
diff --git a/src/connector/handler/capabilities.rs b/crates/ndc-clickhouse/src/connector/handler/capabilities.rs
similarity index 72%
rename from src/connector/handler/capabilities.rs
rename to crates/ndc-clickhouse/src/connector/handler/capabilities.rs
index 0b8dda5..311aa79 100644
--- a/src/connector/handler/capabilities.rs
+++ b/crates/ndc-clickhouse/src/connector/handler/capabilities.rs
@@ -2,13 +2,17 @@ use ndc_sdk::models::{self, LeafCapability, RelationshipCapabilities};
 
 pub fn capabilities() -> models::CapabilitiesResponse {
     models::CapabilitiesResponse {
-        versions: "^0.1.1".to_string(),
+        version: "^0.1.1".to_string(),
         capabilities: models::Capabilities {
             query: models::QueryCapabilities {
                 aggregates: Some(LeafCapability {}),
                 variables: Some(LeafCapability {}),
+                explain: Some(LeafCapability {}),
+            },
+            mutation: models::MutationCapabilities {
+                transactional: None,
+                explain: None,
             },
-            explain: Some(LeafCapability {}),
             relationships: Some(RelationshipCapabilities {
                 relation_comparisons: Some(LeafCapability {}),
                 order_by_aggregate: Some(LeafCapability {}),
diff --git a/src/connector/handler/explain.rs b/crates/ndc-clickhouse/src/connector/handler/explain.rs
similarity index 93%
rename from src/connector/handler/explain.rs
rename to crates/ndc-clickhouse/src/connector/handler/explain.rs
index 77e7cba..2dc9d4a 100644
--- a/src/connector/handler/explain.rs
+++ b/crates/ndc-clickhouse/src/connector/handler/explain.rs
@@ -1,12 +1,11 @@
 use std::collections::BTreeMap;
 
+use client::execute_query;
+use config::ServerConfig;
 use ndc_sdk::{connector::ExplainError, models};
 use serde::{Deserialize, Serialize};
 
-use crate::{
-    connector::{client::execute_query, config::ServerConfig, state::ServerState},
-    sql::QueryBuilder,
-};
+use crate::{connector::state::ServerState, sql::QueryBuilder};
 
 #[derive(Debug, Serialize, Deserialize)]
 struct ExplainRow {
diff --git a/src/connector/handler/query.rs b/crates/ndc-clickhouse/src/connector/handler/query.rs
similarity index 88%
rename from src/connector/handler/query.rs
rename to crates/ndc-clickhouse/src/connector/handler/query.rs
index 8db61ae..33e3322 100644
--- a/src/connector/handler/query.rs
+++ b/crates/ndc-clickhouse/src/connector/handler/query.rs
@@ -1,9 +1,8 @@
+use client::execute_query;
+use config::ServerConfig;
 use ndc_sdk::{connector::QueryError, models};
 
-use crate::{
-    connector::{client::execute_query, config::ServerConfig, state::ServerState},
-    sql::QueryBuilder,
-};
+use crate::{connector::state::ServerState, sql::QueryBuilder};
 
 pub async fn query(
     configuration: &ServerConfig,
diff --git a/src/connector/handler/schema.rs b/crates/ndc-clickhouse/src/connector/handler/schema.rs
similarity index 98%
rename from src/connector/handler/schema.rs
rename to crates/ndc-clickhouse/src/connector/handler/schema.rs
index bf2369a..4d88085 100644
--- a/src/connector/handler/schema.rs
+++ b/crates/ndc-clickhouse/src/connector/handler/schema.rs
@@ -1,11 +1,9 @@
+use config::{PrimaryKey, ServerConfig};
 use ndc_sdk::{connector::SchemaError, models};
 use std::{collections::BTreeMap, str::FromStr};
 use strum::IntoEnumIterator;
 
-use crate::{
-    connector::config::{PrimaryKey, ServerConfig},
-    schema::{ClickHouseScalarType, ClickhouseDataType, Identifier},
-};
+use crate::schema::{ClickHouseScalarType, ClickhouseDataType, Identifier};
 
 pub async fn schema(configuration: &ServerConfig) -> Result<models::SchemaResponse, SchemaError> {
     let scalar_types = BTreeMap::from_iter(ClickHouseScalarType::iter().map(|scalar_type| {
diff --git a/src/connector/state.rs b/crates/ndc-clickhouse/src/connector/state.rs
similarity index 95%
rename from src/connector/state.rs
rename to crates/ndc-clickhouse/src/connector/state.rs
index 76a2ac2..18adfa9 100644
--- a/src/connector/state.rs
+++ b/crates/ndc-clickhouse/src/connector/state.rs
@@ -1,9 +1,9 @@
 use std::{error::Error, sync::Arc};
 
+use client::get_http_client;
+use config::ServerConfig;
 use tokio::sync::RwLock;
 
-use super::{client::get_http_client, config::ServerConfig};
-
 #[derive(Debug, Clone)]
 pub struct ServerState {
     client: Arc<RwLock<Option<reqwest::Client>>>,
diff --git a/src/lib.rs b/crates/ndc-clickhouse/src/lib.rs
similarity index 100%
rename from src/lib.rs
rename to crates/ndc-clickhouse/src/lib.rs
diff --git a/src/main.rs b/crates/ndc-clickhouse/src/main.rs
similarity index 72%
rename from src/main.rs
rename to crates/ndc-clickhouse/src/main.rs
index 67d5da9..ee4e011 100644
--- a/src/main.rs
+++ b/crates/ndc-clickhouse/src/main.rs
@@ -1,8 +1,4 @@
-mod connector;
-mod schema;
-mod sql;
-
-use connector::ClickhouseConnector;
+use ndc_clickhouse::connector::ClickhouseConnector;
 use ndc_sdk::default_main::default_main;
 
 use std::error::Error;
diff --git a/src/schema.rs b/crates/ndc-clickhouse/src/schema.rs
similarity index 100%
rename from src/schema.rs
rename to crates/ndc-clickhouse/src/schema.rs
diff --git a/src/schema/binary_comparison_operator.rs b/crates/ndc-clickhouse/src/schema/binary_comparison_operator.rs
similarity index 80%
rename from src/schema/binary_comparison_operator.rs
rename to crates/ndc-clickhouse/src/schema/binary_comparison_operator.rs
index 920133b..75b1d2e 100644
--- a/src/schema/binary_comparison_operator.rs
+++ b/crates/ndc-clickhouse/src/schema/binary_comparison_operator.rs
@@ -4,13 +4,15 @@ use crate::sql::ast::{BinaryOperator, Expr, Function};
 
 #[derive(Debug, Clone, EnumString, Display, EnumIter)]
 pub enum ClickHouseBinaryComparisonOperator {
-    #[strum(to_string = "_gt", serialize = "greater_than")]
+    #[strum(to_string = "_eq")]
+    Eq,
+    #[strum(to_string = "_gt")]
     Gt,
-    #[strum(to_string = "_lt", serialize = "less_than")]
+    #[strum(to_string = "_lt")]
     Lt,
-    #[strum(to_string = "_gte", serialize = "greater_than_or_equal")]
+    #[strum(to_string = "_gte")]
     GtEq,
-    #[strum(to_string = "_lte", serialize = "less_than_or_equal")]
+    #[strum(to_string = "_lte")]
     LtEq,
     #[strum(to_string = "_neq")]
     NotEq,
@@ -22,6 +24,10 @@ pub enum ClickHouseBinaryComparisonOperator {
     ILike,
     #[strum(to_string = "_nilike")]
     NotILike,
+    #[strum(to_string = "_in")]
+    In,
+    #[strum(to_string = "_nin")]
+    NotIn,
     #[strum(to_string = "_match")]
     Match,
 }
@@ -43,6 +49,7 @@ impl ClickHouseBinaryComparisonOperator {
         use ClickHouseBinaryComparisonOperator as CBO;
 
         match self {
+            CBO::Eq => apply_operator(BinaryOperator::Eq, left, right),
             CBO::Gt => apply_operator(BinaryOperator::Gt, left, right),
             CBO::Lt => apply_operator(BinaryOperator::Lt, left, right),
             CBO::GtEq => apply_operator(BinaryOperator::GtEq, left, right),
@@ -52,6 +59,8 @@ impl ClickHouseBinaryComparisonOperator {
             CBO::NotLike => apply_operator(BinaryOperator::NotLike, left, right),
             CBO::ILike => apply_operator(BinaryOperator::ILike, left, right),
             CBO::NotILike => apply_operator(BinaryOperator::NotILike, left, right),
+            CBO::In => apply_operator(BinaryOperator::In, left, right),
+            CBO::NotIn => apply_operator(BinaryOperator::NotIn, left, right),
             CBO::Match => apply_function("match", left, right),
         }
     }
diff --git a/src/schema/clickhouse_data_type.rs b/crates/ndc-clickhouse/src/schema/clickhouse_data_type.rs
similarity index 100%
rename from src/schema/clickhouse_data_type.rs
rename to crates/ndc-clickhouse/src/schema/clickhouse_data_type.rs
diff --git a/src/schema/scalar_type.rs b/crates/ndc-clickhouse/src/schema/scalar_type.rs
similarity index 91%
rename from src/schema/scalar_type.rs
rename to crates/ndc-clickhouse/src/schema/scalar_type.rs
index 4998570..729fd11 100644
--- a/src/schema/scalar_type.rs
+++ b/crates/ndc-clickhouse/src/schema/scalar_type.rs
@@ -262,11 +262,14 @@ impl ClickHouseScalarType {
         use ClickHouseBinaryComparisonOperator as BC;
         use ClickHouseScalarType as ST;
         let base_operators = vec![
+            (BC::Eq, self.to_owned()),
             (BC::Gt, self.to_owned()),
             (BC::Lt, self.to_owned()),
             (BC::GtEq, self.to_owned()),
             (BC::LtEq, self.to_owned()),
             (BC::NotEq, self.to_owned()),
+            (BC::In, self.to_owned()),
+            (BC::NotIn, self.to_owned()),
         ];
 
         BTreeMap::from_iter(
@@ -279,9 +282,20 @@ impl ClickHouseScalarType {
             .map(|(name, argument_type)| {
                 (
                     name.to_string(),
-                    models::ComparisonOperatorDefinition {
-                        argument_type: models::Type::Named {
-                            name: argument_type.to_string(),
+                    match name {
+                        BC::Eq => models::ComparisonOperatorDefinition::Equal,
+                        BC::In => models::ComparisonOperatorDefinition::In,
+                        BC::NotIn => models::ComparisonOperatorDefinition::Custom {
+                            argument_type: models::Type::Array {
+                                element_type: Box::new(models::Type::Named {
+                                    name: argument_type.to_string(),
+                                }),
+                            },
+                        },
+                        _ => models::ComparisonOperatorDefinition::Custom {
+                            argument_type: models::Type::Named {
+                                name: argument_type.to_string(),
+                            },
                         },
                     },
                 )
diff --git a/src/schema/single_column_aggregate_function.rs b/crates/ndc-clickhouse/src/schema/single_column_aggregate_function.rs
similarity index 100%
rename from src/schema/single_column_aggregate_function.rs
rename to crates/ndc-clickhouse/src/schema/single_column_aggregate_function.rs
diff --git a/src/sql.rs b/crates/ndc-clickhouse/src/sql.rs
similarity index 100%
rename from src/sql.rs
rename to crates/ndc-clickhouse/src/sql.rs
diff --git a/src/sql/ast.rs b/crates/ndc-clickhouse/src/sql/ast.rs
similarity index 95%
rename from src/sql/ast.rs
rename to crates/ndc-clickhouse/src/sql/ast.rs
index ebdfb16..d9015a5 100644
--- a/src/sql/ast.rs
+++ b/crates/ndc-clickhouse/src/sql/ast.rs
@@ -458,29 +458,13 @@ pub enum Expr {
         op: BinaryOperator,
         right: Box<Expr>,
     },
-    UnaryOp {
-        op: UnaryOperator,
-        expr: Box<Expr>,
-    },
+    Not(Box<Expr>),
     Nested(Box<Expr>),
     Value(Value),
     Parameter(Parameter),
     Function(Function),
     Lambda(Lambda),
-    IsFalse(Box<Expr>),
-    IsNotFalse(Box<Expr>),
-    IsTrue(Box<Expr>),
-    IsNotTrue(Box<Expr>),
-    IsNull(Box<Expr>),
-    IsNotNull(Box<Expr>),
-    InList {
-        expr: Box<Expr>,
-        list: Vec<Expr>,
-    },
-    NotInList {
-        expr: Box<Expr>,
-        list: Vec<Expr>,
-    },
+    List(Vec<Expr>),
 }
 
 impl Expr {
@@ -516,7 +500,7 @@ impl fmt::Display for Expr {
             Expr::Identifier(ident) => write!(f, "{}", ident),
             Expr::CompoundIdentifier(idents) => write!(f, "{}", display_separated(idents, ".")),
             Expr::BinaryOp { left, op, right } => write!(f, "{} {} {}", left, op, right),
-            Expr::UnaryOp { op, expr } => write!(f, "{} {}", op, expr),
+            Expr::Not(expr) => write!(f, "NOT {expr}"),
             Expr::Nested(expr) => write!(f, "({})", expr),
             Expr::Value(value) => write!(f, "{}", value),
             Expr::Parameter(p) => match p {
@@ -530,18 +514,7 @@ impl fmt::Display for Expr {
             },
             Expr::Function(function) => write!(f, "{}", function),
             Expr::Lambda(lambda) => write!(f, "{}", lambda),
-            Expr::IsTrue(expr) => write!(f, "{expr} IS TRUE"),
-            Expr::IsNotTrue(expr) => write!(f, "{expr} IS NOT TRUE"),
-            Expr::IsFalse(expr) => write!(f, "{expr} IS FALSE"),
-            Expr::IsNotFalse(expr) => write!(f, "{expr} IS NOT FALSE"),
-            Expr::IsNull(expr) => write!(f, "{expr} IS NULL"),
-            Expr::IsNotNull(expr) => write!(f, "{expr} IS NOT NULL"),
-            Expr::InList { expr, list } => {
-                write!(f, "{} IN ({})", expr, display_separated(list, ", "),)
-            }
-            Expr::NotInList { expr, list } => {
-                write!(f, "{} NOT IN ({})", expr, display_separated(list, ", "),)
-            }
+            Expr::List(list) => write!(f, "({})", display_separated(list, ", ")),
         }
     }
 }
@@ -721,6 +694,10 @@ pub enum BinaryOperator {
     NotILike,
     And,
     Or,
+    In,
+    NotIn,
+    Is,
+    IsNot,
 }
 
 impl fmt::Display for BinaryOperator {
@@ -738,6 +715,10 @@ impl fmt::Display for BinaryOperator {
             BinaryOperator::NotILike => write!(f, "NOT ILIKE"),
             BinaryOperator::And => write!(f, "AND"),
             BinaryOperator::Or => write!(f, "OR"),
+            BinaryOperator::In => write!(f, "IN"),
+            BinaryOperator::NotIn => write!(f, "NOT IN"),
+            BinaryOperator::Is => write!(f, "IS"),
+            BinaryOperator::IsNot => write!(f, "IS NOT"),
         }
     }
 }
@@ -750,6 +731,12 @@ pub enum Value {
     Null,
 }
 
+impl Value {
+    pub fn into_expr(self) -> Expr {
+        Expr::Value(self)
+    }
+}
+
 impl From<serde_json::Value> for Value {
     fn from(value: serde_json::Value) -> Self {
         match value {
diff --git a/src/sql/ast/parameter_extractor.rs b/crates/ndc-clickhouse/src/sql/ast/parameter_extractor.rs
similarity index 87%
rename from src/sql/ast/parameter_extractor.rs
rename to crates/ndc-clickhouse/src/sql/ast/parameter_extractor.rs
index 20b0eee..6113fbf 100644
--- a/src/sql/ast/parameter_extractor.rs
+++ b/crates/ndc-clickhouse/src/sql/ast/parameter_extractor.rs
@@ -72,29 +72,16 @@ impl ParameterExtractor {
             Expr::BinaryOp { left, op: _, right } => {
                 self.visit_expr(left);
                 self.visit_expr(right);
-            }
-            Expr::UnaryOp { op: _, expr } => self.visit_expr(expr),
+            },
+            Expr::Not(expr) => self.visit_expr(expr),
             Expr::Nested(expr) => self.visit_expr(expr),
             Expr::Value(_) => {}
             Expr::Parameter(parameter) => self.visit_parameter(parameter),
             Expr::Function(function) => self.visit_function(function),
             Expr::Lambda(lambda) => self.visit_expr(&mut lambda.expr),
-            Expr::IsFalse(expr) => self.visit_expr(expr),
-            Expr::IsNotFalse(expr) => self.visit_expr(expr),
-            Expr::IsTrue(expr) => self.visit_expr(expr),
-            Expr::IsNotTrue(expr) => self.visit_expr(expr),
-            Expr::IsNull(expr) => self.visit_expr(expr),
-            Expr::IsNotNull(expr) => self.visit_expr(expr),
-            Expr::InList { expr, list } => {
-                self.visit_expr(expr);
-                for element in list.iter_mut() {
-                    self.visit_expr(element)
-                }
-            }
-            Expr::NotInList { expr, list } => {
-                self.visit_expr(expr);
-                for element in list.iter_mut() {
-                    self.visit_expr(element)
+            Expr::List(list) => {
+                for expr in list.iter_mut() {
+                    self.visit_expr(expr)
                 }
             }
         }
diff --git a/src/sql/query_builder.rs b/crates/ndc-clickhouse/src/sql/query_builder.rs
similarity index 88%
rename from src/sql/query_builder.rs
rename to crates/ndc-clickhouse/src/sql/query_builder.rs
index beb96c0..0d1e625 100644
--- a/src/sql/query_builder.rs
+++ b/crates/ndc-clickhouse/src/sql/query_builder.rs
@@ -1,5 +1,6 @@
 use std::str::FromStr;
 
+use config::ServerConfig;
 use indexmap::IndexMap;
 use ndc_sdk::models;
 
@@ -12,10 +13,7 @@ pub use error::QueryBuilderError;
 use typecasting::{AggregatesTypeString, RowsTypeString};
 
 use super::ast::*;
-use crate::{
-    connector::config::ServerConfig,
-    schema::{ClickHouseBinaryComparisonOperator, ClickHouseSingleColumnAggregateFunction},
-};
+use crate::schema::{ClickHouseBinaryComparisonOperator, ClickHouseSingleColumnAggregateFunction};
 
 pub struct QueryBuilder<'r, 'c> {
     request: &'r models::QueryRequest,
@@ -303,10 +301,15 @@ impl<'r, 'c> QueryBuilder<'r, 'c> {
         if let Some(fields) = &query.fields {
             for (alias, field) in fields {
                 let expr = match field {
-                    models::Field::Column { column } => Expr::CompoundIdentifier(vec![
-                        Ident::new_quoted("_origin"),
-                        self.column_ident(column, current_collection)?,
-                    ]),
+                    models::Field::Column { column, fields } => {
+                        if fields.is_some() {
+                            todo!("support nested field selection")
+                        }
+                        Expr::CompoundIdentifier(vec![
+                            Ident::new_quoted("_origin"),
+                            self.column_ident(column, current_collection)?,
+                        ])
+                    }
                     models::Field::Relationship { .. } => Expr::CompoundIdentifier(vec![
                         Ident::new_quoted(format!("_rel_{alias}")),
                         Ident::new_quoted("_rowset"),
@@ -557,16 +560,24 @@ impl<'r, 'c> QueryBuilder<'r, 'c> {
 
                             let mut join_index = 1;
 
-                            let (predicate, predicate_joins) = self.filter_expression(
-                                &first_element.predicate,
-                                &join_alias,
-                                &relationship.target_collection,
-                                false,
-                                &mut join_index,
-                            )?;
+                            let mut additional_joins = vec![];
+                            let mut additional_predicate = vec![];
 
-                            let mut additional_joins = predicate_joins;
-                            let mut additional_predicate = vec![predicate];
+                            if let Some(expression) = &first_element.predicate {
+                                let (predicate, predicate_joins) = self.filter_expression(
+                                    expression,
+                                    &join_alias,
+                                    &relationship.target_collection,
+                                    false,
+                                    &mut join_index,
+                                )?;
+
+                                additional_predicate.push(predicate);
+
+                                for predicate_join in predicate_joins {
+                                    additional_joins.push(predicate_join);
+                                }
+                            }
 
                             let mut last_join_alias = join_alias;
                             let mut last_collection_name = &relationship.target_collection;
@@ -624,18 +635,20 @@ impl<'r, 'c> QueryBuilder<'r, 'c> {
 
                                 additional_joins.push(join);
 
-                                let (predicate, predicate_joins) = self.filter_expression(
-                                    &path_element.predicate,
-                                    &join_alias,
-                                    &relationship.target_collection,
-                                    false,
-                                    &mut join_index,
-                                )?;
+                                if let Some(expression) = &path_element.predicate {
+                                    let (predicate, predicate_joins) = self.filter_expression(
+                                        expression,
+                                        &join_alias,
+                                        &relationship.target_collection,
+                                        false,
+                                        &mut join_index,
+                                    )?;
 
-                                additional_predicate.push(predicate);
+                                    additional_predicate.push(predicate);
 
-                                for predicate_join in predicate_joins {
-                                    additional_joins.push(predicate_join);
+                                    for predicate_join in predicate_joins {
+                                        additional_joins.push(predicate_join);
+                                    }
                                 }
 
                                 last_join_alias = join_alias;
@@ -902,10 +915,7 @@ impl<'r, 'c> QueryBuilder<'r, 'c> {
                     current_is_origin,
                     name_index,
                 )?;
-                let not_expression = Expr::UnaryOp {
-                    op: UnaryOperator::Not,
-                    expr: expression.into_nested().into_box(),
-                };
+                let not_expression = Expr::Not(expression.into_nested().into_box());
                 Ok((not_expression, joins))
             }
             models::Expression::UnaryComparisonOperator { column, operator } => {
@@ -919,9 +929,11 @@ impl<'r, 'c> QueryBuilder<'r, 'c> {
 
                 let (expression, joins) = left_col.apply(|left_col| {
                     let expr = match operator {
-                        models::UnaryComparisonOperator::IsNull => {
-                            Expr::IsNull(left_col.into_nested().into_box())
-                        }
+                        models::UnaryComparisonOperator::IsNull => Expr::BinaryOp {
+                            left: left_col.into_nested().into_box(),
+                            op: BinaryOperator::Is,
+                            right: Value::Null.into_expr().into_box(),
+                        },
                     };
                     (expr, vec![])
                 });
@@ -933,29 +945,10 @@ impl<'r, 'c> QueryBuilder<'r, 'c> {
                 operator,
                 value,
             } => {
-                let custom_operator = match operator {
-                    models::BinaryComparisonOperator::Equal => None,
-                    models::BinaryComparisonOperator::Other { name } => {
-                        let operator =
-                            ClickHouseBinaryComparisonOperator::from_str(name).map_err(|_err| {
-                                QueryBuilderError::UnknownBinaryComparisonOperator(name.to_owned())
-                            })?;
-
-                        Some(operator)
-                    }
-                };
-
-                let apply_operator = |left: Expr, right: Expr| {
-                    if let Some(custom_operator) = custom_operator {
-                        custom_operator.apply(left, right)
-                    } else {
-                        Expr::BinaryOp {
-                            left: left.into_box(),
-                            op: BinaryOperator::Eq,
-                            right: right.into_box(),
-                        }
-                    }
-                };
+                let operator =
+                    ClickHouseBinaryComparisonOperator::from_str(operator).map_err(|_err| {
+                        QueryBuilderError::UnknownBinaryComparisonOperator(operator.to_owned())
+                    })?;
 
                 let left_col = self.comparison_column(
                     column,
@@ -965,6 +958,16 @@ impl<'r, 'c> QueryBuilder<'r, 'c> {
                     name_index,
                 )?;
 
+                // special case: right hand data types is assumed to always be the same type as left hand,
+                // except when the operator is IN/NOT IN, where the type is Array(<left hand data type>)
+                let right_col_type = match operator {
+                    ClickHouseBinaryComparisonOperator::In
+                    | ClickHouseBinaryComparisonOperator::NotIn => {
+                        format!("Array({})", left_col.data_type())
+                    }
+                    _ => left_col.data_type(),
+                };
+
                 let right_col = match value {
                     models::ComparisonValue::Column { column } => self.comparison_column(
                         column,
@@ -974,8 +977,8 @@ impl<'r, 'c> QueryBuilder<'r, 'c> {
                         name_index,
                     )?,
                     models::ComparisonValue::Scalar { value } => ComparisonColumn::new_simple(
-                        Parameter::new(value.into(), left_col.data_type()).into_expr(),
-                        left_col.data_type(),
+                        Parameter::new(value.into(), right_col_type.clone()).into_expr(),
+                        right_col_type,
                     ),
 
                     models::ComparisonValue::Variable { name } => ComparisonColumn::new_simple(
@@ -983,140 +986,19 @@ impl<'r, 'c> QueryBuilder<'r, 'c> {
                             Ident::new_quoted("_vars"),
                             Ident::new_quoted(format!("_var_{name}")),
                         ]),
-                        left_col.data_type(),
+                        right_col_type,
                     ),
                 };
 
                 let (expression, expression_joins) = right_col.apply(|right_col| {
                     left_col.apply(|left_col| {
-                        let expression = apply_operator(left_col, right_col);
+                        let expression = operator.apply(left_col, right_col);
                         (expression, vec![])
                     })
                 });
 
                 Ok((expression, expression_joins))
             }
-            models::Expression::BinaryArrayComparisonOperator {
-                column,
-                operator,
-                values,
-            } => {
-                let left_col = self.comparison_column(
-                    column,
-                    current_join_alias,
-                    current_collection,
-                    current_is_origin,
-                    name_index,
-                )?;
-
-                if values.iter().any(|value| match value {
-                    models::ComparisonValue::Column { .. } => true,
-                    models::ComparisonValue::Scalar { .. } => false,
-                    models::ComparisonValue::Variable { .. } => false,
-                }) {
-                    let right_cols = values
-                        .iter()
-                        .map(|value| match value {
-                            models::ComparisonValue::Column { column } => self.comparison_column(
-                                column,
-                                current_join_alias,
-                                current_collection,
-                                current_is_origin,
-                                name_index,
-                            ),
-                            models::ComparisonValue::Scalar { value } => {
-                                Ok(ComparisonColumn::new_simple(
-                                    Parameter::new(value.into(), left_col.data_type()).into_expr(),
-                                    left_col.data_type(),
-                                ))
-                            }
-                            models::ComparisonValue::Variable { name } => {
-                                Ok(ComparisonColumn::new_simple(
-                                    Expr::CompoundIdentifier(vec![
-                                        Ident::new_quoted("_vars"),
-                                        Ident::new_quoted(format!("_var_{name}")),
-                                    ]),
-                                    left_col.data_type(),
-                                ))
-                            }
-                        })
-                        .collect::<Result<Vec<_>, _>>()?;
-
-                    let (left_col, base_joins) = left_col.extract_joins();
-
-                    let mut additional_joins = vec![];
-                    let mut expressions = vec![];
-
-                    for right_col in right_cols {
-                        let (expression, expression_joins) = left_col.apply(|left_col| {
-                            right_col.apply(|right_col| match operator {
-                                models::BinaryArrayComparisonOperator::In => (
-                                    Expr::BinaryOp {
-                                        left: left_col.into_box(),
-                                        op: BinaryOperator::Eq,
-                                        right: right_col.into_box(),
-                                    },
-                                    vec![],
-                                ),
-                            })
-                        });
-
-                        additional_joins.push(expression_joins);
-                        expressions.push(expression);
-                    }
-
-                    let joins = base_joins
-                        .into_iter()
-                        .chain(additional_joins.into_iter().flatten())
-                        .collect();
-                    let expression = expressions
-                        .into_iter()
-                        .reduce(or_reducer)
-                        .unwrap_or(Expr::Value(Value::Boolean(false)));
-
-                    let expression = if values.len() > 1 {
-                        expression.into_nested()
-                    } else {
-                        expression
-                    };
-
-                    Ok((expression, joins))
-                } else {
-                    let list = values
-                        .iter()
-                        .map(|value| {
-                            Ok(match value {
-                                models::ComparisonValue::Column { .. } => {
-                                    return Err(QueryBuilderError::Unexpected("binary array comparisons with column comparisons are handled earlier and should not show up here".to_string()))
-                                }
-                                models::ComparisonValue::Scalar { value } => {
-                                    Parameter::new(value.into(), left_col.data_type()).into_expr()
-                                },
-
-                                models::ComparisonValue::Variable { name } => {
-                                    Expr::CompoundIdentifier(vec![
-                                        Ident::new_quoted("_vars"),
-                                        Ident::new_quoted(format!("_var_{name}")),
-                                    ])
-                                }
-                            })
-                        })
-                        .collect::<Result<Vec<_>, _>>()?;
-
-                    let (expression, expression_joins) = left_col.apply(|left_col| {
-                        let expression = match operator {
-                            models::BinaryArrayComparisonOperator::In => Expr::InList {
-                                expr: left_col.into_box(),
-                                list,
-                            },
-                        };
-
-                        (expression, vec![])
-                    });
-
-                    Ok((expression, expression_joins))
-                }
-            }
             models::Expression::Exists {
                 in_collection,
                 predicate,
@@ -1132,7 +1014,7 @@ impl<'r, 'c> QueryBuilder<'r, 'c> {
     fn filter_exists_expression(
         &self,
         in_collection: &models::ExistsInCollection,
-        expression: &models::Expression,
+        expression: &Option<Box<models::Expression>>,
         previous_join_alias: &Ident,
         previous_collection: &str,
         name_index: &mut u32,
@@ -1158,13 +1040,19 @@ impl<'r, 'c> QueryBuilder<'r, 'c> {
             let subquery_origin_alias = Ident::new_quoted(format!("_exists_{}", name_index));
             *name_index += 1;
 
-            let (predicate, predicate_joins) = self.filter_expression(
-                expression,
-                &subquery_origin_alias,
-                target_collection,
-                false,
-                name_index,
-            )?;
+            let (predicate, predicate_joins) = match expression {
+                Some(expression) => {
+                    let (predicate, predicate_joins) = self.filter_expression(
+                        expression,
+                        &subquery_origin_alias,
+                        target_collection,
+                        false,
+                        name_index,
+                    )?;
+                    (Some(predicate), predicate_joins)
+                }
+                None => (None, vec![]),
+            };
 
             let table = self
                 .collection_ident(target_collection)?
@@ -1237,7 +1125,7 @@ impl<'r, 'c> QueryBuilder<'r, 'c> {
             Query::new()
                 .select(select)
                 .from(from)
-                .predicate(Some(predicate))
+                .predicate(predicate)
                 .limit(limit)
                 .limit_by(limit_by)
         };
@@ -1403,16 +1291,24 @@ impl<'r, 'c> QueryBuilder<'r, 'c> {
 
                                 let mut join_index = 1;
 
-                                let (predicate, predicate_joins) = self.filter_expression(
-                                    &first_element.predicate,
-                                    &join_alias,
-                                    &relationship.target_collection,
-                                    false,
-                                    &mut join_index,
-                                )?;
+                                let mut additional_joins = vec![];
+                                let mut additional_predicate = vec![];
+
+                                if let Some(expression) = &first_element.predicate {
+                                    let (predicate, predicate_joins) = self.filter_expression(
+                                        expression,
+                                        &join_alias,
+                                        &relationship.target_collection,
+                                        false,
+                                        &mut join_index,
+                                    )?;
+
+                                    additional_predicate.push(predicate);
 
-                                let mut additional_joins = predicate_joins;
-                                let mut additional_predicate = vec![predicate];
+                                    for predicate_join in predicate_joins {
+                                        additional_joins.push(predicate_join);
+                                    }
+                                }
 
                                 let mut last_join_alias = join_alias;
                                 let mut last_collection_name = &relationship.target_collection;
@@ -1470,18 +1366,20 @@ impl<'r, 'c> QueryBuilder<'r, 'c> {
 
                                     additional_joins.push(join);
 
-                                    let (predicate, predicate_joins) = self.filter_expression(
-                                        &path_element.predicate,
-                                        &join_alias,
-                                        &relationship.target_collection,
-                                        false,
-                                        &mut join_index,
-                                    )?;
+                                    if let Some(expression) = &path_element.predicate {
+                                        let (predicate, predicate_joins) = self.filter_expression(
+                                            expression,
+                                            &join_alias,
+                                            &relationship.target_collection,
+                                            false,
+                                            &mut join_index,
+                                        )?;
 
-                                    additional_predicate.push(predicate);
+                                        additional_predicate.push(predicate);
 
-                                    for predicate_join in predicate_joins {
-                                        additional_joins.push(predicate_join);
+                                        for predicate_join in predicate_joins {
+                                            additional_joins.push(predicate_join);
+                                        }
                                     }
 
                                     last_join_alias = join_alias;
@@ -1642,19 +1540,21 @@ impl<'r, 'c> QueryBuilder<'r, 'c> {
 
                             additional_joins.push(join);
 
-                            let (predicate, predicate_joins) = self.filter_expression(
-                                &path_element.predicate,
-                                &join_alias,
-                                &relationship.target_collection,
-                                false,
-                                name_index,
-                            )?;
+                            if let Some(expression) = &path_element.predicate {
+                                let (predicate, predicate_joins) = self.filter_expression(
+                                    expression,
+                                    &join_alias,
+                                    &relationship.target_collection,
+                                    false,
+                                    name_index,
+                                )?;
 
-                            for join in predicate_joins {
-                                additional_joins.push(join)
-                            }
+                                additional_predicates.push(predicate);
 
-                            additional_predicates.push(predicate);
+                                for join in predicate_joins {
+                                    additional_joins.push(join)
+                                }
+                            }
 
                             last_join_alias = join_alias;
                             last_collection_name = &relationship.target_collection;
diff --git a/src/sql/query_builder/comparison_column.rs b/crates/ndc-clickhouse/src/sql/query_builder/comparison_column.rs
similarity index 100%
rename from src/sql/query_builder/comparison_column.rs
rename to crates/ndc-clickhouse/src/sql/query_builder/comparison_column.rs
diff --git a/src/sql/query_builder/error.rs b/crates/ndc-clickhouse/src/sql/query_builder/error.rs
similarity index 100%
rename from src/sql/query_builder/error.rs
rename to crates/ndc-clickhouse/src/sql/query_builder/error.rs
diff --git a/src/sql/query_builder/typecasting.rs b/crates/ndc-clickhouse/src/sql/query_builder/typecasting.rs
similarity index 97%
rename from src/sql/query_builder/typecasting.rs
rename to crates/ndc-clickhouse/src/sql/query_builder/typecasting.rs
index d4e1cb1..2bb0a24 100644
--- a/src/sql/query_builder/typecasting.rs
+++ b/crates/ndc-clickhouse/src/sql/query_builder/typecasting.rs
@@ -1,12 +1,10 @@
 use std::{collections::BTreeMap, fmt::Display, str::FromStr};
 
+use config::{ColumnConfig, ServerConfig};
 use indexmap::IndexMap;
 use ndc_sdk::models;
 
-use crate::{
-    connector::config::{ColumnConfig, ServerConfig},
-    schema::{ClickHouseScalarType, ClickhouseDataType},
-};
+use crate::schema::{ClickHouseScalarType, ClickhouseDataType};
 
 /// Tuple(rows <RowsCastString>, aggregates <RowsCastString>)
 pub struct RowsetTypeString {
@@ -112,7 +110,11 @@ impl RowsTypeString {
                         match field {
                             models::Field::Column {
                                 column: column_alias,
+                                fields,
                             } => {
+                                if fields.is_some() {
+                                    todo!("support nested field selection")
+                                }
                                 let column = get_column(column_alias, table_alias, config)?;
                                 FieldTypeString::Column(column.data_type.to_owned())
                             }
diff --git a/docker-compose.yaml b/docker-compose.yaml
index febfa8e..cfa9ccd 100644
--- a/docker-compose.yaml
+++ b/docker-compose.yaml
@@ -1,12 +1,16 @@
 version: "3.8"
 services:
-  clickhouse_ndc:
+  ndc-clickhouse:
     build:
       context: .
     ports:
       - "4000:4000"
     environment:
       PORT: 4000
+      HASURA_CONFIGURATION_DIRECTORY: /etc/connector
+      CLICKHOUSE_URL: ${CLICKHOUSE_URL}
+      CLICKHOUSE_USERNAME: ${CLICKHOUSE_USERNAME}
+      CLICKHOUSE_PASSWORD: ${CLICKHOUSE_PASSWORD}
     volumes:
-      - ./generated_config.json:/ndc-config.json
-    command: serve --port 4000 --configuration /ndc-config.json
+      - ./config:/etc/connector
+    command: serve --port 4000
diff --git a/src/connector.rs b/src/connector.rs
deleted file mode 100644
index fe07183..0000000
--- a/src/connector.rs
+++ /dev/null
@@ -1,163 +0,0 @@
-pub mod client;
-pub mod config;
-pub mod handler;
-pub mod state;
-
-use async_trait::async_trait;
-use ndc_sdk::{
-    connector::{
-        Connector, ExplainError, FetchMetricsError, HealthError, InitializationError,
-        MutationError, QueryError, SchemaError, UpdateConfigurationError, ValidateError,
-    },
-    json_response::JsonResponse,
-    models,
-};
-
-use self::{config::ServerConfig, state::ServerState};
-
-#[derive(Debug, Clone, Default)]
-pub struct ClickhouseConnector;
-
-#[async_trait]
-impl Connector for ClickhouseConnector {
-    /// The type of unvalidated, raw configuration, as provided by the user.
-    type RawConfiguration = ServerConfig;
-
-    /// The type of validated configuration
-    type Configuration = ServerConfig;
-
-    /// The type of unserializable state
-    type State = ServerState;
-
-    fn make_empty_configuration() -> Self::RawConfiguration {
-        ServerConfig::default()
-    }
-
-    async fn update_configuration(
-        config: Self::RawConfiguration,
-    ) -> Result<Self::RawConfiguration, UpdateConfigurationError> {
-        handler::update_configuration(config).await
-    }
-
-    /// Validate the raw configuration provided by the user,
-    /// returning a configuration error or a validated [`Connector::Configuration`].
-    async fn validate_raw_configuration(
-        configuration: Self::RawConfiguration,
-    ) -> Result<Self::Configuration, ValidateError> {
-        // todo: validate config.
-        // todo: we should take an owned configuration here.
-        Ok(configuration.to_owned())
-    }
-
-    /// Initialize the connector's in-memory state.
-    ///
-    /// For example, any connection pools, prepared queries,
-    /// or other managed resources would be allocated here.
-    ///
-    /// In addition, this function should register any
-    /// connector-specific metrics with the metrics registry.
-    async fn try_init_state(
-        configuration: &Self::Configuration,
-        _metrics: &mut prometheus::Registry,
-    ) -> Result<Self::State, InitializationError> {
-        Ok(ServerState::new(configuration))
-    }
-
-    /// Update any metrics from the state
-    ///
-    /// Note: some metrics can be updated directly, and do not
-    /// need to be updated here. This function can be useful to
-    /// query metrics which cannot be updated directly, e.g.
-    /// the number of idle connections in a connection pool
-    /// can be polled but not updated directly.
-    fn fetch_metrics(
-        _configuration: &Self::Configuration,
-        _state: &Self::State,
-    ) -> Result<(), FetchMetricsError> {
-        Ok(())
-    }
-
-    /// Check the health of the connector.
-    ///
-    /// For example, this function should check that the connector
-    /// is able to reach its data source over the network.
-    async fn health_check(
-        configuration: &Self::Configuration,
-        state: &Self::State,
-    ) -> Result<(), HealthError> {
-        let client = state
-            .client(configuration)
-            .await
-            .map_err(|err| HealthError::Other(err.to_string().into()))?;
-
-        client::ping(&client, &configuration.connection)
-            .await
-            .map_err(|err| HealthError::Other(err.to_string().into()))?;
-
-        Ok(())
-    }
-
-    /// Get the connector's capabilities.
-    ///
-    /// This function implements the [capabilities endpoint](https://hasura.github.io/ndc-spec/specification/capabilities.html)
-    /// from the NDC specification.
-    async fn get_capabilities() -> JsonResponse<models::CapabilitiesResponse> {
-        JsonResponse::Value(handler::capabilities())
-    }
-
-    /// Get the connector's schema.
-    ///
-    /// This function implements the [schema endpoint](https://hasura.github.io/ndc-spec/specification/schema/index.html)
-    /// from the NDC specification.
-    async fn get_schema(
-        configuration: &Self::Configuration,
-    ) -> Result<JsonResponse<models::SchemaResponse>, SchemaError> {
-        handler::schema(configuration)
-            .await
-            .map(|config| JsonResponse::Value(config))
-    }
-
-    /// Explain a query by creating an execution plan
-    ///
-    /// This function implements the [explain endpoint](https://hasura.github.io/ndc-spec/specification/explain.html)
-    /// from the NDC specification.
-    async fn explain(
-        configuration: &Self::Configuration,
-        state: &Self::State,
-        request: models::QueryRequest,
-    ) -> Result<JsonResponse<models::ExplainResponse>, ExplainError> {
-        handler::explain(configuration, state, request)
-            .await
-            .map(|explain| JsonResponse::Value(explain))
-            .map_err(|err| ExplainError::Other(err.to_string().into()))
-    }
-
-    /// Execute a mutation
-    ///
-    /// This function implements the [mutation endpoint](https://hasura.github.io/ndc-spec/specification/mutations/index.html)
-    /// from the NDC specification.
-    async fn mutation(
-        _configuration: &Self::Configuration,
-        _state: &Self::State,
-        _request: models::MutationRequest,
-    ) -> Result<JsonResponse<models::MutationResponse>, MutationError> {
-        Err(MutationError::UnsupportedOperation(
-            "mutation not supported".to_string(),
-        ))
-    }
-
-    /// Execute a query
-    ///
-    /// This function implements the [query endpoint](https://hasura.github.io/ndc-spec/specification/queries/index.html)
-    /// from the NDC specification.
-    async fn query(
-        configuration: &Self::Configuration,
-        state: &Self::State,
-        request: models::QueryRequest,
-    ) -> Result<JsonResponse<models::QueryResponse>, QueryError> {
-        handler::query(configuration, state, request)
-            .await
-            .map(|res| JsonResponse::Value(res))
-            .map_err(|err| QueryError::Other(err.to_string().into()))
-    }
-}
diff --git a/src/connector/config.rs b/src/connector/config.rs
deleted file mode 100644
index e55b3f7..0000000
--- a/src/connector/config.rs
+++ /dev/null
@@ -1,137 +0,0 @@
-use std::error::Error;
-
-use schemars::JsonSchema;
-use serde::{Deserialize, Serialize};
-
-use self::database_introspection::{introspect_database, ColumnInfo, TableInfo};
-
-mod database_introspection;
-
-#[derive(Debug, Default, Clone, Serialize, Deserialize, JsonSchema)]
-pub struct ServerConfig {
-    pub connection: ConnectionConfig,
-    pub tables: Vec<TableConfig>,
-}
-
-#[derive(Debug, Default, Clone, Serialize, Deserialize, JsonSchema)]
-pub struct ConnectionConfig {
-    pub username: String,
-    pub password: String,
-    pub url: String,
-}
-
-#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
-pub struct TableConfig {
-    pub name: String,
-    pub schema: String,
-    pub alias: String,
-    pub primary_key: Option<PrimaryKey>,
-    pub columns: Vec<ColumnConfig>,
-}
-
-#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
-pub struct PrimaryKey {
-    pub name: String,
-    pub columns: Vec<String>,
-}
-
-#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
-pub struct ColumnConfig {
-    pub name: String,
-    pub alias: String,
-    pub data_type: String,
-}
-
-/// using a server config that may not contain any table information,
-/// produce a new configuration that copies the connection information from the original
-/// and additionally includes table configuration information obtained by introspecting the database
-pub async fn get_server_config(
-    connection_config: &ConnectionConfig,
-) -> Result<ServerConfig, Box<dyn Error>> {
-    let table_infos = introspect_database(connection_config).await?;
-
-    let tables = table_infos
-        .iter()
-        .map(|table| TableConfig {
-            name: table.table_name.to_owned(),
-            schema: table.table_schema.to_owned(),
-            alias: get_table_alias(table),
-            primary_key: table.primary_key.as_ref().map(|primary_key| {
-                PrimaryKey {
-                    name: primary_key.to_owned(),
-                    columns: table
-                        .columns
-                        .iter()
-                        .filter_map(|column| {
-                            if column.is_in_primary_key {
-                                // note: we should alias the column here.
-                                Some(get_column_alias(column, table))
-                            } else {
-                                None
-                            }
-                        })
-                        .collect(),
-                }
-            }),
-            columns: table
-                .columns
-                .iter()
-                .map(|column| ColumnConfig {
-                    name: column.column_name.to_owned(),
-                    alias: get_column_alias(column, table),
-                    data_type: column.data_type.to_owned(),
-                })
-                .collect(),
-        })
-        .collect();
-
-    Ok(ServerConfig {
-        tables,
-        connection: connection_config.to_owned(),
-    })
-}
-
-fn get_table_alias(table: &TableInfo) -> String {
-    // TODO: ensure a valid graphql name, I think?
-    // unsure if HGE will complain if the names are not valid graphql identifiers
-    if table.table_schema == "default" {
-        table.table_name.to_owned()
-    } else {
-        format!("{}_{}", table.table_schema, table.table_name)
-    }
-}
-fn get_column_alias(column: &ColumnInfo, _table: &TableInfo) -> String {
-    // TODO: ensure a valid graphql name, I think?
-    // unsure if HGE will complain if the names are not valid graphql identifiers
-    column.column_name.to_owned()
-}
-
-/// generate an updated configuration by introspecting the database
-/// expects a generated_config.json file to exists with connection information.
-/// If not such file exists, you can create one by using this template:
-/// ```json
-/// {
-///   "connection": {
-///     "username": "",
-///     "password": "",
-///     "url": ""
-///   },
-///   "tables": []
-/// }
-/// ```
-#[tokio::test]
-async fn update_config() -> Result<(), Box<dyn Error>> {
-    let current_config = std::fs::read_to_string("./generated_config.json")
-        .expect("Unable to read generated_config");
-
-    let current_config: ServerConfig = serde_json::from_str(&current_config)?;
-
-    let updated_config = get_server_config(&current_config.connection).await?;
-
-    let updated_config = serde_json::to_string_pretty(&updated_config)?;
-
-    std::fs::write("./generated_config.json", updated_config)
-        .expect("Unable to write generated config");
-
-    Ok(())
-}
diff --git a/src/connector/handler/update_configuration.rs b/src/connector/handler/update_configuration.rs
deleted file mode 100644
index 997b49c..0000000
--- a/src/connector/handler/update_configuration.rs
+++ /dev/null
@@ -1,11 +0,0 @@
-use ndc_sdk::connector::UpdateConfigurationError;
-
-use crate::connector::config::{get_server_config, ServerConfig};
-
-pub async fn update_configuration(
-    config: ServerConfig,
-) -> Result<ServerConfig, UpdateConfigurationError> {
-    get_server_config(&config.connection)
-        .await
-        .map_err(|err| UpdateConfigurationError::Other(err.to_string().into()))
-}