This is an automated email from the ASF dual-hosted git repository.

lidavidm pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow-adbc.git


The following commit(s) were added to refs/heads/main by this push:
     new 8de65c816 feat(rust/driver/snowflake): add `adbc_snowflake` crate with 
Go driver wrapper (#2207)
8de65c816 is described below

commit 8de65c8160135d326227ac4e5686946a96c464d0
Author: Matthijs Brobbel <[email protected]>
AuthorDate: Fri Nov 22 04:39:29 2024 +0100

    feat(rust/driver/snowflake): add `adbc_snowflake` crate with Go driver 
wrapper (#2207)
    
    This PR adds the `adbc_snowflake` crate to the Rust workspace. This
    crate provides a Snowflake ADBC driver for Rust by wrapping the Go
    driver, loaded by the Rust driver manager implementation. The `build.rs`
    script builds the Go driver and links it statically.
    
    - [x] Add methods to the wrapper structs to handle Snowflake specific
    configurations
    - [x] Add a feature to support loading the Go driver dynamically
    - [x] Docs
    - [x] Tests
---
 .github/workflows/rust.yml                         |   15 +
 dev/release/post-08-rust.sh                        |    4 +-
 rust/Cargo.lock                                    |  496 +++++++--
 rust/Cargo.toml                                    |    3 +-
 rust/core/src/driver_manager.rs                    |    5 +
 rust/core/src/ffi/methods.rs                       |    2 +-
 rust/core/src/options.rs                           |   30 +-
 rust/{drivers => driver}/datafusion/Cargo.toml     |    0
 rust/{drivers => driver}/datafusion/README.md      |    0
 rust/{drivers => driver}/datafusion/src/lib.rs     |    0
 .../datafusion/tests/test_datafusion.rs            |    0
 rust/{drivers => driver}/dummy/Cargo.toml          |    0
 rust/{drivers => driver}/dummy/src/lib.rs          |    0
 .../dummy/tests/driver_exporter_dummy.rs           |    0
 .../Cargo.toml => driver/snowflake/.gitignore}     |   20 +-
 .../datafusion => driver/snowflake}/Cargo.toml     |   45 +-
 rust/driver/snowflake/README.md                    |  101 ++
 rust/driver/snowflake/build.rs                     |   93 ++
 rust/driver/snowflake/src/builder.rs               |   50 +
 rust/driver/snowflake/src/connection.rs            |  139 +++
 rust/driver/snowflake/src/connection/builder.rs    |  115 +++
 rust/driver/snowflake/src/database.rs              |  181 ++++
 rust/driver/snowflake/src/database/builder.rs      | 1078 ++++++++++++++++++++
 rust/driver/snowflake/src/driver.rs                |  201 ++++
 rust/driver/snowflake/src/driver/builder.rs        |   81 ++
 rust/driver/snowflake/src/duration.rs              |  281 +++++
 rust/driver/snowflake/src/lib.rs                   |   40 +
 rust/driver/snowflake/src/statement.rs             |  102 ++
 rust/driver/snowflake/tests/driver.rs              |  172 ++++
 29 files changed, 3107 insertions(+), 147 deletions(-)

diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml
index 63150469d..ab0554fe2 100644
--- a/.github/workflows/rust.yml
+++ b/.github/workflows/rust.yml
@@ -70,6 +70,15 @@ jobs:
         run: |
           rustup toolchain install stable --no-self-update
           rustup default stable
+      - name: Get required Go version
+        run: |
+          (. ../.env && echo "GO_VERSION=${GO}") >> $GITHUB_ENV
+      - uses: actions/setup-go@v5
+        with:
+          go-version: "${{ env.GO_VERSION }}"
+          check-latest: true
+          cache: true
+          cache-dependency-path: go/adbc/go.sum
       - name: Install Protoc
         if: runner.os == 'Linux'
         run: |
@@ -104,10 +113,16 @@ jobs:
         if: matrix.os == 'macos-13'
         run: |
           echo "DYLD_LIBRARY_PATH=/usr/local/opt/sqlite/lib:${{ 
github.workspace }}/build/lib:$DYLD_LIBRARY_PATH" >> "$GITHUB_ENV"
+      - name: Set search dir for Snowflake Go lib
+        run: echo "ADBC_SNOWFLAKE_GO_LIB_DIR=${{ github.workspace 
}}/build/lib" >> "$GITHUB_ENV"
       - name: Clippy
         run: cargo clippy --workspace --all-targets --all-features -- 
-Dwarnings
       - name: Test
         run: cargo test --workspace --all-targets --all-features
+        # env:
+        # ADBC_SNOWFLAKE_TESTS: 1
+        # ADBC_SNOWFLAKE_URI: ${{ secrets.SNOWFLAKE_URI }}
+        # ADBC_SNOWFLAKE_SQL_DB: ADBC_TESTING
       - name: Doctests
         run: cargo test --workspace --doc --all-features
       - name: Check docs
diff --git a/dev/release/post-08-rust.sh b/dev/release/post-08-rust.sh
index e31427d5b..1d9eaf54b 100755
--- a/dev/release/post-08-rust.sh
+++ b/dev/release/post-08-rust.sh
@@ -42,10 +42,12 @@ main() {
 
   pushd "${SOURCE_TOP_DIR}/rust"
   cargo publish --all-features -p adbc_core
+  cargo publish -p adbc_snowflake
   popd
 
-  echo "Success! The released Cargo crate is available here:"
+  echo "Success! The released Cargo crates are available here:"
   echo "  https://crates.io/crates/adbc_core";
+  echo "  https://crates.io/crates/adbc_snowflake";
 }
 
 main "$@"
diff --git a/rust/Cargo.lock b/rust/Cargo.lock
index 7339324fa..405a2c1c7 100644
--- a/rust/Cargo.lock
+++ b/rust/Cargo.lock
@@ -39,6 +39,19 @@ dependencies = [
  "arrow-select",
 ]
 
+[[package]]
+name = "adbc_snowflake"
+version = "0.16.0"
+dependencies = [
+ "adbc_core",
+ "arrow-array",
+ "arrow-schema",
+ "dotenvy",
+ "regex",
+ "test-with",
+ "url",
+]
+
 [[package]]
 name = "addr2line"
 version = "0.22.0"
@@ -100,9 +113,9 @@ dependencies = [
 
 [[package]]
 name = "allocator-api2"
-version = "0.2.18"
+version = "0.2.20"
 source = "registry+https://github.com/rust-lang/crates.io-index";
-checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f"
+checksum = "45862d1c77f2228b9e10bc609d5bc203d86ebc9b87ad8d5d5167a6c9abf739d9"
 
 [[package]]
 name = "android-tzdata"
@@ -121,9 +134,9 @@ dependencies = [
 
 [[package]]
 name = "anyhow"
-version = "1.0.90"
+version = "1.0.93"
 source = "registry+https://github.com/rust-lang/crates.io-index";
-checksum = "37bf3594c4c988a53154954629820791dde498571819ae4ca50ca811e060cc95"
+checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775"
 
 [[package]]
 name = "arrayref"
@@ -594,9 +607,9 @@ dependencies = [
 
 [[package]]
 name = "comfy-table"
-version = "7.1.1"
+version = "7.1.2"
 source = "registry+https://github.com/rust-lang/crates.io-index";
-checksum = "b34115915337defe99b2aff5c2ce6771e5fbc4079f4b506301f5cf394c8452f7"
+checksum = "e0d05af1e006a2407bedef5af410552494ce5be9090444dbbcb57258c1af3d56"
 dependencies = [
  "strum",
  "strum_macros",
@@ -637,9 +650,9 @@ checksum = 
"06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f"
 
 [[package]]
 name = "cpufeatures"
-version = "0.2.14"
+version = "0.2.15"
 source = "registry+https://github.com/rust-lang/crates.io-index";
-checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0"
+checksum = "0ca741a962e1b0bff6d724a1a0958b686406e853bb14061f218562e1896f95e6"
 dependencies = [
  "libc",
 ]
@@ -677,9 +690,9 @@ dependencies = [
 
 [[package]]
 name = "csv"
-version = "1.3.0"
+version = "1.3.1"
 source = "registry+https://github.com/rust-lang/crates.io-index";
-checksum = "ac574ff4d437a7b5ad237ef331c17ccca63c46479e5b5453eb8e10bb99a759fe"
+checksum = "acdc4883a9c96732e4733212c01447ebd805833b7275a73ca3ee080fd77afdaf"
 dependencies = [
  "csv-core",
  "itoa",
@@ -712,9 +725,9 @@ dependencies = [
 
 [[package]]
 name = "datafusion"
-version = "42.1.0"
+version = "42.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index";
-checksum = "3e8053b4cedc24eb158e4c041b38cfa0677ef5f4a7ccaa31ee5dcad61dd7aa54"
+checksum = "dae5f2abc725737d6e87b6d348a5aa2d0a77e4cf873045f004546da946e6e619"
 dependencies = [
  "ahash",
  "arrow",
@@ -769,9 +782,9 @@ dependencies = [
 
 [[package]]
 name = "datafusion-catalog"
-version = "42.1.0"
+version = "42.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index";
-checksum = "7d95efedb3a32f6f74df5bb8fda7b69fb9babe80e92137f25de6ddb15e8e8801"
+checksum = "998761705551f11ffa4ee692cc285b44eb1def6e0d28c4eaf5041b9e2810dc1e"
 dependencies = [
  "arrow-schema",
  "async-trait",
@@ -784,9 +797,9 @@ dependencies = [
 
 [[package]]
 name = "datafusion-common"
-version = "42.1.0"
+version = "42.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index";
-checksum = "b7d766e0d3dec01a0ab70b1b31678c286cddc0bd7afc9bd82504a1d9a70a7d94"
+checksum = "11986f191e88d950f10a5cc512a598afba27d92e04a0201215ad60785005115a"
 dependencies = [
  "ahash",
  "arrow",
@@ -808,9 +821,9 @@ dependencies = [
 
 [[package]]
 name = "datafusion-common-runtime"
-version = "42.1.0"
+version = "42.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index";
-checksum = "4e55db6df319f9e7cf366d0d4ffae793c863823421b2f2b7314a0fefd8e8c11a"
+checksum = "694c9d7ea1b82f95768215c4cb5c2d5c613690624e832a7ee64be563139d582f"
 dependencies = [
  "log",
  "tokio",
@@ -818,9 +831,9 @@ dependencies = [
 
 [[package]]
 name = "datafusion-execution"
-version = "42.1.0"
+version = "42.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index";
-checksum = "6d0c6dc013f955c382438a78fa3de8b0a8bf7b1a4cda5bc46335fe445ff3ff1a"
+checksum = "30b4cedcd98151e0a297f34021b6b232ff0ebc0f2f18ea5e7446b5ebda99b1a1"
 dependencies = [
  "arrow",
  "chrono",
@@ -839,9 +852,9 @@ dependencies = [
 
 [[package]]
 name = "datafusion-expr"
-version = "42.1.0"
+version = "42.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index";
-checksum = "7f31405c0bb854451d755b224d41dc466a8f7fd36f8c041c29d2d8432bd0c08c"
+checksum = "a8dd114dc0296cacaee98ad3165724529fcca9a65b2875abcd447b9cc02b2b74"
 dependencies = [
  "ahash",
  "arrow",
@@ -861,9 +874,9 @@ dependencies = [
 
 [[package]]
 name = "datafusion-expr-common"
-version = "42.1.0"
+version = "42.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index";
-checksum = "ebc8266b6627c8264c87bc7c82564e3d89ed5f0f9943b49a30dac1f1ac12e4c0"
+checksum = "5d1ba2bb018218d9260bbd7de6a46a20f61b93d4911dba8aa07735625004c4fb"
 dependencies = [
  "arrow",
  "datafusion-common",
@@ -872,9 +885,9 @@ dependencies = [
 
 [[package]]
 name = "datafusion-functions"
-version = "42.1.0"
+version = "42.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index";
-checksum = "5712668780bc43666ecd10acd188b7df58e2a5501d4dbbd972bf209f1790138b"
+checksum = "547cb780a4ac51fd8e52c0fb9188bc16cea4e35aebf6c454bda0b82a7a417304"
 dependencies = [
  "arrow",
  "arrow-buffer",
@@ -899,9 +912,9 @@ dependencies = [
 
 [[package]]
 name = "datafusion-functions-aggregate"
-version = "42.1.0"
+version = "42.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index";
-checksum = "8ec138af6b7482fb726f1bfeec010fc063b9614594c36a1051a4d3b365ba6a5f"
+checksum = "e68cf5aa7ebcac08bd04bb709a9a6d4963eafd227da62b628133bc509c40f5a0"
 dependencies = [
  "ahash",
  "arrow",
@@ -920,9 +933,9 @@ dependencies = [
 
 [[package]]
 name = "datafusion-functions-aggregate-common"
-version = "42.1.0"
+version = "42.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index";
-checksum = "564499c6bdd3ab9f76c7ad727e858bc6791e4de6c1a484d21d2bf49daaa658d6"
+checksum = "e2285d080dfecdfb8605b0ab2f1a41e2473208dc8e9bd6f5d1dbcfe97f517e6f"
 dependencies = [
  "ahash",
  "arrow",
@@ -934,9 +947,9 @@ dependencies = [
 
 [[package]]
 name = "datafusion-functions-nested"
-version = "42.1.0"
+version = "42.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index";
-checksum = "9b55ea2221ae1c1e37d524f8330f763dcdc205edb74fe5f54cbdea475c17fd18"
+checksum = "6b6ffbbb7cf7bf0c0e05eb6207023fef341cac83a593a5365a6fc83803c572a9"
 dependencies = [
  "arrow",
  "arrow-array",
@@ -957,9 +970,9 @@ dependencies = [
 
 [[package]]
 name = "datafusion-functions-window"
-version = "42.1.0"
+version = "42.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index";
-checksum = "6932996c4407ee1ebf23ffd706e982729cb9b6f7a31a281abac51fe524c3a049"
+checksum = "6e78d30ebd6e9f74d4aeddec32744f5a18b5f9584591bc586fb5259c4848bac5"
 dependencies = [
  "datafusion-common",
  "datafusion-expr",
@@ -969,9 +982,9 @@ dependencies = [
 
 [[package]]
 name = "datafusion-optimizer"
-version = "42.1.0"
+version = "42.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index";
-checksum = "f7d8afa1eb44e2f00cc8d82b88803e456a681474b8877ceecc04e9517d5c843c"
+checksum = "be172c44bf344df707e0c041fa3f41e6dc5fb0976f539c68bc442bca150ee58c"
 dependencies = [
  "arrow",
  "async-trait",
@@ -989,9 +1002,9 @@ dependencies = [
 
 [[package]]
 name = "datafusion-physical-expr"
-version = "42.1.0"
+version = "42.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index";
-checksum = "570666d84df483473626fab4e69997d048b40d0e7c67c540299714f244d99e73"
+checksum = "43b86b7fa0b8161c49b0f005b0df193fc6d9b65ceec675f155422cda5d1583ca"
 dependencies = [
  "ahash",
  "arrow",
@@ -1021,9 +1034,9 @@ dependencies = [
 
 [[package]]
 name = "datafusion-physical-expr-common"
-version = "42.1.0"
+version = "42.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index";
-checksum = "3746cbdfb32d67399dcaad17042e419ac6da454a7e38ff098aa2fbf0a7388982"
+checksum = "242ba8a26351d9ca16295814c46743b0d1b00ec372174bdfbba991d0953dd596"
 dependencies = [
  "ahash",
  "arrow",
@@ -1035,9 +1048,9 @@ dependencies = [
 
 [[package]]
 name = "datafusion-physical-optimizer"
-version = "42.1.0"
+version = "42.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index";
-checksum = "696f06e79d44f7c50f57cea23493881d86d9d9647884d38ce467c7f75c13e286"
+checksum = "25ca088eb904bf1cfc9c5e5653110c70a6eaba43164085a9d180b35b77ce3b8b"
 dependencies = [
  "arrow-schema",
  "datafusion-common",
@@ -1049,9 +1062,9 @@ dependencies = [
 
 [[package]]
 name = "datafusion-physical-plan"
-version = "42.1.0"
+version = "42.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index";
-checksum = "04e1d084224023e09cdea14d01ded0f2092c319c7b4594ebc821283b9c7c4a35"
+checksum = "4989a53b824abc759685eb643f4d604c2fc2fea4e2c309ac3473bea263ecbbeb"
 dependencies = [
  "ahash",
  "arrow",
@@ -1084,9 +1097,9 @@ dependencies = [
 
 [[package]]
 name = "datafusion-sql"
-version = "42.1.0"
+version = "42.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index";
-checksum = "c105148357dcbd9e4c97eada2930a59f7923215461d9f47de6e76edd60eab2d5"
+checksum = "66b9b75b9da10ed656073ac0553708f17eb8fa5a7b065ef9848914c93150ab9e"
 dependencies = [
  "arrow",
  "arrow-array",
@@ -1101,9 +1114,9 @@ dependencies = [
 
 [[package]]
 name = "datafusion-substrait"
-version = "42.1.0"
+version = "42.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index";
-checksum = "f99fba176491c566f38e314becb0009532a6a6feceaa17094192a9b84965a839"
+checksum = "220d7ab0ffadd8b1af753904b18dd92d270271810b1ce9f8be3c3dbe2392b636"
 dependencies = [
  "arrow-buffer",
  "async-recursion",
@@ -1128,6 +1141,23 @@ dependencies = [
  "subtle",
 ]
 
+[[package]]
+name = "displaydoc"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index";
+checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "dotenvy"
+version = "0.15.7"
+source = "registry+https://github.com/rust-lang/crates.io-index";
+checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
+
 [[package]]
 name = "dyn-clone"
 version = "1.0.17"
@@ -1158,9 +1188,9 @@ dependencies = [
 
 [[package]]
 name = "fastrand"
-version = "2.1.1"
+version = "2.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index";
-checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6"
+checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4"
 
 [[package]]
 name = "fixedbitset"
@@ -1342,9 +1372,9 @@ dependencies = [
 
 [[package]]
 name = "hashbrown"
-version = "0.15.0"
+version = "0.15.1"
 source = "registry+https://github.com/rust-lang/crates.io-index";
-checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb"
+checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3"
 
 [[package]]
 name = "heck"
@@ -1393,14 +1423,143 @@ dependencies = [
  "cc",
 ]
 
+[[package]]
+name = "icu_collections"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index";
+checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526"
+dependencies = [
+ "displaydoc",
+ "yoke",
+ "zerofrom",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_locid"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index";
+checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637"
+dependencies = [
+ "displaydoc",
+ "litemap",
+ "tinystr",
+ "writeable",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_locid_transform"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index";
+checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e"
+dependencies = [
+ "displaydoc",
+ "icu_locid",
+ "icu_locid_transform_data",
+ "icu_provider",
+ "tinystr",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_locid_transform_data"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index";
+checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e"
+
+[[package]]
+name = "icu_normalizer"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index";
+checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f"
+dependencies = [
+ "displaydoc",
+ "icu_collections",
+ "icu_normalizer_data",
+ "icu_properties",
+ "icu_provider",
+ "smallvec",
+ "utf16_iter",
+ "utf8_iter",
+ "write16",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_normalizer_data"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index";
+checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516"
+
+[[package]]
+name = "icu_properties"
+version = "1.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index";
+checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5"
+dependencies = [
+ "displaydoc",
+ "icu_collections",
+ "icu_locid_transform",
+ "icu_properties_data",
+ "icu_provider",
+ "tinystr",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_properties_data"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index";
+checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569"
+
+[[package]]
+name = "icu_provider"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index";
+checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9"
+dependencies = [
+ "displaydoc",
+ "icu_locid",
+ "icu_provider_macros",
+ "stable_deref_trait",
+ "tinystr",
+ "writeable",
+ "yoke",
+ "zerofrom",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_provider_macros"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index";
+checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
 [[package]]
 name = "idna"
-version = "0.5.0"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index";
+checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e"
+dependencies = [
+ "idna_adapter",
+ "smallvec",
+ "utf8_iter",
+]
+
+[[package]]
+name = "idna_adapter"
+version = "1.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index";
-checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6"
+checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71"
 dependencies = [
- "unicode-bidi",
- "unicode-normalization",
+ "icu_normalizer",
+ "icu_properties",
 ]
 
 [[package]]
@@ -1410,7 +1569,7 @@ source = 
"registry+https://github.com/rust-lang/crates.io-index";
 checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da"
 dependencies = [
  "equivalent",
- "hashbrown 0.15.0",
+ "hashbrown 0.15.1",
 ]
 
 [[package]]
@@ -1562,6 +1721,12 @@ version = "0.4.14"
 source = "registry+https://github.com/rust-lang/crates.io-index";
 checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89"
 
+[[package]]
+name = "litemap"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index";
+checksum = "643cb0b8d4fcc284004d5fd0d67ccf61dfffadb7f75e1e71bc420f4688a3a704"
+
 [[package]]
 name = "lock_api"
 version = "0.4.12"
@@ -1934,9 +2099,9 @@ dependencies = [
 
 [[package]]
 name = "pin-project-lite"
-version = "0.2.14"
+version = "0.2.15"
 source = "registry+https://github.com/rust-lang/crates.io-index";
-checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02"
+checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff"
 
 [[package]]
 name = "pin-utils"
@@ -1961,14 +2126,36 @@ dependencies = [
 
 [[package]]
 name = "prettyplease"
-version = "0.2.24"
+version = "0.2.25"
 source = "registry+https://github.com/rust-lang/crates.io-index";
-checksum = "910d41a655dac3b764f1ade94821093d3610248694320cd072303a8eedcf221d"
+checksum = "64d1ec885c64d0457d564db4ec299b2dae3f9c02808b8ad9c3a089c591b18033"
 dependencies = [
  "proc-macro2",
  "syn",
 ]
 
+[[package]]
+name = "proc-macro-error-attr2"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index";
+checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5"
+dependencies = [
+ "proc-macro2",
+ "quote",
+]
+
+[[package]]
+name = "proc-macro-error2"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index";
+checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802"
+dependencies = [
+ "proc-macro-error-attr2",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
 [[package]]
 name = "proc-macro2"
 version = "1.0.83"
@@ -2081,9 +2268,9 @@ dependencies = [
 
 [[package]]
 name = "regex"
-version = "1.11.0"
+version = "1.11.1"
 source = "registry+https://github.com/rust-lang/crates.io-index";
-checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8"
+checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
 dependencies = [
  "aho-corasick",
  "memchr",
@@ -2093,9 +2280,9 @@ dependencies = [
 
 [[package]]
 name = "regex-automata"
-version = "0.4.8"
+version = "0.4.9"
 source = "registry+https://github.com/rust-lang/crates.io-index";
-checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3"
+checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
 dependencies = [
  "aho-corasick",
  "memchr",
@@ -2214,18 +2401,18 @@ checksum = 
"a3f0bf26fd526d2a95683cd0f87bf103b8539e2ca1ef48ce002d67aad59aa0b4"
 
 [[package]]
 name = "serde"
-version = "1.0.210"
+version = "1.0.215"
 source = "registry+https://github.com/rust-lang/crates.io-index";
-checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a"
+checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f"
 dependencies = [
  "serde_derive",
 ]
 
 [[package]]
 name = "serde_derive"
-version = "1.0.210"
+version = "1.0.215"
 source = "registry+https://github.com/rust-lang/crates.io-index";
-checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f"
+checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -2366,6 +2553,12 @@ dependencies = [
  "syn",
 ]
 
+[[package]]
+name = "stable_deref_trait"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index";
+checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
+
 [[package]]
 name = "static_assertions"
 version = "1.1.0"
@@ -2435,6 +2628,17 @@ dependencies = [
  "unicode-ident",
 ]
 
+[[package]]
+name = "synstructure"
+version = "0.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index";
+checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
 [[package]]
 name = "tempfile"
 version = "3.11.0"
@@ -2448,20 +2652,33 @@ dependencies = [
  "windows-sys",
 ]
 
+[[package]]
+name = "test-with"
+version = "0.14.4"
+source = "registry+https://github.com/rust-lang/crates.io-index";
+checksum = "c68b467eef3b91443aaf01fee27fa016ea346d8c5b4ea1b85d0bd2375fa4c72f"
+dependencies = [
+ "proc-macro-error2",
+ "proc-macro2",
+ "quote",
+ "regex",
+ "syn",
+]
+
 [[package]]
 name = "thiserror"
-version = "1.0.64"
+version = "1.0.65"
 source = "registry+https://github.com/rust-lang/crates.io-index";
-checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84"
+checksum = "5d11abd9594d9b38965ef50805c5e469ca9cc6f197f883f717e0269a3057b3d5"
 dependencies = [
  "thiserror-impl",
 ]
 
 [[package]]
 name = "thiserror-impl"
-version = "1.0.64"
+version = "1.0.65"
 source = "registry+https://github.com/rust-lang/crates.io-index";
-checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3"
+checksum = "ae71770322cbd277e69d762a16c444af02aa0575ac0d174f0b9562d3b37f8602"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -2489,25 +2706,20 @@ dependencies = [
 ]
 
 [[package]]
-name = "tinyvec"
-version = "1.8.0"
+name = "tinystr"
+version = "0.7.6"
 source = "registry+https://github.com/rust-lang/crates.io-index";
-checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938"
+checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f"
 dependencies = [
- "tinyvec_macros",
+ "displaydoc",
+ "zerovec",
 ]
 
-[[package]]
-name = "tinyvec_macros"
-version = "0.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index";
-checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
-
 [[package]]
 name = "tokio"
-version = "1.40.0"
+version = "1.41.1"
 source = "registry+https://github.com/rust-lang/crates.io-index";
-checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998"
+checksum = "22cfb5bee7a6a52939ca9224d6ac897bb669134078daa8735560897f69de4d33"
 dependencies = [
  "backtrace",
  "bytes",
@@ -2633,27 +2845,12 @@ dependencies = [
  "typify-impl",
 ]
 
-[[package]]
-name = "unicode-bidi"
-version = "0.3.17"
-source = "registry+https://github.com/rust-lang/crates.io-index";
-checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893"
-
 [[package]]
 name = "unicode-ident"
 version = "1.0.12"
 source = "registry+https://github.com/rust-lang/crates.io-index";
 checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
 
-[[package]]
-name = "unicode-normalization"
-version = "0.1.24"
-source = "registry+https://github.com/rust-lang/crates.io-index";
-checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956"
-dependencies = [
- "tinyvec",
-]
-
 [[package]]
 name = "unicode-segmentation"
 version = "1.12.0"
@@ -2662,9 +2859,9 @@ checksum = 
"f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
 
 [[package]]
 name = "unicode-width"
-version = "0.1.14"
+version = "0.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index";
-checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
+checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd"
 
 [[package]]
 name = "unsafe-libyaml"
@@ -2674,15 +2871,27 @@ checksum = 
"673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861"
 
 [[package]]
 name = "url"
-version = "2.5.2"
+version = "2.5.3"
 source = "registry+https://github.com/rust-lang/crates.io-index";
-checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c"
+checksum = "8d157f1b96d14500ffdc1f10ba712e780825526c03d9a49b4d0324b0d9113ada"
 dependencies = [
  "form_urlencoded",
  "idna",
  "percent-encoding",
 ]
 
+[[package]]
+name = "utf16_iter"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index";
+checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246"
+
+[[package]]
+name = "utf8_iter"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index";
+checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
+
 [[package]]
 name = "uuid"
 version = "1.11.0"
@@ -2869,6 +3078,18 @@ version = "0.52.5"
 source = "registry+https://github.com/rust-lang/crates.io-index";
 checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0"
 
+[[package]]
+name = "write16"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index";
+checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936"
+
+[[package]]
+name = "writeable"
+version = "0.5.5"
+source = "registry+https://github.com/rust-lang/crates.io-index";
+checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51"
+
 [[package]]
 name = "xz2"
 version = "0.1.7"
@@ -2878,6 +3099,30 @@ dependencies = [
  "lzma-sys",
 ]
 
+[[package]]
+name = "yoke"
+version = "0.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index";
+checksum = "6c5b1314b079b0930c31e3af543d8ee1757b1951ae1e1565ec704403a7240ca5"
+dependencies = [
+ "serde",
+ "stable_deref_trait",
+ "yoke-derive",
+ "zerofrom",
+]
+
+[[package]]
+name = "yoke-derive"
+version = "0.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index";
+checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "synstructure",
+]
+
 [[package]]
 name = "zerocopy"
 version = "0.7.34"
@@ -2899,6 +3144,49 @@ dependencies = [
  "syn",
 ]
 
+[[package]]
+name = "zerofrom"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index";
+checksum = "91ec111ce797d0e0784a1116d0ddcdbea84322cd79e5d5ad173daeba4f93ab55"
+dependencies = [
+ "zerofrom-derive",
+]
+
+[[package]]
+name = "zerofrom-derive"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index";
+checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "synstructure",
+]
+
+[[package]]
+name = "zerovec"
+version = "0.10.4"
+source = "registry+https://github.com/rust-lang/crates.io-index";
+checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079"
+dependencies = [
+ "yoke",
+ "zerofrom",
+ "zerovec-derive",
+]
+
+[[package]]
+name = "zerovec-derive"
+version = "0.10.3"
+source = "registry+https://github.com/rust-lang/crates.io-index";
+checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
 [[package]]
 name = "zstd"
 version = "0.13.2"
diff --git a/rust/Cargo.toml b/rust/Cargo.toml
index c314b6c44..dfb056422 100644
--- a/rust/Cargo.toml
+++ b/rust/Cargo.toml
@@ -16,13 +16,14 @@
 # under the License.
 
 [workspace]
-members = ["core", "drivers/*"]
+members = ["core", "driver/*"]
 resolver = "2"
 
 [workspace.package]
 version = "0.16.0"
 description = "Rust implementation of Arrow Database Connectivity (ADBC)"
 edition = "2021"
+rust-version = "1.80.1"
 authors = ["Apache Arrow <[email protected]>"]
 license = "Apache-2.0"
 documentation = "https://docs.rs/adbc_core/";
diff --git a/rust/core/src/driver_manager.rs b/rust/core/src/driver_manager.rs
index c486aaaab..8e066a027 100644
--- a/rust/core/src/driver_manager.rs
+++ b/rust/core/src/driver_manager.rs
@@ -168,6 +168,11 @@ pub struct ManagedDriver {
 }
 
 impl ManagedDriver {
+    /// Returns the [`AdbcVersion`] of this driver.
+    pub fn version(&self) -> AdbcVersion {
+        self.inner.version
+    }
+
     /// Load a driver from an initialization function.
     pub fn load_static(init: &ffi::FFI_AdbcDriverInitFunc, version: 
AdbcVersion) -> Result<Self> {
         let driver = Self::load_impl(init, version)?;
diff --git a/rust/core/src/ffi/methods.rs b/rust/core/src/ffi/methods.rs
index e47fe4fff..0b065c3be 100644
--- a/rust/core/src/ffi/methods.rs
+++ b/rust/core/src/ffi/methods.rs
@@ -64,7 +64,7 @@ method!(StatementSetSqlQuery ; FuncStatementSetSqlQuery ; 
FFI_AdbcStatusCode ; A
 method!(StatementSetSubstraitPlan ; FuncStatementSetSubstraitPlan ; 
FFI_AdbcStatusCode ; ADBC_STATUS_NOT_IMPLEMENTED ; *mut FFI_AdbcStatement, 
*const u8, usize, *mut FFI_AdbcError);
 method!(ErrorGetDetailCount ; FuncErrorGetDetailCount ; c_int ; 0 ; *const 
FFI_AdbcError);
 method!(ErrorGetDetail ; FuncErrorGetDetail ; FFI_AdbcErrorDetail ; 
FFI_AdbcErrorDetail::default() ; *const FFI_AdbcError, c_int);
-method!(ErrorFromArrayStream ; FuncErrorFromArrayStream ; *const FFI_AdbcError 
; std::ptr::null() ; *mut FFI_ArrowArrayStream, *mut FFI_AdbcStatusCode);
+method!(_ErrorFromArrayStream ; FuncErrorFromArrayStream ; *const 
FFI_AdbcError ; std::ptr::null() ; *mut FFI_ArrowArrayStream, *mut 
FFI_AdbcStatusCode);
 method!(DatabaseGetOption ; FuncDatabaseGetOption ; FFI_AdbcStatusCode ; 
ADBC_STATUS_NOT_IMPLEMENTED ; *mut FFI_AdbcDatabase, *const c_char, *mut 
c_char, *mut usize, *mut FFI_AdbcError);
 method!(DatabaseGetOptionBytes ; FuncDatabaseGetOptionBytes ; 
FFI_AdbcStatusCode ; ADBC_STATUS_NOT_IMPLEMENTED ; *mut FFI_AdbcDatabase, 
*const c_char, *mut u8, *mut usize, *mut FFI_AdbcError);
 method!(DatabaseGetOptionDouble ; FuncDatabaseGetOptionDouble ; 
FFI_AdbcStatusCode ; ADBC_STATUS_NOT_IMPLEMENTED ; *mut FFI_AdbcDatabase, 
*const c_char, *mut f64, *mut FFI_AdbcError);
diff --git a/rust/core/src/options.rs b/rust/core/src/options.rs
index 144e46aeb..0d3af92ff 100644
--- a/rust/core/src/options.rs
+++ b/rust/core/src/options.rs
@@ -16,7 +16,7 @@
 // under the License.
 
 //! Various option and configuration types.
-use std::os::raw::c_int;
+use std::{os::raw::c_int, str::FromStr};
 
 use crate::{
     error::{Error, Status},
@@ -96,12 +96,17 @@ impl<const N: usize> From<&[u8; N]> for OptionValue {
 }
 
 /// ADBC revision versions.
-#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+///
+/// The [`Default`] implementation returns the latest version.
+#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
 #[non_exhaustive]
 pub enum AdbcVersion {
     /// Version 1.0.0.
     V100,
     /// Version 1.1.0.
+    ///
+    /// 
<https://arrow.apache.org/adbc/current/format/specification.html#version-1-1-0>
+    #[default]
     V110,
 }
 
@@ -120,8 +125,23 @@ impl TryFrom<c_int> for AdbcVersion {
         match value {
             constants::ADBC_VERSION_1_0_0 => Ok(AdbcVersion::V100),
             constants::ADBC_VERSION_1_1_0 => Ok(AdbcVersion::V110),
-            _ => Err(Error::with_message_and_status(
-                format!("Unknown ADBC version: {}", value),
+            value => Err(Error::with_message_and_status(
+                format!("Unknown ADBC version: {value}"),
+                Status::InvalidArguments,
+            )),
+        }
+    }
+}
+
+impl FromStr for AdbcVersion {
+    type Err = Error;
+
+    fn from_str(s: &str) -> Result<Self, Self::Err> {
+        match s {
+            "1.0.0" | "1_0_0" | "100" => Ok(AdbcVersion::V100),
+            "1.1.0" | "1_1_0" | "110" => Ok(AdbcVersion::V110),
+            value => Err(Error::with_message_and_status(
+                format!("Unknown ADBC version: {value}"),
                 Status::InvalidArguments,
             )),
         }
@@ -129,7 +149,7 @@ impl TryFrom<c_int> for AdbcVersion {
 }
 
 /// Info codes for database/driver metadata.
-#[derive(Debug, PartialEq, Eq, Hash)]
+#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
 #[non_exhaustive]
 pub enum InfoCode {
     /// The database vendor/product name (type: utf8).
diff --git a/rust/drivers/datafusion/Cargo.toml 
b/rust/driver/datafusion/Cargo.toml
similarity index 100%
copy from rust/drivers/datafusion/Cargo.toml
copy to rust/driver/datafusion/Cargo.toml
diff --git a/rust/drivers/datafusion/README.md 
b/rust/driver/datafusion/README.md
similarity index 100%
rename from rust/drivers/datafusion/README.md
rename to rust/driver/datafusion/README.md
diff --git a/rust/drivers/datafusion/src/lib.rs 
b/rust/driver/datafusion/src/lib.rs
similarity index 100%
rename from rust/drivers/datafusion/src/lib.rs
rename to rust/driver/datafusion/src/lib.rs
diff --git a/rust/drivers/datafusion/tests/test_datafusion.rs 
b/rust/driver/datafusion/tests/test_datafusion.rs
similarity index 100%
rename from rust/drivers/datafusion/tests/test_datafusion.rs
rename to rust/driver/datafusion/tests/test_datafusion.rs
diff --git a/rust/drivers/dummy/Cargo.toml b/rust/driver/dummy/Cargo.toml
similarity index 100%
copy from rust/drivers/dummy/Cargo.toml
copy to rust/driver/dummy/Cargo.toml
diff --git a/rust/drivers/dummy/src/lib.rs b/rust/driver/dummy/src/lib.rs
similarity index 100%
rename from rust/drivers/dummy/src/lib.rs
rename to rust/driver/dummy/src/lib.rs
diff --git a/rust/drivers/dummy/tests/driver_exporter_dummy.rs 
b/rust/driver/dummy/tests/driver_exporter_dummy.rs
similarity index 100%
rename from rust/drivers/dummy/tests/driver_exporter_dummy.rs
rename to rust/driver/dummy/tests/driver_exporter_dummy.rs
diff --git a/rust/drivers/dummy/Cargo.toml b/rust/driver/snowflake/.gitignore
similarity index 64%
rename from rust/drivers/dummy/Cargo.toml
rename to rust/driver/snowflake/.gitignore
index ec690af84..fed460a7c 100644
--- a/rust/drivers/dummy/Cargo.toml
+++ b/rust/driver/snowflake/.gitignore
@@ -15,22 +15,4 @@
 # specific language governing permissions and limitations
 # under the License.
 
-[package]
-name = "adbc_dummy"
-description = "A dummy ADBC driver for testing purposes"
-version = { workspace = true }
-edition = { workspace = true }
-authors = { workspace = true }
-license = { workspace = true }
-
-[dependencies]
-adbc_core = { path = "../../core" }
-arrow-array.workspace = true
-arrow-buffer.workspace = true
-arrow-schema.workspace = true
-
-[dev-dependencies]
-arrow-select.workspace = true
-
-[lib]
-crate-type = ["lib", "cdylib"]
+.env
diff --git a/rust/drivers/datafusion/Cargo.toml 
b/rust/driver/snowflake/Cargo.toml
similarity index 50%
rename from rust/drivers/datafusion/Cargo.toml
rename to rust/driver/snowflake/Cargo.toml
index c7ea978cb..d563f219a 100644
--- a/rust/drivers/datafusion/Cargo.toml
+++ b/rust/driver/snowflake/Cargo.toml
@@ -16,25 +16,38 @@
 # under the License.
 
 [package]
-name = "adbc_datafusion"
-description = "ADBC driver for Apache DataFusion"
-version = { workspace = true }
-edition = { workspace = true }
-authors = { workspace = true }
-license = { workspace = true }
+name = "adbc_snowflake"
+description = "Snowflake Arrow Database Connectivity (ADBC) driver"
+version.workspace = true
+edition.workspace = true
+authors.workspace = true
+license.workspace = true
+homepage.workspace = true
+repository.workspace = true
+rust-version.workspace = true
+keywords.workspace = true
+categories.workspace = true
+documentation = "http://docs.rs/adbc_snowflake/";
+readme = "README.md"
+
+[features]
+default = ["bundled", "env", "dotenv"]
+
+# Building/linking Go driver
+bundled = []
+linked = []
+
+# Configuration
+env = ["dep:regex"]
+dotenv = ["env", "dep:dotenvy"]
 
 [dependencies]
-adbc_core = { path = "../../core" }
+adbc_core = { path = "../../core", features = ["driver_manager"] }
 arrow-array.workspace = true
-arrow-buffer.workspace = true
 arrow-schema.workspace = true
-datafusion = "42.0.0"
-datafusion-substrait = "42.0.0"
-tokio = { version = "1.0", features = ["rt-multi-thread"] }
-prost = "0.13.3"
+dotenvy = { version = "0.15.7", default-features = false, optional = true }
+regex = { version = "1.11.1", default-features = false, optional = true }
+url = "2.5.3"
 
 [dev-dependencies]
-arrow-select.workspace = true
-
-[lib]
-crate-type = ["lib", "cdylib"]
+test-with = { version = "0.14.4", default-features = false }
diff --git a/rust/driver/snowflake/README.md b/rust/driver/snowflake/README.md
new file mode 100644
index 000000000..5f8c143f0
--- /dev/null
+++ b/rust/driver/snowflake/README.md
@@ -0,0 +1,101 @@
+<!---
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing,
+  software distributed under the License is distributed on an
+  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  KIND, either express or implied.  See the License for the
+  specific language governing permissions and limitations
+  under the License.
+-->
+
+![logo](https://raw.githubusercontent.com/apache/arrow/refs/heads/main/docs/source/_static/favicon.ico)
+
+[![crates.io](https://img.shields.io/crates/v/adbc_snowflake.svg)](https://crates.io/crates/adbc_snowflake)
+[![docs.rs](https://docs.rs/adbc_snowflake/badge.svg)](https://docs.rs/c)
+
+# Snowflake driver for Arrow Database Connectivity (ADBC)
+
+A [Snowflake](https://www.snowflake.com) [ADBC](https://arrow.apache.org/adbc/)
+driver, based on the
+[ADBC Snowflake Go 
driver](https://github.com/apache/arrow-adbc/tree/main/go/adbc/driver/snowflake).
+
+## Example
+
+```rust,no_run
+use adbc_core::{Connection, Statement};
+use adbc_snowflake::{connection, database, Driver};
+use arrow_array::{cast::AsArray, types::Decimal128Type};
+
+# fn main() -> Result<(), Box<dyn std::error::Error>> {
+
+// Load the driver
+let mut driver = Driver::try_load()?;
+
+// Construct a database using environment variables
+let mut database = database::Builder::from_env().build(&mut driver)?;
+
+// Create a connection to the database
+let mut connection = connection::Builder::from_env().build(&mut database)?;
+
+// Construct a statement to execute a query
+let mut statement = connection.new_statement()?;
+
+// Execute a query
+statement.set_sql_query("SELECT 21 + 21")?;
+let mut reader = statement.execute()?;
+
+// Check the result
+let batch = reader.next().expect("a record batch")?;
+assert_eq!(
+    batch.column(0).as_primitive::<Decimal128Type>().value(0),
+    42
+);
+
+# Ok(()) }
+```
+
+## Crate features
+
+### Linking Go driver
+
+This crate is a wrapper around the Go driver.
+
+There are different methods to load the Go driver:
+
+#### `bundled` (default)
+
+Builds the driver from source and links it statically. This requires a Go
+compiler to be available at build time. This is the default behavior.
+
+#### `linked`
+
+Link the driver at build time. This requires the driver library to be available
+both at build- and runtime. Set `ADBC_SNOWFLAKE_GO_LIB_DIR` during the build to
+add search paths for the linker.
+
+#### Runtime only
+
+It's also possible to build this crate without the driver and only link at
+runtime. This requires disabling the `bundled` and `linked` features. Linking
+at runtime is also available when the other features are enabled.
+
+### Configuration
+
+The crate provides builders that can be initialized from environment variables.
+
+#### `env` (default)
+
+Adds `from_env` methods to initialize builders from environment variables.
+
+#### `dotenv`: `env` (default)
+
+Loads environment variables from `.env` files in `from_env` methods.
diff --git a/rust/driver/snowflake/build.rs b/rust/driver/snowflake/build.rs
new file mode 100644
index 000000000..914595064
--- /dev/null
+++ b/rust/driver/snowflake/build.rs
@@ -0,0 +1,93 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+use std::error::Error;
+
+/// Build and link the Go driver statically.
+#[cfg(feature = "bundled")]
+fn bundled() -> Result<(), Box<dyn Error>> {
+    use std::{env, path::PathBuf, process::Command};
+
+    let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR")?);
+    let go_dir = manifest_dir.ancestors().nth(3).unwrap().join("go");
+    let go_pkg = go_dir.join("adbc/pkg/snowflake");
+
+    let out_dir = PathBuf::from(env::var("OUT_DIR")?);
+    let archive = out_dir.join("libsnowflake.a");
+
+    // Build the Go driver
+    let status = Command::new("go")
+        .current_dir(go_pkg.as_path())
+        .arg("build")
+        .arg("-tags")
+        .arg("driverlib")
+        .arg("-buildmode=c-archive")
+        .arg("-o")
+        .arg(&archive)
+        .arg(".")
+        .status()?;
+    assert!(status.success(), "Go build failed");
+
+    // Rebuild when the Go pkg changes.
+    println!("cargo:rerun-if-changed={}", go_pkg.display());
+
+    // Link the driver statically.
+    println!("cargo:rustc-link-search=native={}", out_dir.display());
+    println!("cargo:rustc-link-lib=static=snowflake");
+
+    // Link other dependencies.
+    println!("cargo:rustc-link-lib=resolv");
+    #[cfg(target_os = "macos")]
+    {
+        println!("cargo:rustc-link-lib=framework=CoreFoundation");
+        println!("cargo:rustc-link-lib=framework=Security");
+    }
+
+    Ok(())
+}
+
+/// Link the Go driver.
+#[cfg(feature = "linked")]
+fn linked() -> Result<(), Box<dyn Error>> {
+    // Rebuild when config var changes.
+    println!("cargo:rerun-if-env-changed=ADBC_SNOWFLAKE_GO_LIB_DIR");
+
+    // Add search path if requested.
+    if let Some(path) = option_env!("ADBC_SNOWFLAKE_GO_LIB_DIR") {
+        println!("cargo:rustc-link-search={path}");
+    }
+
+    // Link the driver.
+    println!("cargo:rustc-link-lib=adbc_driver_snowflake");
+
+    Ok(())
+}
+
+fn main() -> Result<(), Box<dyn Error>> {
+    // Bundle the Go driver.
+    #[cfg(feature = "bundled")]
+    bundled()?;
+
+    // Link the Go driver.
+    #[cfg(feature = "linked")]
+    linked()?;
+
+    // Rebuild when tests are enabled.
+    println!("cargo:rerun-if-env-changed=ADBC_SNOWFLAKE_TESTS");
+
+    Ok(())
+}
diff --git a/rust/driver/snowflake/src/builder.rs 
b/rust/driver/snowflake/src/builder.rs
new file mode 100644
index 000000000..766cb6bed
--- /dev/null
+++ b/rust/driver/snowflake/src/builder.rs
@@ -0,0 +1,50 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+//! Builder utilities
+//!
+//!
+
+use std::iter::{Chain, Flatten};
+
+use adbc_core::options::OptionValue;
+
+/// An iterator over the builder options.
+pub struct BuilderIter<T, const COUNT: usize>(
+    #[allow(clippy::type_complexity)]
+    Chain<
+        Flatten<<[Option<(T, OptionValue)>; COUNT] as IntoIterator>::IntoIter>,
+        <Vec<(T, OptionValue)> as IntoIterator>::IntoIter,
+    >,
+);
+
+impl<T, const COUNT: usize> BuilderIter<T, COUNT> {
+    pub(crate) fn new(
+        fixed: [Option<(T, OptionValue)>; COUNT],
+        other: Vec<(T, OptionValue)>,
+    ) -> Self {
+        Self(fixed.into_iter().flatten().chain(other))
+    }
+}
+
+impl<T, const COUNT: usize> Iterator for BuilderIter<T, COUNT> {
+    type Item = (T, OptionValue);
+
+    fn next(&mut self) -> Option<Self::Item> {
+        self.0.next()
+    }
+}
diff --git a/rust/driver/snowflake/src/connection.rs 
b/rust/driver/snowflake/src/connection.rs
new file mode 100644
index 000000000..222cf3b01
--- /dev/null
+++ b/rust/driver/snowflake/src/connection.rs
@@ -0,0 +1,139 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+//! Snowflake ADBC Connection
+//!
+//!
+
+use std::collections::HashSet;
+
+use adbc_core::{
+    driver_manager::ManagedConnection,
+    error::Result,
+    options::{InfoCode, OptionConnection, OptionValue},
+    Optionable,
+};
+use arrow_array::RecordBatchReader;
+use arrow_schema::Schema;
+
+use crate::Statement;
+
+mod builder;
+pub use builder::*;
+
+/// Snowflake ADBC Connection.
+#[derive(Clone)]
+pub struct Connection(pub(crate) ManagedConnection);
+
+impl Optionable for Connection {
+    type Option = OptionConnection;
+
+    fn set_option(&mut self, key: Self::Option, value: OptionValue) -> 
Result<()> {
+        self.0.set_option(key, value)
+    }
+
+    fn get_option_string(&self, key: Self::Option) -> Result<String> {
+        self.0.get_option_string(key)
+    }
+
+    fn get_option_bytes(&self, key: Self::Option) -> Result<Vec<u8>> {
+        self.0.get_option_bytes(key)
+    }
+
+    fn get_option_int(&self, key: Self::Option) -> Result<i64> {
+        self.0.get_option_int(key)
+    }
+
+    fn get_option_double(&self, key: Self::Option) -> Result<f64> {
+        self.0.get_option_double(key)
+    }
+}
+
+impl adbc_core::Connection for Connection {
+    type StatementType = Statement;
+
+    fn new_statement(&mut self) -> Result<Self::StatementType> {
+        self.0.new_statement().map(Statement)
+    }
+
+    fn cancel(&mut self) -> Result<()> {
+        self.0.cancel()
+    }
+
+    fn get_info(&self, codes: Option<HashSet<InfoCode>>) -> Result<impl 
RecordBatchReader + Send> {
+        self.0.get_info(codes)
+    }
+
+    fn get_objects(
+        &self,
+        depth: adbc_core::options::ObjectDepth,
+        catalog: Option<&str>,
+        db_schema: Option<&str>,
+        table_name: Option<&str>,
+        table_type: Option<Vec<&str>>,
+        column_name: Option<&str>,
+    ) -> Result<impl RecordBatchReader + Send> {
+        self.0.get_objects(
+            depth,
+            catalog,
+            db_schema,
+            table_name,
+            table_type,
+            column_name,
+        )
+    }
+
+    fn get_table_schema(
+        &self,
+        catalog: Option<&str>,
+        db_schema: Option<&str>,
+        table_name: &str,
+    ) -> Result<Schema> {
+        self.0.get_table_schema(catalog, db_schema, table_name)
+    }
+
+    fn get_table_types(&self) -> Result<impl RecordBatchReader + Send> {
+        self.0.get_table_types()
+    }
+
+    fn get_statistic_names(&self) -> Result<impl RecordBatchReader + Send> {
+        self.0.get_statistic_names()
+    }
+
+    fn get_statistics(
+        &self,
+        catalog: Option<&str>,
+        db_schema: Option<&str>,
+        table_name: Option<&str>,
+        approximate: bool,
+    ) -> Result<impl RecordBatchReader + Send> {
+        self.0
+            .get_statistics(catalog, db_schema, table_name, approximate)
+    }
+
+    fn commit(&mut self) -> Result<()> {
+        self.0.commit()
+    }
+
+    fn rollback(&mut self) -> Result<()> {
+        self.0.rollback()
+    }
+
+    fn read_partition(&self, partition: impl AsRef<[u8]>) -> Result<impl 
RecordBatchReader + Send> {
+        self.0.read_partition(partition)
+    }
+}
diff --git a/rust/driver/snowflake/src/connection/builder.rs 
b/rust/driver/snowflake/src/connection/builder.rs
new file mode 100644
index 000000000..8b6f0e857
--- /dev/null
+++ b/rust/driver/snowflake/src/connection/builder.rs
@@ -0,0 +1,115 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+//! A builder for a [`Connection`]
+//!
+//!
+
+#[cfg(feature = "env")]
+use std::env;
+use std::fmt;
+
+use adbc_core::{
+    error::Result,
+    options::{OptionConnection, OptionValue},
+    Database as _,
+};
+
+#[cfg(feature = "env")]
+use crate::database;
+use crate::{builder::BuilderIter, Connection, Database};
+
+/// A builder for [`Connection`].
+///
+/// The builder can be used to initialize a [`Connection`] with
+/// [`Builder::build`] or by directly passing it to
+/// [`Database::new_connection_with_opts`].
+#[derive(Clone, Default)]
+#[non_exhaustive]
+pub struct Builder {
+    /// Use high precision ([`Self::USE_HIGH_PRECISION`]).
+    pub use_high_precision: Option<bool>,
+
+    /// Other options.
+    pub other: Vec<(OptionConnection, OptionValue)>,
+}
+
+impl fmt::Debug for Builder {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        f.debug_struct("Builder").field("...", &self.other).finish()
+    }
+}
+
+#[cfg(feature = "env")]
+impl Builder {
+    /// See [`Self::use_high_precision`].
+    pub const USE_HIGH_PRECISION_ENV: &str = 
database::Builder::USE_HIGH_PRECISION_ENV;
+
+    /// Construct a builder, setting values based on values of the
+    /// configuration environment variables.
+    pub fn from_env() -> Self {
+        #[cfg(feature = "dotenv")]
+        let _ = dotenvy::dotenv();
+
+        let use_high_precision = env::var(Self::USE_HIGH_PRECISION_ENV)
+            .ok()
+            .as_deref()
+            .and_then(|value| value.parse().ok());
+        Self {
+            use_high_precision,
+            ..Default::default()
+        }
+    }
+}
+
+impl Builder {
+    /// Number of fields in the builder (except other).
+    const COUNT: usize = 1;
+
+    pub const USE_HIGH_PRECISION: &str = 
"adbc.snowflake.sql.client_option.use_high_precision";
+
+    /// Use high precision ([`Self::use_high_precision`]).
+    pub fn with_high_precision(mut self, use_high_precision: bool) -> Self {
+        self.use_high_precision = Some(use_high_precision);
+        self
+    }
+}
+
+impl Builder {
+    /// Attempt to initialize a [`Connection`] using the values provided to
+    /// this builder using the provided [`Database`].
+    pub fn build(self, database: &mut Database) -> Result<Connection> {
+        database.new_connection_with_opts(self)
+    }
+}
+
+impl IntoIterator for Builder {
+    type Item = (OptionConnection, OptionValue);
+    type IntoIter = BuilderIter<OptionConnection, { Builder::COUNT }>;
+
+    fn into_iter(self) -> Self::IntoIter {
+        BuilderIter::new(
+            [self
+                .use_high_precision
+                .as_ref()
+                .map(ToString::to_string)
+                .map(OptionValue::String)
+                .map(|value| (Builder::USE_HIGH_PRECISION.into(), value))],
+            self.other,
+        )
+    }
+}
diff --git a/rust/driver/snowflake/src/database.rs 
b/rust/driver/snowflake/src/database.rs
new file mode 100644
index 000000000..006c20802
--- /dev/null
+++ b/rust/driver/snowflake/src/database.rs
@@ -0,0 +1,181 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+//! Snowflake ADBC Database
+//!
+//!
+
+use std::{collections::HashSet, ffi::c_int, sync::Arc};
+
+use adbc_core::{
+    driver_manager::ManagedDatabase,
+    error::{Error, Result, Status},
+    options::{AdbcVersion, InfoCode, OptionConnection, OptionDatabase, 
OptionValue},
+    Connection as _, Database as _, Optionable,
+};
+use arrow_array::{
+    cast::AsArray,
+    types::{Int64Type, UInt32Type},
+    Array,
+};
+
+use crate::Connection;
+
+mod builder;
+pub use builder::*;
+
+/// Snowflake ADBC Database.
+#[derive(Clone)]
+pub struct Database(pub(crate) ManagedDatabase);
+
+impl Database {
+    fn get_info(&mut self, info_code: InfoCode) -> Result<Arc<dyn Array>> {
+        self.new_connection()?
+            .get_info(Some(HashSet::from_iter([info_code])))?
+            .next()
+            .ok_or(Error::with_message_and_status(
+                "failed to get info",
+                Status::Internal,
+            ))?
+            .map_err(Into::into)
+            .and_then(|record_batch| {
+                if 
InfoCode::try_from(record_batch.column(0).as_primitive::<UInt32Type>().value(0))?
+                    == info_code
+                {
+                    Ok(record_batch.column(1).as_union().value(0))
+                } else {
+                    Err(Error::with_message_and_status(
+                        "invalid get info reply",
+                        Status::Internal,
+                    ))
+                }
+            })
+    }
+
+    /// Returns the name of the vendor.
+    pub fn vendor_name(&mut self) -> Result<String> {
+        self.get_info(InfoCode::VendorName)
+            .map(|array| array.as_string::<i32>().value(0).to_owned())
+    }
+
+    /// Returns the version of the vendor.
+    pub fn vendor_version(&mut self) -> Result<String> {
+        self.get_info(InfoCode::VendorVersion)
+            .map(|array| array.as_string::<i32>().value(0).to_owned())
+    }
+
+    /// Returns the Arrow version of the vendor.
+    pub fn vendor_arrow_version(&mut self) -> Result<String> {
+        self.get_info(InfoCode::VendorArrowVersion)
+            .map(|array| array.as_string::<i32>().value(0).to_owned())
+    }
+
+    /// Returns true if SQL queries are supported.
+    pub fn vendor_sql(&mut self) -> Result<bool> {
+        self.get_info(InfoCode::VendorSql)
+            .map(|array| array.as_boolean().value(0))
+    }
+
+    /// Returns true if Substrait queries are supported.
+    pub fn vendor_substrait(&mut self) -> Result<bool> {
+        self.get_info(InfoCode::VendorSubstrait)
+            .map(|array| array.as_boolean().value(0))
+    }
+
+    /// Returns the name of the wrapped Go driver.
+    pub fn driver_name(&mut self) -> Result<String> {
+        self.get_info(InfoCode::DriverName)
+            .map(|array| array.as_string::<i32>().value(0).to_owned())
+    }
+
+    /// Returns the version of the wrapped Go driver.
+    pub fn driver_version(&mut self) -> Result<String> {
+        self.get_info(InfoCode::DriverVersion)
+            .map(|array| array.as_string::<i32>().value(0).to_owned())
+    }
+
+    /// Returns the Arrow version of the wrapped Go driver.
+    pub fn driver_arrow_version(&mut self) -> Result<String> {
+        self.get_info(InfoCode::DriverArrowVersion)
+            .map(|array| array.as_string::<i32>().value(0).to_owned())
+    }
+
+    /// Returns the [`AdbcVersion`] reported by the driver.
+    pub fn adbc_version(&mut self) -> Result<AdbcVersion> {
+        self.new_connection()?
+            .get_info(Some(HashSet::from_iter([InfoCode::DriverAdbcVersion])))?
+            .next()
+            .ok_or(Error::with_message_and_status(
+                "failed to get info",
+                Status::Internal,
+            ))?
+            .map_err(Into::into)
+            .and_then(|record_batch| {
+                assert_eq!(
+                    
record_batch.column(0).as_primitive::<UInt32Type>().value(0),
+                    u32::from(&InfoCode::DriverAdbcVersion)
+                );
+                AdbcVersion::try_from(
+                    record_batch
+                        .column(1)
+                        .as_union()
+                        .value(0)
+                        .as_primitive::<Int64Type>()
+                        .value(0) as c_int,
+                )
+            })
+    }
+}
+
+impl Optionable for Database {
+    type Option = OptionDatabase;
+
+    fn set_option(&mut self, key: Self::Option, value: OptionValue) -> 
Result<()> {
+        self.0.set_option(key, value)
+    }
+
+    fn get_option_string(&self, key: Self::Option) -> Result<String> {
+        self.0.get_option_string(key)
+    }
+
+    fn get_option_bytes(&self, key: Self::Option) -> Result<Vec<u8>> {
+        self.0.get_option_bytes(key)
+    }
+
+    fn get_option_int(&self, key: Self::Option) -> Result<i64> {
+        self.0.get_option_int(key)
+    }
+
+    fn get_option_double(&self, key: Self::Option) -> Result<f64> {
+        self.0.get_option_double(key)
+    }
+}
+
+impl adbc_core::Database for Database {
+    type ConnectionType = Connection;
+
+    fn new_connection(&mut self) -> Result<Self::ConnectionType> {
+        self.0.new_connection().map(Connection)
+    }
+
+    fn new_connection_with_opts(
+        &mut self,
+        opts: impl IntoIterator<Item = (OptionConnection, OptionValue)>,
+    ) -> Result<Self::ConnectionType> {
+        self.0.new_connection_with_opts(opts).map(Connection)
+    }
+}
diff --git a/rust/driver/snowflake/src/database/builder.rs 
b/rust/driver/snowflake/src/database/builder.rs
new file mode 100644
index 000000000..3279927bc
--- /dev/null
+++ b/rust/driver/snowflake/src/database/builder.rs
@@ -0,0 +1,1078 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+//! A builder for a [`Database`]
+//!
+//!
+
+use core::str;
+#[cfg(feature = "env")]
+use std::env;
+use std::{fmt, path::PathBuf, str::FromStr, time::Duration};
+
+use adbc_core::{
+    error::{Error, Result, Status},
+    options::{OptionDatabase, OptionValue},
+    Driver as _,
+};
+use url::{Host, Url};
+
+#[cfg(feature = "env")]
+use crate::duration::parse_duration;
+use crate::{builder::BuilderIter, Database, Driver};
+
+/// Authentication types.
+#[derive(Copy, Clone, Debug, Default)]
+#[non_exhaustive]
+pub enum AuthType {
+    /// General username password authentication
+    #[default]
+    Snowflake,
+    /// OAuth authentication
+    OAuth,
+    /// Use a browser to access an Fed and perform SSO authentication
+    ExternalBrowser,
+    /// Native okta URL to perform SSO authentication on Okta
+    Okta,
+    /// Use Jwt to perform authentication
+    Jwt,
+    /// Username and password with mfa
+    UsernamePasswordMFA,
+}
+
+impl fmt::Display for AuthType {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(
+            f,
+            "{}",
+            match self {
+                Self::Snowflake => "auth_snowflake",
+                Self::OAuth => "auth_oauth",
+                Self::ExternalBrowser => "auth_ext_browser",
+                Self::Okta => "auth_okta",
+                Self::Jwt => "auth_jwt",
+                Self::UsernamePasswordMFA => "auth_mfa",
+            }
+        )
+    }
+}
+
+impl str::FromStr for AuthType {
+    type Err = Error;
+
+    fn from_str(s: &str) -> Result<Self> {
+        match s {
+            "auth_snowflake" => Ok(Self::Snowflake),
+            "auth_oauth" => Ok(Self::OAuth),
+            "auth_ext_browser" => Ok(Self::ExternalBrowser),
+            "auth_okta" => Ok(Self::Okta),
+            "auth_jwt" => Ok(Self::Jwt),
+            "auth_mfa" => Ok(Self::UsernamePasswordMFA),
+            _ => Err(Error::with_message_and_status(
+                format!("invalid auth type: {s}"),
+                Status::InvalidArguments,
+            )),
+        }
+    }
+}
+
+/// Protocol types.
+#[derive(Copy, Clone, Debug, Default)]
+#[non_exhaustive]
+pub enum Protocol {
+    /// Use `https`.
+    #[default]
+    Https,
+    /// Use `http`.
+    Http,
+}
+
+impl fmt::Display for Protocol {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(
+            f,
+            "{}",
+            match self {
+                Self::Https => "https",
+                Self::Http => "http",
+            }
+        )
+    }
+}
+
+impl str::FromStr for Protocol {
+    type Err = Error;
+
+    fn from_str(s: &str) -> Result<Self> {
+        match s {
+            "https" | "HTTPS" => Ok(Self::Https),
+            "http" | "HTTP" => Ok(Self::Http),
+            _ => Err(Error::with_message_and_status(
+                format!("invalid protocol type: {s}"),
+                Status::InvalidArguments,
+            )),
+        }
+    }
+}
+
+/// Log levels.
+#[derive(Copy, Clone, Debug, Default)]
+#[non_exhaustive]
+pub enum LogLevel {
+    /// Trace level
+    Trace,
+    /// Debug level
+    Debug,
+    /// Info level
+    Info,
+    /// Warn level
+    Warn,
+    /// Error level
+    Error,
+    /// Fatal level
+    Fatal,
+    /// Off
+    #[default]
+    Off,
+}
+
+impl fmt::Display for LogLevel {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(
+            f,
+            "{}",
+            match self {
+                Self::Trace => "trace",
+                Self::Debug => "debug",
+                Self::Info => "info",
+                Self::Warn => "warn",
+                Self::Error => "error",
+                Self::Fatal => "fatal",
+                Self::Off => "off",
+            }
+        )
+    }
+}
+
+impl str::FromStr for LogLevel {
+    type Err = Error;
+
+    fn from_str(s: &str) -> Result<Self> {
+        match s {
+            "trace" => Ok(Self::Trace),
+            "debug" => Ok(Self::Debug),
+            "info" => Ok(Self::Info),
+            "warn" => Ok(Self::Warn),
+            "error" => Ok(Self::Error),
+            "fatal" => Ok(Self::Fatal),
+            "off" => Ok(Self::Off),
+            _ => Err(Error::with_message_and_status(
+                format!("invalid log level: {s}"),
+                Status::InvalidArguments,
+            )),
+        }
+    }
+}
+
+/// A builder for [`Database`].
+///
+/// The builder can be used to initialize a [`Database`] with
+/// [`Builder::build`] or by directly passing it to
+/// [`Driver::new_database_with_opts`].
+#[derive(Clone, Default)]
+#[non_exhaustive]
+pub struct Builder {
+    /// The URI ([`OptionDatabase::Uri`]).
+    pub uri: Option<Url>,
+
+    /// The username ([`OptionDatabase::Username`]).
+    pub username: Option<String>,
+
+    /// The password ([`OptionDatabase::Password`]).
+    pub password: Option<String>,
+
+    /// The name of the database ([`Self::DATABASE`]).
+    pub database: Option<String>,
+
+    /// The name of the schema ([`Self::SCHEMA`]).
+    pub schema: Option<String>,
+
+    /// The name of the warehouse ([`Self::WAREHOUSE`]).
+    pub warehouse: Option<String>,
+
+    /// The name of the role ([`Self::ROLE`]).
+    pub role: Option<String>,
+
+    /// The name of the region ([`Self::REGION`]).
+    pub region: Option<String>,
+
+    /// The name of the account ([`Self::ACCOUNT`]).
+    pub account: Option<String>,
+
+    /// The protocol ([`Self::PROTOCOL`]).
+    pub protocol: Option<Protocol>,
+
+    /// The port ([`Self::PORT`]).
+    pub port: Option<u16>,
+
+    /// The host ([`Self::HOST`]).
+    pub host: Option<Host>,
+
+    /// The authentication type ([`Self::AUTH_TYPE`]).
+    pub auth_type: Option<AuthType>,
+
+    /// Login timeout duration ([`Self::LOGIN_TIMEOUT`]).
+    pub login_timeout: Option<Duration>,
+
+    /// Request timeout duration ([`Self::REQUEST_TIMEOUT`]).
+    pub request_timeout: Option<Duration>,
+
+    /// JWT expire timeout duration ([`Self::JWT_EXPIRE_TIMEOUT`]).
+    pub jwt_expire_timeout: Option<Duration>,
+
+    /// Client timeout duration ([`Self::CLIENT_TIMEOUT`]).
+    pub client_timeout: Option<Duration>,
+
+    /// Use high precision ([`Self::USE_HIGH_PRECISION`]).
+    pub use_high_precision: Option<bool>,
+
+    /// Application name ([`Self::APPLICATION_NAME`]).
+    pub application_name: Option<String>,
+
+    /// SSL skip verify ([`Self::SSL_SKIP_VERIFY`]).
+    pub ssl_skip_verify: Option<bool>,
+
+    /// OCSP fail open mode ([`Self::OCSP_FAIL_OPEN_MODE`]).
+    pub ocsp_fail_open_mode: Option<bool>,
+
+    /// Auth token ([`Self::AUTH_TOKEN`]).
+    pub auth_token: Option<String>,
+
+    /// Auth Okta url ([`Self::AUTH_OKTA_URL`]).
+    pub auth_okta_url: Option<Url>,
+
+    /// Keep session alive ([`Self::KEEP_SESSION_ALIVE`]).
+    pub keep_session_alive: Option<bool>,
+
+    /// JWT private key file path ([`Self::JWT_PRIVATE_KEY`]).
+    pub jwt_private_key: Option<PathBuf>,
+
+    /// JWT private key PKCS 8 value ([`Self::JWT_PRIVATE_KEY_PKCS8_VALUE`]).
+    pub jwt_private_key_pkcs8_value: Option<String>,
+
+    /// JWT private key PKCS 8 password 
([`Self::JWT_PRIVATE_KEY_PKCS8_PASSWORD`]).
+    pub jwt_private_key_pkcs8_password: Option<String>,
+
+    /// Disable telemetry ([`Self::DISABLE_TELEMETRY`]).
+    pub disable_telemetry: Option<bool>,
+
+    /// Log tracing level ([`Self::LOG_TRACING`]).
+    pub log_tracing: Option<LogLevel>,
+
+    /// Client config file path ([`Self::CLIENT_CONFIG_FILE`]).
+    pub client_config_file: Option<PathBuf>,
+
+    /// Client cache MFA token ([`Self::CLIENT_CACHE_MFA_TOKEN`]).
+    pub client_cache_mfa_token: Option<bool>,
+
+    /// Client store temporary credentials ([`Self::CLIENT_STORE_TEMP_CREDS`]).
+    pub client_store_temp_creds: Option<bool>,
+
+    /// Other options.
+    pub other: Vec<(OptionDatabase, OptionValue)>,
+}
+
+impl fmt::Debug for Builder {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        const HIDDEN: &str = "*****";
+        f.debug_struct("Builder")
+            .field(
+                "uri",
+                &self.uri.clone().map(|mut url| {
+                    let _ = url.set_password(url.password().and(Some(HIDDEN)));
+                    url
+                }),
+            )
+            .field("username", &self.username)
+            .field("password", &self.password.as_ref().map(|_| HIDDEN))
+            .field("database", &self.database)
+            .field("schema", &self.schema)
+            .field("warehouse", &self.warehouse)
+            .field("role", &self.role)
+            .field("region", &self.region)
+            .field("account", &self.account)
+            .field("protocol", &self.protocol)
+            .field("port", &self.port)
+            .field("host", &self.host)
+            .field("auth_type", &self.auth_type)
+            .field("login_timeout", &self.login_timeout)
+            .field("request_timeout", &self.request_timeout)
+            .field("jwt_expire_timeout", &self.jwt_expire_timeout)
+            .field("client_timeout", &self.client_timeout)
+            .field("use_high_precision", &self.use_high_precision)
+            .field("application_name", &self.application_name)
+            .field("ssl_skip_verify", &self.ssl_skip_verify)
+            .field("ocsp_fail_open_mode", &self.ocsp_fail_open_mode)
+            .field("auth_token", &self.auth_token.as_ref().map(|_| HIDDEN))
+            .field("auth_okta_url", &self.auth_okta_url)
+            .field("keep_session_alive", &self.keep_session_alive)
+            .field("jwt_private_key", &self.jwt_private_key)
+            .field(
+                "jwt_private_key_pkcs8_value",
+                &self.jwt_private_key_pkcs8_value.as_ref().map(|_| HIDDEN),
+            )
+            .field(
+                "jwt_private_key_pkcs8_password",
+                &self.jwt_private_key_pkcs8_password.as_ref().map(|_| HIDDEN),
+            )
+            .field("disable_telemetry", &self.disable_telemetry)
+            .field("log_tracing", &self.log_tracing)
+            .field("client_config_file", &self.client_config_file)
+            .field("client_cache_mfa_token", &self.client_cache_mfa_token)
+            .field("client_store_temp_creds", &self.client_store_temp_creds)
+            .field("...", &self.other)
+            .finish()
+    }
+}
+
+#[cfg(feature = "env")]
+impl Builder {
+    /// See [`Self::uri`].
+    pub const URI_ENV: &str = "ADBC_SNOWFLAKE_URI";
+
+    /// See [`Self::username`].
+    pub const USERNAME_ENV: &str = "ADBC_SNOWFLAKE_USERNAME";
+
+    /// See [`Self::password`].
+    pub const PASSWORD_ENV: &str = "ADBC_SNOWFLAKE_PASSWORD";
+
+    /// See [`Self::database`].
+    pub const DATABASE_ENV: &str = "ADBC_SNOWFLAKE_SQL_DB";
+
+    /// See [`Self::schema`].
+    pub const SCHEMA_ENV: &str = "ADBC_SNOWFLAKE_SQL_SCHEMA";
+
+    /// See [`Self::warehouse`].
+    pub const WAREHOUSE_ENV: &str = "ADBC_SNOWFLAKE_SQL_WAREHOUSE";
+
+    /// See [`Self::role`].
+    pub const ROLE_ENV: &str = "ADBC_SNOWFLAKE_SQL_ROLE";
+
+    /// See [`Self::region`].
+    pub const REGION_ENV: &str = "ADBC_SNOWFLAKE_SQL_REGION";
+
+    /// See [`Self::account`].
+    pub const ACCOUNT_ENV: &str = "ADBC_SNOWFLAKE_SQL_ACCOUNT";
+
+    /// See [`Self::protocol`].
+    pub const PROTOCOL_ENV: &str = "ADBC_SNOWFLAKE_SQL_URI_PROTOCOL";
+
+    /// See [`Self::port`].
+    pub const PORT_ENV: &str = "ADBC_SNOWFLAKE_SQL_URI_PORT";
+
+    /// See [`Self::host`].
+    pub const HOST_ENV: &str = "ADBC_SNOWFLAKE_SQL_URI_HOST";
+
+    /// See [`Self::auth_type`].
+    pub const AUTH_TYPE_ENV: &str = "ADBC_SNOWFLAKE_SQL_AUTH_TYPE";
+
+    /// See [`Self::login_timeout`].
+    pub const LOGIN_TIMEOUT_ENV: &str = 
"ADBC_SNOWFLAKE_SQL_CLIENT_OPTIONS_LOGIN_TIMEOUT";
+
+    /// See [`Self::request_timeout`].
+    pub const REQUEST_TIMEOUT_ENV: &str = 
"ADBC_SNOWFLAKE_SQL_CLIENT_OPTION_REQUEST_TIMEOUT";
+
+    /// See [`Self::jwt_expire_timeout`].
+    pub const JWT_EXPIRE_TIMEOUT_ENV: &str = 
"ADBC_SNOWFLAKE_SQL_CLIENT_OPTION_JWT_EXPIRE_TIMEOUT";
+
+    /// See [`Self::client_timeout`].
+    pub const CLIENT_TIMEOUT_ENV: &str = 
"ADBC_SNOWFLAKE_SQL_CLIENT_OPTION_CLIENT_TIMEOUT";
+
+    /// See [`Self::use_high_precision`].
+    pub const USE_HIGH_PRECISION_ENV: &str = 
"ADBC_SNOWFLAKE_SQL_CLIENT_OPTION_USE_HIGH_PRECISION";
+
+    /// See [`Self::application_name`].
+    pub const APPLICATION_NAME_ENV: &str = 
"ADBC_SNOWFLAKE_SQL_CLIENT_OPTION_APP_NAME";
+
+    /// See [`Self::ssl_skip_verify`].
+    pub const SSL_SKIP_VERIFY_ENV: &str = 
"ADBC_SNOWFLAKE_SQL_CLIENT_OPTION_TLS_SKIP_VERIFY";
+
+    /// See [`Self::ocsp_fail_open_mode`].
+    pub const OCSP_FAIL_OPEN_MODE_ENV: &str =
+        "ADBC_SNOWFLAKE_SQL_CLIENT_OPTION_OCSP_FAIL_OPEN_MODE";
+
+    /// See [`Self::auth_token`].
+    pub const AUTH_TOKEN_ENV: &str = 
"ADBC_SNOWFLAKE_SQL_CLIENT_OPTION_AUTH_TOKEN";
+
+    /// See [`Self::auth_okta_url`].
+    pub const AUTH_OKTA_URL_ENV: &str = 
"ADBC_SNOWFLAKE_SQL_CLIENT_OPTION_OKTA_URL";
+
+    /// See [`Self::keep_session_alive`].
+    pub const KEEP_SESSION_ALIVE_ENV: &str = 
"ADBC_SNOWFLAKE_SQL_CLIENT_OPTION_KEEP_SESSION_ALIVE";
+
+    /// See [`Self::jwt_private_key`].
+    pub const JWT_PRIVATE_KEY_ENV: &str = 
"ADBC_SNOWFLAKE_SQL_CLIENT_OPTION_JWT_PRIVATE_KEY";
+
+    /// See [`Self::jwt_private_key_pkcs8_value`].
+    pub const JWT_PRIVATE_KEY_PKCS8_VALUE_ENV: &str =
+        "ADBC_SNOWFLAKE_SQL_CLIENT_OPTION_JWT_PRIVATE_KEY_PKCS8_VALUE";
+
+    /// See [`Self::jwt_private_key_pkcs8_password`].
+    pub const JWT_PRIVATE_KEY_PKCS8_PASSWORD_ENV: &str =
+        "ADBC_SNOWFLAKE_SQL_CLIENT_OPTION_JWT_PRIVATE_KEY_PKCS8_PASSWORD";
+
+    /// See [`Self::disable_telemetry`].
+    pub const DISABLE_TELEMETRY_ENV: &str = 
"ADBC_SNOWFLAKE_SQL_CLIENT_OPTION_DISABLE_TELEMETRY";
+
+    /// See [`Self::log_tracing`].
+    pub const LOG_TRACING_ENV: &str = 
"ADBC_SNOWFLAKE_SQL_CLIENT_OPTION_TRACING";
+
+    /// See [`Self::client_config_file`].
+    pub const CLIENT_CONFIG_FILE_ENV: &str = 
"ADBC_SNOWFLAKE_SQL_CLIENT_OPTION_CONFIG_FILE";
+
+    /// See [`Self::client_cache_mfa_token`].
+    pub const CLIENT_CACHE_MFA_TOKEN_ENV: &str = 
"ADBC_SNOWFLAKE_SQL_CLIENT_OPTION_CACHE_MFA_TOKEN";
+
+    /// See [`Self::client_store_temp_creds`].
+    pub const CLIENT_STORE_TEMP_CREDS_ENV: &str =
+        "ADBC_SNOWFLAKE_SQL_CLIENT_OPTION_STORE_TEMP_CREDS";
+
+    /// Construct a builder, setting values based on values of the
+    /// configuration environment variables.
+    pub fn from_env() -> Self {
+        #[cfg(feature = "dotenv")]
+        let _ = dotenvy::dotenv();
+
+        let uri = env::var(Self::URI_ENV)
+            .ok()
+            .as_deref()
+            .and_then(|value| Url::parse(value).ok());
+        let username = env::var(Self::USERNAME_ENV).ok();
+        let password = env::var(Self::PASSWORD_ENV).ok();
+        let database = env::var(Self::DATABASE_ENV).ok();
+        let schema = env::var(Self::SCHEMA_ENV).ok();
+        let warehouse = env::var(Self::WAREHOUSE_ENV).ok();
+        let role = env::var(Self::ROLE_ENV).ok();
+        let region = env::var(Self::REGION_ENV).ok();
+        let account = env::var(Self::ACCOUNT_ENV).ok();
+        let protocol = env::var(Self::PROTOCOL_ENV)
+            .ok()
+            .as_deref()
+            .and_then(|value| value.parse().ok());
+        let port = env::var(Self::PORT_ENV)
+            .ok()
+            .and_then(|value| value.parse().ok());
+        let host = env::var(Self::HOST_ENV)
+            .ok()
+            .as_deref()
+            .and_then(|value| Host::parse(value).ok());
+        let auth_type = env::var(Self::AUTH_TYPE_ENV)
+            .ok()
+            .and_then(|value| value.parse().ok());
+        let login_timeout = env::var(Self::LOGIN_TIMEOUT_ENV)
+            .ok()
+            .as_deref()
+            .and_then(|value| parse_duration(value).ok());
+        let request_timeout = env::var(Self::REQUEST_TIMEOUT_ENV)
+            .ok()
+            .as_deref()
+            .and_then(|value| parse_duration(value).ok());
+        let jwt_expire_timeout = env::var(Self::JWT_EXPIRE_TIMEOUT_ENV)
+            .ok()
+            .as_deref()
+            .and_then(|value| parse_duration(value).ok());
+        let client_timeout = env::var(Self::CLIENT_TIMEOUT_ENV)
+            .ok()
+            .as_deref()
+            .and_then(|value| parse_duration(value).ok());
+        let use_high_precision = env::var(Self::USE_HIGH_PRECISION_ENV)
+            .ok()
+            .as_deref()
+            .and_then(|value| value.parse().ok());
+        let application_name = env::var(Self::APPLICATION_NAME_ENV).ok();
+        let ssl_skip_verify = env::var(Self::SSL_SKIP_VERIFY_ENV)
+            .ok()
+            .as_deref()
+            .and_then(|value| value.parse().ok());
+        let ocsp_fail_open_mode = env::var(Self::OCSP_FAIL_OPEN_MODE_ENV)
+            .ok()
+            .as_deref()
+            .and_then(|value| value.parse().ok());
+        let auth_token = env::var(Self::AUTH_TOKEN_ENV).ok();
+        let auth_okta_url = env::var(Self::AUTH_OKTA_URL_ENV)
+            .ok()
+            .as_deref()
+            .and_then(|value| Url::parse(value).ok());
+        let keep_session_alive = env::var(Self::OCSP_FAIL_OPEN_MODE_ENV)
+            .ok()
+            .as_deref()
+            .and_then(|value| value.parse().ok());
+        let jwt_private_key = env::var(Self::JWT_PRIVATE_KEY_ENV)
+            .ok()
+            .as_deref()
+            .and_then(|value| value.parse().ok());
+        let jwt_private_key_pkcs8_value = 
env::var(Self::JWT_PRIVATE_KEY_PKCS8_VALUE_ENV).ok();
+        let jwt_private_key_pkcs8_password =
+            env::var(Self::JWT_PRIVATE_KEY_PKCS8_PASSWORD_ENV).ok();
+        let disable_telemetry = env::var(Self::DISABLE_TELEMETRY_ENV)
+            .ok()
+            .as_deref()
+            .and_then(|value| value.parse().ok());
+        let log_tracing = env::var(Self::LOG_TRACING_ENV)
+            .ok()
+            .as_deref()
+            .and_then(|value| value.parse().ok());
+        let client_config_file = env::var(Self::CLIENT_CONFIG_FILE_ENV)
+            .ok()
+            .as_deref()
+            .and_then(|value| value.parse().ok());
+        let client_cache_mfa_token = env::var(Self::CLIENT_CACHE_MFA_TOKEN_ENV)
+            .ok()
+            .as_deref()
+            .and_then(|value| value.parse().ok());
+        let client_store_temp_creds = 
env::var(Self::CLIENT_STORE_TEMP_CREDS_ENV)
+            .ok()
+            .as_deref()
+            .and_then(|value| value.parse().ok());
+        Self {
+            uri,
+            username,
+            password,
+            database,
+            schema,
+            warehouse,
+            role,
+            region,
+            account,
+            protocol,
+            port,
+            host,
+            auth_type,
+            login_timeout,
+            request_timeout,
+            jwt_expire_timeout,
+            client_timeout,
+            use_high_precision,
+            application_name,
+            ssl_skip_verify,
+            ocsp_fail_open_mode,
+            auth_token,
+            auth_okta_url,
+            keep_session_alive,
+            jwt_private_key,
+            jwt_private_key_pkcs8_value,
+            jwt_private_key_pkcs8_password,
+            disable_telemetry,
+            log_tracing,
+            client_config_file,
+            client_cache_mfa_token,
+            client_store_temp_creds,
+            ..Default::default()
+        }
+    }
+}
+
+impl Builder {
+    /// Number of fields in the builder (except other).
+    const COUNT: usize = 32;
+
+    pub const DATABASE: &str = "adbc.snowflake.sql.db";
+    pub const SCHEMA: &str = "adbc.snowflake.sql.schema";
+    pub const WAREHOUSE: &str = "adbc.snowflake.sql.warehouse";
+    pub const ROLE: &str = "adbc.snowflake.sql.role";
+    pub const REGION: &str = "adbc.snowflake.sql.region";
+    pub const ACCOUNT: &str = "adbc.snowflake.sql.account";
+    pub const PROTOCOL: &str = "adbc.snowflake.sql.uri.protocol";
+    pub const PORT: &str = "adbc.snowflake.sql.uri.port";
+    pub const HOST: &str = "adbc.snowflake.sql.uri.host";
+    pub const AUTH_TYPE: &str = "adbc.snowflake.sql.auth_type";
+    pub const LOGIN_TIMEOUT: &str = 
"adbc.snowflake.sql.client_option.login_timeout";
+    pub const REQUEST_TIMEOUT: &str = 
"adbc.snowflake.sql.client_option.request_timeout";
+    pub const JWT_EXPIRE_TIMEOUT: &str = 
"adbc.snowflake.sql.client_option.jwt_expire_timeout";
+    pub const CLIENT_TIMEOUT: &str = 
"adbc.snowflake.sql.client_option.client_timeout";
+    pub const USE_HIGH_PRECISION: &str = 
"adbc.snowflake.sql.client_option.use_high_precision";
+    pub const APPLICATION_NAME: &str = 
"adbc.snowflake.sql.client_option.app_name";
+    pub const SSL_SKIP_VERIFY: &str = 
"adbc.snowflake.sql.client_option.tls_skip_verify";
+    pub const OCSP_FAIL_OPEN_MODE: &str = 
"adbc.snowflake.sql.client_option.ocsp_fail_open_mode";
+    pub const AUTH_TOKEN: &str = "adbc.snowflake.sql.client_option.auth_token";
+    pub const AUTH_OKTA_URL: &str = 
"adbc.snowflake.sql.client_option.okta_url";
+    pub const KEEP_SESSION_ALIVE: &str = 
"adbc.snowflake.sql.client_option.keep_session_alive";
+    pub const JWT_PRIVATE_KEY: &str = 
"adbc.snowflake.sql.client_option.jwt_private_key";
+    pub const JWT_PRIVATE_KEY_PKCS8_VALUE: &str =
+        "adbc.snowflake.sql.client_option.jwt_private_key_pkcs8_value";
+    pub const JWT_PRIVATE_KEY_PKCS8_PASSWORD: &str =
+        "adbc.snowflake.sql.client_option.jwt_private_key_pkcs8_password";
+    pub const DISABLE_TELEMETRY: &str = 
"adbc.snowflake.sql.client_option.disable_telemetry";
+    pub const LOG_TRACING: &str = "adbc.snowflake.sql.client_option.tracing";
+    pub const CLIENT_CONFIG_FILE: &str = 
"adbc.snowflake.sql.client_option.config_file";
+    pub const CLIENT_CACHE_MFA_TOKEN: &str = 
"adbc.snowflake.sql.client_option.cache_mfa_token";
+    pub const CLIENT_STORE_TEMP_CREDS: &str = 
"adbc.snowflake.sql.client_option.store_temp_creds";
+
+    /// Use the provided URI ([`Self::uri`]).
+    pub fn with_uri(mut self, uri: Url) -> Self {
+        self.uri = Some(uri);
+        self
+    }
+
+    /// Parse and use the provided URI ([`Self::uri`]).
+    ///
+    /// Returns an error when parsing fails.
+    pub fn with_parse_uri(self, uri: impl AsRef<str>) -> Result<Self> {
+        Url::parse(uri.as_ref())
+            .map_err(|error| {
+                Error::with_message_and_status(error.to_string(), 
Status::InvalidArguments)
+            })
+            .map(|uri| self.with_uri(uri))
+    }
+
+    /// Use the provided username ([`Self::username`]).
+    pub fn with_username(mut self, username: impl Into<String>) -> Self {
+        self.username = Some(username.into());
+        self
+    }
+
+    /// Use the provided password ([`Self::password`]).
+    pub fn with_password(mut self, password: impl Into<String>) -> Self {
+        self.password = Some(password.into());
+        self
+    }
+
+    /// Use the provided database ([`Self::database`]).
+    pub fn with_database(mut self, database: impl Into<String>) -> Self {
+        self.database = Some(database.into());
+        self
+    }
+
+    /// Use the provided schema ([`Self::schema`]).
+    pub fn with_schema(mut self, schema: impl Into<String>) -> Self {
+        self.schema = Some(schema.into());
+        self
+    }
+
+    /// Use the provided warehouse ([`Self::warehouse`]).
+    pub fn with_warehouse(mut self, warehouse: impl Into<String>) -> Self {
+        self.warehouse = Some(warehouse.into());
+        self
+    }
+
+    /// Use the provided role ([`Self::role`]).
+    pub fn with_role(mut self, role: impl Into<String>) -> Self {
+        self.role = Some(role.into());
+        self
+    }
+
+    /// Use the provided region ([`Self::region`]).
+    pub fn with_region(mut self, region: impl Into<String>) -> Self {
+        self.region = Some(region.into());
+        self
+    }
+
+    /// Use the provided account ([`Self::account`]).
+    pub fn with_account(mut self, account: impl Into<String>) -> Self {
+        self.account = Some(account.into());
+        self
+    }
+
+    /// Use the provided protocol ([`Self::protocol`]).
+    pub fn with_protocol(mut self, protocol: Protocol) -> Self {
+        self.protocol = Some(protocol);
+        self
+    }
+
+    /// Parse and use the provided protocol ([`Self::protocol`]).
+    ///
+    /// Returns an error when parsing fails.
+    pub fn with_parse_protocol(self, protocol: impl AsRef<str>) -> 
Result<Self> {
+        Protocol::from_str(protocol.as_ref()).map(|protocol| 
self.with_protocol(protocol))
+    }
+
+    /// Use the provided port ([`Self::port`]).
+    ///
+    /// # Example
+    ///
+    /// ```
+    /// # use adbc_snowflake::database::Builder;
+    /// let builder = Builder::default().with_port(443);
+    ///
+    /// assert_eq!(builder.port, Some(443));
+    /// ```
+    pub fn with_port(mut self, port: u16) -> Self {
+        self.port = Some(port);
+        self
+    }
+
+    /// Use the provided host ([`Self::host`]).
+    pub fn with_host(mut self, host: Host) -> Self {
+        self.host = Some(host);
+        self
+    }
+
+    /// Parse and use the provided host ([`Self::host`]).
+    ///
+    /// Returns an error when parsing fails.
+    pub fn with_parse_host(self, host: impl AsRef<str>) -> Result<Self> {
+        Host::parse(host.as_ref())
+            .map_err(|error| {
+                Error::with_message_and_status(error.to_string(), 
Status::InvalidArguments)
+            })
+            .map(|host| self.with_host(host))
+    }
+
+    /// Use the provided auth type ([`Self::auth_type`]).
+    pub fn with_auth_type(mut self, auth_type: AuthType) -> Self {
+        self.auth_type = Some(auth_type);
+        self
+    }
+
+    /// Parse and use the provided auth type ([`Self::auth_type`]).
+    ///
+    /// Returns an error when parsing fails.
+    pub fn with_parse_auth_type(self, auth_type: impl AsRef<str>) -> 
Result<Self> {
+        AuthType::from_str(auth_type.as_ref()).map(|auth_type| 
self.with_auth_type(auth_type))
+    }
+
+    /// Use the provided login timeout ([`Self::login_timeout`]).
+    pub fn with_login_timeout(mut self, login_timeout: Duration) -> Self {
+        self.login_timeout = Some(login_timeout);
+        self
+    }
+
+    /// Parse and use the provided login timeout ([`Self::login_timeout`]).
+    ///
+    /// Returns an error when parsing fails.
+    #[cfg(feature = "env")]
+    pub fn with_parse_login_timeout(self, login_timeout: impl AsRef<str>) -> 
Result<Self> {
+        parse_duration(login_timeout.as_ref())
+            .map(|login_timeout| self.with_login_timeout(login_timeout))
+    }
+
+    /// Use the provided request timeout ([`Self::request_timeout`]).
+    pub fn with_request_timeout(mut self, request_timeout: Duration) -> Self {
+        self.request_timeout = Some(request_timeout);
+        self
+    }
+
+    /// Parse and use the provided request timeout ([`Self::request_timeout`]).
+    ///
+    /// Returns an error when parsing fails.
+    #[cfg(feature = "env")]
+    pub fn with_parse_request_timeout(self, request_timeout: impl AsRef<str>) 
-> Result<Self> {
+        parse_duration(request_timeout.as_ref())
+            .map(|request_timeout| self.with_request_timeout(request_timeout))
+    }
+
+    /// Use the provided JWT expire timeout ([`Self::jwt_expire_timeout`]).
+    pub fn with_jwt_expire_timeout(mut self, jwt_expire_timeout: Duration) -> 
Self {
+        self.jwt_expire_timeout = Some(jwt_expire_timeout);
+        self
+    }
+
+    /// Parse and use the provided JWT expire timeout 
([`Self::jwt_expire_timeout`]).
+    ///
+    /// Returns an error when parsing fails.
+    #[cfg(feature = "env")]
+    pub fn with_parse_jwt_expire_timeout(
+        self,
+        jwt_expire_timeout: impl AsRef<str>,
+    ) -> Result<Self> {
+        parse_duration(jwt_expire_timeout.as_ref())
+            .map(|jwt_expire_timeout| 
self.with_jwt_expire_timeout(jwt_expire_timeout))
+    }
+
+    /// Use the provided client timeout ([`Self::client_timeout`]).
+    pub fn with_client_timeout(mut self, client_timeout: Duration) -> Self {
+        self.client_timeout = Some(client_timeout);
+        self
+    }
+
+    /// Parse and use the provided client timeout ([`Self::client_timeout`]).
+    ///
+    /// Returns an error when parsing fails.
+    #[cfg(feature = "env")]
+    pub fn with_parse_client_timeout(self, client_timeout: impl AsRef<str>) -> 
Result<Self> {
+        parse_duration(client_timeout.as_ref())
+            .map(|client_timeout| self.with_client_timeout(client_timeout))
+    }
+
+    /// Use high precision ([`Self::use_high_precision`]).
+    pub fn with_high_precision(mut self, use_high_precision: bool) -> Self {
+        self.use_high_precision = Some(use_high_precision);
+        self
+    }
+
+    /// Use the provided application name ([`Self::application_name`]).
+    pub fn with_application_name(mut self, application_name: impl 
Into<String>) -> Self {
+        self.application_name = Some(application_name.into());
+        self
+    }
+
+    /// Use SSL skip verify ([`Self::ssl_skip_verify`]).
+    pub fn with_ssl_skip_verify(mut self, ssl_skip_verify: bool) -> Self {
+        self.ssl_skip_verify = Some(ssl_skip_verify);
+        self
+    }
+
+    /// Use OCSP fail open mode ([`Self::ocsp_fail_open_mode`]).
+    pub fn with_ocsp_fail_open_mode(mut self, ocsp_fail_open_mode: bool) -> 
Self {
+        self.ocsp_fail_open_mode = Some(ocsp_fail_open_mode);
+        self
+    }
+
+    /// Use the provided auth token ([`Self::auth_token`]).
+    pub fn with_auth_token(mut self, auth_token: impl Into<String>) -> Self {
+        self.auth_token = Some(auth_token.into());
+        self
+    }
+
+    /// Use the provided auth Okta URL ([`Self::auth_okta_url`]).
+    pub fn with_auth_okta_url(mut self, auth_okta_url: Url) -> Self {
+        self.auth_okta_url = Some(auth_okta_url);
+        self
+    }
+
+    /// Parse and use the provided auth Okta URL ([`Self::auth_okta_url`]).
+    ///
+    /// Returns an error when parsing fails.
+    pub fn with_parse_auth_okta_url(self, auth_okta_url: impl AsRef<str>) -> 
Result<Self> {
+        Url::parse(auth_okta_url.as_ref())
+            .map_err(|error| {
+                Error::with_message_and_status(error.to_string(), 
Status::InvalidArguments)
+            })
+            .map(|auth_okta_url| self.with_auth_okta_url(auth_okta_url))
+    }
+
+    /// Use keep session alive ([`Self::keep_session_alive`]).
+    pub fn with_keep_session_alive(mut self, keep_session_alive: bool) -> Self 
{
+        self.keep_session_alive = Some(keep_session_alive);
+        self
+    }
+
+    /// Use the provided JWT private key path ([`Self::jwt_private_key`]).
+    pub fn with_jwt_private_key(mut self, jwt_private_key: PathBuf) -> Self {
+        self.jwt_private_key = Some(jwt_private_key);
+        self
+    }
+
+    /// Use the provided JWT private key PKCS 8 value 
([`Self::jwt_private_key_pkcs8_value`]).
+    pub fn with_jwt_private_key_pkcs8_value(mut self, 
jwt_private_key_pkcs8_value: String) -> Self {
+        self.jwt_private_key_pkcs8_value = Some(jwt_private_key_pkcs8_value);
+        self
+    }
+
+    /// Use the provided JWT private key PKCS 8 password 
([`Self::jwt_private_key_pkcs8_password`]).
+    pub fn with_jwt_private_key_pkcs8_password(
+        mut self,
+        jwt_private_key_pkcs8_password: String,
+    ) -> Self {
+        self.jwt_private_key_pkcs8_password = 
Some(jwt_private_key_pkcs8_password);
+        self
+    }
+
+    /// Use disable telemetry ([`Self::disable_telemetry`]).
+    pub fn with_disable_telemetry(mut self, disable_telemetry: bool) -> Self {
+        self.disable_telemetry = Some(disable_telemetry);
+        self
+    }
+
+    /// Use the provided tracing log level ([`Self::log_tracing`]).
+    pub fn with_log_tracing(mut self, log_tracing: LogLevel) -> Self {
+        self.log_tracing = Some(log_tracing);
+        self
+    }
+
+    /// Use the provided client config file path 
([`Self::client_config_file`]).
+    pub fn with_client_config_file(mut self, client_config_file: PathBuf) -> 
Self {
+        self.client_config_file = Some(client_config_file);
+        self
+    }
+
+    /// Use cache MFA token ([`Self::client_cache_mfa_token`]).
+    pub fn with_client_cache_mfa_token(mut self, client_cache_mfa_token: bool) 
-> Self {
+        self.client_cache_mfa_token = Some(client_cache_mfa_token);
+        self
+    }
+
+    /// Use store temporary credentials ([`Self::client_store_temp_creds`]).
+    pub fn with_client_store_temp_creds(mut self, client_store_temp_creds: 
bool) -> Self {
+        self.client_store_temp_creds = Some(client_store_temp_creds);
+        self
+    }
+}
+
+impl Builder {
+    /// Attempt to initialize a [`Database`] using the values provided to this
+    /// builder using the provided [`Driver`].
+    pub fn build(self, driver: &mut Driver) -> Result<Database> {
+        driver.new_database_with_opts(self)
+    }
+}
+
+impl IntoIterator for Builder {
+    type Item = (OptionDatabase, OptionValue);
+    type IntoIter = BuilderIter<OptionDatabase, { Self::COUNT }>;
+
+    fn into_iter(self) -> Self::IntoIter {
+        BuilderIter::new(
+            [
+                self.uri
+                    .as_ref()
+                    .map(ToString::to_string)
+                    .map(OptionValue::String)
+                    .map(|value| (OptionDatabase::Uri, value)),
+                self.username
+                    .map(OptionValue::String)
+                    .map(|value| (OptionDatabase::Username, value)),
+                self.password
+                    .map(OptionValue::String)
+                    .map(|value| (OptionDatabase::Password, value)),
+                self.database
+                    .map(OptionValue::String)
+                    .map(|value| (Builder::DATABASE.into(), value)),
+                self.schema
+                    .map(OptionValue::String)
+                    .map(|value| (Builder::SCHEMA.into(), value)),
+                self.warehouse
+                    .map(OptionValue::String)
+                    .map(|value| (Builder::WAREHOUSE.into(), value)),
+                self.role
+                    .map(OptionValue::String)
+                    .map(|value| (Builder::ROLE.into(), value)),
+                self.region
+                    .map(OptionValue::String)
+                    .map(|value| (Builder::REGION.into(), value)),
+                self.account
+                    .map(OptionValue::String)
+                    .map(|value| (Builder::ACCOUNT.into(), value)),
+                self.protocol
+                    .as_ref()
+                    .map(ToString::to_string)
+                    .map(OptionValue::String)
+                    .map(|value| (Builder::PROTOCOL.into(), value)),
+                self.port
+                    .as_ref()
+                    // TODO(mbrobbel): the driver expects a string here?
+                    // .map(i64::from)
+                    // .map(OptionValue::Int)
+                    .map(ToString::to_string)
+                    .map(OptionValue::String)
+                    .map(|value| (Builder::PORT.into(), value)),
+                self.host
+                    .as_ref()
+                    .map(ToString::to_string)
+                    .map(OptionValue::String)
+                    .map(|value| (Builder::HOST.into(), value)),
+                self.auth_type
+                    .as_ref()
+                    .map(ToString::to_string)
+                    .map(OptionValue::String)
+                    .map(|value| (Builder::AUTH_TYPE.into(), value)),
+                self.login_timeout
+                    .as_ref()
+                    .map(|duration| format!("{duration:?}"))
+                    .map(OptionValue::String)
+                    .map(|value| (Builder::LOGIN_TIMEOUT.into(), value)),
+                self.request_timeout
+                    .as_ref()
+                    .map(|duration| format!("{duration:?}"))
+                    .map(OptionValue::String)
+                    .map(|value| (Builder::REQUEST_TIMEOUT.into(), value)),
+                self.jwt_expire_timeout
+                    .as_ref()
+                    .map(|duration| format!("{duration:?}"))
+                    .map(OptionValue::String)
+                    .map(|value| (Builder::JWT_EXPIRE_TIMEOUT.into(), value)),
+                self.client_timeout
+                    .as_ref()
+                    .map(|duration| format!("{duration:?}"))
+                    .map(OptionValue::String)
+                    .map(|value| (Builder::CLIENT_TIMEOUT.into(), value)),
+                self.use_high_precision
+                    .as_ref()
+                    .map(ToString::to_string)
+                    .map(OptionValue::String)
+                    .map(|value| (Builder::USE_HIGH_PRECISION.into(), value)),
+                self.application_name
+                    .map(OptionValue::String)
+                    .map(|value| (Builder::APPLICATION_NAME.into(), value)),
+                self.ssl_skip_verify
+                    .as_ref()
+                    .map(ToString::to_string)
+                    .map(OptionValue::String)
+                    .map(|value| (Builder::SSL_SKIP_VERIFY.into(), value)),
+                self.ocsp_fail_open_mode
+                    .as_ref()
+                    .map(ToString::to_string)
+                    .map(OptionValue::String)
+                    .map(|value| (Builder::OCSP_FAIL_OPEN_MODE.into(), value)),
+                self.auth_token
+                    .map(OptionValue::String)
+                    .map(|value| (Builder::AUTH_TOKEN.into(), value)),
+                self.auth_okta_url
+                    .as_ref()
+                    .map(ToString::to_string)
+                    .map(OptionValue::String)
+                    .map(|value| (Builder::AUTH_OKTA_URL.into(), value)),
+                self.keep_session_alive
+                    .as_ref()
+                    .map(ToString::to_string)
+                    .map(OptionValue::String)
+                    .map(|value| (Builder::KEEP_SESSION_ALIVE.into(), value)),
+                self.jwt_private_key
+                    .as_ref()
+                    .map(|path| path.display().to_string())
+                    .map(OptionValue::String)
+                    .map(|value| (Builder::JWT_PRIVATE_KEY.into(), value)),
+                self.jwt_private_key_pkcs8_value
+                    .as_ref()
+                    .map(ToString::to_string)
+                    .map(OptionValue::String)
+                    .map(|value| (Builder::JWT_PRIVATE_KEY_PKCS8_VALUE.into(), 
value)),
+                self.jwt_private_key_pkcs8_password
+                    .as_ref()
+                    .map(ToString::to_string)
+                    .map(OptionValue::String)
+                    .map(|value| 
(Builder::JWT_PRIVATE_KEY_PKCS8_PASSWORD.into(), value)),
+                self.disable_telemetry
+                    .as_ref()
+                    .map(ToString::to_string)
+                    .map(OptionValue::String)
+                    .map(|value| (Builder::DISABLE_TELEMETRY.into(), value)),
+                self.log_tracing
+                    .as_ref()
+                    .map(ToString::to_string)
+                    .map(OptionValue::String)
+                    .map(|value| (Builder::LOG_TRACING.into(), value)),
+                self.client_config_file
+                    .as_ref()
+                    .map(|path| path.display().to_string())
+                    .map(OptionValue::String)
+                    .map(|value| (Builder::CLIENT_CONFIG_FILE.into(), value)),
+                self.client_cache_mfa_token
+                    .as_ref()
+                    .map(ToString::to_string)
+                    .map(OptionValue::String)
+                    .map(|value| (Builder::CLIENT_CACHE_MFA_TOKEN.into(), 
value)),
+                self.client_store_temp_creds
+                    .as_ref()
+                    .map(ToString::to_string)
+                    .map(OptionValue::String)
+                    .map(|value| (Builder::CLIENT_STORE_TEMP_CREDS.into(), 
value)),
+            ],
+            self.other,
+        )
+    }
+}
diff --git a/rust/driver/snowflake/src/driver.rs 
b/rust/driver/snowflake/src/driver.rs
new file mode 100644
index 000000000..825cd61ae
--- /dev/null
+++ b/rust/driver/snowflake/src/driver.rs
@@ -0,0 +1,201 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+//! Snowflake ADBC Driver
+//!
+//!
+
+#[cfg(any(feature = "bundled", feature = "linked"))]
+use std::ffi::{c_int, c_void};
+use std::{fmt, sync::LazyLock};
+
+#[cfg(any(feature = "bundled", feature = "linked"))]
+use adbc_core::ffi::{FFI_AdbcDriverInitFunc, FFI_AdbcError, 
FFI_AdbcStatusCode};
+use adbc_core::{
+    driver_manager::ManagedDriver,
+    error::Result,
+    options::{AdbcVersion, OptionDatabase, OptionValue},
+};
+
+use crate::Database;
+
+mod builder;
+pub use builder::*;
+
+static DRIVER: LazyLock<Result<ManagedDriver>> = LazyLock::new(|| {
+    ManagedDriver::load_dynamic_from_name(
+        "adbc_driver_snowflake",
+        Some(b"SnowflakeDriverInit"),
+        Default::default(),
+    )
+});
+
+/// Snowflake ADBC Driver.
+#[derive(Clone)]
+pub struct Driver(ManagedDriver);
+
+/// Panics when the drivers fails to load.
+impl Default for Driver {
+    fn default() -> Self {
+        Self::try_load().expect("driver init")
+    }
+}
+
+impl fmt::Debug for Driver {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        f.debug_struct("SnowflakeDriver")
+            .field("version", &self.0.version())
+            .finish_non_exhaustive()
+    }
+}
+
+#[cfg(any(feature = "bundled", feature = "linked"))]
+extern "C" {
+    #[link_name = "SnowflakeDriverInit"]
+    fn init(version: c_int, raw_driver: *mut c_void, err: *mut FFI_AdbcError)
+        -> FFI_AdbcStatusCode;
+}
+
+impl Driver {
+    /// Returns a [`Driver`].
+    ///
+    /// Defaults to the loading the latest [`AdbcVersion`]. To load the
+    /// [`Driver`] with a different version use [`Builder::with_adbc_version`],
+    /// or to load the configuration from environment variables use
+    /// [`Builder::from_env`].
+    ///
+    /// If the crate was built without the `bundled` and `linked` features this
+    /// will attempt to dynamically load the driver.
+    ///
+    /// # Error
+    ///
+    /// Returns an error when the driver fails to load.
+    ///
+    /// # Example
+    ///
+    /// ## Using the default ADBC version
+    ///
+    /// ```rust
+    /// # use adbc_core::error::Result;
+    /// # use adbc_snowflake::Driver;
+    /// # fn main() -> Result<()> {
+    /// let mut driver = Driver::try_load()?;
+    /// # Ok(()) }
+    /// ```
+    ///
+    /// ## Using a different ADBC version
+    ///
+    /// ```rust
+    /// # use adbc_core::{error::Result, options::AdbcVersion};
+    /// # use adbc_snowflake::{driver::Builder, Driver};
+    /// # fn main() -> Result<()> {
+    /// let mut driver = Builder::default()
+    ///    .with_adbc_version(AdbcVersion::V100)
+    ///    .try_load()?;
+    /// # Ok(()) }
+    /// ```
+    pub fn try_load() -> Result<Self> {
+        Self::try_new(Default::default())
+    }
+
+    fn try_new(version: AdbcVersion) -> Result<Self> {
+        // Load the bundled or linked driver.
+        #[cfg(any(feature = "bundled", feature = "linked"))]
+        {
+            let driver_init: FFI_AdbcDriverInitFunc = init;
+            ManagedDriver::load_static(&driver_init, version).map(Self)
+        }
+        // Fallback: attempt to dynamically load the driver.
+        #[cfg(not(any(feature = "bundled", feature = "linked")))]
+        {
+            let _ = version;
+            Self::try_new_dynamic()
+        }
+    }
+
+    fn try_new_dynamic() -> Result<Self> {
+        DRIVER.clone().map(Self)
+    }
+
+    /// Returns a dynamically loaded [`Driver`].
+    ///
+    /// This attempts to load the `adbc_driver_snowflake` library using the
+    /// default `AdbcVersion`.
+    ///
+    /// # Error
+    ///
+    /// Returns an error when the driver fails to load.
+    ///
+    /// # Example
+    ///
+    /// ```rust,no_run
+    /// # use adbc_core::error::Result;
+    /// # use adbc_snowflake::Driver;
+    /// # fn main() -> Result<()> {
+    /// let mut driver = Driver::try_load_dynamic()?;
+    /// # Ok(()) }
+    /// ```
+    pub fn try_load_dynamic() -> Result<Self> {
+        Self::try_new_dynamic()
+    }
+}
+
+impl adbc_core::Driver for Driver {
+    type DatabaseType = Database;
+
+    fn new_database(&mut self) -> Result<Self::DatabaseType> {
+        self.0.new_database().map(Database)
+    }
+
+    fn new_database_with_opts(
+        &mut self,
+        opts: impl IntoIterator<Item = (OptionDatabase, OptionValue)>,
+    ) -> Result<Self::DatabaseType> {
+        self.0.new_database_with_opts(opts).map(Database)
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    fn try_load(version: AdbcVersion) -> Result<()> {
+        Builder::default().with_adbc_version(version).try_load()?;
+        Ok(())
+    }
+
+    #[test]
+    fn load_v1_0_0() -> Result<()> {
+        try_load(AdbcVersion::V100)
+    }
+
+    #[test]
+    fn load_v1_1_0() -> Result<()> {
+        try_load(AdbcVersion::V110)
+    }
+
+    #[test]
+    #[cfg_attr(
+        not(feature = "linked"),
+        ignore = "because the `linked` feature is not enabled"
+    )]
+    fn dynamic() -> Result<()> {
+        let _a = Driver::try_load_dynamic()?;
+        let _b = Driver::try_load_dynamic()?;
+        Ok(())
+    }
+}
diff --git a/rust/driver/snowflake/src/driver/builder.rs 
b/rust/driver/snowflake/src/driver/builder.rs
new file mode 100644
index 000000000..d1137cc3e
--- /dev/null
+++ b/rust/driver/snowflake/src/driver/builder.rs
@@ -0,0 +1,81 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+//! A builder for [`Driver`]
+//!
+//!
+
+#[cfg(feature = "env")]
+use std::env;
+
+use adbc_core::{
+    error::{Error, Result},
+    options::AdbcVersion,
+};
+
+use crate::Driver;
+
+/// A builder for [`Driver`].
+///
+/// The builder can be used to initialize a [`Driver`] with
+/// [`Builder::try_load`].
+#[derive(Clone, Debug, Default)]
+#[non_exhaustive]
+pub struct Builder {
+    /// The [`AdbcVersion`] version of the driver.
+    pub adbc_version: Option<AdbcVersion>,
+}
+
+#[cfg(feature = "env")]
+impl Builder {
+    /// See [`Self::adbc_version`].
+    pub const ADBC_VERSION_ENV: &str = "ADBC_SNOWFLAKE_ADBC_VERSION";
+
+    /// Construct a builder, setting values based on values of the
+    /// configuration environment variables.
+    pub fn from_env() -> Self {
+        #[cfg(feature = "dotenv")]
+        let _ = dotenvy::dotenv();
+
+        let adbc_version = env::var(Self::ADBC_VERSION_ENV)
+            .ok()
+            .as_deref()
+            .and_then(|value| value.parse().ok());
+        Self { adbc_version }
+    }
+}
+
+impl Builder {
+    /// Use the provided [`AdbcVersion`] when loading the driver.
+    pub fn with_adbc_version(mut self, version: AdbcVersion) -> Self {
+        self.adbc_version = Some(version);
+        self
+    }
+
+    /// Try to load the [`Driver`] using the values provided to this builder.
+    pub fn try_load(self) -> Result<Driver> {
+        Driver::try_new(self.adbc_version.unwrap_or_default())
+    }
+}
+
+impl TryFrom<Builder> for Driver {
+    type Error = Error;
+
+    fn try_from(value: Builder) -> Result<Self> {
+        value.try_load()
+    }
+}
diff --git a/rust/driver/snowflake/src/duration.rs 
b/rust/driver/snowflake/src/duration.rs
new file mode 100644
index 000000000..642851e65
--- /dev/null
+++ b/rust/driver/snowflake/src/duration.rs
@@ -0,0 +1,281 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+//! A parser for [`Duration`] following <https://pkg.go.dev/time#ParseDuration>
+//!
+//!
+
+use std::{error::Error as StdError, num::IntErrorKind, sync::LazyLock, 
time::Duration};
+
+use adbc_core::error::{Error, Result, Status};
+use regex::Regex;
+
+// The regular expression used to parse durations.
+static RE: LazyLock<Regex> = LazyLock::new(|| {
+    
Regex::new(r"(?P<int>[0-9]*)\.?(?P<frac>[0-9]*)(?P<unit>ns|us|µs|μs|ms|s|m|h)")
+        .expect("valid regex")
+});
+
+fn invalid_arg(err: impl Into<String>) -> Error {
+    Error::with_message_and_status(err.into(), Status::InvalidArguments)
+}
+
+fn arg_err(err: impl StdError) -> Error {
+    invalid_arg(err.to_string())
+}
+
+fn overflow() -> Error {
+    invalid_arg("overflow")
+}
+
+fn bad_input<T>() -> Result<T> {
+    Err(invalid_arg("bad input"))
+}
+
+/// Parse the given string to a [`Duration`], returning an eror when parsing
+/// fails.
+///
+/// Following the logic of <https://pkg.go.dev/time#ParseDuration>, except this
+/// implementation does not support negative values i.e. it ignores sign
+/// symbols, because [`Duration`] does not support negative values, and the Go
+/// Snowflake driver uses absolute values for these durations.
+pub(crate) fn parse_duration(input: &str) -> Result<Duration> {
+    // Drop sign symbols.
+    let input = input.replace(['+', '-'], "");
+
+    // Special case for zero.
+    if input == "0" {
+        return Ok(Duration::ZERO);
+    }
+
+    // Other bad input when there is no match.
+    if RE.find(&input).is_none() {
+        return bad_input();
+    }
+
+    // Aggregate durations.
+    RE.captures_iter(&input)
+        .map(|caps| caps.extract())
+        .try_fold(Duration::ZERO, |mut duration, (_, [int, frac, unit])| {
+            // Int and frac can't both be empty.
+            if int.is_empty() && frac.is_empty() {
+                return bad_input();
+            }
+
+            // Parse int part.
+            let int = int
+                .parse::<u64>()
+                .or_else(|err| match err.kind() {
+                    // Int can be empty to support .<frac> notation.
+                    &IntErrorKind::Empty => Ok(0),
+                    _ => Err(err),
+                })
+                .map_err(arg_err)?;
+
+            // Parse frac part.
+            let frac = format!("0.{frac}").parse::<f64>().map_err(arg_err)?;
+
+            // Add this part to the duration.
+            duration += match unit {
+                "h" => {
+                    
Duration::from_secs(3600u64.checked_mul(int).ok_or(overflow())?)
+                        + Duration::try_from_secs_f64(3600f64 * 
frac).map_err(arg_err)?
+                }
+                "m" => {
+                    
Duration::from_secs(60u64.checked_mul(int).ok_or(overflow())?)
+                        + Duration::try_from_secs_f64(60f64 * 
frac).map_err(arg_err)?
+                }
+                "s" => {
+                    Duration::from_secs(int) + 
Duration::try_from_secs_f64(frac).map_err(arg_err)?
+                }
+                "ms" => {
+                    Duration::from_millis(int)
+                        + Duration::try_from_secs_f64(frac / 
1e3).map_err(arg_err)?
+                }
+                "us" | "µs" | "μs" => {
+                    Duration::from_micros(int)
+                        + Duration::try_from_secs_f64(frac / 
1e6).map_err(arg_err)?
+                }
+                "ns" => {
+                    if frac != 0f64 {
+                        return Err(invalid_arg(
+                            "unexpected fractional part for duration with ns 
unit",
+                        ));
+                    }
+                    Duration::from_nanos(int)
+                }
+                _ => unreachable!("<unit> matching group prevents this 
branch"),
+            };
+
+            Ok(duration)
+        })
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn parse() {
+        assert_eq!(parse_duration("0"), Ok(Duration::from_secs(0)));
+        assert_eq!(parse_duration("0000.0000s"), Ok(Duration::from_secs(0)));
+        assert_eq!(parse_duration("5s"), Ok(Duration::from_secs(5)));
+        assert_eq!(parse_duration("30s"), Ok(Duration::from_secs(30)));
+        assert_eq!(parse_duration("1478s"), Ok(Duration::from_secs(1478)));
+        assert_eq!(parse_duration("-5s"), Ok(Duration::from_secs(5)));
+        assert_eq!(parse_duration("+5s"), Ok(Duration::from_secs(5)));
+        assert_eq!(parse_duration("-0"), Ok(Duration::from_secs(0)));
+        assert_eq!(parse_duration("+0"), Ok(Duration::from_secs(0)));
+        assert_eq!(parse_duration("5.0s"), Ok(Duration::from_secs(5)));
+        assert_eq!(
+            parse_duration("5.6s"),
+            Ok(Duration::from_secs(5) + Duration::from_millis(600))
+        );
+        assert_eq!(parse_duration("5.s"), Ok(Duration::from_secs(5)));
+        assert_eq!(parse_duration(".5s"), Ok(Duration::from_millis(500)));
+        assert_eq!(parse_duration("1.0s"), Ok(Duration::from_secs(1)));
+        assert_eq!(parse_duration("1.00s"), Ok(Duration::from_secs(1)));
+        assert_eq!(
+            parse_duration("1.004s"),
+            Ok(Duration::from_secs(1) + Duration::from_millis(4))
+        );
+        assert_eq!(
+            parse_duration("1.0040s"),
+            Ok(Duration::from_secs(1) + Duration::from_millis(4))
+        );
+        assert_eq!(
+            parse_duration("100.00100s"),
+            Ok(Duration::from_secs(100) + Duration::from_millis(1))
+        );
+        assert_eq!(parse_duration("10ns"), Ok(Duration::from_nanos(10)));
+        assert_eq!(parse_duration("11us"), Ok(Duration::from_micros(11)));
+        assert_eq!(parse_duration("12µs"), Ok(Duration::from_micros(12)));
+        assert_eq!(parse_duration("12μs"), Ok(Duration::from_micros(12)));
+        assert_eq!(parse_duration("13ms"), Ok(Duration::from_millis(13)));
+        assert_eq!(parse_duration("14s"), Ok(Duration::from_secs(14)));
+        assert_eq!(parse_duration("15m"), Ok(Duration::from_secs(60 * 15)));
+        assert_eq!(parse_duration("16h"), Ok(Duration::from_secs(60 * 60 * 
16)));
+        assert_eq!(
+            parse_duration("3h30m"),
+            Ok(Duration::from_secs(60 * 60 * 3) + Duration::from_secs(60 * 30))
+        );
+        assert_eq!(
+            parse_duration("10.5s4m"),
+            Ok(Duration::from_secs(10) + Duration::from_millis(500) + 
Duration::from_secs(60 * 4))
+        );
+        assert_eq!(
+            parse_duration("-2m3.4s"),
+            Ok(Duration::from_secs(60 * 2) + Duration::from_secs(3) + 
Duration::from_millis(400))
+        );
+        assert_eq!(
+            parse_duration("1h2m3s4ms5us6ns"),
+            Ok(Duration::from_secs(60 * 60)
+                + Duration::from_secs(60 * 2)
+                + Duration::from_secs(3)
+                + Duration::from_millis(4)
+                + Duration::from_micros(5)
+                + Duration::from_nanos(6))
+        );
+        assert_eq!(
+            parse_duration("39h9m14.425s"),
+            Ok(Duration::from_secs(60 * 60 * 39)
+                + Duration::from_secs(60 * 9)
+                + Duration::from_secs(14)
+                + Duration::from_millis(425))
+        );
+        assert_eq!(
+            parse_duration("52763797000ns"),
+            Ok(Duration::from_nanos(52763797000))
+        );
+        assert_eq!(
+            parse_duration("0.3333333333333333333h"),
+            Ok(Duration::from_secs(60 * 20))
+        );
+        assert_eq!(
+            parse_duration("9007199254740993ns"),
+            Ok(Duration::from_nanos(9007199254740993))
+        );
+        assert_eq!(
+            parse_duration("9223372036854775807ns"),
+            Ok(Duration::from_nanos(9223372036854775807))
+        );
+        assert_eq!(
+            parse_duration("9223372036854775.807us"),
+            Ok(Duration::from_micros(9223372036854775) + 
Duration::from_nanos(807))
+        );
+        assert_eq!(
+            parse_duration("9223372036s854ms775us807ns"),
+            Ok(Duration::from_secs(9223372036)
+                + Duration::from_millis(854)
+                + Duration::from_micros(775)
+                + Duration::from_nanos(807))
+        );
+        assert_eq!(
+            parse_duration("-9223372036854775808ns"),
+            Ok(Duration::from_nanos(9223372036854775808))
+        );
+        assert_eq!(
+            parse_duration("-9223372036854775.808us"),
+            Ok(Duration::from_micros(9223372036854775) + 
Duration::from_nanos(808))
+        );
+        assert_eq!(
+            parse_duration("-9223372036s854ms775us808ns"),
+            Ok(Duration::from_secs(9223372036)
+                + Duration::from_millis(854)
+                + Duration::from_micros(775)
+                + Duration::from_nanos(808))
+        );
+        assert_eq!(
+            parse_duration("-9223372036854775808ns"),
+            Ok(Duration::from_nanos(9223372036854775808))
+        );
+        assert_eq!(
+            parse_duration("-2562047h47m16.854775808s"),
+            Ok(Duration::from_secs(60 * 60 * 2562047)
+                + Duration::from_secs(60 * 47)
+                + Duration::from_secs(16)
+                + Duration::from_nanos(854775808))
+        );
+        assert_eq!(
+            parse_duration("0.100000000000000000000h"),
+            Ok(Duration::from_secs(60 * 6))
+        );
+        assert_eq!(
+            parse_duration("0.830103483285477580700h"),
+            Ok(Duration::from_secs(60 * 49)
+                + Duration::from_secs(48)
+                + Duration::from_nanos(372539828))
+        );
+        let bad_input = Err(invalid_arg("bad input"));
+        assert_eq!(parse_duration(""), bad_input);
+        assert_eq!(parse_duration("3"), bad_input);
+        assert_eq!(parse_duration("-"), bad_input);
+        assert_eq!(parse_duration("s"), bad_input);
+        assert_eq!(parse_duration("."), bad_input);
+        assert_eq!(parse_duration("-."), bad_input);
+        assert_eq!(parse_duration(".s"), bad_input);
+        assert_eq!(parse_duration("+.s"), bad_input);
+        assert_eq!(parse_duration("1d"), bad_input);
+        assert_eq!(
+            parse_duration("1.1ns"),
+            Err(invalid_arg(
+                "unexpected fractional part for duration with ns unit"
+            ))
+        );
+        assert_eq!(parse_duration("9999999999999999h"), Err(overflow()));
+    }
+}
diff --git a/rust/driver/snowflake/src/lib.rs b/rust/driver/snowflake/src/lib.rs
new file mode 100644
index 000000000..9ce3b0765
--- /dev/null
+++ b/rust/driver/snowflake/src/lib.rs
@@ -0,0 +1,40 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+#![cfg_attr(docsrs, feature(doc_auto_cfg, doc_cfg))]
+#![doc(
+    html_logo_url = 
"https://raw.githubusercontent.com/apache/arrow/refs/heads/main/docs/source/_static/favicon.ico";,
+    html_favicon_url = 
"https://raw.githubusercontent.com/apache/arrow/refs/heads/main/docs/source/_static/favicon.ico";
+)]
+#![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/README.md"))]
+
+pub mod driver;
+pub use driver::Driver;
+
+pub mod database;
+pub use database::Database;
+
+pub mod connection;
+pub use connection::Connection;
+
+pub mod statement;
+pub use statement::Statement;
+
+pub(crate) mod builder;
+
+#[cfg(feature = "env")]
+pub(crate) mod duration;
diff --git a/rust/driver/snowflake/src/statement.rs 
b/rust/driver/snowflake/src/statement.rs
new file mode 100644
index 000000000..34b83f09b
--- /dev/null
+++ b/rust/driver/snowflake/src/statement.rs
@@ -0,0 +1,102 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+//! Snowflake ADBC Statement
+//!
+//!
+
+use adbc_core::{
+    driver_manager::ManagedStatement,
+    error::Result,
+    options::{OptionStatement, OptionValue},
+    Optionable, PartitionedResult,
+};
+use arrow_array::{RecordBatch, RecordBatchReader};
+use arrow_schema::Schema;
+
+/// Snowflake ADBC Statement.
+pub struct Statement(pub(crate) ManagedStatement);
+
+impl Optionable for Statement {
+    type Option = OptionStatement;
+
+    fn set_option(&mut self, key: Self::Option, value: OptionValue) -> 
Result<()> {
+        self.0.set_option(key, value)
+    }
+
+    fn get_option_string(&self, key: Self::Option) -> Result<String> {
+        self.0.get_option_string(key)
+    }
+
+    fn get_option_bytes(&self, key: Self::Option) -> Result<Vec<u8>> {
+        self.0.get_option_bytes(key)
+    }
+
+    fn get_option_int(&self, key: Self::Option) -> Result<i64> {
+        self.0.get_option_int(key)
+    }
+
+    fn get_option_double(&self, key: Self::Option) -> Result<f64> {
+        self.0.get_option_double(key)
+    }
+}
+
+impl adbc_core::Statement for Statement {
+    fn bind(&mut self, batch: RecordBatch) -> Result<()> {
+        self.0.bind(batch)
+    }
+
+    fn bind_stream(&mut self, reader: Box<dyn RecordBatchReader + Send>) -> 
Result<()> {
+        self.0.bind_stream(reader)
+    }
+
+    fn execute(&mut self) -> Result<impl RecordBatchReader + Send> {
+        self.0.execute()
+    }
+
+    fn execute_update(&mut self) -> Result<Option<i64>> {
+        self.0.execute_update()
+    }
+
+    fn execute_schema(&mut self) -> Result<Schema> {
+        self.0.execute_schema()
+    }
+
+    fn execute_partitions(&mut self) -> Result<PartitionedResult> {
+        self.0.execute_partitions()
+    }
+
+    fn get_parameter_schema(&self) -> Result<Schema> {
+        self.0.get_parameter_schema()
+    }
+
+    fn prepare(&mut self) -> Result<()> {
+        self.0.prepare()
+    }
+
+    fn set_sql_query(&mut self, query: impl AsRef<str>) -> Result<()> {
+        self.0.set_sql_query(query)
+    }
+
+    fn set_substrait_plan(&mut self, plan: impl AsRef<[u8]>) -> Result<()> {
+        self.0.set_substrait_plan(plan)
+    }
+
+    fn cancel(&mut self) -> Result<()> {
+        self.0.cancel()
+    }
+}
diff --git a/rust/driver/snowflake/tests/driver.rs 
b/rust/driver/snowflake/tests/driver.rs
new file mode 100644
index 000000000..8f2a28d72
--- /dev/null
+++ b/rust/driver/snowflake/tests/driver.rs
@@ -0,0 +1,172 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+//! Snowflake ADBC driver tests
+//!
+//! These tests are disabled by default because they require a Snowflake
+//! account.
+//!
+//! To enable these tests set the `ADBC_SNOWFLAKE_TESTS` environment variable
+//! when building these tests.
+//!
+//! These tests load the configuration from environment variables:
+//! - Driver: [`adbc_snowflake::driver::Builder::from_env`]
+//! - Database: [`adbc_snowflake::database::Builder::from_env`]
+//! - Connection: [`adbc_snowflake::connection::Builder::from_env`]
+//! - Statement: ...
+//!
+//! These methods are available when the `env` crate feature is enabled.
+//!
+
+#[cfg(feature = "env")]
+mod tests {
+    use std::{collections::HashSet, ops::Deref, sync::LazyLock};
+
+    use adbc_core::{
+        error::{Error, Result},
+        options::AdbcVersion,
+        Connection as _, Statement as _,
+    };
+    use adbc_snowflake::{connection, database, driver, Connection, Database, 
Driver, Statement};
+    use arrow_array::{cast::AsArray, types::Decimal128Type};
+
+    const ADBC_VERSION: AdbcVersion = AdbcVersion::V110;
+
+    static DRIVER: LazyLock<Result<Driver>> = LazyLock::new(|| {
+        driver::Builder::from_env()
+            .with_adbc_version(ADBC_VERSION)
+            .try_load()
+    });
+
+    static DATABASE: LazyLock<Result<Database>> =
+        LazyLock::new(|| database::Builder::from_env().build(&mut 
DRIVER.deref().clone()?));
+
+    static CONNECTION: LazyLock<Result<Connection>> =
+        LazyLock::new(|| connection::Builder::from_env().build(&mut 
DATABASE.deref().clone()?));
+
+    fn with_database(func: impl FnOnce(Database) -> Result<()>) -> Result<()> {
+        DATABASE.deref().clone().and_then(func)
+    }
+
+    fn with_connection(func: impl FnOnce(Connection) -> Result<()>) -> 
Result<()> {
+        // This always clones the connection because connection methods require
+        // exclusive access (&mut Connection). The alternative would be an
+        // `Arc<Mutex<Connection>>` however any test failure is a panic and
+        // would trigger mutex poisoning.
+        //
+        // TODO(mbrobbel): maybe force interior mutability via the core traits?
+        CONNECTION.deref().clone().and_then(func)
+    }
+
+    fn with_empty_statement(func: impl FnOnce(Statement) -> Result<()>) -> 
Result<()> {
+        with_connection(|mut connection| 
connection.new_statement().and_then(func))
+    }
+
+    #[test_with::env(ADBC_SNOWFLAKE_TESTS)]
+    /// Check the returned info by the driver using the database methods.
+    fn database_get_info() -> Result<()> {
+        with_database(|mut database| {
+            assert_eq!(database.vendor_name(), Ok("Snowflake".to_owned()));
+            assert!(database
+                .vendor_version()
+                .is_ok_and(|version| version.starts_with("v")));
+            assert!(database.vendor_arrow_version().is_ok());
+            assert_eq!(database.vendor_sql(), Ok(true));
+            assert_eq!(database.vendor_substrait(), Ok(false));
+            assert_eq!(
+                database.driver_name(),
+                Ok("ADBC Snowflake Driver - Go".to_owned())
+            );
+            assert!(database.driver_version().is_ok());
+            assert!(database
+                .driver_arrow_version()
+                .is_ok_and(|version| version.starts_with("v")));
+            assert_eq!(database.adbc_version(), Ok(ADBC_VERSION));
+            Ok(())
+        })
+    }
+
+    #[test_with::env(ADBC_SNOWFLAKE_TESTS)]
+    /// Check execute of statement with `SELECT 21 + 21` query.
+    fn statement_execute() -> Result<()> {
+        with_empty_statement(|mut statement| {
+            statement.set_sql_query("SELECT 21 + 21")?;
+            let batch = statement
+                .execute()?
+                .next()
+                .expect("a record batch")
+                .map_err(Error::from)?;
+            assert_eq!(
+                batch.column(0).as_primitive::<Decimal128Type>().value(0),
+                42
+            );
+            Ok(())
+        })
+    }
+
+    #[test_with::env(ADBC_SNOWFLAKE_TESTS)]
+    /// Check execute schema of statement with `SHOW WAREHOUSES` query.
+    fn statement_execute_schema() -> Result<()> {
+        with_empty_statement(|mut statement| {
+            statement.set_sql_query("SHOW WAREHOUSES")?;
+            let schema = statement.execute_schema()?;
+            let field_names = schema
+                .fields()
+                .into_iter()
+                .map(|field| field.name().as_ref())
+                .collect::<HashSet<_>>();
+            let expected_field_names = [
+                "name",
+                "state",
+                "type",
+                "size",
+                "running",
+                "queued",
+                "is_default",
+                "is_current",
+                "auto_suspend",
+                "auto_resume",
+                "available",
+                "provisioning",
+                "quiescing",
+                "other",
+                "created_on",
+                "resumed_on",
+                "updated_on",
+                "owner",
+                "comment",
+                "resource_monitor",
+                "actives",
+                "pendings",
+                "failed",
+                "suspended",
+                "uuid",
+                "budget",
+                "owner_role_type",
+            ]
+            .into_iter()
+            .collect::<HashSet<_>>();
+            assert_eq!(
+                expected_field_names
+                    .difference(&field_names)
+                    .collect::<Vec<_>>(),
+                Vec::<&&str>::default()
+            );
+            Ok(())
+        })
+    }
+}

Reply via email to