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.
+-->
+
+
+
+[](https://crates.io/crates/adbc_snowflake)
+[](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(())
+ })
+ }
+}