From 4641cffb66adbdc944b83c139da93661eff2cc8c Mon Sep 17 00:00:00 2001 From: Bogdan Mircea Date: Fri, 6 Jun 2025 13:47:18 +0300 Subject: [PATCH 001/102] initial compile-time support --- .zed/settings.json | 9 +- Cargo.toml | 154 +++++++--- sqlx-exasol-cli/Cargo.toml | 21 ++ sqlx-exasol-cli/src/main.rs | 20 ++ sqlx-exasol-impl/Cargo.toml | 68 +++++ sqlx-exasol-impl/src/any.rs | 288 ++++++++++++++++++ {src => sqlx-exasol-impl/src}/arguments.rs | 2 +- {src => sqlx-exasol-impl/src}/column.rs | 14 +- .../src}/connection/etl/error.rs | 0 .../src}/connection/etl/export/compression.rs | 0 .../src}/connection/etl/export/mod.rs | 2 +- .../src}/connection/etl/export/options.rs | 2 +- .../src}/connection/etl/export/reader.rs | 2 +- .../src}/connection/etl/import/compression.rs | 0 .../src}/connection/etl/import/mod.rs | 0 .../src}/connection/etl/import/options.rs | 0 .../src}/connection/etl/import/writer.rs | 0 .../src}/connection/etl/job/maybe_tls/mod.rs | 0 .../connection/etl/job/maybe_tls/tls/mod.rs | 0 .../etl/job/maybe_tls/tls/native_tls.rs | 4 +- .../etl/job/maybe_tls/tls/rustls.rs | 4 +- .../etl/job/maybe_tls/tls/sync_socket.rs | 0 .../src}/connection/etl/job/mod.rs | 0 .../src}/connection/etl/mod.rs | 0 .../src}/connection/executor.rs | 0 .../src}/connection/mod.rs | 4 +- .../src}/connection/stream.rs | 4 +- .../src}/connection/websocket/future.rs | 0 .../src}/connection/websocket/mod.rs | 0 .../src}/connection/websocket/request.rs | 0 .../src}/connection/websocket/socket.rs | 0 .../src}/connection/websocket/tls.rs | 0 .../websocket/transport/compressed.rs | 0 .../connection/websocket/transport/mod.rs | 0 .../websocket/transport/uncompressed.rs | 0 {src => sqlx-exasol-impl/src}/database.rs | 0 {src => sqlx-exasol-impl/src}/error.rs | 0 sqlx-exasol-impl/src/lib.rs | 70 +++++ {src => sqlx-exasol-impl/src}/migrate.rs | 0 .../src}/options/builder.rs | 0 .../src}/options/error.rs | 0 {src => sqlx-exasol-impl/src}/options/mod.rs | 4 +- .../src}/options/protocol_version.rs | 0 .../src}/options/ssl_mode.rs | 0 {src => sqlx-exasol-impl/src}/query_result.rs | 0 .../src}/responses/attributes.rs | 0 .../src}/responses/columns.rs | 0 .../src}/responses/describe.rs | 0 .../src}/responses/error.rs | 0 .../src}/responses/fetch.rs | 0 .../src}/responses/hosts.rs | 0 .../src}/responses/mod.rs | 0 .../src}/responses/prepared_stmt.rs | 0 .../src}/responses/public_key.rs | 0 .../src}/responses/result.rs | 0 .../src}/responses/session_info.rs | 0 {src => sqlx-exasol-impl/src}/row.rs | 6 +- {src => sqlx-exasol-impl/src}/statement.rs | 20 +- {src => sqlx-exasol-impl/src}/testing.rs | 0 {src => sqlx-exasol-impl/src}/transaction.rs | 0 sqlx-exasol-impl/src/type_checking.rs | 60 ++++ {src => sqlx-exasol-impl/src}/type_info.rs | 2 +- {src => sqlx-exasol-impl/src}/types/bool.rs | 0 {src => sqlx-exasol-impl/src}/types/chrono.rs | 0 {src => sqlx-exasol-impl/src}/types/float.rs | 0 {src => sqlx-exasol-impl/src}/types/int.rs | 4 +- {src => sqlx-exasol-impl/src}/types/iter.rs | 1 + {src => sqlx-exasol-impl/src}/types/mod.rs | 0 {src => sqlx-exasol-impl/src}/types/option.rs | 0 .../src}/types/rust_decimal.rs | 0 {src => sqlx-exasol-impl/src}/types/str.rs | 0 {src => sqlx-exasol-impl/src}/types/text.rs | 20 +- {src => sqlx-exasol-impl/src}/types/uint.rs | 4 +- {src => sqlx-exasol-impl/src}/types/uuid.rs | 0 {src => sqlx-exasol-impl/src}/value.rs | 0 sqlx-exasol-macros/Cargo.toml | 23 ++ sqlx-exasol-macros/src/lib.rs | 23 ++ src/lib.rs | 70 +---- src/macros.rs | 133 ++++++++ tests/describe.rs | 3 +- tests/error.rs | 3 +- tests/from_row.rs | 7 +- tests/macros.rs | 18 +- 83 files changed, 892 insertions(+), 177 deletions(-) create mode 100644 sqlx-exasol-cli/Cargo.toml create mode 100644 sqlx-exasol-cli/src/main.rs create mode 100644 sqlx-exasol-impl/Cargo.toml create mode 100644 sqlx-exasol-impl/src/any.rs rename {src => sqlx-exasol-impl/src}/arguments.rs (99%) rename {src => sqlx-exasol-impl/src}/column.rs (75%) rename {src => sqlx-exasol-impl/src}/connection/etl/error.rs (100%) rename {src => sqlx-exasol-impl/src}/connection/etl/export/compression.rs (100%) rename {src => sqlx-exasol-impl/src}/connection/etl/export/mod.rs (99%) rename {src => sqlx-exasol-impl/src}/connection/etl/export/options.rs (99%) rename {src => sqlx-exasol-impl/src}/connection/etl/export/reader.rs (99%) rename {src => sqlx-exasol-impl/src}/connection/etl/import/compression.rs (100%) rename {src => sqlx-exasol-impl/src}/connection/etl/import/mod.rs (100%) rename {src => sqlx-exasol-impl/src}/connection/etl/import/options.rs (100%) rename {src => sqlx-exasol-impl/src}/connection/etl/import/writer.rs (100%) rename {src => sqlx-exasol-impl/src}/connection/etl/job/maybe_tls/mod.rs (100%) rename {src => sqlx-exasol-impl/src}/connection/etl/job/maybe_tls/tls/mod.rs (100%) rename {src => sqlx-exasol-impl/src}/connection/etl/job/maybe_tls/tls/native_tls.rs (99%) rename {src => sqlx-exasol-impl/src}/connection/etl/job/maybe_tls/tls/rustls.rs (99%) rename {src => sqlx-exasol-impl/src}/connection/etl/job/maybe_tls/tls/sync_socket.rs (100%) rename {src => sqlx-exasol-impl/src}/connection/etl/job/mod.rs (100%) rename {src => sqlx-exasol-impl/src}/connection/etl/mod.rs (100%) rename {src => sqlx-exasol-impl/src}/connection/executor.rs (100%) rename {src => sqlx-exasol-impl/src}/connection/mod.rs (99%) rename {src => sqlx-exasol-impl/src}/connection/stream.rs (99%) rename {src => sqlx-exasol-impl/src}/connection/websocket/future.rs (100%) rename {src => sqlx-exasol-impl/src}/connection/websocket/mod.rs (100%) rename {src => sqlx-exasol-impl/src}/connection/websocket/request.rs (100%) rename {src => sqlx-exasol-impl/src}/connection/websocket/socket.rs (100%) rename {src => sqlx-exasol-impl/src}/connection/websocket/tls.rs (100%) rename {src => sqlx-exasol-impl/src}/connection/websocket/transport/compressed.rs (100%) rename {src => sqlx-exasol-impl/src}/connection/websocket/transport/mod.rs (100%) rename {src => sqlx-exasol-impl/src}/connection/websocket/transport/uncompressed.rs (100%) rename {src => sqlx-exasol-impl/src}/database.rs (100%) rename {src => sqlx-exasol-impl/src}/error.rs (100%) create mode 100644 sqlx-exasol-impl/src/lib.rs rename {src => sqlx-exasol-impl/src}/migrate.rs (100%) rename {src => sqlx-exasol-impl/src}/options/builder.rs (100%) rename {src => sqlx-exasol-impl/src}/options/error.rs (100%) rename {src => sqlx-exasol-impl/src}/options/mod.rs (99%) rename {src => sqlx-exasol-impl/src}/options/protocol_version.rs (100%) rename {src => sqlx-exasol-impl/src}/options/ssl_mode.rs (100%) rename {src => sqlx-exasol-impl/src}/query_result.rs (100%) rename {src => sqlx-exasol-impl/src}/responses/attributes.rs (100%) rename {src => sqlx-exasol-impl/src}/responses/columns.rs (100%) rename {src => sqlx-exasol-impl/src}/responses/describe.rs (100%) rename {src => sqlx-exasol-impl/src}/responses/error.rs (100%) rename {src => sqlx-exasol-impl/src}/responses/fetch.rs (100%) rename {src => sqlx-exasol-impl/src}/responses/hosts.rs (100%) rename {src => sqlx-exasol-impl/src}/responses/mod.rs (100%) rename {src => sqlx-exasol-impl/src}/responses/prepared_stmt.rs (100%) rename {src => sqlx-exasol-impl/src}/responses/public_key.rs (100%) rename {src => sqlx-exasol-impl/src}/responses/result.rs (100%) rename {src => sqlx-exasol-impl/src}/responses/session_info.rs (100%) rename {src => sqlx-exasol-impl/src}/row.rs (88%) rename {src => sqlx-exasol-impl/src}/statement.rs (78%) rename {src => sqlx-exasol-impl/src}/testing.rs (100%) rename {src => sqlx-exasol-impl/src}/transaction.rs (100%) create mode 100644 sqlx-exasol-impl/src/type_checking.rs rename {src => sqlx-exasol-impl/src}/type_info.rs (99%) rename {src => sqlx-exasol-impl/src}/types/bool.rs (100%) rename {src => sqlx-exasol-impl/src}/types/chrono.rs (100%) rename {src => sqlx-exasol-impl/src}/types/float.rs (100%) rename {src => sqlx-exasol-impl/src}/types/int.rs (99%) rename {src => sqlx-exasol-impl/src}/types/iter.rs (99%) rename {src => sqlx-exasol-impl/src}/types/mod.rs (100%) rename {src => sqlx-exasol-impl/src}/types/option.rs (100%) rename {src => sqlx-exasol-impl/src}/types/rust_decimal.rs (100%) rename {src => sqlx-exasol-impl/src}/types/str.rs (100%) rename {src => sqlx-exasol-impl/src}/types/text.rs (77%) rename {src => sqlx-exasol-impl/src}/types/uint.rs (99%) rename {src => sqlx-exasol-impl/src}/types/uuid.rs (100%) rename {src => sqlx-exasol-impl/src}/value.rs (100%) create mode 100644 sqlx-exasol-macros/Cargo.toml create mode 100644 sqlx-exasol-macros/src/lib.rs create mode 100644 src/macros.rs diff --git a/.zed/settings.json b/.zed/settings.json index 76f75727..4684992c 100644 --- a/.zed/settings.json +++ b/.zed/settings.json @@ -7,13 +7,18 @@ "rust-analyzer": { "initialization_options": { "cargo": { + "extraEnv": { + "DATABASE_URL": "exa://sys:exasol@127.0.0.1:8563" + }, "features": [ - "etl_native_tls", + "any", + "etl_rustls", "compression", "uuid", "chrono", "rust_decimal", - "migrate" + "migrate", + "macros" ] } } diff --git a/Cargo.toml b/Cargo.toml index f4c67f7c..ff9f2c49 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,92 +1,150 @@ -[package] -name = "sqlx-exasol" -version = "0.8.6-hotifx1" -edition = "2024" -authors = ["bobozaur"] -rust-version = "1.85.0" -description = "Exasol driver for the SQLx framework." +[workspace] +members = [".", "sqlx-exasol-cli", "sqlx-exasol-impl", "sqlx-exasol-macros"] + +[workspace.package] +version = "0.8.6" license = "MIT OR Apache-2.0" +edition = "2024" +rust-version = "1.86.0" repository = "https://github.com/bobozaur/sqlx-exasol" keywords = ["database", "sql", "exasol", "sqlx", "driver"] +categories = ["database", "asynchronous"] +authors = ["Bogdan Mircea "] + +[package] +name = "sqlx-exasol" +documentation = "https://docs.rs/sqlx-exasol" +description = "Exasol driver for the SQLx framework." +version.workspace = true +license.workspace = true +edition.workspace = true +rust-version.workspace = true +authors.workspace = true +repository.workspace = true exclude = ["tests/*"] -categories = ["database"] [package.metadata.docs.rs] -features = ["etl", "chrono", "rust_decimal", "uuid", "compression", "migrate"] +features = [ + "etl", + "chrono", + "rust_decimal", + "uuid", + "compression", + "migrate", + "macros", +] [features] # ############################################ # ########### User-facing features ########### # ############################################ +default = ["macros", "migrate"] -compression = ["dep:async-compression"] -uuid = ["dep:uuid"] -chrono = ["dep:chrono"] -rust_decimal = ["dep:rust_decimal"] -migrate = ["sqlx-core/migrate", "dep:dotenvy", "dep:hex"] -etl = ["dep:http-body-util", "dep:hyper", "dep:futures-channel"] -etl_rustls = ["dep:rustls", "dep:rcgen", "etl"] -etl_native_tls = ["dep:native-tls", "dep:rcgen", "etl"] +macros = ["dep:sqlx-exasol-macros"] +migrate = ["sqlx-exasol-impl/migrate"] + +compression = ["sqlx-exasol-impl/compression"] + +etl = ["sqlx-exasol-impl/etl"] +etl_rustls = ["sqlx-exasol-impl/etl_rustls"] +etl_native_tls = ["sqlx-exasol-impl/etl_native_tls"] + +uuid = ["sqlx-exasol-impl/uuid"] +chrono = ["sqlx-exasol-impl/chrono"] +rust_decimal = ["sqlx-exasol-impl/rust_decimal"] # ############################################ # ############################################ # ############################################ -[dependencies] +[workspace.dependencies] +# Internal +sqlx-exasol-macros = { path = "sqlx-exasol-macros" } +sqlx-exasol-impl = { path = "sqlx-exasol-impl" } + +# External +anyhow = "1" arrayvec = "0.7" +async-compression = { version = "0.4", features = [ + "futures-io", + "gzip", + "zlib", +] } async-tungstenite = "0.29" base64 = "0.22" +chrono = { version = "0.4", features = ["serde"] } +clap = { version = "4", features = ["derive", "env", "wrap_help"] } +console = "0.15" +dotenvy = "0.15" futures-io = "0.3" futures-util = "0.3" +futures-channel = { version = "0.3", features = ["sink"] } futures-core = "0.3" +hex = "0.4" +http-body-util = "0.1" +hyper = { version = "1.4", features = ["server", "http1"] } lru = "0.12" +native-tls = "0.2" +paste = "1" +quote = { version = "1", default-features = false } rand = "0.8" rsa = "0.9" serde = { version = "1", features = ["derive", "rc"] } serde_json = { version = "1", features = ["raw_value"] } -sqlx-core = "=0.8.6" +# sqlx-core = "=0.8.6" +# sqlx-macros-core = { version = "=0.8.6", features = ["macros"] } +# sqlx = { version = "=0.8.6", features = [ +# "runtime-tokio", +# "tls-native-tls", +# "migrate", +# ] } +sqlx = { path = "../sqlx", features = [ + "runtime-tokio", + "tls-native-tls", + "migrate", +] } +sqlx-cli = { path = "../sqlx/sqlx-cli" } +sqlx-core = { path = "../sqlx/sqlx-core" } +sqlx-macros-core = { path = "../sqlx/sqlx-macros-core", features = ["macros"] } +syn = { version = "2", default-features = false, features = [ + "parsing", + "proc-macro", +] } thiserror = "1" +tokio = { version = "1", features = ["full"] } tracing = { version = "0.1", features = ["log"] } url = "2" - -# Feature flagged optional dependencies -async-compression = { version = "0.4", features = [ - "futures-io", - "gzip", - "zlib", -], optional = true } -chrono = { version = "0.4", features = ["serde"], optional = true } -dotenvy = { version = "0.15", optional = true } -futures-channel = { version = "0.3", features = ["sink"], optional = true } -hex = { version = "0.4", optional = true } -http-body-util = { version = "0.1", optional = true } -hyper = { version = "1.4", features = ["server", "http1"], optional = true } -native-tls = { version = "0.2", optional = true } -uuid = { version = "1", features = ["serde"], optional = true } -rcgen = { version = "0.13", optional = true } -rust_decimal = { version = "1", optional = true } +uuid = { version = "1", features = ["serde"] } +rcgen = "0.13" +rust_decimal = "1" rustls = { version = "0.23", default-features = false, features = [ "std", "tls12", -], optional = true } +] } + +[dependencies] +sqlx-exasol-macros = { workspace = true, optional = true } +sqlx-exasol-impl = { workspace = true } [dev-dependencies] -anyhow = "1" -paste = "1" -rustls = "0.23" -sqlx = { version = "=0.8.6", features = [ - "runtime-tokio", - "tls-native-tls", - "migrate", -] } -tokio = { version = "1", features = ["full"] } +anyhow = { workspace = true } +chrono = { workspace = true } +dotenvy = { workspace = true } +futures-util = { workspace = true } +paste = { workspace = true } +rustls = { workspace = true } +rust_decimal = { workspace = true } +sqlx = { workspace = true } +tokio = { workspace = true } +url = { workspace = true } +uuid = { workspace = true } -[lints.clippy] +[workspace.lints.clippy] all = { level = "warn", priority = -1 } pedantic = { level = "warn", priority = -1 } module_name_repetitions = "allow" -[lints.rust] +[workspace.lints.rust] rust_2018_idioms = { level = "warn", priority = -1 } rust_2021_compatibility = { level = "warn", priority = -1 } meta_variable_misuse = "warn" diff --git a/sqlx-exasol-cli/Cargo.toml b/sqlx-exasol-cli/Cargo.toml new file mode 100644 index 00000000..75efc46e --- /dev/null +++ b/sqlx-exasol-cli/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "sqlx-exasol-cli" +version.workspace = true +license.workspace = true +edition.workspace = true +rust-version.workspace = true +repository.workspace = true +keywords.workspace = true +categories.workspace = true +authors.workspace = true + +[dependencies] +sqlx = { workspace = true } +sqlx-exasol-impl = { workspace = true } +sqlx-cli = { workspace = true } +clap = { workspace = true } +console = { workspace = true } +tokio = { workspace = true } + +[lints] +workspace = true diff --git a/sqlx-exasol-cli/src/main.rs b/sqlx-exasol-cli/src/main.rs new file mode 100644 index 00000000..53f4d183 --- /dev/null +++ b/sqlx-exasol-cli/src/main.rs @@ -0,0 +1,20 @@ +use clap::Parser; +use console::style; +use sqlx_cli::Opt; +use sqlx_exasol_impl::any::DRIVER; + +#[tokio::main] +async fn main() { + // Checks for `--no-dotenv` before parsing. + sqlx_cli::maybe_apply_dotenv(); + + sqlx::any::install_drivers(&[DRIVER]); + + let opt = Opt::parse(); + + // no special handling here + if let Err(error) = sqlx_cli::run(opt).await { + println!("{} {}", style("error:").bold().red(), error); + std::process::exit(1); + } +} diff --git a/sqlx-exasol-impl/Cargo.toml b/sqlx-exasol-impl/Cargo.toml new file mode 100644 index 00000000..91740314 --- /dev/null +++ b/sqlx-exasol-impl/Cargo.toml @@ -0,0 +1,68 @@ +[package] +name = "sqlx-exasol-impl" +description = "Driver implementation for sqlx-exasol. Not meant to be used directly." +version.workspace = true +license.workspace = true +edition.workspace = true +rust-version.workspace = true +repository.workspace = true +keywords.workspace = true +categories.workspace = true +authors.workspace = true + +[features] +any = ["sqlx-core/any"] +migrate = ["sqlx-core/migrate", "dep:dotenvy", "dep:hex"] +macros = ["dep:sqlx-macros-core"] + +compression = ["dep:async-compression"] + +etl = ["dep:http-body-util", "dep:hyper", "dep:futures-channel"] +etl_rustls = ["dep:rustls", "dep:rcgen", "etl"] +etl_native_tls = ["dep:native-tls", "dep:rcgen", "etl"] + +uuid = ["dep:uuid", "sqlx-core/uuid"] +chrono = ["dep:chrono", "sqlx-core/chrono"] +rust_decimal = ["dep:rust_decimal", "sqlx-core/rust_decimal"] + +[dependencies] +arrayvec = { workspace = true } +async-tungstenite = { workspace = true } +base64 = { workspace = true } +futures-io = { workspace = true } +futures-util = { workspace = true } +futures-core = { workspace = true } +lru = { workspace = true } +rand = { workspace = true } +rsa = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true } +sqlx-core = { workspace = true } +thiserror = { workspace = true } +tracing = { workspace = true } +url = { workspace = true } + +# Feature flagged optional dependencies +async-compression = { workspace = true, optional = true } +chrono = { workspace = true, optional = true } +dotenvy = { workspace = true, optional = true } +futures-channel = { workspace = true, optional = true } +hex = { workspace = true, optional = true } +http-body-util = { workspace = true, optional = true } +hyper = { workspace = true, optional = true } +native-tls = { workspace = true, optional = true } +uuid = { workspace = true, optional = true } +rcgen = { workspace = true, optional = true } +rust_decimal = { workspace = true, optional = true } +rustls = { workspace = true, optional = true } +sqlx-macros-core = { workspace = true, optional = true } + +[dev-dependencies] +anyhow = { workspace = true } +paste = { workspace = true } +rustls = { workspace = true } +sqlx = { workspace = true } +tokio = { workspace = true } + +[lints] +workspace = true diff --git a/sqlx-exasol-impl/src/any.rs b/sqlx-exasol-impl/src/any.rs new file mode 100644 index 00000000..0ab41996 --- /dev/null +++ b/sqlx-exasol-impl/src/any.rs @@ -0,0 +1,288 @@ +use std::{borrow::Cow, future}; + +use futures_core::{future::BoxFuture, stream::BoxStream}; +use futures_util::{stream, StreamExt, TryStreamExt}; +#[cfg(feature = "migrate")] +use sqlx_core::migrate::Migrate; +use sqlx_core::{ + any::{ + Any, AnyArguments, AnyColumn, AnyConnectOptions, AnyConnectionBackend, AnyQueryResult, + AnyRow, AnyStatement, AnyTypeInfo, AnyTypeInfoKind, AnyValue, AnyValueKind, + }, + column::Column, + connection::{ConnectOptions, Connection}, + database::Database, + decode::Decode, + describe::Describe, + executor::Executor, + logger::QueryLogger, + row::Row, + transaction::TransactionManager, + value::ValueRef, + Either, +}; + +#[cfg(feature = "migrate")] +use crate::SqlxResult; +use crate::{ + connection::{ + stream::ResultStream, + websocket::future::{Execute, ExecuteBatch, ExecutePrepared}, + }, + type_info::ExaDataType, + ExaColumn, ExaConnectOptions, ExaConnection, ExaQueryResult, ExaRow, ExaTransactionManager, + ExaTypeInfo, ExaValueRef, Exasol, SqlxError, +}; + +sqlx_core::declare_driver_with_optional_migrate!(DRIVER = Exasol); + +impl AnyConnectionBackend for ExaConnection { + fn name(&self) -> &str { + ::NAME + } + + fn close(self: Box) -> BoxFuture<'static, SqlxResult<()>> { + Connection::close(*self) + } + + fn close_hard(self: Box) -> BoxFuture<'static, SqlxResult<()>> { + Connection::close_hard(*self) + } + + fn ping(&mut self) -> BoxFuture<'_, SqlxResult<()>> { + Connection::ping(self) + } + + fn begin(&mut self, statement: Option>) -> BoxFuture<'_, SqlxResult<()>> { + ExaTransactionManager::begin(self, statement) + } + + fn commit(&mut self) -> BoxFuture<'_, SqlxResult<()>> { + ExaTransactionManager::commit(self) + } + + fn rollback(&mut self) -> BoxFuture<'_, SqlxResult<()>> { + ExaTransactionManager::rollback(self) + } + + fn start_rollback(&mut self) { + ExaTransactionManager::start_rollback(self); + } + + fn get_transaction_depth(&self) -> usize { + ExaTransactionManager::get_transaction_depth(self) + } + + fn shrink_buffers(&mut self) { + Connection::shrink_buffers(self); + } + + fn flush(&mut self) -> BoxFuture<'_, SqlxResult<()>> { + Connection::flush(self) + } + + fn should_flush(&self) -> bool { + Connection::should_flush(self) + } + + #[cfg(feature = "migrate")] + fn as_migrate(&mut self) -> SqlxResult<&mut (dyn Migrate + Send + 'static)> { + Ok(self) + } + + fn fetch_many<'q>( + &'q mut self, + sql: &'q str, + persistent: bool, + arguments: Option>, + ) -> BoxStream<'q, SqlxResult>> { + let logger = QueryLogger::new(sql, self.log_settings.clone()); + let arguments = match arguments.as_ref().map(AnyArguments::convert_to).transpose() { + Ok(arguments) => arguments, + Err(error) => { + return stream::once(future::ready(Err(sqlx_core::Error::Encode(error)))).boxed() + } + }; + + let filter_fn = |step| async move { + match step { + Either::Left(qr) => Ok(Some(Either::Left(map_result(qr)))), + Either::Right(row) => AnyRow::try_from(&row).map(Either::Right).map(Some), + } + }; + + if let Some(arguments) = arguments { + let future = ExecutePrepared::new(sql, persistent, arguments); + ResultStream::new(&mut self.ws, logger, future) + .try_filter_map(filter_fn) + .boxed() + } else { + let future = ExecuteBatch::new(sql); + ResultStream::new(&mut self.ws, logger, future) + .try_filter_map(filter_fn) + .boxed() + } + } + + fn fetch_optional<'q>( + &'q mut self, + sql: &'q str, + persistent: bool, + arguments: Option>, + ) -> BoxFuture<'q, SqlxResult>> { + let logger = QueryLogger::new(sql, self.log_settings.clone()); + let arguments = arguments + .as_ref() + .map(AnyArguments::convert_to) + .transpose() + .map_err(sqlx_core::Error::Encode); + + Box::pin(async move { + let arguments = arguments?; + + let mut stream = if let Some(arguments) = arguments { + let future = ExecutePrepared::new(sql, persistent, arguments); + ResultStream::new(&mut self.ws, logger, future) + } else { + let future = Execute::new(sql); + ResultStream::new(&mut self.ws, logger, future) + }; + + while let Some(result) = stream.try_next().await? { + if let Either::Right(row) = result { + return Ok(Some(AnyRow::try_from(&row)?)); + } + } + + Ok(None) + }) + } + + fn prepare_with<'c, 'q: 'c>( + &'c mut self, + sql: &'q str, + _parameters: &[AnyTypeInfo], + ) -> BoxFuture<'c, SqlxResult>> { + Box::pin(async move { + let statement = Executor::prepare_with(self, sql, &[]).await?; + AnyStatement::try_from_statement( + sql, + &statement, + statement.metadata.column_names.clone(), + ) + }) + } + + fn describe<'q>(&'q mut self, sql: &'q str) -> BoxFuture<'q, SqlxResult>> { + Box::pin(async move { + let describe = Executor::describe(self, sql).await?; + describe.try_into_any() + }) + } +} + +impl<'a> TryFrom<&'a ExaTypeInfo> for AnyTypeInfo { + type Error = SqlxError; + + fn try_from(type_info: &'a ExaTypeInfo) -> Result { + Ok(AnyTypeInfo { + kind: match &type_info.data_type { + ExaDataType::Null => AnyTypeInfoKind::Null, + ExaDataType::Boolean => AnyTypeInfoKind::Bool, + ExaDataType::Decimal(_) => AnyTypeInfoKind::BigInt, + ExaDataType::Double => AnyTypeInfoKind::Double, + ExaDataType::HashType(_) | ExaDataType::Char(_) | ExaDataType::Varchar(_) => { + AnyTypeInfoKind::Text + } + _ => { + return Err(sqlx_core::Error::AnyDriverError( + format!("Any driver does not support Exasol type {type_info:?}").into(), + )) + } + }, + }) + } +} + +impl<'a> TryFrom<&'a ExaColumn> for AnyColumn { + type Error = sqlx_core::Error; + + fn try_from(column: &'a ExaColumn) -> Result { + let type_info = AnyTypeInfo::try_from(&column.data_type)?; + + Ok(AnyColumn { + ordinal: column.ordinal, + name: column.name.to_string().into(), + type_info, + }) + } +} + +impl<'a> TryFrom<&'a ExaRow> for AnyRow { + type Error = sqlx_core::Error; + + fn try_from(row: &'a ExaRow) -> Result { + fn decode<'r, T: Decode<'r, Exasol>>(valueref: ExaValueRef<'r>) -> SqlxResult { + Decode::decode(valueref).map_err(SqlxError::decode) + } + + let mut row_out = AnyRow { + column_names: row.column_names.clone(), + columns: Vec::with_capacity(row.columns().len()), + values: Vec::with_capacity(row.columns().len()), + }; + + for col in row.columns() { + let i = col.ordinal(); + + let any_col = AnyColumn::try_from(col)?; + + let value = row.try_get_raw(i)?; + + // Map based on the _value_ type info, not the column type info. + let type_info = AnyTypeInfo::try_from(value.type_info().as_ref()).map_err(|e| { + SqlxError::ColumnDecode { + index: col.ordinal().to_string(), + source: e.into(), + } + })?; + + let value_kind = match type_info.kind { + k if value.is_null() => AnyValueKind::Null(k), + AnyTypeInfoKind::Null => AnyValueKind::Null(AnyTypeInfoKind::Null), + AnyTypeInfoKind::Bool => AnyValueKind::Bool(decode(value)?), + AnyTypeInfoKind::SmallInt => AnyValueKind::SmallInt(decode(value)?), + AnyTypeInfoKind::Integer => AnyValueKind::Integer(decode(value)?), + AnyTypeInfoKind::BigInt => AnyValueKind::BigInt(decode(value)?), + AnyTypeInfoKind::Real => AnyValueKind::Real(decode(value)?), + AnyTypeInfoKind::Double => AnyValueKind::Double(decode(value)?), + AnyTypeInfoKind::Text => AnyValueKind::Text(decode::(value)?.into()), + AnyTypeInfoKind::Blob => Err(SqlxError::decode( + "unsupported data type by the `any` driver", + ))?, + }; + + row_out.columns.push(any_col); + row_out.values.push(AnyValue { kind: value_kind }); + } + + Ok(row_out) + } +} + +impl<'a> TryFrom<&'a AnyConnectOptions> for ExaConnectOptions { + type Error = sqlx_core::Error; + + fn try_from(any_opts: &'a AnyConnectOptions) -> Result { + let mut opts = Self::from_url(&any_opts.database_url)?; + opts.log_settings = any_opts.log_settings.clone(); + Ok(opts) + } +} + +fn map_result(result: ExaQueryResult) -> AnyQueryResult { + AnyQueryResult { + rows_affected: result.rows_affected(), + last_insert_id: None, + } +} diff --git a/src/arguments.rs b/sqlx-exasol-impl/src/arguments.rs similarity index 99% rename from src/arguments.rs rename to sqlx-exasol-impl/src/arguments.rs index 626e1608..777b52ba 100644 --- a/src/arguments.rs +++ b/sqlx-exasol-impl/src/arguments.rs @@ -137,7 +137,7 @@ impl ExaBuffer { Some(n) if n == count => (), Some(n) => Err(ExaProtocolError::ParameterLengthMismatch(count, n))?, None => self.first_col_params_num = Some(count), - }; + } Ok(()) } diff --git a/src/column.rs b/sqlx-exasol-impl/src/column.rs similarity index 75% rename from src/column.rs rename to sqlx-exasol-impl/src/column.rs index 341cc2f4..d72623b2 100644 --- a/src/column.rs +++ b/sqlx-exasol-impl/src/column.rs @@ -1,23 +1,23 @@ -use std::{fmt::Display, sync::Arc}; +use std::fmt::Display; -use serde::{Deserialize, Deserializer}; -use sqlx_core::{column::Column, database::Database}; +use serde::{Deserialize, Deserializer, Serialize}; +use sqlx_core::{column::Column, database::Database, ext::ustr::UStr}; use crate::{database::Exasol, type_info::ExaTypeInfo}; /// Implementor of [`Column`]. -#[derive(Debug, Clone, Deserialize)] +#[derive(Debug, Clone, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct ExaColumn { - #[serde(skip)] + #[serde(default)] pub(crate) ordinal: usize, #[serde(deserialize_with = "ExaColumn::lowercase_name")] - pub(crate) name: Arc, + pub(crate) name: UStr, pub(crate) data_type: ExaTypeInfo, } impl ExaColumn { - fn lowercase_name<'de, D>(deserializer: D) -> Result, D::Error> + fn lowercase_name<'de, D>(deserializer: D) -> Result where D: Deserializer<'de>, { diff --git a/src/connection/etl/error.rs b/sqlx-exasol-impl/src/connection/etl/error.rs similarity index 100% rename from src/connection/etl/error.rs rename to sqlx-exasol-impl/src/connection/etl/error.rs diff --git a/src/connection/etl/export/compression.rs b/sqlx-exasol-impl/src/connection/etl/export/compression.rs similarity index 100% rename from src/connection/etl/export/compression.rs rename to sqlx-exasol-impl/src/connection/etl/export/compression.rs diff --git a/src/connection/etl/export/mod.rs b/sqlx-exasol-impl/src/connection/etl/export/mod.rs similarity index 99% rename from src/connection/etl/export/mod.rs rename to sqlx-exasol-impl/src/connection/etl/export/mod.rs index 985b0354..f94ebbd9 100644 --- a/src/connection/etl/export/mod.rs +++ b/sqlx-exasol-impl/src/connection/etl/export/mod.rs @@ -43,7 +43,7 @@ impl AsyncRead for ExaExport { let reader = ExaExportReader::new(socket, *with_compression); self.set(Self(ExaExportState::Poll(reader))); } - }; + } } } } diff --git a/src/connection/etl/export/options.rs b/sqlx-exasol-impl/src/connection/etl/export/options.rs similarity index 99% rename from src/connection/etl/export/options.rs rename to sqlx-exasol-impl/src/connection/etl/export/options.rs index e6f3794e..799fd442 100644 --- a/src/connection/etl/export/options.rs +++ b/sqlx-exasol-impl/src/connection/etl/export/options.rs @@ -150,7 +150,7 @@ impl EtlJob for ExportBuilder<'_> { query.push_str(qr); query.push_str("\n)"); } - }; + } query.push(' '); diff --git a/src/connection/etl/export/reader.rs b/sqlx-exasol-impl/src/connection/etl/export/reader.rs similarity index 99% rename from src/connection/etl/export/reader.rs rename to sqlx-exasol-impl/src/connection/etl/export/reader.rs index ec1dfacd..469ce9e2 100644 --- a/src/connection/etl/export/reader.rs +++ b/sqlx-exasol-impl/src/connection/etl/export/reader.rs @@ -73,7 +73,7 @@ impl Future for ExportFuture { match ready!(self.0.poll_unpin(cx)) { Ok(()) => (), Err(e) => return Poll::Ready(Err(e)), - }; + } let response = Response::builder() .status(StatusCode::OK) diff --git a/src/connection/etl/import/compression.rs b/sqlx-exasol-impl/src/connection/etl/import/compression.rs similarity index 100% rename from src/connection/etl/import/compression.rs rename to sqlx-exasol-impl/src/connection/etl/import/compression.rs diff --git a/src/connection/etl/import/mod.rs b/sqlx-exasol-impl/src/connection/etl/import/mod.rs similarity index 100% rename from src/connection/etl/import/mod.rs rename to sqlx-exasol-impl/src/connection/etl/import/mod.rs diff --git a/src/connection/etl/import/options.rs b/sqlx-exasol-impl/src/connection/etl/import/options.rs similarity index 100% rename from src/connection/etl/import/options.rs rename to sqlx-exasol-impl/src/connection/etl/import/options.rs diff --git a/src/connection/etl/import/writer.rs b/sqlx-exasol-impl/src/connection/etl/import/writer.rs similarity index 100% rename from src/connection/etl/import/writer.rs rename to sqlx-exasol-impl/src/connection/etl/import/writer.rs diff --git a/src/connection/etl/job/maybe_tls/mod.rs b/sqlx-exasol-impl/src/connection/etl/job/maybe_tls/mod.rs similarity index 100% rename from src/connection/etl/job/maybe_tls/mod.rs rename to sqlx-exasol-impl/src/connection/etl/job/maybe_tls/mod.rs diff --git a/src/connection/etl/job/maybe_tls/tls/mod.rs b/sqlx-exasol-impl/src/connection/etl/job/maybe_tls/tls/mod.rs similarity index 100% rename from src/connection/etl/job/maybe_tls/tls/mod.rs rename to sqlx-exasol-impl/src/connection/etl/job/maybe_tls/tls/mod.rs diff --git a/src/connection/etl/job/maybe_tls/tls/native_tls.rs b/sqlx-exasol-impl/src/connection/etl/job/maybe_tls/tls/native_tls.rs similarity index 99% rename from src/connection/etl/job/maybe_tls/tls/native_tls.rs rename to sqlx-exasol-impl/src/connection/etl/job/maybe_tls/tls/native_tls.rs index 8a37734e..6ee460f7 100644 --- a/src/connection/etl/job/maybe_tls/tls/native_tls.rs +++ b/sqlx-exasol-impl/src/connection/etl/job/maybe_tls/tls/native_tls.rs @@ -72,7 +72,7 @@ impl WithNativeTlsSocket { poll_fn(|cx| h.get_mut().poll_write_ready(cx)).await?; res = h.handshake(); } - }; + } } } } @@ -113,7 +113,7 @@ where match self.0.shutdown() { Err(e) if e.kind() == io::ErrorKind::WouldBlock => (), ready => return Poll::Ready(ready), - }; + } ready!(self.0.get_mut().poll_read_ready(cx))?; self.0.get_mut().poll_write_ready(cx) diff --git a/src/connection/etl/job/maybe_tls/tls/rustls.rs b/sqlx-exasol-impl/src/connection/etl/job/maybe_tls/tls/rustls.rs similarity index 99% rename from src/connection/etl/job/maybe_tls/tls/rustls.rs rename to sqlx-exasol-impl/src/connection/etl/job/maybe_tls/tls/rustls.rs index 40ec4d4b..c95d5c99 100644 --- a/src/connection/etl/job/maybe_tls/tls/rustls.rs +++ b/sqlx-exasol-impl/src/connection/etl/job/maybe_tls/tls/rustls.rs @@ -121,7 +121,7 @@ where match self.state.complete_io(&mut self.inner) { Err(e) if e.kind() == io::ErrorKind::WouldBlock => (), ready => return Poll::Ready(ready.map(|_| ())), - }; + } ready!(self.inner.poll_read_ready(cx))?; } @@ -132,7 +132,7 @@ where match self.state.complete_io(&mut self.inner) { Err(e) if e.kind() == io::ErrorKind::WouldBlock => (), ready => return Poll::Ready(ready.map(|_| ())), - }; + } ready!(self.inner.poll_write_ready(cx))?; } diff --git a/src/connection/etl/job/maybe_tls/tls/sync_socket.rs b/sqlx-exasol-impl/src/connection/etl/job/maybe_tls/tls/sync_socket.rs similarity index 100% rename from src/connection/etl/job/maybe_tls/tls/sync_socket.rs rename to sqlx-exasol-impl/src/connection/etl/job/maybe_tls/tls/sync_socket.rs diff --git a/src/connection/etl/job/mod.rs b/sqlx-exasol-impl/src/connection/etl/job/mod.rs similarity index 100% rename from src/connection/etl/job/mod.rs rename to sqlx-exasol-impl/src/connection/etl/job/mod.rs diff --git a/src/connection/etl/mod.rs b/sqlx-exasol-impl/src/connection/etl/mod.rs similarity index 100% rename from src/connection/etl/mod.rs rename to sqlx-exasol-impl/src/connection/etl/mod.rs diff --git a/src/connection/executor.rs b/sqlx-exasol-impl/src/connection/executor.rs similarity index 100% rename from src/connection/executor.rs rename to sqlx-exasol-impl/src/connection/executor.rs diff --git a/src/connection/mod.rs b/sqlx-exasol-impl/src/connection/mod.rs similarity index 99% rename from src/connection/mod.rs rename to sqlx-exasol-impl/src/connection/mod.rs index ef775b1d..ff175dc2 100644 --- a/src/connection/mod.rs +++ b/sqlx-exasol-impl/src/connection/mod.rs @@ -1,7 +1,7 @@ #[cfg(feature = "etl")] pub mod etl; mod executor; -mod stream; +pub mod stream; pub mod websocket; use std::net::SocketAddr; @@ -30,8 +30,8 @@ use crate::{ #[derive(Debug)] pub struct ExaConnection { pub(crate) ws: ExaWebSocket, + pub(crate) log_settings: LogSettings, session_info: SessionInfo, - log_settings: LogSettings, } impl ExaConnection { diff --git a/src/connection/stream.rs b/sqlx-exasol-impl/src/connection/stream.rs similarity index 99% rename from src/connection/stream.rs rename to sqlx-exasol-impl/src/connection/stream.rs index e7cee25c..50ba4c36 100644 --- a/src/connection/stream.rs +++ b/sqlx-exasol-impl/src/connection/stream.rs @@ -14,7 +14,7 @@ use std::{ use futures_core::ready; use futures_util::Stream; use serde_json::Value; -use sqlx_core::{logger::QueryLogger, Either, HashMap}; +use sqlx_core::{ext::ustr::UStr, logger::QueryLogger, Either, HashMap}; use crate::{ column::ExaColumn, @@ -445,7 +445,7 @@ enum MultiChunkStreamState { /// This is the lowest level of the streaming hierarchy and merely iterates over an already /// retrieved chunk of rows, not dealing at all with async operations. struct ChunkIter { - column_names: Arc, usize>>, + column_names: Arc>, columns: Arc<[ExaColumn]>, chunk_rows_total: usize, chunk_rows_pos: usize, diff --git a/src/connection/websocket/future.rs b/sqlx-exasol-impl/src/connection/websocket/future.rs similarity index 100% rename from src/connection/websocket/future.rs rename to sqlx-exasol-impl/src/connection/websocket/future.rs diff --git a/src/connection/websocket/mod.rs b/sqlx-exasol-impl/src/connection/websocket/mod.rs similarity index 100% rename from src/connection/websocket/mod.rs rename to sqlx-exasol-impl/src/connection/websocket/mod.rs diff --git a/src/connection/websocket/request.rs b/sqlx-exasol-impl/src/connection/websocket/request.rs similarity index 100% rename from src/connection/websocket/request.rs rename to sqlx-exasol-impl/src/connection/websocket/request.rs diff --git a/src/connection/websocket/socket.rs b/sqlx-exasol-impl/src/connection/websocket/socket.rs similarity index 100% rename from src/connection/websocket/socket.rs rename to sqlx-exasol-impl/src/connection/websocket/socket.rs diff --git a/src/connection/websocket/tls.rs b/sqlx-exasol-impl/src/connection/websocket/tls.rs similarity index 100% rename from src/connection/websocket/tls.rs rename to sqlx-exasol-impl/src/connection/websocket/tls.rs diff --git a/src/connection/websocket/transport/compressed.rs b/sqlx-exasol-impl/src/connection/websocket/transport/compressed.rs similarity index 100% rename from src/connection/websocket/transport/compressed.rs rename to sqlx-exasol-impl/src/connection/websocket/transport/compressed.rs diff --git a/src/connection/websocket/transport/mod.rs b/sqlx-exasol-impl/src/connection/websocket/transport/mod.rs similarity index 100% rename from src/connection/websocket/transport/mod.rs rename to sqlx-exasol-impl/src/connection/websocket/transport/mod.rs diff --git a/src/connection/websocket/transport/uncompressed.rs b/sqlx-exasol-impl/src/connection/websocket/transport/uncompressed.rs similarity index 100% rename from src/connection/websocket/transport/uncompressed.rs rename to sqlx-exasol-impl/src/connection/websocket/transport/uncompressed.rs diff --git a/src/database.rs b/sqlx-exasol-impl/src/database.rs similarity index 100% rename from src/database.rs rename to sqlx-exasol-impl/src/database.rs diff --git a/src/error.rs b/sqlx-exasol-impl/src/error.rs similarity index 100% rename from src/error.rs rename to sqlx-exasol-impl/src/error.rs diff --git a/sqlx-exasol-impl/src/lib.rs b/sqlx-exasol-impl/src/lib.rs new file mode 100644 index 00000000..503a9e97 --- /dev/null +++ b/sqlx-exasol-impl/src/lib.rs @@ -0,0 +1,70 @@ +#![cfg_attr(not(test), warn(unused_crate_dependencies))] +//! **EXASOL** database driver. + +#[cfg(feature = "any")] +pub mod any; +mod arguments; +mod column; +mod connection; +mod database; +mod error; +#[cfg(feature = "migrate")] +mod migrate; +mod options; +mod query_result; +mod responses; +mod row; +mod statement; +#[cfg(feature = "migrate")] +mod testing; +mod transaction; +#[cfg(feature = "macros")] +mod type_checking; +mod type_info; +mod types; +mod value; + +pub use arguments::ExaArguments; +pub use column::ExaColumn; +#[cfg(feature = "etl")] +pub use connection::etl; +pub use connection::ExaConnection; +pub use database::Exasol; +pub use options::{ExaConnectOptions, ExaConnectOptionsBuilder, ExaSslMode, ProtocolVersion}; +pub use query_result::ExaQueryResult; +pub use responses::{ExaAttributes, ExaDatabaseError, SessionInfo}; +pub use row::ExaRow; +use sqlx_core::{ + executor::Executor, impl_acquire, impl_column_index_for_row, impl_column_index_for_statement, + impl_into_arguments_for_arguments, +}; +pub use statement::ExaStatement; +pub use transaction::ExaTransactionManager; +#[cfg(feature = "macros")] +pub use type_checking::QUERY_DRIVER; +pub use type_info::ExaTypeInfo; +pub use types::ExaIter; +#[cfg(feature = "chrono")] +pub use types::Months; +pub use value::{ExaValue, ExaValueRef}; + +/// An alias for [`Pool`][sqlx_core::pool::Pool], specialized for Exasol. +pub type ExaPool = sqlx_core::pool::Pool; + +/// An alias for [`PoolOptions`][sqlx_core::pool::PoolOptions], specialized for Exasol. +pub type ExaPoolOptions = sqlx_core::pool::PoolOptions; + +/// An alias for [`Executor<'_, Database = Exasol>`][Executor]. +pub trait ExaExecutor<'c>: Executor<'c, Database = Exasol> {} +impl<'c, T: Executor<'c, Database = Exasol>> ExaExecutor<'c> for T {} + +impl_into_arguments_for_arguments!(ExaArguments); +impl_acquire!(Exasol, ExaConnection); +impl_column_index_for_row!(ExaRow); +impl_column_index_for_statement!(ExaStatement); + +// ################### +// ##### Aliases ##### +// ################### +type SqlxError = sqlx_core::Error; +type SqlxResult = sqlx_core::Result; diff --git a/src/migrate.rs b/sqlx-exasol-impl/src/migrate.rs similarity index 100% rename from src/migrate.rs rename to sqlx-exasol-impl/src/migrate.rs diff --git a/src/options/builder.rs b/sqlx-exasol-impl/src/options/builder.rs similarity index 100% rename from src/options/builder.rs rename to sqlx-exasol-impl/src/options/builder.rs diff --git a/src/options/error.rs b/sqlx-exasol-impl/src/options/error.rs similarity index 100% rename from src/options/error.rs rename to sqlx-exasol-impl/src/options/error.rs diff --git a/src/options/mod.rs b/sqlx-exasol-impl/src/options/mod.rs similarity index 99% rename from src/options/mod.rs rename to sqlx-exasol-impl/src/options/mod.rs index dfb13527..2e217105 100644 --- a/src/options/mod.rs +++ b/sqlx-exasol-impl/src/options/mod.rs @@ -62,12 +62,12 @@ pub struct ExaConnectOptions { pub(crate) statement_cache_capacity: NonZeroUsize, pub(crate) schema: Option, pub(crate) compression: bool, + pub(crate) log_settings: LogSettings, login: Login, protocol_version: ProtocolVersion, fetch_size: usize, query_timeout: u64, feedback_interval: u64, - log_settings: LogSettings, } impl ExaConnectOptions { @@ -194,7 +194,7 @@ impl ConnectOptions for ExaConnectOptions { "Unknown connection string parameter: {value}" ))) } - }; + } } builder.build() diff --git a/src/options/protocol_version.rs b/sqlx-exasol-impl/src/options/protocol_version.rs similarity index 100% rename from src/options/protocol_version.rs rename to sqlx-exasol-impl/src/options/protocol_version.rs diff --git a/src/options/ssl_mode.rs b/sqlx-exasol-impl/src/options/ssl_mode.rs similarity index 100% rename from src/options/ssl_mode.rs rename to sqlx-exasol-impl/src/options/ssl_mode.rs diff --git a/src/query_result.rs b/sqlx-exasol-impl/src/query_result.rs similarity index 100% rename from src/query_result.rs rename to sqlx-exasol-impl/src/query_result.rs diff --git a/src/responses/attributes.rs b/sqlx-exasol-impl/src/responses/attributes.rs similarity index 100% rename from src/responses/attributes.rs rename to sqlx-exasol-impl/src/responses/attributes.rs diff --git a/src/responses/columns.rs b/sqlx-exasol-impl/src/responses/columns.rs similarity index 100% rename from src/responses/columns.rs rename to sqlx-exasol-impl/src/responses/columns.rs diff --git a/src/responses/describe.rs b/sqlx-exasol-impl/src/responses/describe.rs similarity index 100% rename from src/responses/describe.rs rename to sqlx-exasol-impl/src/responses/describe.rs diff --git a/src/responses/error.rs b/sqlx-exasol-impl/src/responses/error.rs similarity index 100% rename from src/responses/error.rs rename to sqlx-exasol-impl/src/responses/error.rs diff --git a/src/responses/fetch.rs b/sqlx-exasol-impl/src/responses/fetch.rs similarity index 100% rename from src/responses/fetch.rs rename to sqlx-exasol-impl/src/responses/fetch.rs diff --git a/src/responses/hosts.rs b/sqlx-exasol-impl/src/responses/hosts.rs similarity index 100% rename from src/responses/hosts.rs rename to sqlx-exasol-impl/src/responses/hosts.rs diff --git a/src/responses/mod.rs b/sqlx-exasol-impl/src/responses/mod.rs similarity index 100% rename from src/responses/mod.rs rename to sqlx-exasol-impl/src/responses/mod.rs diff --git a/src/responses/prepared_stmt.rs b/sqlx-exasol-impl/src/responses/prepared_stmt.rs similarity index 100% rename from src/responses/prepared_stmt.rs rename to sqlx-exasol-impl/src/responses/prepared_stmt.rs diff --git a/src/responses/public_key.rs b/sqlx-exasol-impl/src/responses/public_key.rs similarity index 100% rename from src/responses/public_key.rs rename to sqlx-exasol-impl/src/responses/public_key.rs diff --git a/src/responses/result.rs b/sqlx-exasol-impl/src/responses/result.rs similarity index 100% rename from src/responses/result.rs rename to sqlx-exasol-impl/src/responses/result.rs diff --git a/src/responses/session_info.rs b/sqlx-exasol-impl/src/responses/session_info.rs similarity index 100% rename from src/responses/session_info.rs rename to sqlx-exasol-impl/src/responses/session_info.rs diff --git a/src/row.rs b/sqlx-exasol-impl/src/row.rs similarity index 88% rename from src/row.rs rename to sqlx-exasol-impl/src/row.rs index f60fa338..17f6dda1 100644 --- a/src/row.rs +++ b/sqlx-exasol-impl/src/row.rs @@ -1,14 +1,14 @@ use std::{fmt::Debug, sync::Arc}; use serde_json::Value; -use sqlx_core::{column::ColumnIndex, database::Database, row::Row, HashMap}; +use sqlx_core::{column::ColumnIndex, database::Database, ext::ustr::UStr, row::Row, HashMap}; use crate::{column::ExaColumn, database::Exasol, value::ExaValueRef, SqlxError, SqlxResult}; /// Struct representing a result set row. Implementor of [`Row`]. #[derive(Debug)] pub struct ExaRow { - column_names: Arc, usize>>, + pub(crate) column_names: Arc>, columns: Arc<[ExaColumn]>, data: Vec, } @@ -18,7 +18,7 @@ impl ExaRow { pub fn new( data: Vec, columns: Arc<[ExaColumn]>, - column_names: Arc, usize>>, + column_names: Arc>, ) -> Self { Self { column_names, diff --git a/src/statement.rs b/sqlx-exasol-impl/src/statement.rs similarity index 78% rename from src/statement.rs rename to sqlx-exasol-impl/src/statement.rs index 8174ce05..84d9e702 100644 --- a/src/statement.rs +++ b/sqlx-exasol-impl/src/statement.rs @@ -1,7 +1,8 @@ -use std::{borrow::Cow, collections::HashMap, sync::Arc}; +use std::{borrow::Cow, sync::Arc}; use sqlx_core::{ - column::ColumnIndex, database::Database, impl_statement_query, statement::Statement, Either, + column::ColumnIndex, database::Database, ext::ustr::UStr, impl_statement_query, + statement::Statement, Either, HashMap, }; use crate::{ @@ -19,21 +20,22 @@ pub struct ExaStatement<'q> { #[derive(Debug, Clone)] pub struct ExaStatementMetadata { pub columns: Arc<[ExaColumn]>, - pub column_names: HashMap, usize>, + pub column_names: Arc>, pub parameters: Arc<[ExaTypeInfo]>, } impl ExaStatementMetadata { pub fn new(columns: Arc<[ExaColumn]>, parameters: Arc<[ExaTypeInfo]>) -> Self { - let mut column_names = HashMap::with_capacity(columns.len()); - - for (idx, col) in columns.as_ref().iter().enumerate() { - column_names.insert(col.name.clone(), idx); - } + let column_names = columns + .as_ref() + .iter() + .enumerate() + .map(|(idx, col)| (col.name.clone(), idx)) + .collect(); Self { columns, - column_names, + column_names: Arc::new(column_names), parameters, } } diff --git a/src/testing.rs b/sqlx-exasol-impl/src/testing.rs similarity index 100% rename from src/testing.rs rename to sqlx-exasol-impl/src/testing.rs diff --git a/src/transaction.rs b/sqlx-exasol-impl/src/transaction.rs similarity index 100% rename from src/transaction.rs rename to sqlx-exasol-impl/src/transaction.rs diff --git a/sqlx-exasol-impl/src/type_checking.rs b/sqlx-exasol-impl/src/type_checking.rs new file mode 100644 index 00000000..95d1ca51 --- /dev/null +++ b/sqlx-exasol-impl/src/type_checking.rs @@ -0,0 +1,60 @@ +#[allow(unused_imports)] +use sqlx_core as sqlx; +use sqlx_core::{describe::Describe, impl_type_checking}; +use sqlx_macros_core::{ + database::{CachingDescribeBlocking, DatabaseExt}, + query::QueryDriver, +}; + +use crate::{Exasol, SqlxResult}; + +pub const QUERY_DRIVER: QueryDriver = QueryDriver::new::(); + +impl DatabaseExt for Exasol { + const DATABASE_PATH: &'static str = "sqlx_exasol::Exasol"; + + const ROW_PATH: &'static str = "sqlx_exasol::ExaRow"; + + fn describe_blocking(query: &str, database_url: &str) -> SqlxResult> { + static CACHE: CachingDescribeBlocking = CachingDescribeBlocking::new(); + + CACHE.describe(query, database_url) + } +} + +impl_type_checking!( + Exasol { + bool, + u8, + u16, + u32, + u64, + u128, + i8, + i16, + i32, + i64, + i128, + f32, + f64, + String, + + // External types + #[cfg(feature = "chrono")] + sqlx::types::chrono::NaiveDate, + + #[cfg(feature = "chrono")] + sqlx::types::chrono::NaiveDateTime, + + #[cfg(feature = "chrono")] + sqlx::types::chrono::DateTime, + + #[cfg(feature = "rust_decimal")] + sqlx::types::Decimal, + + #[cfg(feature = "uuid")] + sqlx::types::Uuid, + }, + ParamChecking::Weak, + feature-types: _info => None, +); diff --git a/src/type_info.rs b/sqlx-exasol-impl/src/type_info.rs similarity index 99% rename from src/type_info.rs rename to sqlx-exasol-impl/src/type_info.rs index 23b1907f..6e22cd6c 100644 --- a/src/type_info.rs +++ b/sqlx-exasol-impl/src/type_info.rs @@ -15,7 +15,7 @@ use sqlx_core::type_info::TypeInfo; pub struct ExaTypeInfo { #[serde(skip_serializing)] pub(crate) name: DataTypeName, - data_type: ExaDataType, + pub(crate) data_type: ExaDataType, } impl ExaTypeInfo { diff --git a/src/types/bool.rs b/sqlx-exasol-impl/src/types/bool.rs similarity index 100% rename from src/types/bool.rs rename to sqlx-exasol-impl/src/types/bool.rs diff --git a/src/types/chrono.rs b/sqlx-exasol-impl/src/types/chrono.rs similarity index 100% rename from src/types/chrono.rs rename to sqlx-exasol-impl/src/types/chrono.rs diff --git a/src/types/float.rs b/sqlx-exasol-impl/src/types/float.rs similarity index 100% rename from src/types/float.rs rename to sqlx-exasol-impl/src/types/float.rs diff --git a/src/types/int.rs b/sqlx-exasol-impl/src/types/int.rs similarity index 99% rename from src/types/int.rs rename to sqlx-exasol-impl/src/types/int.rs index 5ebe9ca4..81ab28ae 100644 --- a/src/types/int.rs +++ b/sqlx-exasol-impl/src/types/int.rs @@ -142,7 +142,7 @@ impl Encode<'_, Exasol> for i64 { } else { // Large numbers get serialized as strings buf.append(format_args!("{self}"))?; - }; + } Ok(IsNull::No) } @@ -185,7 +185,7 @@ impl Encode<'_, Exasol> for i128 { } else { // Large numbers get serialized as strings buf.append(format_args!("{self}"))?; - }; + } Ok(IsNull::No) } diff --git a/src/types/iter.rs b/sqlx-exasol-impl/src/types/iter.rs similarity index 99% rename from src/types/iter.rs rename to sqlx-exasol-impl/src/types/iter.rs index 70ceae5b..c3d62fae 100644 --- a/src/types/iter.rs +++ b/sqlx-exasol-impl/src/types/iter.rs @@ -15,6 +15,7 @@ use crate::{arguments::ExaBuffer, Exasol}; /// references than owning variants. /// /// ```rust +/// # use sqlx_exasol_impl as sqlx_exasol; /// use sqlx_exasol::ExaIter; /// /// // Don't do this, as the iterator gets cloned internally. diff --git a/src/types/mod.rs b/sqlx-exasol-impl/src/types/mod.rs similarity index 100% rename from src/types/mod.rs rename to sqlx-exasol-impl/src/types/mod.rs diff --git a/src/types/option.rs b/sqlx-exasol-impl/src/types/option.rs similarity index 100% rename from src/types/option.rs rename to sqlx-exasol-impl/src/types/option.rs diff --git a/src/types/rust_decimal.rs b/sqlx-exasol-impl/src/types/rust_decimal.rs similarity index 100% rename from src/types/rust_decimal.rs rename to sqlx-exasol-impl/src/types/rust_decimal.rs diff --git a/src/types/str.rs b/sqlx-exasol-impl/src/types/str.rs similarity index 100% rename from src/types/str.rs rename to sqlx-exasol-impl/src/types/str.rs diff --git a/src/types/text.rs b/sqlx-exasol-impl/src/types/text.rs similarity index 77% rename from src/types/text.rs rename to sqlx-exasol-impl/src/types/text.rs index 6bcf884a..e708c62f 100644 --- a/src/types/text.rs +++ b/sqlx-exasol-impl/src/types/text.rs @@ -53,14 +53,13 @@ where mod tests { use sqlx::{types::Text, Encode}; - use crate::ExaArguments; + use crate::{ExaArguments, Exasol}; #[test] fn test_text_null_string() { let mut arg_buffer = ExaArguments::default(); - let is_null = Text(String::new()) - .encode_by_ref(&mut arg_buffer.buf) - .unwrap(); + let value = Text(String::new()); + let is_null = Encode::::encode_by_ref(&value, &mut arg_buffer.buf).unwrap(); assert!(is_null.is_null()); } @@ -68,7 +67,8 @@ mod tests { #[test] fn test_text_null_str() { let mut arg_buffer = ExaArguments::default(); - let is_null = Text("").encode_by_ref(&mut arg_buffer.buf).unwrap(); + let value = Text(""); + let is_null = Encode::::encode_by_ref(&value, &mut arg_buffer.buf).unwrap(); assert!(is_null.is_null()); } @@ -76,9 +76,8 @@ mod tests { #[test] fn test_text_non_null_string() { let mut arg_buffer = ExaArguments::default(); - let is_null = Text(String::from("something")) - .encode_by_ref(&mut arg_buffer.buf) - .unwrap(); + let value = Text(String::from("something")); + let is_null = Encode::::encode_by_ref(&value, &mut arg_buffer.buf).unwrap(); assert!(!is_null.is_null()); } @@ -86,9 +85,8 @@ mod tests { #[test] fn test_text_non_null_str() { let mut arg_buffer = ExaArguments::default(); - let is_null = Text("something") - .encode_by_ref(&mut arg_buffer.buf) - .unwrap(); + let value = Text("something"); + let is_null = Encode::::encode_by_ref(&value, &mut arg_buffer.buf).unwrap(); assert!(!is_null.is_null()); } diff --git a/src/types/uint.rs b/sqlx-exasol-impl/src/types/uint.rs similarity index 99% rename from src/types/uint.rs rename to sqlx-exasol-impl/src/types/uint.rs index 66951acd..2ddb38ce 100644 --- a/src/types/uint.rs +++ b/sqlx-exasol-impl/src/types/uint.rs @@ -132,7 +132,7 @@ impl Encode<'_, Exasol> for u64 { } else { // Large numbers get serialized as strings buf.append(format_args!("{self}"))?; - }; + } Ok(IsNull::No) } @@ -174,7 +174,7 @@ impl Encode<'_, Exasol> for u128 { } else { // Large numbers get serialized as strings buf.append(format_args!("{self}"))?; - }; + } Ok(IsNull::No) } diff --git a/src/types/uuid.rs b/sqlx-exasol-impl/src/types/uuid.rs similarity index 100% rename from src/types/uuid.rs rename to sqlx-exasol-impl/src/types/uuid.rs diff --git a/src/value.rs b/sqlx-exasol-impl/src/value.rs similarity index 100% rename from src/value.rs rename to sqlx-exasol-impl/src/value.rs diff --git a/sqlx-exasol-macros/Cargo.toml b/sqlx-exasol-macros/Cargo.toml new file mode 100644 index 00000000..3d19aaa4 --- /dev/null +++ b/sqlx-exasol-macros/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "sqlx-exasol-macros" +description = "Compile-time macros support for sqlx-exasol. Not meant to be used directly." +version.workspace = true +license.workspace = true +edition.workspace = true +rust-version.workspace = true +repository.workspace = true +keywords.workspace = true +categories.workspace = true +authors.workspace = true + +[lib] +proc-macro = true + +[dependencies] +sqlx-macros-core = { workspace = true } +sqlx-exasol-impl = { workspace = true, features = ["macros"] } +syn = { workspace = true } +quote = { workspace = true } + +[lints] +workspace = true diff --git a/sqlx-exasol-macros/src/lib.rs b/sqlx-exasol-macros/src/lib.rs new file mode 100644 index 00000000..e8835051 --- /dev/null +++ b/sqlx-exasol-macros/src/lib.rs @@ -0,0 +1,23 @@ +#![cfg_attr(not(test), warn(unused_crate_dependencies))] + +use proc_macro::TokenStream; +use quote::quote; +use sqlx_exasol_impl::QUERY_DRIVER; +use sqlx_macros_core::query; + +#[proc_macro] +pub fn expand_query(input: TokenStream) -> TokenStream { + let input = syn::parse_macro_input!(input as query::QueryMacroInput); + + match query::expand_input(input, &[QUERY_DRIVER]) { + Ok(ts) => ts.into(), + Err(e) => { + if let Some(parse_err) = e.downcast_ref::() { + parse_err.to_compile_error().into() + } else { + let msg = e.to_string(); + quote!(::std::compile_error!(#msg)).into() + } + } + } +} diff --git a/src/lib.rs b/src/lib.rs index b4a6323b..ed347a52 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -217,65 +217,13 @@ //! nullable or not, so the driver cannot implicitly decide whether a `NULL` value can go into a //! certain database column or not until it actually tries. -/// Gets rid of unused dependencies warning from dev-dependencies. -mod arguments; -mod column; -mod connection; -mod database; -mod error; -#[cfg(feature = "migrate")] -mod migrate; -mod options; -mod query_result; -mod responses; -mod row; -mod statement; -#[cfg(feature = "migrate")] -mod testing; -mod transaction; -mod type_info; -mod types; -mod value; +pub use sqlx_exasol_impl::*; +#[cfg(feature = "macros")] +pub use sqlx_exasol_macros; +#[cfg(feature = "macros")] +mod macros; -pub use arguments::ExaArguments; -pub use column::ExaColumn; -#[cfg(feature = "etl")] -pub use connection::etl; -pub use connection::ExaConnection; -pub use database::Exasol; -pub use options::{ExaConnectOptions, ExaConnectOptionsBuilder, ExaSslMode, ProtocolVersion}; -pub use query_result::ExaQueryResult; -pub use responses::{ExaAttributes, ExaDatabaseError, SessionInfo}; -pub use row::ExaRow; -use sqlx_core::{ - executor::Executor, impl_acquire, impl_column_index_for_row, impl_column_index_for_statement, - impl_into_arguments_for_arguments, -}; -pub use statement::ExaStatement; -pub use transaction::ExaTransactionManager; -pub use type_info::ExaTypeInfo; -pub use types::ExaIter; -#[cfg(feature = "chrono")] -pub use types::Months; -pub use value::{ExaValue, ExaValueRef}; - -/// An alias for [`Pool`][sqlx_core::pool::Pool], specialized for Exasol. -pub type ExaPool = sqlx_core::pool::Pool; - -/// An alias for [`PoolOptions`][sqlx_core::pool::PoolOptions], specialized for Exasol. -pub type ExaPoolOptions = sqlx_core::pool::PoolOptions; - -/// An alias for [`Executor<'_, Database = Exasol>`][Executor]. -pub trait ExaExecutor<'c>: Executor<'c, Database = Exasol> {} -impl<'c, T: Executor<'c, Database = Exasol>> ExaExecutor<'c> for T {} - -impl_into_arguments_for_arguments!(ExaArguments); -impl_acquire!(Exasol, ExaConnection); -impl_column_index_for_row!(ExaRow); -impl_column_index_for_statement!(ExaStatement); - -// ################### -// ##### Aliases ##### -// ################### -type SqlxError = sqlx_core::Error; -type SqlxResult = sqlx_core::Result; +// TODO: +// - Optimize ExaIter? Maybe with peek? +// - Re-export sqlx? +// - Make use of sqlx_core::Ustr? diff --git a/src/macros.rs b/src/macros.rs new file mode 100644 index 00000000..eac467ff --- /dev/null +++ b/src/macros.rs @@ -0,0 +1,133 @@ +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "macros")))] +macro_rules! query ( + ($query:expr) => ({ + $crate::sqlx_exasol_macros::expand_query!(source = $query) + }); + ($query:expr, $($args:tt)*) => ({ + $crate::sqlx_exasol_macros::expand_query!(source = $query, args = [$($args)*]) + }) +); + +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "macros")))] +macro_rules! query_unchecked ( + ($query:expr) => ({ + $crate::sqlx_exasol_macros::expand_query!(source = $query, checked = false) + }); + ($query:expr, $($args:tt)*) => ({ + $crate::sqlx_exasol_macros::expand_query!(source = $query, args = [$($args)*], checked = false) + }) +); + +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "macros")))] +macro_rules! query_file ( + ($path:literal) => ({ + $crate::sqlx_exasol_macros::expand_query!(source_file = $path) + }); + ($path:literal, $($args:tt)*) => ({ + $crate::sqlx_exasol_macros::expand_query!(source_file = $path, args = [$($args)*]) + }) +); + +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "macros")))] +macro_rules! query_file_unchecked ( + ($path:literal) => ({ + $crate::sqlx_exasol_macros::expand_query!(source_file = $path, checked = false) + }); + ($path:literal, $($args:tt)*) => ({ + $crate::sqlx_exasol_macros::expand_query!(source_file = $path, args = [$($args)*], checked = false) + }) +); + +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "macros")))] +macro_rules! query_as ( + ($out_struct:path, $query:expr) => ( { + $crate::sqlx_exasol_macros::expand_query!(record = $out_struct, source = $query) + }); + ($out_struct:path, $query:expr, $($args:tt)*) => ( { + $crate::sqlx_exasol_macros::expand_query!(record = $out_struct, source = $query, args = [$($args)*]) + }) +); + +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "macros")))] +macro_rules! query_file_as ( + ($out_struct:path, $path:literal) => ( { + $crate::sqlx_exasol_macros::expand_query!(record = $out_struct, source_file = $path) + }); + ($out_struct:path, $path:literal, $($args:tt)*) => ( { + $crate::sqlx_exasol_macros::expand_query!(record = $out_struct, source_file = $path, args = [$($args)*]) + }) +); + +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "macros")))] +macro_rules! query_as_unchecked ( + ($out_struct:path, $query:expr) => ( { + $crate::sqlx_exasol_macros::expand_query!(record = $out_struct, source = $query, checked = false) + }); + + ($out_struct:path, $query:expr, $($args:tt)*) => ( { + $crate::sqlx_exasol_macros::expand_query!(record = $out_struct, source = $query, args = [$($args)*], checked = false) + }) +); + +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "macros")))] +macro_rules! query_file_as_unchecked ( + ($out_struct:path, $path:literal) => ( { + $crate::sqlx_exasol_macros::expand_query!(record = $out_struct, source_file = $path, checked = false) + }); + + ($out_struct:path, $path:literal, $($args:tt)*) => ( { + $crate::sqlx_exasol_macros::expand_query!(record = $out_struct, source_file = $path, args = [$($args)*], checked = false) + }) +); + +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "macros")))] +macro_rules! query_scalar ( + ($query:expr) => ( + $crate::sqlx_exasol_macros::expand_query!(scalar = _, source = $query) + ); + ($query:expr, $($args:tt)*) => ( + $crate::sqlx_exasol_macros::expand_query!(scalar = _, source = $query, args = [$($args)*]) + ) +); + +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "macros")))] +macro_rules! query_file_scalar ( + ($path:literal) => ( + $crate::sqlx_exasol_macros::expand_query!(scalar = _, source_file = $path) + ); + ($path:literal, $($args:tt)*) => ( + $crate::sqlx_exasol_macros::expand_query!(scalar = _, source_file = $path, args = [$($args)*]) + ) +); + +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "macros")))] +macro_rules! query_scalar_unchecked ( + ($query:expr) => ( + $crate::sqlx_exasol_macros::expand_query!(scalar = _, source = $query, checked = false) + ); + ($query:expr, $($args:tt)*) => ( + $crate::sqlx_exasol_macros::expand_query!(scalar = _, source = $query, args = [$($args)*], checked = false) + ) +); + +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "macros")))] +macro_rules! query_file_scalar_unchecked ( + ($path:literal) => ( + $crate::sqlx_exasol_macros::expand_query!(scalar = _, source_file = $path, checked = false) + ); + ($path:literal, $($args:tt)*) => ( + $crate::sqlx_exasol_macros::expand_query!(scalar = _, source_file = $path, args = [$($args)*], checked = false) + ) +); diff --git a/tests/describe.rs b/tests/describe.rs index 14edc740..c3546eaa 100644 --- a/tests/describe.rs +++ b/tests/describe.rs @@ -1,7 +1,6 @@ #![cfg(feature = "migrate")] -use sqlx::{Column, Executor, Type, TypeInfo}; -use sqlx_core::pool::PoolConnection; +use sqlx::{pool::PoolConnection, Column, Executor, Type, TypeInfo}; use sqlx_exasol::Exasol; #[sqlx::test(migrations = "tests/setup")] diff --git a/tests/error.rs b/tests/error.rs index 65d95929..3558b2fb 100644 --- a/tests/error.rs +++ b/tests/error.rs @@ -1,7 +1,6 @@ #![cfg(feature = "migrate")] -use sqlx::error::ErrorKind; -use sqlx_core::pool::PoolConnection; +use sqlx::{error::ErrorKind, pool::PoolConnection}; use sqlx_exasol::Exasol; #[sqlx::test(migrations = "tests/setup")] diff --git a/tests/from_row.rs b/tests/from_row.rs index 79955189..663c8a15 100644 --- a/tests/from_row.rs +++ b/tests/from_row.rs @@ -1,7 +1,6 @@ #![cfg(feature = "migrate")] -use sqlx::{Executor, FromRow}; -use sqlx_core::pool::PoolConnection; +use sqlx::{pool::PoolConnection, Executor, FromRow}; use sqlx_exasol::Exasol; #[derive(Debug, FromRow, PartialEq, Eq)] @@ -16,8 +15,8 @@ async fn test_from_row(mut conn: PoolConnection) -> anyhow::Result<()> { conn.execute( r" CREATE TABLE TEST_FROM_ROW ( - name VARCHAR(200), - age DECIMAL(3, 0), + name VARCHAR(200), + age DECIMAL(3, 0), amount DECIMAL(15, 0) );", ) diff --git a/tests/macros.rs b/tests/macros.rs index 3041b9a4..d63d17e6 100644 --- a/tests/macros.rs +++ b/tests/macros.rs @@ -6,9 +6,9 @@ macro_rules! test_type_valid { paste::item! { #[sqlx::test] async fn [< test_type_valid_ $name >] ( - mut con: sqlx_core::pool::PoolConnection, - ) -> Result<(), sqlx_core::error::BoxDynError> { - use sqlx_core::{executor::Executor, query::query, query_scalar::query_scalar}; + mut con: sqlx::pool::PoolConnection, + ) -> Result<(), sqlx::error::BoxDynError> { + use sqlx::{Executor, query, query_scalar}; let create_sql = concat!("CREATE TABLE sqlx_test_type ( col ", $datatype, " );"); con.execute(create_sql).await?; @@ -63,9 +63,9 @@ macro_rules! test_type_array { paste::item! { #[sqlx::test] async fn [< test_type_array_ $name >] ( - mut con: sqlx_core::pool::PoolConnection, - ) -> Result<(), sqlx_core::error::BoxDynError> { - use sqlx_core::{executor::Executor, query::query, query_scalar::query_scalar}; + mut con: sqlx::pool::PoolConnection, + ) -> Result<(), sqlx::error::BoxDynError> { + use sqlx::{Executor, query, query_scalar}; let create_sql = concat!("CREATE TABLE sqlx_test_type ( col ", $datatype, " );"); con.execute(create_sql).await?; @@ -97,9 +97,9 @@ macro_rules! test_type_invalid { paste::item! { #[sqlx::test] async fn [< test_type_invalid_ $name >] ( - mut con: sqlx_core::pool::PoolConnection, - ) -> Result<(), sqlx_core::error::BoxDynError> { - use sqlx_core::{executor::Executor, query::query, query_scalar::query_scalar}; + mut con: sqlx::pool::PoolConnection, + ) -> Result<(), sqlx::error::BoxDynError> { + use sqlx::{Executor, query, query_scalar}; let create_sql = concat!("CREATE TABLE sqlx_test_type ( col ", $datatype, " );"); con.execute(create_sql).await?; From f16442990e11779049a9b9c1ba1d91b4256b480c Mon Sep 17 00:00:00 2001 From: Bogdan Mircea Date: Fri, 6 Jun 2025 19:13:28 +0300 Subject: [PATCH 002/102] re-export sqlx --- Cargo.toml | 4 +++- sqlx-exasol-impl/src/lib.rs | 1 + src/lib.rs | 4 ++-- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ff9f2c49..13b8b3ae 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -98,7 +98,8 @@ serde_json = { version = "1", features = ["raw_value"] } # "tls-native-tls", # "migrate", # ] } -sqlx = { path = "../sqlx", features = [ +sqlx = { path = "../sqlx" } +sqlx-dev = { path = "../sqlx", package = "sqlx", features = [ "runtime-tokio", "tls-native-tls", "migrate", @@ -125,6 +126,7 @@ rustls = { version = "0.23", default-features = false, features = [ [dependencies] sqlx-exasol-macros = { workspace = true, optional = true } sqlx-exasol-impl = { workspace = true } +sqlx = { workspace = true } [dev-dependencies] anyhow = { workspace = true } diff --git a/sqlx-exasol-impl/src/lib.rs b/sqlx-exasol-impl/src/lib.rs index 503a9e97..51de29be 100644 --- a/sqlx-exasol-impl/src/lib.rs +++ b/sqlx-exasol-impl/src/lib.rs @@ -40,6 +40,7 @@ use sqlx_core::{ }; pub use statement::ExaStatement; pub use transaction::ExaTransactionManager; +#[doc(hidden)] #[cfg(feature = "macros")] pub use type_checking::QUERY_DRIVER; pub use type_info::ExaTypeInfo; diff --git a/src/lib.rs b/src/lib.rs index ed347a52..504d0fa8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -217,6 +217,8 @@ //! nullable or not, so the driver cannot implicitly decide whether a `NULL` value can go into a //! certain database column or not until it actually tries. +#[allow(ambiguous_glob_reexports)] +pub use sqlx::*; pub use sqlx_exasol_impl::*; #[cfg(feature = "macros")] pub use sqlx_exasol_macros; @@ -225,5 +227,3 @@ mod macros; // TODO: // - Optimize ExaIter? Maybe with peek? -// - Re-export sqlx? -// - Make use of sqlx_core::Ustr? From c5d1b980482dda8ceaeb29ea4a1cc4f843747778 Mon Sep 17 00:00:00 2001 From: Bogdan Mircea Date: Fri, 6 Jun 2025 20:01:08 +0300 Subject: [PATCH 003/102] tweak feature flags --- .zed/settings.json | 15 +++-- Cargo.toml | 60 ++++++++++--------- sqlx-exasol-cli/src/main.rs | 3 +- sqlx-exasol-impl/Cargo.toml | 21 ++++--- .../src/connection/etl/job/maybe_tls/mod.rs | 12 ++-- .../connection/etl/job/maybe_tls/tls/mod.rs | 19 +++--- sqlx-exasol-impl/src/types/chrono.rs | 6 +- sqlx-exasol-impl/src/types/rust_decimal.rs | 8 +-- sqlx-exasol-impl/src/types/uuid.rs | 3 +- sqlx-exasol-macros/Cargo.toml | 2 +- src/lib.rs | 1 + 11 files changed, 79 insertions(+), 71 deletions(-) diff --git a/.zed/settings.json b/.zed/settings.json index 4684992c..d8f19269 100644 --- a/.zed/settings.json +++ b/.zed/settings.json @@ -11,14 +11,19 @@ "DATABASE_URL": "exa://sys:exasol@127.0.0.1:8563" }, "features": [ + // SQLx + "migrate", + "macros", "any", - "etl_rustls", - "compression", - "uuid", + // Types "chrono", "rust_decimal", - "migrate", - "macros" + "uuid", + // Driver + "etl", + "compression", + // TLS + "native-tls" ] } } diff --git a/Cargo.toml b/Cargo.toml index 13b8b3ae..d19934ef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,27 +35,41 @@ features = [ ] [features] -# ############################################ -# ########### User-facing features ########### -# ############################################ -default = ["macros", "migrate"] +default = ["any", "macros", "migrate"] -macros = ["dep:sqlx-exasol-macros"] -migrate = ["sqlx-exasol-impl/migrate"] +derive = ["sqlx/derive"] +macros = ["sqlx/macros", "sqlx-exasol-impl/macros", "dep:sqlx-exasol-macros"] +migrate = ["sqlx/migrate", "sqlx-exasol-impl/migrate"] -compression = ["sqlx-exasol-impl/compression"] +# Base runtime features without TLS +runtime-async-std = ["sqlx/runtime-async-std"] +runtime-tokio = ["sqlx/runtime-tokio"] -etl = ["sqlx-exasol-impl/etl"] -etl_rustls = ["sqlx-exasol-impl/etl_rustls"] -etl_native_tls = ["sqlx-exasol-impl/etl_native_tls"] +# TLS features +tls-native-tls = ["sqlx/tls-native-tls", "sqlx-exasol-impl/native-tls"] +tls-rustls = ["sqlx/tls-rustls", "sqlx-exasol-impl/rustls"] +tls-rustls-aws-lc-rs = ["sqlx/tls-rustls-aws-lc-rs", "sqlx-exasol-impl/rustls"] +tls-rustls-ring = ["sqlx/tls-rustls-ring", "sqlx-exasol-impl/rustls"] +tls-rustls-ring-webpki = [ + "sqlx/tls-rustls-ring-webpki", + "sqlx-exasol-impl/rustls", +] +tls-rustls-ring-native-roots = [ + "sqlx/tls-rustls-ring-native-roots", + "sqlx-exasol-impl/rustls", +] -uuid = ["sqlx-exasol-impl/uuid"] -chrono = ["sqlx-exasol-impl/chrono"] -rust_decimal = ["sqlx-exasol-impl/rust_decimal"] +# Database +any = ["sqlx/any", "sqlx-exasol-impl/any"] -# ############################################ -# ############################################ -# ############################################ +# Types +chrono = ["sqlx/chrono", "sqlx-exasol-impl/chrono"] +rust_decimal = ["sqlx/rust_decimal", "sqlx-exasol-impl/rust_decimal"] +uuid = ["sqlx/uuid", "sqlx-exasol-impl/uuid"] + +# Driver specific features +compression = ["sqlx-exasol-impl/compression"] +etl = ["sqlx-exasol-impl/etl"] [workspace.dependencies] # Internal @@ -91,21 +105,9 @@ rand = "0.8" rsa = "0.9" serde = { version = "1", features = ["derive", "rc"] } serde_json = { version = "1", features = ["raw_value"] } -# sqlx-core = "=0.8.6" -# sqlx-macros-core = { version = "=0.8.6", features = ["macros"] } -# sqlx = { version = "=0.8.6", features = [ -# "runtime-tokio", -# "tls-native-tls", -# "migrate", -# ] } sqlx = { path = "../sqlx" } -sqlx-dev = { path = "../sqlx", package = "sqlx", features = [ - "runtime-tokio", - "tls-native-tls", - "migrate", -] } sqlx-cli = { path = "../sqlx/sqlx-cli" } -sqlx-core = { path = "../sqlx/sqlx-core" } +sqlx-core = { path = "../sqlx/sqlx-core", features = ["offline"] } sqlx-macros-core = { path = "../sqlx/sqlx-macros-core", features = ["macros"] } syn = { version = "2", default-features = false, features = [ "parsing", diff --git a/sqlx-exasol-cli/src/main.rs b/sqlx-exasol-cli/src/main.rs index 53f4d183..e34390bb 100644 --- a/sqlx-exasol-cli/src/main.rs +++ b/sqlx-exasol-cli/src/main.rs @@ -7,8 +7,7 @@ use sqlx_exasol_impl::any::DRIVER; async fn main() { // Checks for `--no-dotenv` before parsing. sqlx_cli::maybe_apply_dotenv(); - - sqlx::any::install_drivers(&[DRIVER]); + sqlx::any::install_drivers(&[DRIVER]).expect("driver installation failed"); let opt = Opt::parse(); diff --git a/sqlx-exasol-impl/Cargo.toml b/sqlx-exasol-impl/Cargo.toml index 91740314..5cfa1b87 100644 --- a/sqlx-exasol-impl/Cargo.toml +++ b/sqlx-exasol-impl/Cargo.toml @@ -11,19 +11,24 @@ categories.workspace = true authors.workspace = true [features] +# SQLx inherited features any = ["sqlx-core/any"] migrate = ["sqlx-core/migrate", "dep:dotenvy", "dep:hex"] macros = ["dep:sqlx-macros-core"] -compression = ["dep:async-compression"] +# Type Integration features +uuid = ["sqlx-core/uuid"] +chrono = ["sqlx-core/chrono", "dep:chrono"] +rust_decimal = ["sqlx-core/rust_decimal"] -etl = ["dep:http-body-util", "dep:hyper", "dep:futures-channel"] -etl_rustls = ["dep:rustls", "dep:rcgen", "etl"] -etl_native_tls = ["dep:native-tls", "dep:rcgen", "etl"] +# TLS features +tls = [] +rustls = ["dep:rustls", "dep:rcgen", "tls"] +native-tls = ["dep:native-tls", "dep:rcgen", "tls"] -uuid = ["dep:uuid", "sqlx-core/uuid"] -chrono = ["dep:chrono", "sqlx-core/chrono"] -rust_decimal = ["dep:rust_decimal", "sqlx-core/rust_decimal"] +# Driver specific features +compression = ["dep:async-compression"] +etl = ["dep:http-body-util", "dep:hyper", "dep:futures-channel"] [dependencies] arrayvec = { workspace = true } @@ -51,9 +56,7 @@ hex = { workspace = true, optional = true } http-body-util = { workspace = true, optional = true } hyper = { workspace = true, optional = true } native-tls = { workspace = true, optional = true } -uuid = { workspace = true, optional = true } rcgen = { workspace = true, optional = true } -rust_decimal = { workspace = true, optional = true } rustls = { workspace = true, optional = true } sqlx-macros-core = { workspace = true, optional = true } diff --git a/sqlx-exasol-impl/src/connection/etl/job/maybe_tls/mod.rs b/sqlx-exasol-impl/src/connection/etl/job/maybe_tls/mod.rs index febc535f..ce3f4403 100644 --- a/sqlx-exasol-impl/src/connection/etl/job/maybe_tls/mod.rs +++ b/sqlx-exasol-impl/src/connection/etl/job/maybe_tls/mod.rs @@ -1,4 +1,4 @@ -#[cfg(any(feature = "etl_native_tls", feature = "etl_rustls"))] +#[cfg(feature = "tls")] pub mod tls; use futures_util::FutureExt; @@ -13,14 +13,14 @@ use crate::{ /// Implementor of [`WithSocketMaker`] that abstracts away the TLS/non-TLS socket creation. pub enum WithMaybeTlsSocketMaker { NonTls, - #[cfg(any(feature = "etl_native_tls", feature = "etl_rustls"))] + #[cfg(feature = "tls")] Tls(tls::WithTlsSocketMaker), } impl WithMaybeTlsSocketMaker { pub fn new(with_tls: bool) -> SqlxResult { match with_tls { - #[cfg(any(feature = "etl_native_tls", feature = "etl_rustls"))] + #[cfg(feature = "tls")] true => tls::with_worker().map(Self::Tls), #[allow(unreachable_patterns, reason = "reachable with no TLS feature ")] true => Err(SqlxError::Tls("No ETL TLS feature set".into())), @@ -35,7 +35,7 @@ impl WithSocketMaker for WithMaybeTlsSocketMaker { fn make_with_socket(&self, with_socket: WithExaSocket) -> Self::WithSocket { match self { Self::NonTls => WithMaybeTlsSocket::NonTls(with_socket), - #[cfg(any(feature = "etl_native_tls", feature = "etl_rustls"))] + #[cfg(feature = "tls")] Self::Tls(w) => WithMaybeTlsSocket::Tls(w.make_with_socket(with_socket)), } } @@ -44,7 +44,7 @@ impl WithSocketMaker for WithMaybeTlsSocketMaker { /// Implementor of [`WithSocket`] that abstracts away the TLS/non-TLS socket creation. pub enum WithMaybeTlsSocket { NonTls(WithExaSocket), - #[cfg(any(feature = "etl_native_tls", feature = "etl_rustls"))] + #[cfg(feature = "tls")] Tls(tls::WithTlsSocket), } @@ -54,7 +54,7 @@ impl WithSocket for WithMaybeTlsSocket { async fn with_socket(self, socket: S) -> Self::Output { match self { WithMaybeTlsSocket::NonTls(w) => w.with_socket(socket).map(Ok).boxed(), - #[cfg(any(feature = "etl_native_tls", feature = "etl_rustls"))] + #[cfg(feature = "tls")] WithMaybeTlsSocket::Tls(w) => w.with_socket(socket).await, } } diff --git a/sqlx-exasol-impl/src/connection/etl/job/maybe_tls/tls/mod.rs b/sqlx-exasol-impl/src/connection/etl/job/maybe_tls/tls/mod.rs index ee951fc6..5d06e1e2 100644 --- a/sqlx-exasol-impl/src/connection/etl/job/maybe_tls/tls/mod.rs +++ b/sqlx-exasol-impl/src/connection/etl/job/maybe_tls/tls/mod.rs @@ -1,6 +1,6 @@ -#[cfg(feature = "etl_native_tls")] +#[cfg(feature = "native-tls")] mod native_tls; -#[cfg(feature = "etl_rustls")] +#[cfg(feature = "rustls")] mod rustls; mod sync_socket; @@ -12,17 +12,14 @@ use rsa::{ use crate::{error::ToSqlxError, SqlxError, SqlxResult}; -#[cfg(all(feature = "etl_native_tls", feature = "etl_rustls"))] -compile_error!("Only enable one of 'etl_antive_tls' or 'etl_rustls' features"); - -#[cfg(feature = "etl_native_tls")] +#[cfg(feature = "native-tls")] pub type WithTlsSocketMaker = native_tls::WithNativeTlsSocketMaker; -#[cfg(feature = "etl_rustls")] +#[cfg(feature = "rustls")] pub type WithTlsSocketMaker = rustls::WithRustlsSocketMaker; -#[cfg(feature = "etl_native_tls")] +#[cfg(feature = "native-tls")] pub type WithTlsSocket = native_tls::WithNativeTlsSocket; -#[cfg(feature = "etl_rustls")] +#[cfg(feature = "rustls")] pub type WithTlsSocket = rustls::WithRustlsSocket; /// Returns the dedicated [`impl WithSocketMaker`] for the chosen TLS implementation. @@ -42,9 +39,9 @@ pub fn with_worker() -> SqlxResult { .self_signed(&key_pair) .map_err(ToSqlxError::to_sqlx_err)?; - #[cfg(feature = "etl_native_tls")] + #[cfg(feature = "native-tls")] return native_tls::WithNativeTlsSocketMaker::new(&cert, &key_pair); - #[cfg(feature = "etl_rustls")] + #[cfg(feature = "rustls")] return rustls::WithRustlsSocketMaker::new(&cert, &key_pair); } diff --git a/sqlx-exasol-impl/src/types/chrono.rs b/sqlx-exasol-impl/src/types/chrono.rs index 34112373..f9be842c 100644 --- a/sqlx-exasol-impl/src/types/chrono.rs +++ b/sqlx-exasol-impl/src/types/chrono.rs @@ -3,13 +3,15 @@ use std::{ ops::{Add, Sub}, }; -use chrono::{DateTime, Local, NaiveDate, NaiveDateTime, TimeZone, Utc}; use serde::Deserialize; use sqlx_core::{ decode::Decode, encode::{Encode, IsNull}, error::BoxDynError, - types::Type, + types::{ + chrono::{DateTime, Local, NaiveDate, NaiveDateTime, TimeZone, Utc}, + Type, + }, }; use crate::{ diff --git a/sqlx-exasol-impl/src/types/rust_decimal.rs b/sqlx-exasol-impl/src/types/rust_decimal.rs index fb11f35d..abd1167e 100644 --- a/sqlx-exasol-impl/src/types/rust_decimal.rs +++ b/sqlx-exasol-impl/src/types/rust_decimal.rs @@ -3,7 +3,7 @@ use sqlx_core::{ decode::Decode, encode::{Encode, IsNull}, error::BoxDynError, - types::Type, + types::{Decimal as RustDecimal, Type}, }; use crate::{ @@ -16,7 +16,7 @@ use crate::{ /// Scale limit set by `rust_decimal` crate. const RUST_DECIMAL_MAX_SCALE: u32 = 28; -impl Type for rust_decimal::Decimal { +impl Type for RustDecimal { fn type_info() -> ExaTypeInfo { // This is not a valid Exasol datatype defintion, // but defining it like this means that we can accommodate @@ -31,7 +31,7 @@ impl Type for rust_decimal::Decimal { } } -impl Encode<'_, Exasol> for rust_decimal::Decimal { +impl Encode<'_, Exasol> for RustDecimal { fn encode_by_ref(&self, buf: &mut ExaBuffer) -> Result { buf.append(format_args!("{self}"))?; Ok(IsNull::No) @@ -60,7 +60,7 @@ impl Encode<'_, Exasol> for rust_decimal::Decimal { } } -impl Decode<'_, Exasol> for rust_decimal::Decimal { +impl Decode<'_, Exasol> for RustDecimal { fn decode(value: ExaValueRef<'_>) -> Result { ::deserialize(value.value).map_err(From::from) } diff --git a/sqlx-exasol-impl/src/types/uuid.rs b/sqlx-exasol-impl/src/types/uuid.rs index fe6249c5..8f87d5dd 100644 --- a/sqlx-exasol-impl/src/types/uuid.rs +++ b/sqlx-exasol-impl/src/types/uuid.rs @@ -3,9 +3,8 @@ use sqlx_core::{ decode::Decode, encode::{Encode, IsNull}, error::BoxDynError, - types::Type, + types::{Type, Uuid}, }; -use uuid::Uuid; use crate::{ arguments::ExaBuffer, diff --git a/sqlx-exasol-macros/Cargo.toml b/sqlx-exasol-macros/Cargo.toml index 3d19aaa4..4260d68a 100644 --- a/sqlx-exasol-macros/Cargo.toml +++ b/sqlx-exasol-macros/Cargo.toml @@ -14,7 +14,7 @@ authors.workspace = true proc-macro = true [dependencies] -sqlx-macros-core = { workspace = true } +sqlx-macros-core = { workspace = true, features = ["macros"] } sqlx-exasol-impl = { workspace = true, features = ["macros"] } syn = { workspace = true } quote = { workspace = true } diff --git a/src/lib.rs b/src/lib.rs index 504d0fa8..37f97166 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -227,3 +227,4 @@ mod macros; // TODO: // - Optimize ExaIter? Maybe with peek? +// - Get features in sync with `sqlx` and fix README and lib.rs From 8dbcc38bb9edbe4823c3ba957e034d524758870e Mon Sep 17 00:00:00 2001 From: Bogdan Mircea Date: Fri, 6 Jun 2025 20:24:37 +0300 Subject: [PATCH 004/102] more feature flags cleanup --- Cargo.toml | 81 +++++++++++++--------- sqlx-exasol-cli/src/main.rs | 3 - sqlx-exasol-impl/Cargo.toml | 8 ++- sqlx-exasol-impl/src/types/chrono.rs | 22 +++--- sqlx-exasol-impl/src/types/rust_decimal.rs | 8 +-- sqlx-exasol-impl/src/types/uuid.rs | 3 +- tests/etl.rs | 6 +- 7 files changed, 72 insertions(+), 59 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d19934ef..5ffb9d6a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -77,49 +77,66 @@ sqlx-exasol-macros = { path = "sqlx-exasol-macros" } sqlx-exasol-impl = { path = "sqlx-exasol-impl" } # External -anyhow = "1" -arrayvec = "0.7" -async-compression = { version = "0.4", features = [ +anyhow = { version = "1", default-features = false } +arrayvec = { version = "0.7", default-features = false } +async-compression = { version = "0.4", default-features = false, features = [ "futures-io", "gzip", "zlib", ] } -async-tungstenite = "0.29" -base64 = "0.22" -chrono = { version = "0.4", features = ["serde"] } -clap = { version = "4", features = ["derive", "env", "wrap_help"] } -console = "0.15" -dotenvy = "0.15" -futures-io = "0.3" -futures-util = "0.3" -futures-channel = { version = "0.3", features = ["sink"] } -futures-core = "0.3" -hex = "0.4" -http-body-util = "0.1" -hyper = { version = "1.4", features = ["server", "http1"] } -lru = "0.12" -native-tls = "0.2" -paste = "1" +async-tungstenite = { version = "0.29", default-features = false, features = [ + "handshake", + "futures-03-sink", +] } +base64 = { version = "0.22", default-features = false } +chrono = { version = "0.4", default-features = false, features = ["serde"] } +clap = { version = "4", default-features = false, features = ["derive"] } +console = { version = "0.15", default-features = false } +dotenvy = { version = "0.15", default-features = false } +futures-io = { version = "0.3", default-features = false } +futures-util = { version = "0.3", default-features = false } +futures-channel = { version = "0.3", default-features = false, features = [ + "sink", +] } +futures-core = { version = "0.3", default-features = false } +hex = { version = "0.4", default-features = false } +http-body-util = { version = "0.1", default-features = false } +hyper = { version = "1.4", default-features = false, features = [ + "server", + "http1", +] } +lru = { version = "0.12", default-features = false } +native-tls = { version = "0.2", default-features = false } +paste = { version = "1", default-features = false } quote = { version = "1", default-features = false } -rand = "0.8" -rsa = "0.9" -serde = { version = "1", features = ["derive", "rc"] } -serde_json = { version = "1", features = ["raw_value"] } -sqlx = { path = "../sqlx" } -sqlx-cli = { path = "../sqlx/sqlx-cli" } -sqlx-core = { path = "../sqlx/sqlx-core", features = ["offline"] } -sqlx-macros-core = { path = "../sqlx/sqlx-macros-core", features = ["macros"] } +rand = { version = "0.8", default-features = false, features = [ + "std", + "std_rng", +] } +rsa = { version = "0.9", default-features = false, features = ["pem", "std"] } +serde = { version = "1", default-features = false, features = ["derive", "rc"] } +serde_json = { version = "1", default-features = false, features = [ + "raw_value", +] } +sqlx = { path = "../sqlx", default-features = false } +sqlx-cli = { path = "../sqlx/sqlx-cli", default-features = false } +sqlx-core = { path = "../sqlx/sqlx-core", default-features = false, features = [ + "offline", +] } +sqlx-macros-core = { path = "../sqlx/sqlx-macros-core", default-features = false } syn = { version = "2", default-features = false, features = [ "parsing", "proc-macro", ] } thiserror = "1" -tokio = { version = "1", features = ["full"] } -tracing = { version = "0.1", features = ["log"] } +tokio = { version = "1", default-features = false, features = [ + "rt-multi-thread", +] } +tracing = { version = "0.1", default-features = false, features = ["log"] } url = "2" -uuid = { version = "1", features = ["serde"] } +uuid = { version = "1", default-features = false, features = ["serde"] } rcgen = "0.13" -rust_decimal = "1" +rust_decimal = { version = "1", default-features = false, features = ["serde"] } rustls = { version = "0.23", default-features = false, features = [ "std", "tls12", @@ -136,7 +153,7 @@ chrono = { workspace = true } dotenvy = { workspace = true } futures-util = { workspace = true } paste = { workspace = true } -rustls = { workspace = true } +rustls = { workspace = true, features = ["aws_lc_rs"] } rust_decimal = { workspace = true } sqlx = { workspace = true } tokio = { workspace = true } diff --git a/sqlx-exasol-cli/src/main.rs b/sqlx-exasol-cli/src/main.rs index e34390bb..d3586980 100644 --- a/sqlx-exasol-cli/src/main.rs +++ b/sqlx-exasol-cli/src/main.rs @@ -5,13 +5,10 @@ use sqlx_exasol_impl::any::DRIVER; #[tokio::main] async fn main() { - // Checks for `--no-dotenv` before parsing. sqlx_cli::maybe_apply_dotenv(); sqlx::any::install_drivers(&[DRIVER]).expect("driver installation failed"); - let opt = Opt::parse(); - // no special handling here if let Err(error) = sqlx_cli::run(opt).await { println!("{} {}", style("error:").bold().red(), error); std::process::exit(1); diff --git a/sqlx-exasol-impl/Cargo.toml b/sqlx-exasol-impl/Cargo.toml index 5cfa1b87..b55a81e6 100644 --- a/sqlx-exasol-impl/Cargo.toml +++ b/sqlx-exasol-impl/Cargo.toml @@ -14,12 +14,12 @@ authors.workspace = true # SQLx inherited features any = ["sqlx-core/any"] migrate = ["sqlx-core/migrate", "dep:dotenvy", "dep:hex"] -macros = ["dep:sqlx-macros-core"] +macros = ["dep:sqlx-macros-core", "sqlx-macros-core?/macros"] # Type Integration features -uuid = ["sqlx-core/uuid"] +uuid = ["sqlx-core/uuid", "dep:uuid"] chrono = ["sqlx-core/chrono", "dep:chrono"] -rust_decimal = ["sqlx-core/rust_decimal"] +rust_decimal = ["sqlx-core/rust_decimal", "dep:rust_decimal"] # TLS features tls = [] @@ -58,7 +58,9 @@ hyper = { workspace = true, optional = true } native-tls = { workspace = true, optional = true } rcgen = { workspace = true, optional = true } rustls = { workspace = true, optional = true } +rust_decimal = { workspace = true, optional = true } sqlx-macros-core = { workspace = true, optional = true } +uuid = { workspace = true, optional = true } [dev-dependencies] anyhow = { workspace = true } diff --git a/sqlx-exasol-impl/src/types/chrono.rs b/sqlx-exasol-impl/src/types/chrono.rs index f9be842c..f92ea4fe 100644 --- a/sqlx-exasol-impl/src/types/chrono.rs +++ b/sqlx-exasol-impl/src/types/chrono.rs @@ -3,15 +3,13 @@ use std::{ ops::{Add, Sub}, }; +use chrono::{DateTime, Duration, Local, NaiveDate, NaiveDateTime, TimeZone, Utc}; use serde::Deserialize; use sqlx_core::{ decode::Decode, encode::{Encode, IsNull}, error::BoxDynError, - types::{ - chrono::{DateTime, Local, NaiveDate, NaiveDateTime, TimeZone, Utc}, - Type, - }, + types::Type, }; use crate::{ @@ -111,7 +109,7 @@ impl Decode<'_, Exasol> for NaiveDateTime { } } -impl Type for chrono::Duration { +impl Type for Duration { fn type_info() -> ExaTypeInfo { let ids = IntervalDayToSecond::new( IntervalDayToSecond::MAX_PRECISION, @@ -125,7 +123,7 @@ impl Type for chrono::Duration { } } -impl Encode<'_, Exasol> for chrono::Duration { +impl Encode<'_, Exasol> for Duration { fn encode_by_ref(&self, buf: &mut ExaBuffer) -> Result { buf.append(format_args!( "{} {}:{}:{}.{}", @@ -165,7 +163,7 @@ impl Encode<'_, Exasol> for chrono::Duration { } } -impl<'r> Decode<'r, Exasol> for chrono::Duration { +impl<'r> Decode<'r, Exasol> for Duration { fn decode(value: ExaValueRef<'r>) -> Result { let input = Cow::::deserialize(value.value).map_err(Box::new)?; let input_err_fn = || format!("could not parse {input} as INTERVAL DAY TO SECOND"); @@ -182,11 +180,11 @@ impl<'r> Decode<'r, Exasol> for chrono::Duration { let millis: i64 = millis.parse().map_err(Box::new)?; let sign = if days.is_negative() { -1 } else { 1 }; - let duration = chrono::Duration::try_days(days).ok_or_else(input_err_fn)? - + chrono::Duration::try_hours(hours * sign).ok_or_else(input_err_fn)? - + chrono::Duration::try_minutes(minutes * sign).ok_or_else(input_err_fn)? - + chrono::Duration::try_seconds(seconds * sign).ok_or_else(input_err_fn)? - + chrono::Duration::try_milliseconds(millis * sign).ok_or_else(input_err_fn)?; + let duration = Duration::try_days(days).ok_or_else(input_err_fn)? + + Duration::try_hours(hours * sign).ok_or_else(input_err_fn)? + + Duration::try_minutes(minutes * sign).ok_or_else(input_err_fn)? + + Duration::try_seconds(seconds * sign).ok_or_else(input_err_fn)? + + Duration::try_milliseconds(millis * sign).ok_or_else(input_err_fn)?; Ok(duration) } diff --git a/sqlx-exasol-impl/src/types/rust_decimal.rs b/sqlx-exasol-impl/src/types/rust_decimal.rs index abd1167e..fb11f35d 100644 --- a/sqlx-exasol-impl/src/types/rust_decimal.rs +++ b/sqlx-exasol-impl/src/types/rust_decimal.rs @@ -3,7 +3,7 @@ use sqlx_core::{ decode::Decode, encode::{Encode, IsNull}, error::BoxDynError, - types::{Decimal as RustDecimal, Type}, + types::Type, }; use crate::{ @@ -16,7 +16,7 @@ use crate::{ /// Scale limit set by `rust_decimal` crate. const RUST_DECIMAL_MAX_SCALE: u32 = 28; -impl Type for RustDecimal { +impl Type for rust_decimal::Decimal { fn type_info() -> ExaTypeInfo { // This is not a valid Exasol datatype defintion, // but defining it like this means that we can accommodate @@ -31,7 +31,7 @@ impl Type for RustDecimal { } } -impl Encode<'_, Exasol> for RustDecimal { +impl Encode<'_, Exasol> for rust_decimal::Decimal { fn encode_by_ref(&self, buf: &mut ExaBuffer) -> Result { buf.append(format_args!("{self}"))?; Ok(IsNull::No) @@ -60,7 +60,7 @@ impl Encode<'_, Exasol> for RustDecimal { } } -impl Decode<'_, Exasol> for RustDecimal { +impl Decode<'_, Exasol> for rust_decimal::Decimal { fn decode(value: ExaValueRef<'_>) -> Result { ::deserialize(value.value).map_err(From::from) } diff --git a/sqlx-exasol-impl/src/types/uuid.rs b/sqlx-exasol-impl/src/types/uuid.rs index 8f87d5dd..fe6249c5 100644 --- a/sqlx-exasol-impl/src/types/uuid.rs +++ b/sqlx-exasol-impl/src/types/uuid.rs @@ -3,8 +3,9 @@ use sqlx_core::{ decode::Decode, encode::{Encode, IsNull}, error::BoxDynError, - types::{Type, Uuid}, + types::Type, }; +use uuid::Uuid; use crate::{ arguments::ExaBuffer, diff --git a/tests/etl.rs b/tests/etl.rs index 481438b4..344188d6 100644 --- a/tests/etl.rs +++ b/tests/etl.rs @@ -12,12 +12,10 @@ use futures_util::{ }; use rustls::crypto::{aws_lc_rs, CryptoProvider}; use sqlx::{Connection, Executor}; -use sqlx_core::{ - error::BoxDynError, - pool::{PoolConnection, PoolOptions}, -}; use sqlx_exasol::{ + error::BoxDynError, etl::{ExaExport, ExaImport, ExportBuilder, ExportSource, ImportBuilder}, + pool::{PoolConnection, PoolOptions}, ExaConnectOptions, Exasol, }; From 1acfd71137aef85787a99e8ca23b8a178451bb44 Mon Sep 17 00:00:00 2001 From: Bogdan Mircea Date: Fri, 6 Jun 2025 20:58:17 +0300 Subject: [PATCH 005/102] various fixes --- .zed/settings.json | 4 +-- Cargo.toml | 3 +- sqlx-exasol-cli/Cargo.toml | 3 +- sqlx-exasol-cli/src/main.rs | 4 +-- sqlx-exasol-impl/src/any.rs | 4 +-- sqlx-exasol-impl/src/connection/executor.rs | 34 +++++++++++++++++++++ src/lib.rs | 7 ++++- 7 files changed, 48 insertions(+), 11 deletions(-) diff --git a/.zed/settings.json b/.zed/settings.json index d8f19269..fb4640a1 100644 --- a/.zed/settings.json +++ b/.zed/settings.json @@ -21,9 +21,9 @@ "uuid", // Driver "etl", - "compression", + "compression" // TLS - "native-tls" + // "native-tls" ] } } diff --git a/Cargo.toml b/Cargo.toml index 5ffb9d6a..45693345 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -73,6 +73,7 @@ etl = ["sqlx-exasol-impl/etl"] [workspace.dependencies] # Internal +sqlx-exasol = { path = "." } sqlx-exasol-macros = { path = "sqlx-exasol-macros" } sqlx-exasol-impl = { path = "sqlx-exasol-impl" } @@ -99,7 +100,7 @@ futures-channel = { version = "0.3", default-features = false, features = [ "sink", ] } futures-core = { version = "0.3", default-features = false } -hex = { version = "0.4", default-features = false } +hex = { version = "0.4", default-features = false, features = ["std"] } http-body-util = { version = "0.1", default-features = false } hyper = { version = "1.4", default-features = false, features = [ "server", diff --git a/sqlx-exasol-cli/Cargo.toml b/sqlx-exasol-cli/Cargo.toml index 75efc46e..d6b0c5ff 100644 --- a/sqlx-exasol-cli/Cargo.toml +++ b/sqlx-exasol-cli/Cargo.toml @@ -10,8 +10,7 @@ categories.workspace = true authors.workspace = true [dependencies] -sqlx = { workspace = true } -sqlx-exasol-impl = { workspace = true } +sqlx-exasol = { workspace = true, features = ["any", "migrate", "tls-rustls-aws-lc-rs"] } sqlx-cli = { workspace = true } clap = { workspace = true } console = { workspace = true } diff --git a/sqlx-exasol-cli/src/main.rs b/sqlx-exasol-cli/src/main.rs index d3586980..86911bd0 100644 --- a/sqlx-exasol-cli/src/main.rs +++ b/sqlx-exasol-cli/src/main.rs @@ -1,12 +1,12 @@ use clap::Parser; use console::style; use sqlx_cli::Opt; -use sqlx_exasol_impl::any::DRIVER; +use sqlx_exasol::any::DRIVER; #[tokio::main] async fn main() { sqlx_cli::maybe_apply_dotenv(); - sqlx::any::install_drivers(&[DRIVER]).expect("driver installation failed"); + sqlx_exasol::any::install_drivers(&[DRIVER]).expect("driver installation failed"); let opt = Opt::parse(); if let Err(error) = sqlx_cli::run(opt).await { diff --git a/sqlx-exasol-impl/src/any.rs b/sqlx-exasol-impl/src/any.rs index 0ab41996..be0dfa0c 100644 --- a/sqlx-exasol-impl/src/any.rs +++ b/sqlx-exasol-impl/src/any.rs @@ -22,8 +22,6 @@ use sqlx_core::{ Either, }; -#[cfg(feature = "migrate")] -use crate::SqlxResult; use crate::{ connection::{ stream::ResultStream, @@ -31,7 +29,7 @@ use crate::{ }, type_info::ExaDataType, ExaColumn, ExaConnectOptions, ExaConnection, ExaQueryResult, ExaRow, ExaTransactionManager, - ExaTypeInfo, ExaValueRef, Exasol, SqlxError, + ExaTypeInfo, ExaValueRef, Exasol, SqlxError, SqlxResult, }; sqlx_core::declare_driver_with_optional_migrate!(DRIVER = Exasol); diff --git a/sqlx-exasol-impl/src/connection/executor.rs b/sqlx-exasol-impl/src/connection/executor.rs index 6ef2626c..7ce582fd 100644 --- a/sqlx-exasol-impl/src/connection/executor.rs +++ b/sqlx-exasol-impl/src/connection/executor.rs @@ -81,6 +81,40 @@ impl<'c> Executor<'c> for &'c mut ExaConnection { } } + fn fetch_all<'e, 'q: 'e, E>(self, query: E) -> BoxFuture<'e, SqlxResult>> + where + 'c: 'e, + E: 'q + Execute<'q, Self::Database>, + { + match self.fetch_impl(query) { + Ok(stream) => stream + .try_filter_map(|v| std::future::ready(Ok(v.right()))) + .try_collect() + .boxed(), + Err(e) => std::future::ready(Err(e)).boxed(), + } + } + + fn fetch_one<'e, 'q: 'e, E>(self, query: E) -> BoxFuture<'e, SqlxResult> + where + 'c: 'e, + E: 'q + Execute<'q, Self::Database>, + { + let stream = match self.fetch_impl(query) { + Ok(stream) => stream, + Err(e) => return std::future::ready(Err(e)).boxed(), + }; + + Box::pin(async move { + stream + .try_filter_map(|v| std::future::ready(Ok(v.right()))) + .try_next() + .await + .transpose() + .unwrap_or(Err(SqlxError::RowNotFound)) + }) + } + fn fetch_optional<'e, 'q, E>(self, query: E) -> BoxFuture<'e, SqlxResult>> where 'q: 'e, diff --git a/src/lib.rs b/src/lib.rs index 37f97166..5dcb762b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -217,9 +217,13 @@ //! nullable or not, so the driver cannot implicitly decide whether a `NULL` value can go into a //! certain database column or not until it actually tries. -#[allow(ambiguous_glob_reexports)] pub use sqlx::*; pub use sqlx_exasol_impl::*; + +pub mod any { + pub use sqlx::any::*; + pub use sqlx_exasol_impl::any::DRIVER; +} #[cfg(feature = "macros")] pub use sqlx_exasol_macros; #[cfg(feature = "macros")] @@ -228,3 +232,4 @@ mod macros; // TODO: // - Optimize ExaIter? Maybe with peek? // - Get features in sync with `sqlx` and fix README and lib.rs +// - Properly organize modules From 6068aa87f73bfc3565276a689517fecc919c6ecd Mon Sep 17 00:00:00 2001 From: Bogdan Mircea Date: Fri, 6 Jun 2025 21:06:08 +0300 Subject: [PATCH 006/102] cosmetic changes --- sqlx-exasol-impl/Cargo.toml | 4 ---- sqlx-exasol-impl/src/lib.rs | 7 +++++++ 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/sqlx-exasol-impl/Cargo.toml b/sqlx-exasol-impl/Cargo.toml index b55a81e6..77c5436e 100644 --- a/sqlx-exasol-impl/Cargo.toml +++ b/sqlx-exasol-impl/Cargo.toml @@ -63,11 +63,7 @@ sqlx-macros-core = { workspace = true, optional = true } uuid = { workspace = true, optional = true } [dev-dependencies] -anyhow = { workspace = true } -paste = { workspace = true } -rustls = { workspace = true } sqlx = { workspace = true } -tokio = { workspace = true } [lints] workspace = true diff --git a/sqlx-exasol-impl/src/lib.rs b/sqlx-exasol-impl/src/lib.rs index 51de29be..b356c145 100644 --- a/sqlx-exasol-impl/src/lib.rs +++ b/sqlx-exasol-impl/src/lib.rs @@ -1,6 +1,13 @@ #![cfg_attr(not(test), warn(unused_crate_dependencies))] //! **EXASOL** database driver. +#[cfg(feature = "native-tls")] +use native_tls as _; +#[cfg(feature = "tls")] +use rcgen as _; +#[cfg(feature = "rustls")] +use rustls as _; + #[cfg(feature = "any")] pub mod any; mod arguments; From a860116af4c78eb347adbe96769e14f9585eb68a Mon Sep 17 00:00:00 2001 From: Bogdan Mircea Date: Fri, 6 Jun 2025 21:25:39 +0300 Subject: [PATCH 007/102] fix MigrateDatabase::database_exists --- sqlx-exasol-impl/src/migrate.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/sqlx-exasol-impl/src/migrate.rs b/sqlx-exasol-impl/src/migrate.rs index 499ecc4c..f7a67ac1 100644 --- a/sqlx-exasol-impl/src/migrate.rs +++ b/sqlx-exasol-impl/src/migrate.rs @@ -59,8 +59,9 @@ impl MigrateDatabase for Exasol { let query = "SELECT true FROM exa_schemas WHERE schema_name = ?"; let exists: bool = query_scalar(query) .bind(database) - .fetch_one(&mut conn) - .await?; + .fetch_optional(&mut conn) + .await? + .unwrap_or_default(); Ok(exists) }) From abc96c84b2ac9652ef71162c2fdf6f6de22b172a Mon Sep 17 00:00:00 2001 From: Bogdan Mircea Date: Fri, 6 Jun 2025 21:36:43 +0300 Subject: [PATCH 008/102] Use git SQLx dependencies --- Cargo.toml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 45693345..134bbeca 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -119,12 +119,12 @@ serde = { version = "1", default-features = false, features = ["derive", "rc"] } serde_json = { version = "1", default-features = false, features = [ "raw_value", ] } -sqlx = { path = "../sqlx", default-features = false } -sqlx-cli = { path = "../sqlx/sqlx-cli", default-features = false } -sqlx-core = { path = "../sqlx/sqlx-core", default-features = false, features = [ +sqlx = { git = "https://github.com/bobozaur/sqlx", branch = "external-drivers-macros", default-features = false } +sqlx-cli = { git = "https://github.com/bobozaur/sqlx", branch = "external-drivers-macros", default-features = false } +sqlx-core = { git = "https://github.com/bobozaur/sqlx", branch = "external-drivers-macros", default-features = false, features = [ "offline", ] } -sqlx-macros-core = { path = "../sqlx/sqlx-macros-core", default-features = false } +sqlx-macros-core = { git = "https://github.com/bobozaur/sqlx", branch = "external-drivers-macros", default-features = false } syn = { version = "2", default-features = false, features = [ "parsing", "proc-macro", From fbd5944e1d4d32b5bda1bf765c59e0379cb27da8 Mon Sep 17 00:00:00 2001 From: Bogdan Mircea Date: Fri, 6 Jun 2025 21:37:08 +0300 Subject: [PATCH 009/102] Fix MigrateDatabase::drop_database --- sqlx-exasol-impl/src/migrate.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sqlx-exasol-impl/src/migrate.rs b/sqlx-exasol-impl/src/migrate.rs index f7a67ac1..b0a1a951 100644 --- a/sqlx-exasol-impl/src/migrate.rs +++ b/sqlx-exasol-impl/src/migrate.rs @@ -72,7 +72,7 @@ impl MigrateDatabase for Exasol { let (options, database) = parse_for_maintenance(url)?; let mut conn = options.connect().await?; - let query = format!("DROP SCHEMA IF EXISTS `{database}`"); + let query = format!(r#"DROP SCHEMA IF EXISTS "{database}""#); let _ = conn.execute(&*query).await?; Ok(()) From b088b5a9765e049dd67d49f3110bdf6ec25a18cd Mon Sep 17 00:00:00 2001 From: Bogdan Mircea Date: Fri, 6 Jun 2025 21:37:21 +0300 Subject: [PATCH 010/102] Remove DATABASE_URL from Zed settings --- .zed/settings.json | 3 --- 1 file changed, 3 deletions(-) diff --git a/.zed/settings.json b/.zed/settings.json index fb4640a1..8f7ca468 100644 --- a/.zed/settings.json +++ b/.zed/settings.json @@ -7,9 +7,6 @@ "rust-analyzer": { "initialization_options": { "cargo": { - "extraEnv": { - "DATABASE_URL": "exa://sys:exasol@127.0.0.1:8563" - }, "features": [ // SQLx "migrate", From 6fee18cff81437011768066cec172cc7b75994b6 Mon Sep 17 00:00:00 2001 From: Bogdan Mircea Date: Fri, 6 Jun 2025 21:43:12 +0300 Subject: [PATCH 011/102] Bump MSRV --- .github/workflows/ci.yaml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 990bd938..639bf6b9 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -48,7 +48,7 @@ jobs: - uses: actions/checkout@v4 - uses: Swatinem/rust-cache@v2 - - uses: dtolnay/rust-toolchain@1.85.0 + - uses: dtolnay/rust-toolchain@1.86.0 with: components: clippy @@ -65,7 +65,7 @@ jobs: - uses: actions/checkout@v4 - uses: Swatinem/rust-cache@v2 - - uses: dtolnay/rust-toolchain@1.85.0 + - uses: dtolnay/rust-toolchain@1.86.0 with: components: clippy @@ -84,7 +84,7 @@ jobs: - uses: actions/checkout@v4 - uses: Swatinem/rust-cache@v2 - - uses: dtolnay/rust-toolchain@1.85.0 + - uses: dtolnay/rust-toolchain@1.86.0 with: components: clippy @@ -112,7 +112,7 @@ jobs: exasol-version: ${{ env.EXASOL_VERSION }} num-nodes: ${{ env.NUM_NODES }} - - uses: dtolnay/rust-toolchain@1.85.0 + - uses: dtolnay/rust-toolchain@1.86.0 - uses: Swatinem/rust-cache@v2 - name: Connection tests @@ -146,7 +146,7 @@ jobs: exasol-version: ${{ env.EXASOL_VERSION }} num-nodes: ${{ env.NUM_NODES }} - - uses: dtolnay/rust-toolchain@1.85.0 + - uses: dtolnay/rust-toolchain@1.86.0 - uses: Swatinem/rust-cache@v2 - name: TLS connection tests @@ -180,7 +180,7 @@ jobs: exasol-version: ${{ env.EXASOL_VERSION }} num-nodes: ${{ env.NUM_NODES }} - - uses: dtolnay/rust-toolchain@1.85.0 + - uses: dtolnay/rust-toolchain@1.86.0 - uses: Swatinem/rust-cache@v2 - name: ETL tests From eb7d748bf4eedc578e21fbf82951269917d539a6 Mon Sep 17 00:00:00 2001 From: Bogdan Mircea Date: Fri, 6 Jun 2025 21:45:53 +0300 Subject: [PATCH 012/102] Fix CI clippy --- .github/workflows/ci.yaml | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 639bf6b9..ee8b4429 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -36,14 +36,13 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - etl: + tls: [ - "--features etl_native_tls", - "--features etl_rustls", - "--features etl", + "--features native-tls", + "--features rustls", "", ] - other: ["--features compression,migrate,rust_decimal,uuid,chrono", ""] + other: ["--features compression,migrate,rust_decimal,uuid,chrono,etl", ""] steps: - uses: actions/checkout@v4 - uses: Swatinem/rust-cache@v2 @@ -53,7 +52,7 @@ jobs: components: clippy - name: Run clippy - run: cargo clippy --tests ${{ matrix.etl }} ${{ matrix.other }} + run: cargo clippy --tests ${{ matrix.tls }} ${{ matrix.other }} env: RUSTFLAGS: -D warnings From 7d1cf2118b5200e99e3ec1132d3e5fef317710c3 Mon Sep 17 00:00:00 2001 From: Bogdan Mircea Date: Fri, 6 Jun 2025 21:50:18 +0300 Subject: [PATCH 013/102] More CI fixes --- .github/workflows/ci.yaml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index ee8b4429..1e1ba260 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -38,8 +38,8 @@ jobs: matrix: tls: [ - "--features native-tls", - "--features rustls", + "--features tls-native-tls", + "--features tls-rustls", "", ] other: ["--features compression,migrate,rust_decimal,uuid,chrono,etl", ""] @@ -150,13 +150,13 @@ jobs: - name: TLS connection tests timeout-minutes: ${{ fromJSON(env.TESTS_TIMEOUT) }} - run: cargo test --features migrate,rust_decimal,uuid,chrono -- --nocapture + run: cargo test --features migrate,rust_decimal,uuid,chrono,tls-native-tls -- --nocapture env: DATABASE_URL: ${{ steps.exa-cluster.outputs.tls-url }} - name: TLS connection tests with compression timeout-minutes: ${{ fromJSON(env.TESTS_TIMEOUT) }} - run: cargo test --features migrate,compression -- --ignored --nocapture + run: cargo test --features migrate,compression,tls-native-tls -- --ignored --nocapture env: DATABASE_URL: ${{ steps.exa-cluster.outputs.tls-url }} @@ -194,18 +194,18 @@ jobs: DATABASE_URL: ${{ steps.exa-cluster.outputs.tls-url }} - name: Tests compilation failure if both ETL TLS features are enabled - run: cargo test --features etl_native_tls,etl_rustls || true + run: cargo test --features tls-native-tls,tls-rustls || true env: DATABASE_URL: ${{ steps.exa-cluster.outputs.tls-url }} - name: Native-TLS ETL tests timeout-minutes: ${{ fromJSON(env.TESTS_TIMEOUT) }} - run: cargo test --features migrate,compression,etl_native_tls -- --ignored --nocapture --test-threads `nproc` + run: cargo test --features migrate,compression,etl,tls-native-tls -- --ignored --nocapture --test-threads `nproc` env: DATABASE_URL: ${{ steps.exa-cluster.outputs.tls-url }} - name: Rustls ETL tests timeout-minutes: ${{ fromJSON(env.TESTS_TIMEOUT) }} - run: cargo test --features migrate,compression,etl_rustls -- --ignored --nocapture --test-threads `nproc` + run: cargo test --features migrate,compression,etl,tls-rustls-aws-lc-rs -- --ignored --nocapture --test-threads `nproc` env: DATABASE_URL: ${{ steps.exa-cluster.outputs.tls-url }} From 9410486c9ef05a604e567ee97b1cf22a0fd12882 Mon Sep 17 00:00:00 2001 From: Bogdan Mircea Date: Fri, 6 Jun 2025 21:55:42 +0300 Subject: [PATCH 014/102] Remove obsolete comments --- src/lib.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 5dcb762b..1f15781d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -228,8 +228,3 @@ pub mod any { pub use sqlx_exasol_macros; #[cfg(feature = "macros")] mod macros; - -// TODO: -// - Optimize ExaIter? Maybe with peek? -// - Get features in sync with `sqlx` and fix README and lib.rs -// - Properly organize modules From 375ff99c9e3d737ed64f4587729ee5c4c21120c5 Mon Sep 17 00:00:00 2001 From: Bogdan Mircea Date: Fri, 11 Jul 2025 13:22:02 +0300 Subject: [PATCH 015/102] Use upstream sqlx --- Cargo.toml | 10 +-- sqlx-exasol-impl/src/migrate.rs | 99 +++++++++++++++++---------- sqlx-exasol-impl/src/type_checking.rs | 38 ++++++---- sqlx-exasol-impl/src/types/chrono.rs | 13 ++-- sqlx-exasol-impl/src/types/str.rs | 16 ----- 5 files changed, 100 insertions(+), 76 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 134bbeca..9c7adc45 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ members = [".", "sqlx-exasol-cli", "sqlx-exasol-impl", "sqlx-exasol-macros"] [workspace.package] -version = "0.8.6" +version = "0.9.0" license = "MIT OR Apache-2.0" edition = "2024" rust-version = "1.86.0" @@ -119,12 +119,12 @@ serde = { version = "1", default-features = false, features = ["derive", "rc"] } serde_json = { version = "1", default-features = false, features = [ "raw_value", ] } -sqlx = { git = "https://github.com/bobozaur/sqlx", branch = "external-drivers-macros", default-features = false } -sqlx-cli = { git = "https://github.com/bobozaur/sqlx", branch = "external-drivers-macros", default-features = false } -sqlx-core = { git = "https://github.com/bobozaur/sqlx", branch = "external-drivers-macros", default-features = false, features = [ +sqlx = { git = "https://github.com/launchbadge/sqlx", rev = "a9fb5816264694880d57c6de7f8acb9509858d3f", default-features = false } +sqlx-cli = { git = "https://github.com/launchbadge/sqlx", rev = "a9fb5816264694880d57c6de7f8acb9509858d3f", default-features = false } +sqlx-core = { git = "https://github.com/launchbadge/sqlx", rev = "a9fb5816264694880d57c6de7f8acb9509858d3f", default-features = false, features = [ "offline", ] } -sqlx-macros-core = { git = "https://github.com/bobozaur/sqlx", branch = "external-drivers-macros", default-features = false } +sqlx-macros-core = { git = "https://github.com/launchbadge/sqlx", rev = "a9fb5816264694880d57c6de7f8acb9509858d3f", default-features = false } syn = { version = "2", default-features = false, features = [ "parsing", "proc-macro", diff --git a/sqlx-exasol-impl/src/migrate.rs b/sqlx-exasol-impl/src/migrate.rs index b0a1a951..b170d4de 100644 --- a/sqlx-exasol-impl/src/migrate.rs +++ b/sqlx-exasol-impl/src/migrate.rs @@ -81,51 +81,74 @@ impl MigrateDatabase for Exasol { } impl Migrate for ExaConnection { - fn ensure_migrations_table(&mut self) -> BoxFuture<'_, Result<(), MigrateError>> { + fn create_schema_if_not_exists<'e>( + &'e mut self, + schema_name: &'e str, + ) -> BoxFuture<'e, Result<(), MigrateError>> { + Box::pin(async move { + let query = format!(r#"CREATE SCHEMA IF NOT EXISTS "{schema_name}";"#); + self.execute(&*query).await?; + Ok(()) + }) + } + + fn ensure_migrations_table<'e>( + &'e mut self, + table_name: &'e str, + ) -> BoxFuture<'e, Result<(), MigrateError>> { Box::pin(async move { - let query = r#" - CREATE TABLE IF NOT EXISTS "_sqlx_migrations" ( + let query = format!( + r#" + CREATE TABLE IF NOT EXISTS "{table_name}" ( version DECIMAL(20, 0), description CLOB NOT NULL, installed_on TIMESTAMP DEFAULT CURRENT_TIMESTAMP, success BOOLEAN NOT NULL, checksum CLOB NOT NULL, execution_time DECIMAL(20, 0) NOT NULL - );"#; + );"# + ); - self.execute(query).await?; + self.execute(&*query).await?; Ok(()) }) } - fn dirty_version(&mut self) -> BoxFuture<'_, Result, MigrateError>> { + fn dirty_version<'e>( + &'e mut self, + table_name: &'e str, + ) -> BoxFuture<'e, Result, MigrateError>> { Box::pin(async move { - let query = r#" + let query = format!( + r#" SELECT version - FROM "_sqlx_migrations" + FROM "{table_name}" WHERE success = false ORDER BY version - LIMIT 1 - "#; + LIMIT 1; + "# + ); - let row: Option<(i64,)> = query_as(query).fetch_optional(self).await?; + let row: Option<(i64,)> = query_as(&query).fetch_optional(self).await?; Ok(row.map(|r| r.0)) }) } - fn list_applied_migrations( - &mut self, - ) -> BoxFuture<'_, Result, MigrateError>> { + fn list_applied_migrations<'e>( + &'e mut self, + table_name: &'e str, + ) -> BoxFuture<'e, Result, MigrateError>> { Box::pin(async move { - let query = r#" + let query = format!( + r#" SELECT version, checksum - FROM "_sqlx_migrations" + FROM "{table_name}" ORDER BY version - "#; - - let rows: Vec<(i64, String)> = query_as(query).fetch_all(self).await?; + "# + ); + let rows: Vec<(i64, String)> = query_as(&query).fetch_all(self).await?; let mut migrations = Vec::with_capacity(rows.len()); for (version, checksum) in rows { @@ -156,10 +179,11 @@ impl Migrate for ExaConnection { }) } - fn apply<'e: 'm, 'm>( + fn apply<'e>( &'e mut self, - migration: &'m Migration, - ) -> BoxFuture<'m, Result> { + table_name: &'e str, + migration: &'e Migration, + ) -> BoxFuture<'e, Result> { Box::pin(async move { let mut tx = self.begin().await?; let start = Instant::now(); @@ -170,12 +194,14 @@ impl Migrate for ExaConnection { let checksum = hex::encode(&*migration.checksum); - let query_str = r#" - INSERT INTO "_sqlx_migrations" ( version, description, success, checksum, execution_time ) + let query_str = format!( + r#" + INSERT INTO "{table_name}" ( version, description, success, checksum, execution_time ) VALUES ( ?, ?, TRUE, ?, -1 ); - "#; + "# + ); - let _ = query(query_str) + let _ = query(&query_str) .bind(migration.version) .bind(&*migration.description) .bind(checksum) @@ -186,13 +212,15 @@ impl Migrate for ExaConnection { let elapsed = start.elapsed(); - let query_str = r#" - UPDATE "_sqlx_migrations" + let query_str = format!( + r#" + UPDATE "{table_name}" SET execution_time = ? WHERE version = ? - "#; + "# + ); - let _ = query(query_str) + let _ = query(&query_str) .bind(elapsed.as_nanos()) .bind(migration.version) .execute(self) @@ -202,10 +230,11 @@ impl Migrate for ExaConnection { }) } - fn revert<'e: 'm, 'm>( + fn revert<'e>( &'e mut self, - migration: &'m Migration, - ) -> BoxFuture<'m, Result> { + table_name: &'e str, + migration: &'e Migration, + ) -> BoxFuture<'e, Result> { Box::pin(async move { let mut tx = self.begin().await?; let start = Instant::now(); @@ -214,8 +243,8 @@ impl Migrate for ExaConnection { .future(&mut tx.ws) .await?; - let query_str = r#" DELETE FROM "_sqlx_migrations" WHERE version = ? "#; - let _ = query(query_str) + let query_str = format!(r#" DELETE FROM "{table_name}" WHERE version = ? "#); + let _ = query(&query_str) .bind(migration.version) .execute(&mut *tx) .await?; diff --git a/sqlx-exasol-impl/src/type_checking.rs b/sqlx-exasol-impl/src/type_checking.rs index 95d1ca51..6b1e0938 100644 --- a/sqlx-exasol-impl/src/type_checking.rs +++ b/sqlx-exasol-impl/src/type_checking.rs @@ -40,21 +40,35 @@ impl_type_checking!( String, // External types - #[cfg(feature = "chrono")] - sqlx::types::chrono::NaiveDate, - - #[cfg(feature = "chrono")] - sqlx::types::chrono::NaiveDateTime, - - #[cfg(feature = "chrono")] - sqlx::types::chrono::DateTime, - - #[cfg(feature = "rust_decimal")] - sqlx::types::Decimal, - #[cfg(feature = "uuid")] sqlx::types::Uuid, }, ParamChecking::Weak, feature-types: _info => None, + // The expansion of the macro automatically applies the correct feature name + // and checks `[macros.preferred-crates]` + datetime-types: { + chrono: { + sqlx::types::chrono::NaiveDate, + + sqlx::types::chrono::NaiveDateTime, + + sqlx::types::chrono::DateTime, + }, + time: { + sqlx::types::time::Date, + + sqlx::types::time::PrimitiveDateTime, + + sqlx::types::time::OffsetDateTime, + }, + }, + numeric-types: { + bigdecimal: { + sqlx::types::BigDecimal, + }, + rust_decimal: { + sqlx::types::Decimal, + }, + }, ); diff --git a/sqlx-exasol-impl/src/types/chrono.rs b/sqlx-exasol-impl/src/types/chrono.rs index f92ea4fe..d3483c04 100644 --- a/sqlx-exasol-impl/src/types/chrono.rs +++ b/sqlx-exasol-impl/src/types/chrono.rs @@ -1,7 +1,4 @@ -use std::{ - borrow::Cow, - ops::{Add, Sub}, -}; +use std::ops::{Add, Sub}; use chrono::{DateTime, Duration, Local, NaiveDate, NaiveDateTime, TimeZone, Utc}; use serde::Deserialize; @@ -102,8 +99,8 @@ impl Encode<'_, Exasol> for NaiveDateTime { impl Decode<'_, Exasol> for NaiveDateTime { fn decode(value: ExaValueRef<'_>) -> Result { - let input = Cow::::deserialize(value.value).map_err(Box::new)?; - Self::parse_from_str(&input, TIMESTAMP_FMT) + let input = <&str>::deserialize(value.value).map_err(Box::new)?; + Self::parse_from_str(input, TIMESTAMP_FMT) .map_err(Box::new) .map_err(From::from) } @@ -165,7 +162,7 @@ impl Encode<'_, Exasol> for Duration { impl<'r> Decode<'r, Exasol> for Duration { fn decode(value: ExaValueRef<'r>) -> Result { - let input = Cow::::deserialize(value.value).map_err(Box::new)?; + let input = <&str>::deserialize(value.value).map_err(Box::new)?; let input_err_fn = || format!("could not parse {input} as INTERVAL DAY TO SECOND"); let (days, rest) = input.split_once(' ').ok_or_else(input_err_fn)?; @@ -274,7 +271,7 @@ impl Encode<'_, Exasol> for Months { impl<'r> Decode<'r, Exasol> for Months { fn decode(value: ExaValueRef<'r>) -> Result { - let input = Cow::::deserialize(value.value).map_err(Box::new)?; + let input = <&str>::deserialize(value.value).map_err(Box::new)?; let input_err_fn = || format!("could not parse {input} as INTERVAL YEAR TO MONTH"); let (years, months) = input.rsplit_once('-').ok_or_else(input_err_fn)?; diff --git a/sqlx-exasol-impl/src/types/str.rs b/sqlx-exasol-impl/src/types/str.rs index 7e7c2fea..921217e1 100644 --- a/sqlx-exasol-impl/src/types/str.rs +++ b/sqlx-exasol-impl/src/types/str.rs @@ -84,16 +84,6 @@ impl Decode<'_, Exasol> for String { } } -impl Type for Cow<'_, str> { - fn type_info() -> ExaTypeInfo { - <&str as Type>::type_info() - } - - fn compatible(ty: &ExaTypeInfo) -> bool { - <&str as Type>::compatible(ty) - } -} - impl Encode<'_, Exasol> for Cow<'_, str> { fn encode_by_ref(&self, buf: &mut ExaBuffer) -> Result { match self { @@ -110,9 +100,3 @@ impl Encode<'_, Exasol> for Cow<'_, str> { <&str as Encode>::size_hint(&&**self) } } - -impl<'r> Decode<'r, Exasol> for Cow<'r, str> { - fn decode(value: ExaValueRef<'r>) -> Result { - Cow::deserialize(value.value).map_err(From::from) - } -} From c70b28a218c5c73a070309c7c708fdeac672c554 Mon Sep 17 00:00:00 2001 From: Bogdan Mircea Date: Fri, 11 Jul 2025 17:00:09 +0300 Subject: [PATCH 016/102] Fix comment --- sqlx-exasol-impl/src/connection/websocket/request.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sqlx-exasol-impl/src/connection/websocket/request.rs b/sqlx-exasol-impl/src/connection/websocket/request.rs index d82685c4..3bafc39d 100644 --- a/sqlx-exasol-impl/src/connection/websocket/request.rs +++ b/sqlx-exasol-impl/src/connection/websocket/request.rs @@ -198,7 +198,7 @@ impl Serialize for WithAttributes<'_, Fetch> { } /// Request to execute a single SQL statement. -// This is internall used in the IMPORT/EXPORT jobs as well, since they rely on query execution too. +// This is internally used in the IMPORT/EXPORT jobs as well, since they rely on query execution too. // However, in these scenarios the query is an owned string, hence the usage of [`Cow`] here to // support that. #[derive(Clone, Debug)] From a8efd4f495b929f87f8faa0f35324c74e3f5f8de Mon Sep 17 00:00:00 2001 From: Bogdan Mircea Date: Fri, 11 Jul 2025 18:56:46 +0300 Subject: [PATCH 017/102] tweak feature flags --- .zed/settings.json | 6 ++++-- Cargo.toml | 10 ++-------- sqlx-exasol-cli/Cargo.toml | 14 +++++++++++++- sqlx-exasol-cli/src/main.rs | 1 + sqlx-exasol-impl/Cargo.toml | 6 +++--- sqlx-exasol-impl/src/lib.rs | 5 +---- sqlx-exasol-impl/src/types/mod.rs | 8 +++++--- src/lib.rs | 10 ++++++++++ tests/bool.rs | 2 +- tests/chrono.rs | 3 +-- tests/etl.rs | 9 --------- tests/macros.rs | 5 +---- tests/rust_decimal.rs | 2 +- tests/uuid.rs | 2 +- 14 files changed, 44 insertions(+), 39 deletions(-) diff --git a/.zed/settings.json b/.zed/settings.json index 8f7ca468..884305ae 100644 --- a/.zed/settings.json +++ b/.zed/settings.json @@ -18,9 +18,11 @@ "uuid", // Driver "etl", - "compression" + "compression", + // Runtime + "runtime-tokio", // TLS - // "native-tls" + "tls-native-tls" ] } } diff --git a/Cargo.toml b/Cargo.toml index 9c7adc45..f038cddc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ members = [".", "sqlx-exasol-cli", "sqlx-exasol-impl", "sqlx-exasol-macros"] [workspace.package] -version = "0.9.0" +version = "0.9.0-alpha.1" license = "MIT OR Apache-2.0" edition = "2024" rust-version = "1.86.0" @@ -129,7 +129,7 @@ syn = { version = "2", default-features = false, features = [ "parsing", "proc-macro", ] } -thiserror = "1" +thiserror = { version = "1", default-features = false } tokio = { version = "1", default-features = false, features = [ "rt-multi-thread", ] } @@ -150,16 +150,10 @@ sqlx = { workspace = true } [dev-dependencies] anyhow = { workspace = true } -chrono = { workspace = true } dotenvy = { workspace = true } futures-util = { workspace = true } paste = { workspace = true } -rustls = { workspace = true, features = ["aws_lc_rs"] } -rust_decimal = { workspace = true } -sqlx = { workspace = true } -tokio = { workspace = true } url = { workspace = true } -uuid = { workspace = true } [workspace.lints.clippy] all = { level = "warn", priority = -1 } diff --git a/sqlx-exasol-cli/Cargo.toml b/sqlx-exasol-cli/Cargo.toml index d6b0c5ff..5c477dea 100644 --- a/sqlx-exasol-cli/Cargo.toml +++ b/sqlx-exasol-cli/Cargo.toml @@ -9,8 +9,20 @@ keywords.workspace = true categories.workspace = true authors.workspace = true +[features] +default = ["native-tls", "completions", "sqlx-toml"] + +rustls = ["sqlx-cli/rustls"] +native-tls = ["sqlx-cli/native-tls"] + +openssl-vendored = ["sqlx-cli/openssl-vendored"] + +completions = ["sqlx-cli/openssl-vendored"] + +sqlx-toml = ["sqlx-cli/sqlx-toml"] + [dependencies] -sqlx-exasol = { workspace = true, features = ["any", "migrate", "tls-rustls-aws-lc-rs"] } +sqlx-exasol = { workspace = true } sqlx-cli = { workspace = true } clap = { workspace = true } console = { workspace = true } diff --git a/sqlx-exasol-cli/src/main.rs b/sqlx-exasol-cli/src/main.rs index 86911bd0..562ed2a9 100644 --- a/sqlx-exasol-cli/src/main.rs +++ b/sqlx-exasol-cli/src/main.rs @@ -5,6 +5,7 @@ use sqlx_exasol::any::DRIVER; #[tokio::main] async fn main() { + sqlx_cli::maybe_apply_dotenv(); sqlx_exasol::any::install_drivers(&[DRIVER]).expect("driver installation failed"); let opt = Opt::parse(); diff --git a/sqlx-exasol-impl/Cargo.toml b/sqlx-exasol-impl/Cargo.toml index 77c5436e..d7fc5d09 100644 --- a/sqlx-exasol-impl/Cargo.toml +++ b/sqlx-exasol-impl/Cargo.toml @@ -22,9 +22,9 @@ chrono = ["sqlx-core/chrono", "dep:chrono"] rust_decimal = ["sqlx-core/rust_decimal", "dep:rust_decimal"] # TLS features -tls = [] -rustls = ["dep:rustls", "dep:rcgen", "tls"] -native-tls = ["dep:native-tls", "dep:rcgen", "tls"] +tls = ["dep:rcgen"] +rustls = ["dep:rustls", "tls"] +native-tls = ["dep:native-tls", "tls"] # Driver specific features compression = ["dep:async-compression"] diff --git a/sqlx-exasol-impl/src/lib.rs b/sqlx-exasol-impl/src/lib.rs index b356c145..2021a869 100644 --- a/sqlx-exasol-impl/src/lib.rs +++ b/sqlx-exasol-impl/src/lib.rs @@ -28,7 +28,7 @@ mod transaction; #[cfg(feature = "macros")] mod type_checking; mod type_info; -mod types; +pub mod types; mod value; pub use arguments::ExaArguments; @@ -51,9 +51,6 @@ pub use transaction::ExaTransactionManager; #[cfg(feature = "macros")] pub use type_checking::QUERY_DRIVER; pub use type_info::ExaTypeInfo; -pub use types::ExaIter; -#[cfg(feature = "chrono")] -pub use types::Months; pub use value::{ExaValue, ExaValueRef}; /// An alias for [`Pool`][sqlx_core::pool::Pool], specialized for Exasol. diff --git a/sqlx-exasol-impl/src/types/mod.rs b/sqlx-exasol-impl/src/types/mod.rs index f05f685e..18f44f23 100644 --- a/sqlx-exasol-impl/src/types/mod.rs +++ b/sqlx-exasol-impl/src/types/mod.rs @@ -13,7 +13,9 @@ mod uint; #[cfg(feature = "uuid")] mod uuid; -pub use iter::ExaIter; - #[cfg(feature = "chrono")] -pub use self::chrono::Months; +pub use chrono::Months; +#[cfg(feature = "chrono")] +pub use ::chrono::Duration; + +pub use iter::ExaIter; diff --git a/src/lib.rs b/src/lib.rs index 1f15781d..b64cc337 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -220,6 +220,16 @@ pub use sqlx::*; pub use sqlx_exasol_impl::*; +pub mod types { + pub use sqlx::types::*; + pub use sqlx_exasol_impl::types::ExaIter; + + pub mod chrono { + pub use sqlx::types::chrono::*; + pub use sqlx_exasol_impl::types::{Months, Duration}; + } +} + pub mod any { pub use sqlx::any::*; pub use sqlx_exasol_impl::any::DRIVER; diff --git a/tests/bool.rs b/tests/bool.rs index fd290c6c..b5e062ab 100644 --- a/tests/bool.rs +++ b/tests/bool.rs @@ -4,7 +4,7 @@ mod macros; use std::collections::HashSet; -use sqlx_exasol::ExaIter; +use sqlx_exasol::types::ExaIter; test_type_valid!(bool::"BOOLEAN"::(false, true)); test_type_valid!(bool_option>::"BOOLEAN"::("NULL" => None::, "true" => Some(true))); diff --git a/tests/chrono.rs b/tests/chrono.rs index c2d6f56c..ec93d272 100644 --- a/tests/chrono.rs +++ b/tests/chrono.rs @@ -3,8 +3,7 @@ mod macros; -use ::chrono::{DateTime, Duration, Local, NaiveDate, NaiveDateTime, Utc}; -use sqlx_exasol::Months; +use sqlx_exasol::types::chrono::{DateTime, Duration, Local, NaiveDate, NaiveDateTime, Utc, Months}; const TIMESTAMP_FMT: &str = "%Y-%m-%d %H:%M:%S%.6f"; const DATE_FMT: &str = "%Y-%m-%d"; diff --git a/tests/etl.rs b/tests/etl.rs index 344188d6..a99a6e01 100644 --- a/tests/etl.rs +++ b/tests/etl.rs @@ -10,7 +10,6 @@ use futures_util::{ future::{try_join, try_join3, try_join_all}, AsyncReadExt, AsyncWriteExt, TryFutureExt, }; -use rustls::crypto::{aws_lc_rs, CryptoProvider}; use sqlx::{Connection, Executor}; use sqlx_exasol::{ error::BoxDynError, @@ -132,8 +131,6 @@ test_etl!( #[ignore] #[sqlx::test] async fn test_etl_invalid_query(mut conn: PoolConnection) -> AnyResult<()> { - CryptoProvider::install_default(aws_lc_rs::default_provider()).ok(); - conn.execute("CREATE TABLE TEST_ETL ( col VARCHAR(200) );") .await?; @@ -159,8 +156,6 @@ async fn test_etl_invalid_query(mut conn: PoolConnection) -> AnyResult<( #[ignore] #[sqlx::test] async fn test_etl_reader_drop(mut conn: PoolConnection) -> AnyResult<()> { - CryptoProvider::install_default(aws_lc_rs::default_provider()).ok(); - conn.execute("CREATE TABLE TEST_ETL ( col VARCHAR(200) );") .await?; @@ -188,8 +183,6 @@ async fn test_etl_reader_drop(mut conn: PoolConnection) -> AnyResult<()> #[ignore] #[sqlx::test] async fn test_etl_transaction_import_rollback(mut conn: PoolConnection) -> AnyResult<()> { - CryptoProvider::install_default(aws_lc_rs::default_provider()).ok(); - conn.execute("CREATE TABLE TEST_ETL ( col VARCHAR(200) );") .await?; @@ -222,8 +215,6 @@ async fn test_etl_transaction_import_rollback(mut conn: PoolConnection) #[ignore] #[sqlx::test] async fn test_etl_transaction_import_commit(mut conn: PoolConnection) -> AnyResult<()> { - CryptoProvider::install_default(aws_lc_rs::default_provider()).ok(); - conn.execute("CREATE TABLE TEST_ETL ( col VARCHAR(200) );") .await?; diff --git a/tests/macros.rs b/tests/macros.rs index d63d17e6..5678e6c6 100644 --- a/tests/macros.rs +++ b/tests/macros.rs @@ -139,9 +139,6 @@ macro_rules! test_etl { #[ignore] #[sqlx::test] async fn [< test_etl_ $kind _ $name >](pool_opts: PoolOptions, exa_opts: ExaConnectOptions) -> AnyResult<()> { - CryptoProvider::install_default(aws_lc_rs::default_provider()) - .ok(); - let pool = pool_opts.min_connections(2).connect_with(exa_opts).await?; let mut conn1 = pool.acquire().await?; @@ -197,6 +194,6 @@ macro_rules! test_etl_multi_threaded { }; ($name:literal, $num_workers:expr, $table:literal, $export:expr, $import:expr, $(#[$attr:meta]),*) => { - $crate::test_etl!("multi_threaded", $name, $num_workers, $table, |(r,w)| tokio::spawn(pipe(r, w)).map_err(From::from).and_then(|r| async { r }), $export, $import, $(#[$attr]),*); + $crate::test_etl!("multi_threaded", $name, $num_workers, $table, |(r,w)| sqlx_exasol::__rt::spawn(pipe(r, w)), $export, $import, $(#[$attr]),*); } } diff --git a/tests/rust_decimal.rs b/tests/rust_decimal.rs index faedd60c..d309e6c0 100644 --- a/tests/rust_decimal.rs +++ b/tests/rust_decimal.rs @@ -3,7 +3,7 @@ mod macros; -use ::rust_decimal::Decimal; +use sqlx_exasol::types::Decimal; test_type_valid!(rust_decimal_i64::"DECIMAL(36, 16)"::(Decimal::new(i64::MIN, 16), Decimal::new(i64::MAX, 16), Decimal::new(i64::MAX, 10), Decimal::new(i64::MAX, 5), Decimal::new(i64::MAX, 0))); test_type_valid!(rust_decimal_i16::"DECIMAL(36, 16)"::(Decimal::new(i64::from(i16::MIN), 5), Decimal::new(i64::from(i16::MAX), 5), Decimal::new(i64::from(i16::MIN), 0), Decimal::new(i64::from(i16::MAX), 0))); diff --git a/tests/uuid.rs b/tests/uuid.rs index 507724bb..18f56541 100644 --- a/tests/uuid.rs +++ b/tests/uuid.rs @@ -3,7 +3,7 @@ mod macros; -use ::uuid::Uuid; +use sqlx_exasol::types::Uuid; test_type_valid!(uuid::"HASHTYPE(16 BYTE)"::(format!("'{}'", Uuid::from_u64_pair(12_345_789, 12_345_789)) => Uuid::from_u64_pair(12_345_789, 12_345_789))); test_type_valid!(uuid_str::"HASHTYPE(16 BYTE)"::("'a1a2a3a4b1b2c1c2d1d2d3d4d5d6d7d8'" => "a1a2a3a4b1b2c1c2d1d2d3d4d5d6d7d8", "'a1a2a3a4-b1b2-c1c2-d1d2-d3d4d5d6d7d8'" => "a1a2a3a4b1b2c1c2d1d2d3d4d5d6d7d8")); From e1e1fef48a9b128ad8448b5193312335d78ff7cb Mon Sep 17 00:00:00 2001 From: Bogdan Mircea Date: Sat, 12 Jul 2025 19:59:42 +0300 Subject: [PATCH 018/102] Compile time query fixes --- .github/workflows/ci.yaml | 68 +++++++++++++++---- ...4277c85ed788706d6f7c124eae06871feba87.json | 24 +++++++ Cargo.toml | 2 + sqlx-exasol-cli/Cargo.toml | 2 +- sqlx-exasol-impl/src/column.rs | 16 +++-- sqlx-exasol-impl/src/type_info.rs | 16 ++++- src/lib.rs | 1 + tests/compile_time.rs | 15 ++++ 8 files changed, 124 insertions(+), 20 deletions(-) create mode 100644 .sqlx/query-07a6c51ee278a56a95e947d1f334277c85ed788706d6f7c124eae06871feba87.json create mode 100644 tests/compile_time.rs diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 1e1ba260..7cc620ac 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -113,16 +113,22 @@ jobs: - uses: dtolnay/rust-toolchain@1.86.0 - uses: Swatinem/rust-cache@v2 - - - name: Connection tests + + - name: Unit connection tests timeout-minutes: ${{ fromJSON(env.TESTS_TIMEOUT) }} - run: cargo test --features migrate,rust_decimal,uuid,chrono -- --nocapture + run: cargo test -p sqlx-exasol-impl --features migrate -- --nocapture + env: + DATABASE_URL: ${{ steps.exa-cluster.outputs.no-tls-url }} + + - name: Unit connection tests with compression + timeout-minutes: ${{ fromJSON(env.TESTS_TIMEOUT) }} + run: cargo test -p sqlx-exasol-impl --features migrate,compression -- --ignored --nocapture env: DATABASE_URL: ${{ steps.exa-cluster.outputs.no-tls-url }} - - name: Connection tests with compression + - name: Integration connection tests timeout-minutes: ${{ fromJSON(env.TESTS_TIMEOUT) }} - run: cargo test --features migrate,compression -- --ignored --nocapture + run: cargo test --features migrate,rust_decimal,uuid,chrono -- --nocapture env: DATABASE_URL: ${{ steps.exa-cluster.outputs.no-tls-url }} @@ -148,15 +154,21 @@ jobs: - uses: dtolnay/rust-toolchain@1.86.0 - uses: Swatinem/rust-cache@v2 - - name: TLS connection tests + - name: Unit TLS connection tests + timeout-minutes: ${{ fromJSON(env.TESTS_TIMEOUT) }} + run: cargo test -p sqlx-exasol-impl --features migrate,native-tls -- --nocapture + env: + DATABASE_URL: ${{ steps.exa-cluster.outputs.tls-url }} + + - name: Unit TLS connection tests with compression timeout-minutes: ${{ fromJSON(env.TESTS_TIMEOUT) }} - run: cargo test --features migrate,rust_decimal,uuid,chrono,tls-native-tls -- --nocapture + run: cargo test -p sqlx-exasol-impl --features migrate,native-tls,compression -- --ignored --nocapture env: DATABASE_URL: ${{ steps.exa-cluster.outputs.tls-url }} - - name: TLS connection tests with compression + - name: Integration TLS connection tests timeout-minutes: ${{ fromJSON(env.TESTS_TIMEOUT) }} - run: cargo test --features migrate,compression,tls-native-tls -- --ignored --nocapture + run: cargo test --features migrate,native-tls,rust_decimal,uuid,chrono -- --nocapture env: DATABASE_URL: ${{ steps.exa-cluster.outputs.tls-url }} @@ -184,12 +196,12 @@ jobs: - name: ETL tests timeout-minutes: ${{ fromJSON(env.TESTS_TIMEOUT) }} - run: cargo test --features migrate,compression,etl -- --ignored --nocapture --test-threads `nproc` + run: cargo test --features migrate,compression,etl -- test_etl --ignored --nocapture env: DATABASE_URL: ${{ steps.exa-cluster.outputs.no-tls-url }} - name: ETL without TLS feature but TLS connection (should fail) - run: cargo test --features migrate,etl -- --ignored --nocapture --test-threads `nproc` || true + run: cargo test --features migrate,etl -- test_etl --ignored --nocapture || true env: DATABASE_URL: ${{ steps.exa-cluster.outputs.tls-url }} @@ -200,12 +212,42 @@ jobs: - name: Native-TLS ETL tests timeout-minutes: ${{ fromJSON(env.TESTS_TIMEOUT) }} - run: cargo test --features migrate,compression,etl,tls-native-tls -- --ignored --nocapture --test-threads `nproc` + run: cargo test --features migrate,compression,etl,tls-native-tls -- test_etl --ignored --nocapture env: DATABASE_URL: ${{ steps.exa-cluster.outputs.tls-url }} - name: Rustls ETL tests timeout-minutes: ${{ fromJSON(env.TESTS_TIMEOUT) }} - run: cargo test --features migrate,compression,etl,tls-rustls-aws-lc-rs -- --ignored --nocapture --test-threads `nproc` + run: cargo test --features migrate,compression,etl,tls-rustls-aws-lc-rs -- test_etl --ignored --nocapture env: DATABASE_URL: ${{ steps.exa-cluster.outputs.tls-url }} + + compile_time_tests: + name: Compile time tests + needs: clippy + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Free disk space + uses: ./.github/actions/free-space + + - name: Create Exasol cluster + timeout-minutes: ${{ fromJSON(env.EXA_CLUSTER_SETUP_TIMEOUT) }} + id: exa-cluster + uses: ./.github/actions/exa-cluster + with: + exasol-version: ${{ env.EXASOL_VERSION }} + num-nodes: ${{ env.NUM_NODES }} + + - uses: dtolnay/rust-toolchain@1.86.0 + - uses: Swatinem/rust-cache@v2 + + - name: Setup database + run: cargo run -p sqlx-exasol-cli -- database create + + - name: Run migrations + run: cargo run -p sqlx-exasol-cli -- migrate run tests/migrations + + - name: Run tests + run: cargo test -- test_compile_time --ignored --nocapture \ No newline at end of file diff --git a/.sqlx/query-07a6c51ee278a56a95e947d1f334277c85ed788706d6f7c124eae06871feba87.json b/.sqlx/query-07a6c51ee278a56a95e947d1f334277c85ed788706d6f7c124eae06871feba87.json new file mode 100644 index 00000000..7d806c59 --- /dev/null +++ b/.sqlx/query-07a6c51ee278a56a95e947d1f334277c85ed788706d6f7c124eae06871feba87.json @@ -0,0 +1,24 @@ +{ + "db_name": "Exasol", + "query": "SELECT dummy FROM DUAL", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "dummy", + "dataType": { + "type": "VARCHAR", + "size": 1, + "characterSet": "UTF8" + } + } + ], + "parameters": { + "Left": [] + }, + "nullable": [ + null + ] + }, + "hash": "07a6c51ee278a56a95e947d1f334277c85ed788706d6f7c124eae06871feba87" +} diff --git a/Cargo.toml b/Cargo.toml index f038cddc..443c7a6a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -117,12 +117,14 @@ rand = { version = "0.8", default-features = false, features = [ rsa = { version = "0.9", default-features = false, features = ["pem", "std"] } serde = { version = "1", default-features = false, features = ["derive", "rc"] } serde_json = { version = "1", default-features = false, features = [ + "std", "raw_value", ] } sqlx = { git = "https://github.com/launchbadge/sqlx", rev = "a9fb5816264694880d57c6de7f8acb9509858d3f", default-features = false } sqlx-cli = { git = "https://github.com/launchbadge/sqlx", rev = "a9fb5816264694880d57c6de7f8acb9509858d3f", default-features = false } sqlx-core = { git = "https://github.com/launchbadge/sqlx", rev = "a9fb5816264694880d57c6de7f8acb9509858d3f", default-features = false, features = [ "offline", + "migrate", ] } sqlx-macros-core = { git = "https://github.com/launchbadge/sqlx", rev = "a9fb5816264694880d57c6de7f8acb9509858d3f", default-features = false } syn = { version = "2", default-features = false, features = [ diff --git a/sqlx-exasol-cli/Cargo.toml b/sqlx-exasol-cli/Cargo.toml index 5c477dea..ecb67d69 100644 --- a/sqlx-exasol-cli/Cargo.toml +++ b/sqlx-exasol-cli/Cargo.toml @@ -17,7 +17,7 @@ native-tls = ["sqlx-cli/native-tls"] openssl-vendored = ["sqlx-cli/openssl-vendored"] -completions = ["sqlx-cli/openssl-vendored"] +completions = ["sqlx-cli/completions"] sqlx-toml = ["sqlx-cli/sqlx-toml"] diff --git a/sqlx-exasol-impl/src/column.rs b/sqlx-exasol-impl/src/column.rs index d72623b2..26f1ed05 100644 --- a/sqlx-exasol-impl/src/column.rs +++ b/sqlx-exasol-impl/src/column.rs @@ -1,4 +1,4 @@ -use std::fmt::Display; +use std::{borrow::Cow, fmt::Display}; use serde::{Deserialize, Deserializer, Serialize}; use sqlx_core::{column::Column, database::Database, ext::ustr::UStr}; @@ -21,9 +21,17 @@ impl ExaColumn { where D: Deserializer<'de>, { - // NOTE: We can borrow because we always deserialize from an owned buffer. - <&str>::deserialize(deserializer) - .map(str::to_lowercase) + /// Intermediate type used to take advantage of [`Cow`] borrowed deserialization when + /// possible. + /// + /// In regular usage, an owned buffer is used and a borrowed [`str`] could be + /// used, but for offline query deserialization a reader seems to be used and the buffer is + /// shortlived, hence a string slice would fail deserialization. + #[derive(Deserialize)] + struct CowStr<'a>(#[serde(borrow)] Cow<'a, str>); + + CowStr::deserialize(deserializer) + .map(|c| c.0.to_lowercase()) .map(From::from) } } diff --git a/sqlx-exasol-impl/src/type_info.rs b/sqlx-exasol-impl/src/type_info.rs index 6e22cd6c..1d6e770d 100644 --- a/sqlx-exasol-impl/src/type_info.rs +++ b/sqlx-exasol-impl/src/type_info.rs @@ -9,15 +9,27 @@ use sqlx_core::type_info::TypeInfo; /// Information about an Exasol data type and implementor of [`TypeInfo`]. // Note that the [`DataTypeName`] is automatically constructed from the provided [`ExaDataType`]. -#[derive(Debug, Clone, Copy, Deserialize, Serialize)] +#[derive(Debug, Clone, Copy, Deserialize)] #[serde(from = "ExaDataType")] #[serde(rename_all = "camelCase")] pub struct ExaTypeInfo { - #[serde(skip_serializing)] pub(crate) name: DataTypeName, pub(crate) data_type: ExaDataType, } +/// Manually implemented because we only want to serialize the `data_type` field while also +/// flattening the structure. +/// +/// On [`Deserialize`] we simply convert from the [`ExaDataType`] to this. +impl Serialize for ExaTypeInfo { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + self.data_type.serialize(serializer) + } +} + impl ExaTypeInfo { /// Checks compatibility with other data types. /// diff --git a/src/lib.rs b/src/lib.rs index b64cc337..08fddac1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -224,6 +224,7 @@ pub mod types { pub use sqlx::types::*; pub use sqlx_exasol_impl::types::ExaIter; + #[cfg(feature = "chrono")] pub mod chrono { pub use sqlx::types::chrono::*; pub use sqlx_exasol_impl::types::{Months, Duration}; diff --git a/tests/compile_time.rs b/tests/compile_time.rs new file mode 100644 index 00000000..b56ca29f --- /dev/null +++ b/tests/compile_time.rs @@ -0,0 +1,15 @@ +//! Command to generate offline query files: +//! ```shell +//! cargo run -p sqlx-exasol-cli prepare -- --features runtime-tokio --tests +//! ``` + +#[sqlx_exasol::test] +async fn test_query( + mut conn: sqlx::pool::PoolConnection, +) -> anyhow::Result<()> { + let x: Option = sqlx_exasol::query_scalar!("SELECT dummy FROM DUAL") + .fetch_one(&mut *conn) + .await?; + + Ok(()) +} From b5e6976b8153da0365e5142716bff77ee57fd2ac Mon Sep 17 00:00:00 2001 From: Bogdan Mircea Date: Mon, 14 Jul 2025 11:44:07 +0300 Subject: [PATCH 019/102] draft --- .github/workflows/ci.yaml | 17 +- ...4277c85ed788706d6f7c124eae06871feba87.json | 24 --- ...bf5923d1d6f9d6992535049cdfb0d7b2843bf.json | 30 ++++ ...ef249429458b2ef54e46bce72f76a1ef63753.json | 40 +++++ ...04ad3cf07b7ccd4f2b3b10e7eed32346b3855.json | 18 ++ sqlx-exasol-impl/Cargo.toml | 6 +- sqlx-exasol-impl/src/column.rs | 21 +++ sqlx-exasol-impl/src/connection/mod.rs | 4 +- .../src/connection/websocket/request.rs | 27 ++- sqlx-exasol-impl/src/type_checking.rs | 2 +- sqlx-exasol-impl/src/type_info.rs | 1 - sqlx-exasol-impl/src/types/iter.rs | 2 +- src/lib.rs | 15 +- tests/common.rs | 161 ++++++++---------- tests/compile_time.rs | 36 +++- tests/describe.rs | 12 +- tests/error.rs | 18 +- tests/etl.rs | 26 +-- tests/from_row.rs | 8 +- tests/macros.rs | 30 ++-- tests/migrate.rs | 6 +- tests/migrations/2_post.sql | 9 +- tests/test-attr.rs | 34 ++-- 23 files changed, 322 insertions(+), 225 deletions(-) delete mode 100644 .sqlx/query-07a6c51ee278a56a95e947d1f334277c85ed788706d6f7c124eae06871feba87.json create mode 100644 .sqlx/query-1798fb8c66823e371b0306d1cefbf5923d1d6f9d6992535049cdfb0d7b2843bf.json create mode 100644 .sqlx/query-946d756b9e0821ef66916722e44ef249429458b2ef54e46bce72f76a1ef63753.json create mode 100644 .sqlx/query-ea76fbbc69c1941fe6baba8969b04ad3cf07b7ccd4f2b3b10e7eed32346b3855.json diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 7cc620ac..d55e6f39 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -122,13 +122,13 @@ jobs: - name: Unit connection tests with compression timeout-minutes: ${{ fromJSON(env.TESTS_TIMEOUT) }} - run: cargo test -p sqlx-exasol-impl --features migrate,compression -- --ignored --nocapture + run: cargo test -p sqlx-exasol-impl --features migrate,compression -- --ignored --nocapture test_compression env: DATABASE_URL: ${{ steps.exa-cluster.outputs.no-tls-url }} - name: Integration connection tests timeout-minutes: ${{ fromJSON(env.TESTS_TIMEOUT) }} - run: cargo test --features migrate,rust_decimal,uuid,chrono -- --nocapture + run: cargo test --features runtime-tokio,migrate,rust_decimal,uuid,chrono -- --nocapture env: DATABASE_URL: ${{ steps.exa-cluster.outputs.no-tls-url }} @@ -156,19 +156,19 @@ jobs: - name: Unit TLS connection tests timeout-minutes: ${{ fromJSON(env.TESTS_TIMEOUT) }} - run: cargo test -p sqlx-exasol-impl --features migrate,native-tls -- --nocapture + run: cargo test -p sqlx-exasol-impl --features native-tls,migrate -- --nocapture env: DATABASE_URL: ${{ steps.exa-cluster.outputs.tls-url }} - name: Unit TLS connection tests with compression timeout-minutes: ${{ fromJSON(env.TESTS_TIMEOUT) }} - run: cargo test -p sqlx-exasol-impl --features migrate,native-tls,compression -- --ignored --nocapture + run: cargo test -p sqlx-exasol-impl --features native-tls,migrate,compression -- --ignored --nocapture test_compression env: DATABASE_URL: ${{ steps.exa-cluster.outputs.tls-url }} - name: Integration TLS connection tests timeout-minutes: ${{ fromJSON(env.TESTS_TIMEOUT) }} - run: cargo test --features migrate,native-tls,rust_decimal,uuid,chrono -- --nocapture + run: cargo test --features runtime-tokio,tls-native-tls,migrate,rust_decimal,uuid,chrono -- --nocapture env: DATABASE_URL: ${{ steps.exa-cluster.outputs.tls-url }} @@ -196,7 +196,7 @@ jobs: - name: ETL tests timeout-minutes: ${{ fromJSON(env.TESTS_TIMEOUT) }} - run: cargo test --features migrate,compression,etl -- test_etl --ignored --nocapture + run: cargo test --features runtime-tokio,migrate,compression,etl -- test_etl --ignored --nocapture env: DATABASE_URL: ${{ steps.exa-cluster.outputs.no-tls-url }} @@ -205,11 +205,6 @@ jobs: env: DATABASE_URL: ${{ steps.exa-cluster.outputs.tls-url }} - - name: Tests compilation failure if both ETL TLS features are enabled - run: cargo test --features tls-native-tls,tls-rustls || true - env: - DATABASE_URL: ${{ steps.exa-cluster.outputs.tls-url }} - - name: Native-TLS ETL tests timeout-minutes: ${{ fromJSON(env.TESTS_TIMEOUT) }} run: cargo test --features migrate,compression,etl,tls-native-tls -- test_etl --ignored --nocapture diff --git a/.sqlx/query-07a6c51ee278a56a95e947d1f334277c85ed788706d6f7c124eae06871feba87.json b/.sqlx/query-07a6c51ee278a56a95e947d1f334277c85ed788706d6f7c124eae06871feba87.json deleted file mode 100644 index 7d806c59..00000000 --- a/.sqlx/query-07a6c51ee278a56a95e947d1f334277c85ed788706d6f7c124eae06871feba87.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "db_name": "Exasol", - "query": "SELECT dummy FROM DUAL", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "dummy", - "dataType": { - "type": "VARCHAR", - "size": 1, - "characterSet": "UTF8" - } - } - ], - "parameters": { - "Left": [] - }, - "nullable": [ - null - ] - }, - "hash": "07a6c51ee278a56a95e947d1f334277c85ed788706d6f7c124eae06871feba87" -} diff --git a/.sqlx/query-1798fb8c66823e371b0306d1cefbf5923d1d6f9d6992535049cdfb0d7b2843bf.json b/.sqlx/query-1798fb8c66823e371b0306d1cefbf5923d1d6f9d6992535049cdfb0d7b2843bf.json new file mode 100644 index 00000000..2de3454b --- /dev/null +++ b/.sqlx/query-1798fb8c66823e371b0306d1cefbf5923d1d6f9d6992535049cdfb0d7b2843bf.json @@ -0,0 +1,30 @@ +{ + "db_name": "Exasol", + "query": "SELECT user_id as \"user_id!\" FROM users WHERE username = ?", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "user_id!", + "dataType": { + "type": "DECIMAL", + "precision": 18, + "scale": 0 + } + } + ], + "parameters": { + "Left": [ + { + "type": "VARCHAR", + "size": 16, + "characterSet": "UTF8" + } + ] + }, + "nullable": [ + null + ] + }, + "hash": "1798fb8c66823e371b0306d1cefbf5923d1d6f9d6992535049cdfb0d7b2843bf" +} diff --git a/.sqlx/query-946d756b9e0821ef66916722e44ef249429458b2ef54e46bce72f76a1ef63753.json b/.sqlx/query-946d756b9e0821ef66916722e44ef249429458b2ef54e46bce72f76a1ef63753.json new file mode 100644 index 00000000..d79589bf --- /dev/null +++ b/.sqlx/query-946d756b9e0821ef66916722e44ef249429458b2ef54e46bce72f76a1ef63753.json @@ -0,0 +1,40 @@ +{ + "db_name": "Exasol", + "query": "SELECT user_id as \"user_id!\", username as \"username!\" FROM users WHERE user_id = ?", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "user_id!", + "dataType": { + "type": "DECIMAL", + "precision": 18, + "scale": 0 + } + }, + { + "ordinal": 0, + "name": "username!", + "dataType": { + "type": "VARCHAR", + "size": 16, + "characterSet": "UTF8" + } + } + ], + "parameters": { + "Left": [ + { + "type": "DECIMAL", + "precision": 18, + "scale": 0 + } + ] + }, + "nullable": [ + null, + null + ] + }, + "hash": "946d756b9e0821ef66916722e44ef249429458b2ef54e46bce72f76a1ef63753" +} diff --git a/.sqlx/query-ea76fbbc69c1941fe6baba8969b04ad3cf07b7ccd4f2b3b10e7eed32346b3855.json b/.sqlx/query-ea76fbbc69c1941fe6baba8969b04ad3cf07b7ccd4f2b3b10e7eed32346b3855.json new file mode 100644 index 00000000..e08d7313 --- /dev/null +++ b/.sqlx/query-ea76fbbc69c1941fe6baba8969b04ad3cf07b7ccd4f2b3b10e7eed32346b3855.json @@ -0,0 +1,18 @@ +{ + "db_name": "Exasol", + "query": "INSERT INTO users (username) VALUES(?);", + "describe": { + "columns": [], + "parameters": { + "Left": [ + { + "type": "VARCHAR", + "size": 16, + "characterSet": "UTF8" + } + ] + }, + "nullable": [] + }, + "hash": "ea76fbbc69c1941fe6baba8969b04ad3cf07b7ccd4f2b3b10e7eed32346b3855" +} diff --git a/sqlx-exasol-impl/Cargo.toml b/sqlx-exasol-impl/Cargo.toml index d7fc5d09..bf1fdede 100644 --- a/sqlx-exasol-impl/Cargo.toml +++ b/sqlx-exasol-impl/Cargo.toml @@ -23,8 +23,8 @@ rust_decimal = ["sqlx-core/rust_decimal", "dep:rust_decimal"] # TLS features tls = ["dep:rcgen"] -rustls = ["dep:rustls", "tls"] -native-tls = ["dep:native-tls", "tls"] +rustls = ["dep:rustls", "sqlx-core/_tls-rustls", "tls"] +native-tls = ["dep:native-tls", "sqlx-core/_tls-native-tls", "tls"] # Driver specific features compression = ["dep:async-compression"] @@ -63,7 +63,7 @@ sqlx-macros-core = { workspace = true, optional = true } uuid = { workspace = true, optional = true } [dev-dependencies] -sqlx = { workspace = true } +sqlx = { workspace = true, features = ["runtime-tokio", "macros", "migrate"] } [lints] workspace = true diff --git a/sqlx-exasol-impl/src/column.rs b/sqlx-exasol-impl/src/column.rs index 26f1ed05..382329fa 100644 --- a/sqlx-exasol-impl/src/column.rs +++ b/sqlx-exasol-impl/src/column.rs @@ -36,6 +36,27 @@ impl ExaColumn { } } +#[test] +fn test_column_serde() { + let column_str = r#" { + "ordinal": 0, + "name": "user_id!", + "dataType": { + "type": "DECIMAL", + "precision": 18, + "scale": 0 + } + }"#; + + let column: ExaColumn = serde_json::from_str(column_str).unwrap(); + println!("{column:?}"); + + let new_column_str = serde_json::to_string(&column).unwrap(); + + let column: ExaColumn = serde_json::from_str(&new_column_str).unwrap(); + println!("{column:?}"); +} + impl Display for ExaColumn { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}: {}", self.name, self.data_type) diff --git a/sqlx-exasol-impl/src/connection/mod.rs b/sqlx-exasol-impl/src/connection/mod.rs index ff175dc2..6b5b9190 100644 --- a/sqlx-exasol-impl/src/connection/mod.rs +++ b/sqlx-exasol-impl/src/connection/mod.rs @@ -203,8 +203,8 @@ mod tests { use crate::{ExaConnectOptions, ExaQueryResult, Exasol}; - #[cfg(feature = "compression")] #[ignore] + #[cfg(feature = "compression")] #[sqlx::test] async fn test_compression_feature( pool_opts: PoolOptions, @@ -233,7 +233,7 @@ mod tests { #[cfg(not(feature = "compression"))] #[sqlx::test] - async fn test_compression_no_feature( + async fn test_enabled_compression_without_feature( pool_opts: PoolOptions, mut exa_opts: ExaConnectOptions, ) { diff --git a/sqlx-exasol-impl/src/connection/websocket/request.rs b/sqlx-exasol-impl/src/connection/websocket/request.rs index 3bafc39d..f34548aa 100644 --- a/sqlx-exasol-impl/src/connection/websocket/request.rs +++ b/sqlx-exasol-impl/src/connection/websocket/request.rs @@ -198,8 +198,8 @@ impl Serialize for WithAttributes<'_, Fetch> { } /// Request to execute a single SQL statement. -// This is internally used in the IMPORT/EXPORT jobs as well, since they rely on query execution too. -// However, in these scenarios the query is an owned string, hence the usage of [`Cow`] here to +// This is internally used in the IMPORT/EXPORT jobs as well, since they rely on query execution +// too. However, in these scenarios the query is an owned string, hence the usage of [`Cow`] here to // support that. #[derive(Clone, Debug)] pub struct Execute<'a>(pub Cow<'a, str>); @@ -443,12 +443,35 @@ enum Command<'a> { num_columns: usize, num_rows: usize, #[serde(skip_serializing_if = "<[ExaTypeInfo]>::is_empty")] + #[serde(serialize_with = "serialize_params")] columns: &'a [ExaTypeInfo], #[serde(skip_serializing_if = "PreparedStmtData::is_empty")] data: &'a PreparedStmtData, }, } +/// Thin serialization wrapper that helps respect the serialization format of +/// prepared statement parameters (same layout as [`crate::ExaColumn`]). +#[derive(Debug, Clone, Serialize)] +#[serde(rename_all = "camelCase")] +struct PreparedStmtParam<'a> { + data_type: &'a ExaTypeInfo, +} + +impl<'a> From<&'a ExaTypeInfo> for PreparedStmtParam<'a> { + fn from(data_type: &'a ExaTypeInfo) -> Self { + Self { data_type } + } +} + +/// Serialization helper function that maps a [`ExaTypeInfo`] reference to [`PreparedStmtParam`]. +fn serialize_params(params: &[ExaTypeInfo], serializer: S) -> Result +where + S: Serializer, +{ + serializer.collect_seq(params.iter().map(PreparedStmtParam::from)) +} + /// Type containing the parameters data to be passed as part of executing a prepared statement. /// It ensures the parameter sequence in the [`ExaBuffer`] is appropriately ended. #[derive(Debug, Clone)] diff --git a/sqlx-exasol-impl/src/type_checking.rs b/sqlx-exasol-impl/src/type_checking.rs index 6b1e0938..47ecfb00 100644 --- a/sqlx-exasol-impl/src/type_checking.rs +++ b/sqlx-exasol-impl/src/type_checking.rs @@ -37,7 +37,7 @@ impl_type_checking!( i128, f32, f64, - String, + String | &str, // External types #[cfg(feature = "uuid")] diff --git a/sqlx-exasol-impl/src/type_info.rs b/sqlx-exasol-impl/src/type_info.rs index 1d6e770d..7cd1a98c 100644 --- a/sqlx-exasol-impl/src/type_info.rs +++ b/sqlx-exasol-impl/src/type_info.rs @@ -11,7 +11,6 @@ use sqlx_core::type_info::TypeInfo; // Note that the [`DataTypeName`] is automatically constructed from the provided [`ExaDataType`]. #[derive(Debug, Clone, Copy, Deserialize)] #[serde(from = "ExaDataType")] -#[serde(rename_all = "camelCase")] pub struct ExaTypeInfo { pub(crate) name: DataTypeName, pub(crate) data_type: ExaDataType, diff --git a/sqlx-exasol-impl/src/types/iter.rs b/sqlx-exasol-impl/src/types/iter.rs index c3d62fae..bdda9c8e 100644 --- a/sqlx-exasol-impl/src/types/iter.rs +++ b/sqlx-exasol-impl/src/types/iter.rs @@ -16,7 +16,7 @@ use crate::{arguments::ExaBuffer, Exasol}; /// /// ```rust /// # use sqlx_exasol_impl as sqlx_exasol; -/// use sqlx_exasol::ExaIter; +/// use sqlx_exasol::types::ExaIter; /// /// // Don't do this, as the iterator gets cloned internally. /// let vector = vec![1, 2, 3]; diff --git a/src/lib.rs b/src/lib.rs index 08fddac1..2917f093 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -56,7 +56,7 @@ //! - [`f32`], [`f64`] //! - [`str`], [`String`], [`std::borrow::Cow`] //! - `chrono` feature: [`chrono::DateTime`], [`chrono::DateTime`], -//! [`chrono::NaiveDateTime`], [`chrono::NaiveDate`], [`chrono::Duration`], [`Months`] (analog of +//! [`chrono::NaiveDateTime`], [`chrono::NaiveDate`], [`chrono::Duration`], [`crate::types::chrono::Months`] (analog of //! [`chrono::Months`]) //! - `uuid` feature: [`uuid::Uuid`] //! - `rust_decimal` feature: [`rust_decimal::Decimal`] @@ -100,7 +100,7 @@ //! let pool = ExaPool::connect(&env::var("DATABASE_URL").unwrap()).await?; //! let mut con = pool.acquire().await?; //! -//! sqlx::query("CREATE SCHEMA RUST_DOC_TEST") +//! sqlx_exasol::query("CREATE SCHEMA RUST_DOC_TEST") //! .execute(&mut *con) //! .await?; //! # @@ -109,7 +109,7 @@ //! # }; //! ``` //! -//! Array-like parameter binding, also featuring the [`ExaIter`] adapter. +//! Array-like parameter binding, also featuring the [`crate::types::ExaIter`] adapter. //! An important thing to note is that the parameter sets must be of equal length, //! otherwise an error is thrown: //! ```rust,no_run @@ -125,9 +125,9 @@ //! let params1 = vec![1, 2, 3]; //! let params2 = HashSet::from([1, 2, 3]); //! -//! sqlx::query("INSERT INTO MY_TABLE VALUES (?, ?)") +//! sqlx_exasol::query("INSERT INTO MY_TABLE VALUES (?, ?)") //! .bind(¶ms1) -//! .bind(ExaIter::from(¶ms2)) +//! .bind(types::ExaIter::from(¶ms2)) //! .execute(&mut *con) //! .await?; //! # @@ -227,7 +227,7 @@ pub mod types { #[cfg(feature = "chrono")] pub mod chrono { pub use sqlx::types::chrono::*; - pub use sqlx_exasol_impl::types::{Months, Duration}; + pub use sqlx_exasol_impl::types::{Duration, Months}; } } @@ -239,3 +239,6 @@ pub mod any { pub use sqlx_exasol_macros; #[cfg(feature = "macros")] mod macros; + +// TODO: +// - Amend PreparedStatement with a wrapper of ExaTypeInfo diff --git a/tests/common.rs b/tests/common.rs index 3f4fdf5c..773f0524 100644 --- a/tests/common.rs +++ b/tests/common.rs @@ -3,13 +3,13 @@ use std::iter::zip; use futures_util::TryStreamExt; -use sqlx::{ - error::BoxDynError, pool::PoolConnection, prelude::FromRow, Column, Connection, Execute, - Executor, Row, Statement, TypeInfo, +use sqlx_exasol::{ + error::BoxDynError, pool::PoolConnection, Column, Connection, Executor, Row, Statement, + TypeInfo, }; use sqlx_exasol::{ExaConnection, ExaPool, ExaPoolOptions, ExaQueryResult, ExaRow, Exasol}; -#[sqlx::test] +#[sqlx_exasol::test] async fn it_connects(mut conn: PoolConnection) -> anyhow::Result<()> { conn.ping().await?; conn.close().await?; @@ -17,9 +17,9 @@ async fn it_connects(mut conn: PoolConnection) -> anyhow::Result<()> { Ok(()) } -#[sqlx::test] +#[sqlx_exasol::test] async fn it_maths(mut conn: PoolConnection) -> anyhow::Result<()> { - let value = sqlx::query("select 1 + CAST(? AS DECIMAL(5, 0))") + let value = sqlx_exasol::query("select 1 + CAST(? AS DECIMAL(5, 0))") .bind(5_i32.to_string()) .try_map(|row: ExaRow| row.try_get::(0)) .fetch_one(&mut *conn) @@ -30,26 +30,26 @@ async fn it_maths(mut conn: PoolConnection) -> anyhow::Result<()> { Ok(()) } -#[sqlx::test] +#[sqlx_exasol::test] async fn it_can_fail_at_querying(mut conn: PoolConnection) -> anyhow::Result<()> { - let _ = conn.execute(sqlx::query("SELECT 1")).await?; + let _ = conn.execute(sqlx_exasol::query("SELECT 1")).await?; // we are testing that this does not cause a panic! let _ = conn - .execute(sqlx::query("SELECT non_existence_table")) + .execute(sqlx_exasol::query("SELECT non_existence_table")) .await; Ok(()) } -#[sqlx::test] +#[sqlx_exasol::test] async fn it_executes(mut conn: PoolConnection) -> anyhow::Result<()> { let _ = conn .execute("CREATE TABLE users (id INTEGER PRIMARY KEY);") .await?; for index in 1..=10_i32 { - let done = sqlx::query("INSERT INTO users (id) VALUES (?)") + let done = sqlx_exasol::query("INSERT INTO users (id) VALUES (?)") .bind(index) .execute(&mut *conn) .await?; @@ -57,7 +57,7 @@ async fn it_executes(mut conn: PoolConnection) -> anyhow::Result<()> { assert_eq!(done.rows_affected(), 1); } - let sum: i64 = sqlx::query("SELECT id FROM users") + let sum: i64 = sqlx_exasol::query("SELECT id FROM users") .try_map(|row: ExaRow| row.try_get::(0)) .fetch(&mut *conn) .try_fold(0, |acc, x| async move { Ok(acc + x) }) @@ -68,7 +68,7 @@ async fn it_executes(mut conn: PoolConnection) -> anyhow::Result<()> { Ok(()) } -#[sqlx::test] +#[sqlx_exasol::test] async fn it_executes_with_pool() -> anyhow::Result<()> { let pool: ExaPool = ExaPoolOptions::new() .min_connections(2) @@ -91,7 +91,7 @@ async fn it_executes_with_pool() -> anyhow::Result<()> { Ok(()) } -#[sqlx::test] +#[sqlx_exasol::test] async fn it_works_with_cache_disabled() -> anyhow::Result<()> { let mut url = url::Url::parse(&dotenvy::var("DATABASE_URL")?)?; url.query_pairs_mut() @@ -100,7 +100,7 @@ async fn it_works_with_cache_disabled() -> anyhow::Result<()> { let mut conn = ExaConnection::connect(url.as_ref()).await?; for index in 1..=10_i32 { - let _ = sqlx::query("SELECT ?") + let _ = sqlx_exasol::query("SELECT ?") .bind(index.to_string()) .execute(&mut conn) .await?; @@ -109,7 +109,7 @@ async fn it_works_with_cache_disabled() -> anyhow::Result<()> { Ok(()) } -#[sqlx::test] +#[sqlx_exasol::test] async fn it_drops_results_in_affected_rows(mut conn: PoolConnection) -> anyhow::Result<()> { // ~1800 rows should be iterated and dropped let done = conn @@ -122,9 +122,9 @@ async fn it_drops_results_in_affected_rows(mut conn: PoolConnection) -> Ok(()) } -#[sqlx::test] +#[sqlx_exasol::test] async fn it_selects_null(mut conn: PoolConnection) -> anyhow::Result<()> { - let (val,): (Option,) = sqlx::query_as("SELECT NULL").fetch_one(&mut *conn).await?; + let (val,): (Option,) = sqlx_exasol::query_as("SELECT NULL").fetch_one(&mut *conn).await?; assert!(val.is_none()); @@ -135,25 +135,25 @@ async fn it_selects_null(mut conn: PoolConnection) -> anyhow::Result<()> Ok(()) } -#[sqlx::test] +#[sqlx_exasol::test] async fn it_can_fetch_one_and_ping(mut conn: PoolConnection) -> anyhow::Result<()> { - let (_id,): (i32,) = sqlx::query_as("SELECT 1 as id") + let (_id,): (i32,) = sqlx_exasol::query_as("SELECT 1 as id") .fetch_one(&mut *conn) .await?; conn.ping().await?; - let (_id,): (i32,) = sqlx::query_as("SELECT 1 as id") + let (_id,): (i32,) = sqlx_exasol::query_as("SELECT 1 as id") .fetch_one(&mut *conn) .await?; Ok(()) } -#[sqlx::test] +#[sqlx_exasol::test] async fn it_caches_statements(mut conn: PoolConnection) -> anyhow::Result<()> { for i in 0..2 { - let row = sqlx::query("SELECT CAST(? as DECIMAL(5, 0)) AS val") + let row = sqlx_exasol::query("SELECT CAST(? as DECIMAL(5, 0)) AS val") .bind(i.to_string()) .persistent(true) .fetch_one(&mut *conn) @@ -169,7 +169,7 @@ async fn it_caches_statements(mut conn: PoolConnection) -> anyhow::Resul assert_eq!(0, conn.cached_statements_size()); for i in 0..2 { - let row = sqlx::query("SELECT CAST(? as DECIMAL(5, 0)) AS val") + let row = sqlx_exasol::query("SELECT CAST(? as DECIMAL(5, 0)) AS val") .bind(i.to_string()) .persistent(false) .fetch_one(&mut *conn) @@ -185,11 +185,11 @@ async fn it_caches_statements(mut conn: PoolConnection) -> anyhow::Resul Ok(()) } -#[sqlx::test] +#[sqlx_exasol::test] async fn it_can_bind_null_and_non_null_issue_540( mut conn: PoolConnection, ) -> anyhow::Result<()> { - let row = sqlx::query("SELECT ?, ?, ?") + let row = sqlx_exasol::query("SELECT ?, ?, ?") .bind(50.to_string()) .bind(None::) .bind("") @@ -207,9 +207,9 @@ async fn it_can_bind_null_and_non_null_issue_540( Ok(()) } -#[sqlx::test] +#[sqlx_exasol::test] async fn it_can_bind_only_null_issue_540(mut conn: PoolConnection) -> anyhow::Result<()> { - let row = sqlx::query("SELECT ?") + let row = sqlx_exasol::query("SELECT ?") .bind(None::) .fetch_one(&mut *conn) .await?; @@ -221,15 +221,15 @@ async fn it_can_bind_only_null_issue_540(mut conn: PoolConnection) -> an Ok(()) } -#[sqlx::test(migrations = "tests/setup")] +#[sqlx_exasol::test(migrations = "tests/setup")] async fn it_can_prepare_then_execute(mut conn: PoolConnection) -> anyhow::Result<()> { let mut tx = conn.begin().await?; - sqlx::query("INSERT INTO tweet ( text ) VALUES ( 'Hello, World' )") + sqlx_exasol::query("INSERT INTO tweet ( text ) VALUES ( 'Hello, World' )") .execute(&mut *tx) .await?; - let tweet_id: u64 = sqlx::query_scalar("SELECT id from tweet;") + let tweet_id: u64 = sqlx_exasol::query_scalar("SELECT id from tweet;") .fetch_one(&mut *tx) .await?; @@ -256,7 +256,7 @@ async fn it_can_prepare_then_execute(mut conn: PoolConnection) -> anyhow Ok(()) } -#[sqlx::test] +#[sqlx_exasol::test] async fn it_can_work_with_transactions(mut conn: PoolConnection) -> anyhow::Result<()> { conn.execute("CREATE TABLE users (id INTEGER PRIMARY KEY);") .await?; @@ -264,17 +264,17 @@ async fn it_can_work_with_transactions(mut conn: PoolConnection) -> anyh // begin .. rollback let mut tx = conn.begin().await?; - sqlx::query("INSERT INTO users (id) VALUES (?)") + sqlx_exasol::query("INSERT INTO users (id) VALUES (?)") .bind(1_i32) .execute(&mut *tx) .await?; - let count: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM users") + let count: i64 = sqlx_exasol::query_scalar("SELECT COUNT(*) FROM users") .fetch_one(&mut *tx) .await?; assert_eq!(count, 1); tx.rollback().await?; - let count: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM users") + let count: i64 = sqlx_exasol::query_scalar("SELECT COUNT(*) FROM users") .fetch_one(&mut *conn) .await?; assert_eq!(count, 0); @@ -282,13 +282,13 @@ async fn it_can_work_with_transactions(mut conn: PoolConnection) -> anyh // begin .. commit let mut tx = conn.begin().await?; - sqlx::query("INSERT INTO users (id) VALUES (?)") + sqlx_exasol::query("INSERT INTO users (id) VALUES (?)") .bind(1_i32) .execute(&mut *tx) .await?; tx.commit().await?; - let count: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM users") + let count: i64 = sqlx_exasol::query_scalar("SELECT COUNT(*) FROM users") .fetch_one(&mut *conn) .await?; assert_eq!(count, 1); @@ -298,19 +298,19 @@ async fn it_can_work_with_transactions(mut conn: PoolConnection) -> anyh { let mut tx = conn.begin().await?; - sqlx::query("INSERT INTO users (id) VALUES (?)") + sqlx_exasol::query("INSERT INTO users (id) VALUES (?)") .bind(2) .execute(&mut *tx) .await?; - let count: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM users") + let count: i64 = sqlx_exasol::query_scalar("SELECT COUNT(*) FROM users") .fetch_one(&mut *tx) .await?; assert_eq!(count, 2); // tx is dropped } - let count: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM users") + let count: i64 = sqlx_exasol::query_scalar("SELECT COUNT(*) FROM users") .fetch_one(&mut *conn) .await?; assert_eq!(count, 1); @@ -318,7 +318,7 @@ async fn it_can_work_with_transactions(mut conn: PoolConnection) -> anyh Ok(()) } -#[sqlx::test] +#[sqlx_exasol::test] async fn it_can_rollback_and_continue(mut conn: PoolConnection) -> anyhow::Result<()> { conn.execute("CREATE TABLE users (id INTEGER PRIMARY KEY);") .await?; @@ -326,22 +326,22 @@ async fn it_can_rollback_and_continue(mut conn: PoolConnection) -> anyho // begin .. rollback let mut tx = conn.begin().await?; - sqlx::query("INSERT INTO users (id) VALUES (?)") + sqlx_exasol::query("INSERT INTO users (id) VALUES (?)") .bind(vec![1, 2]) .execute(&mut *tx) .await?; - let count: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM users") + let count: i64 = sqlx_exasol::query_scalar("SELECT COUNT(*) FROM users") .fetch_one(&mut *tx) .await?; assert_eq!(count, 2); tx.rollback().await?; - sqlx::query("INSERT INTO users (id) VALUES (?)") + sqlx_exasol::query("INSERT INTO users (id) VALUES (?)") .bind(1) .execute(&mut *conn) .await?; - let count: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM users") + let count: i64 = sqlx_exasol::query_scalar("SELECT COUNT(*) FROM users") .fetch_one(&mut *conn) .await?; assert_eq!(count, 1); @@ -349,7 +349,7 @@ async fn it_can_rollback_and_continue(mut conn: PoolConnection) -> anyho Ok(()) } -#[sqlx::test] +#[sqlx_exasol::test] async fn it_cannot_nest_transactions(mut conn: PoolConnection) -> anyhow::Result<()> { let mut tx = conn.begin().await?; // Trying to start a nested one will fail. @@ -367,7 +367,7 @@ async fn it_cannot_nest_transactions(mut conn: PoolConnection) -> anyhow // Therefore, if a transaction is dropped and scheduled for rollback but another connection starts a // conflicting transaction, a deadlock will occur. // -// #[sqlx::test] +// #[sqlx_exasol::test] // async fn it_can_drop_transaction_and_not_deadlock( // pool_opts: PoolOptions, // exa_opts: ExaConnectOptions, @@ -385,27 +385,27 @@ async fn it_cannot_nest_transactions(mut conn: PoolConnection) -> anyhow // { // let mut tx = conn1.begin().await?; -// sqlx::query("INSERT INTO users (id) VALUES (?)") +// sqlx_exasol::query("INSERT INTO users (id) VALUES (?)") // .bind(vec![1, 2]) // .execute(&mut *tx) // .await?; -// let count: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM users") +// let count: i64 = sqlx_exasol::query_scalar("SELECT COUNT(*) FROM users") // .fetch_one(&mut *tx) // .await?; // assert_eq!(count, 2); -// sqlx::query("INSERT INTO users (id) VALUES (?)") +// sqlx_exasol::query("INSERT INTO users (id) VALUES (?)") // .bind(vec![3, 4]) // .execute(&mut *tx) // .await?; // } -// sqlx::query("INSERT INTO users (id) VALUES (?)") +// sqlx_exasol::query("INSERT INTO users (id) VALUES (?)") // .bind(5) // .execute(&mut *conn2) // .await?; -// let count: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM users") +// let count: i64 = sqlx_exasol::query_scalar("SELECT COUNT(*) FROM users") // .fetch_one(&mut *conn2) // .await?; // assert_eq!(count, 1); @@ -413,7 +413,7 @@ async fn it_cannot_nest_transactions(mut conn: PoolConnection) -> anyhow // Ok(()) // } -#[sqlx::test] +#[sqlx_exasol::test] async fn test_equal_arrays(mut con: PoolConnection) -> Result<(), BoxDynError> { con.execute( "CREATE TABLE sqlx_test_type ( col1 BOOLEAN, col2 DECIMAL(10, 0), col3 VARCHAR(100) );", @@ -424,7 +424,7 @@ async fn test_equal_arrays(mut con: PoolConnection) -> Result<(), BoxDyn let ints = vec![1, 2, 3]; let mut strings = vec![Some("one".to_owned()), None, Some(String::new())]; - let query_result = sqlx::query("INSERT INTO sqlx_test_type VALUES (?, ?, ?)") + let query_result = sqlx_exasol::query("INSERT INTO sqlx_test_type VALUES (?, ?, ?)") .bind(&bools) .bind(&ints) .bind(&strings) @@ -434,7 +434,7 @@ async fn test_equal_arrays(mut con: PoolConnection) -> Result<(), BoxDyn assert_eq!(query_result.rows_affected(), 3); let values: Vec<(bool, u32, Option)> = - sqlx::query_as("SELECT * FROM sqlx_test_type ORDER BY col2;") + sqlx_exasol::query_as("SELECT * FROM sqlx_test_type ORDER BY col2;") .fetch_all(&mut *con) .await?; @@ -450,7 +450,7 @@ async fn test_equal_arrays(mut con: PoolConnection) -> Result<(), BoxDyn Ok(()) } -#[sqlx::test] +#[sqlx_exasol::test] async fn test_unequal_arrays(mut con: PoolConnection) -> Result<(), BoxDynError> { con.execute( "CREATE TABLE sqlx_test_type ( col1 BOOLEAN, col2 DECIMAL(10, 0), col3 VARCHAR(100) );", @@ -461,7 +461,7 @@ async fn test_unequal_arrays(mut con: PoolConnection) -> Result<(), BoxD let ints = vec![1, 2, 3, 4]; let strings = vec![Some("one".to_owned()), Some(String::new())]; - sqlx::query("INSERT INTO sqlx_test_type VALUES (?, ?, ?)") + sqlx_exasol::query("INSERT INTO sqlx_test_type VALUES (?, ?, ?)") .bind(&bools) .bind(&ints) .bind(&strings) @@ -472,7 +472,7 @@ async fn test_unequal_arrays(mut con: PoolConnection) -> Result<(), BoxD Ok(()) } -#[sqlx::test] +#[sqlx_exasol::test] async fn test_exceeding_arrays(mut con: PoolConnection) -> Result<(), BoxDynError> { con.execute( "CREATE TABLE sqlx_test_type ( col1 BOOLEAN, col2 DECIMAL(10, 0), col3 VARCHAR(100) );", @@ -483,7 +483,7 @@ async fn test_exceeding_arrays(mut con: PoolConnection) -> Result<(), Bo let ints = vec![1, 2, u64::MAX]; let strings = vec![Some("one".to_owned()), Some(String::new()), None]; - sqlx::query("INSERT INTO sqlx_test_type VALUES (?, ?, ?)") + sqlx_exasol::query("INSERT INTO sqlx_test_type VALUES (?, ?, ?)") .bind(&bools) .bind(&ints) .bind(&strings) @@ -494,17 +494,17 @@ async fn test_exceeding_arrays(mut con: PoolConnection) -> Result<(), Bo Ok(()) } -#[sqlx::test] +#[sqlx_exasol::test] async fn test_decode_error(mut con: PoolConnection) -> Result<(), BoxDynError> { con.execute("CREATE TABLE sqlx_test_type ( col DECIMAL(10, 0) );") .await?; - sqlx::query("INSERT INTO sqlx_test_type VALUES (?)") + sqlx_exasol::query("INSERT INTO sqlx_test_type VALUES (?)") .bind(u32::MAX) .execute(&mut *con) .await?; - let error = sqlx::query_scalar::<_, u8>("SELECT col FROM sqlx_test_type") + let error = sqlx_exasol::query_scalar::<_, u8>("SELECT col FROM sqlx_test_type") .fetch_one(&mut *con) .await .unwrap_err(); @@ -514,7 +514,7 @@ async fn test_decode_error(mut con: PoolConnection) -> Result<(), BoxDyn Ok(()) } -#[sqlx::test] +#[sqlx_exasol::test] async fn test_execute_many_works(mut con: PoolConnection) -> Result<(), BoxDynError> { con.execute_many("SELECT 1; SELECT 2; SELECT 3;") .try_collect::() @@ -525,7 +525,7 @@ async fn test_execute_many_works(mut con: PoolConnection) -> Result<(), /// Ensure that even if errors are not handled a bad statement in a query will still result in the /// stream ending. -#[sqlx::test] +#[sqlx_exasol::test] async fn test_execute_many_fails_bad_query( mut con: PoolConnection, ) -> Result<(), BoxDynError> { @@ -540,12 +540,12 @@ async fn test_execute_many_fails_bad_query( } #[expect(deprecated, reason = "testing deprecation")] -#[sqlx::test] +#[sqlx_exasol::test] async fn test_execute_many_fails_params( mut con: PoolConnection, ) -> Result<(), BoxDynError> { // Fails because this is a multi-statement query. - let is_err = sqlx::query("SELECT ?; SELECT ?") + let is_err = sqlx_exasol::query("SELECT ?; SELECT ?") .bind(1) .bind(2) .execute_many(&mut *con) @@ -559,7 +559,7 @@ async fn test_execute_many_fails_params( Ok(()) } -#[sqlx::test] +#[sqlx_exasol::test] async fn test_fetch_many_works(mut con: PoolConnection) -> Result<(), BoxDynError> { let mut stream = con.fetch_many( " @@ -573,33 +573,6 @@ async fn test_fetch_many_works(mut con: PoolConnection) -> Result<(), Bo Ok(()) } -/// This test checks that [`sqlx::query::QueryAs::fetch`] still misbehaves and calls `fetch_many` -/// internally. -/// -/// When this test starts failing, check again whether the `Executor::fetch_many` can be made -/// to fail on multi-statement queries that have arguments or are meant to be prepared. -/// -/// If all looks good, then `Executor::fetch_many` can be made to exclusively call `ExecuteBatch` -/// and possibly error if query arguments are passed in or the query is meant to be prepared. -#[sqlx::test] -async fn test_multi_statement_in_fetch(mut con: PoolConnection) -> Result<(), BoxDynError> { - #[allow(dead_code)] - #[derive(FromRow)] - struct Test { - col: u8, - } - - // Compose the query and take out the arguments so preparing the statement is not attempted - let mut query = sqlx::query_as::<_, Test>("SELECT 1 as col; SELECT 2 as col; SELECT 3 as col;"); - query.take_arguments()?; - // This calls `fetch_many` internally and the call succeeds. - // If `fetch` gets called underneath, the request fails because it's a multi-statement query. - let mut stream = query.fetch(&mut *con); - while stream.try_next().await?.is_some() {} - - Ok(()) -} - #[sqlx::test] async fn it_works_on_large_datasets(mut con: PoolConnection) -> anyhow::Result<()> { sqlx::query("CREATE TABLE large_dataset (col1 VARCHAR(20), col2 VARCHAR(20));") diff --git a/tests/compile_time.rs b/tests/compile_time.rs index b56ca29f..65ee886c 100644 --- a/tests/compile_time.rs +++ b/tests/compile_time.rs @@ -3,13 +3,39 @@ //! cargo run -p sqlx-exasol-cli prepare -- --features runtime-tokio --tests //! ``` -#[sqlx_exasol::test] -async fn test_query( - mut conn: sqlx::pool::PoolConnection, +#[sqlx_exasol::test(migrations = "tests/migrations")] +#[ignore] +async fn test_compile_time_queries( + mut conn: sqlx_exasol::pool::PoolConnection, ) -> anyhow::Result<()> { - let x: Option = sqlx_exasol::query_scalar!("SELECT dummy FROM DUAL") - .fetch_one(&mut *conn) + struct User { + user_id: u64, + username: String, + } + + let username = "test"; + + sqlx_exasol::query!("INSERT INTO users (username) VALUES(?);", username) + .execute(&mut *conn) .await?; + let user_id: u64 = sqlx_exasol::query_scalar!( + r#"SELECT user_id as "user_id!" FROM users WHERE username = ?"#, + username + ) + .fetch_one(&mut *conn) + .await?; + + let user = sqlx_exasol::query_as!( + User, + r#"SELECT user_id as "user_id!", username as "username!" FROM users WHERE user_id = ?"#, + user_id + ) + .fetch_one(&mut *conn) + .await?; + + assert_eq!(user.user_id, user_id); + assert_eq!(user.username, username); + Ok(()) } diff --git a/tests/describe.rs b/tests/describe.rs index c3546eaa..b8f3ff2e 100644 --- a/tests/describe.rs +++ b/tests/describe.rs @@ -1,9 +1,9 @@ #![cfg(feature = "migrate")] -use sqlx::{pool::PoolConnection, Column, Executor, Type, TypeInfo}; +use sqlx_exasol::{pool::PoolConnection, Column, Executor, Type, TypeInfo}; use sqlx_exasol::Exasol; -#[sqlx::test(migrations = "tests/setup")] +#[sqlx_exasol::test(migrations = "tests/setup")] async fn it_describes_columns(mut conn: PoolConnection) -> anyhow::Result<()> { let d = conn.describe("SELECT * FROM tweet").await?; @@ -25,7 +25,7 @@ async fn it_describes_columns(mut conn: PoolConnection) -> anyhow::Resul Ok(()) } -#[sqlx::test(migrations = "tests/setup")] +#[sqlx_exasol::test(migrations = "tests/setup")] async fn it_describes_params(mut conn: PoolConnection) -> anyhow::Result<()> { conn.execute( r" @@ -55,7 +55,7 @@ CREATE TABLE with_hashtype_and_tinyint ( Ok(()) } -#[sqlx::test(migrations = "tests/setup")] +#[sqlx_exasol::test(migrations = "tests/setup")] async fn it_describes_columns_and_params(mut conn: PoolConnection) -> anyhow::Result<()> { conn.execute( r" @@ -102,7 +102,7 @@ CREATE TABLE with_hashtype_and_tinyint ( Ok(()) } -#[sqlx::test(migrations = "tests/setup")] +#[sqlx_exasol::test(migrations = "tests/setup")] async fn test_boolean(mut conn: PoolConnection) -> anyhow::Result<()> { conn.execute( r" @@ -132,7 +132,7 @@ CREATE TABLE with_hashtype_and_tinyint ( Ok(()) } -#[sqlx::test(migrations = "tests/setup")] +#[sqlx_exasol::test(migrations = "tests/setup")] async fn uses_alias_name(mut conn: PoolConnection) -> anyhow::Result<()> { let d = conn .describe("SELECT text AS tweet_text FROM tweet") diff --git a/tests/error.rs b/tests/error.rs index 3558b2fb..a69f9278 100644 --- a/tests/error.rs +++ b/tests/error.rs @@ -1,15 +1,15 @@ #![cfg(feature = "migrate")] -use sqlx::{error::ErrorKind, pool::PoolConnection}; +use sqlx_exasol::{error::ErrorKind, pool::PoolConnection}; use sqlx_exasol::Exasol; -#[sqlx::test(migrations = "tests/setup")] +#[sqlx_exasol::test(migrations = "tests/setup")] async fn it_fails_with_unique_violation(mut conn: PoolConnection) -> anyhow::Result<()> { - sqlx::query("INSERT INTO tweet(id, text, owner_id) VALUES (1, 'Foo', 1)") + sqlx_exasol::query("INSERT INTO tweet(id, text, owner_id) VALUES (1, 'Foo', 1)") .execute(&mut *conn) .await?; - let res: Result<_, sqlx::Error> = sqlx::query("INSERT INTO tweet VALUES (1, NOW(), 'Foo', 1);") + let res: Result<_, sqlx_exasol::Error> = sqlx_exasol::query("INSERT INTO tweet VALUES (1, NOW(), 'Foo', 1);") .execute(&mut *conn) .await; @@ -22,12 +22,12 @@ async fn it_fails_with_unique_violation(mut conn: PoolConnection) -> any Ok(()) } -#[sqlx::test(migrations = "tests/setup")] +#[sqlx_exasol::test(migrations = "tests/setup")] async fn it_fails_with_foreign_key_violation( mut conn: PoolConnection, ) -> anyhow::Result<()> { - let res: Result<_, sqlx::Error> = - sqlx::query("INSERT INTO tweet_reply (tweet_id, text) VALUES (1, 'Reply!');") + let res: Result<_, sqlx_exasol::Error> = + sqlx_exasol::query("INSERT INTO tweet_reply (tweet_id, text) VALUES (1, 'Reply!');") .execute(&mut *conn) .await; @@ -40,9 +40,9 @@ async fn it_fails_with_foreign_key_violation( Ok(()) } -#[sqlx::test(migrations = "tests/setup")] +#[sqlx_exasol::test(migrations = "tests/setup")] async fn it_fails_with_not_null_violation(mut conn: PoolConnection) -> anyhow::Result<()> { - let res: Result<_, sqlx::Error> = sqlx::query("INSERT INTO tweet (text) VALUES (null);") + let res: Result<_, sqlx_exasol::Error> = sqlx_exasol::query("INSERT INTO tweet (text) VALUES (null);") .execute(&mut *conn) .await; let err = res.unwrap_err(); diff --git a/tests/etl.rs b/tests/etl.rs index a99a6e01..ed9ce6a3 100644 --- a/tests/etl.rs +++ b/tests/etl.rs @@ -10,7 +10,7 @@ use futures_util::{ future::{try_join, try_join3, try_join_all}, AsyncReadExt, AsyncWriteExt, TryFutureExt, }; -use sqlx::{Connection, Executor}; +use sqlx_exasol::{Connection, Executor}; use sqlx_exasol::{ error::BoxDynError, etl::{ExaExport, ExaImport, ExportBuilder, ExportSource, ImportBuilder}, @@ -129,12 +129,12 @@ test_etl!( // ################ Failures ################ // ########################################## #[ignore] -#[sqlx::test] +#[sqlx_exasol::test] async fn test_etl_invalid_query(mut conn: PoolConnection) -> AnyResult<()> { conn.execute("CREATE TABLE TEST_ETL ( col VARCHAR(200) );") .await?; - sqlx::query("INSERT INTO TEST_ETL VALUES (?)") + sqlx_exasol::query("INSERT INTO TEST_ETL VALUES (?)") .bind(vec!["dummy"; NUM_ROWS]) .execute(&mut *conn) .await?; @@ -154,12 +154,12 @@ async fn test_etl_invalid_query(mut conn: PoolConnection) -> AnyResult<( } #[ignore] -#[sqlx::test] +#[sqlx_exasol::test] async fn test_etl_reader_drop(mut conn: PoolConnection) -> AnyResult<()> { conn.execute("CREATE TABLE TEST_ETL ( col VARCHAR(200) );") .await?; - sqlx::query("INSERT INTO TEST_ETL VALUES (?)") + sqlx_exasol::query("INSERT INTO TEST_ETL VALUES (?)") .bind(vec!["dummy"; NUM_ROWS]) .execute(&mut *conn) .await?; @@ -181,12 +181,12 @@ async fn test_etl_reader_drop(mut conn: PoolConnection) -> AnyResult<()> } #[ignore] -#[sqlx::test] +#[sqlx_exasol::test] async fn test_etl_transaction_import_rollback(mut conn: PoolConnection) -> AnyResult<()> { conn.execute("CREATE TABLE TEST_ETL ( col VARCHAR(200) );") .await?; - sqlx::query("INSERT INTO TEST_ETL VALUES (?)") + sqlx_exasol::query("INSERT INTO TEST_ETL VALUES (?)") .bind(vec!["dummy"; NUM_ROWS]) .execute(&mut *conn) .await?; @@ -203,7 +203,7 @@ async fn test_etl_transaction_import_rollback(mut conn: PoolConnection) tx.rollback().await?; - let num_rows: u64 = sqlx::query_scalar("SELECT COUNT(*) FROM TEST_ETL") + let num_rows: u64 = sqlx_exasol::query_scalar("SELECT COUNT(*) FROM TEST_ETL") .fetch_one(&mut *conn) .await?; @@ -213,12 +213,12 @@ async fn test_etl_transaction_import_rollback(mut conn: PoolConnection) } #[ignore] -#[sqlx::test] +#[sqlx_exasol::test] async fn test_etl_transaction_import_commit(mut conn: PoolConnection) -> AnyResult<()> { conn.execute("CREATE TABLE TEST_ETL ( col VARCHAR(200) );") .await?; - sqlx::query("INSERT INTO TEST_ETL VALUES (?)") + sqlx_exasol::query("INSERT INTO TEST_ETL VALUES (?)") .bind(vec!["dummy"; NUM_ROWS]) .execute(&mut *conn) .await?; @@ -236,7 +236,7 @@ async fn test_etl_transaction_import_commit(mut conn: PoolConnection) -> tx.commit().await?; - let num_rows: u64 = sqlx::query_scalar("SELECT COUNT(*) FROM TEST_ETL") + let num_rows: u64 = sqlx_exasol::query_scalar("SELECT COUNT(*) FROM TEST_ETL") .fetch_one(&mut *conn) .await?; @@ -250,7 +250,7 @@ async fn test_etl_transaction_import_commit(mut conn: PoolConnection) -> // // // // This will thus fail, because Exasol will just keep sending new requests. // #[ignore] -// #[sqlx::test] +// #[sqlx_exasol::test] // async fn test_etl_close_writer(mut conn: PoolConnection) -> AnyResult<()> { // // async fn pipe_close_writers(mut writer: ExaImport) -> AnyResult<()> { @@ -273,7 +273,7 @@ async fn test_etl_transaction_import_commit(mut conn: PoolConnection) -> // .await // .map_err(|e| anyhow::anyhow!("{e}"))?; -// let num_rows: u64 = sqlx::query_scalar("SELECT COUNT(*) FROM TEST_ETL") +// let num_rows: u64 = sqlx_exasol::query_scalar("SELECT COUNT(*) FROM TEST_ETL") // .fetch_one(&mut *conn) // .await?; diff --git a/tests/from_row.rs b/tests/from_row.rs index 663c8a15..6dfff330 100644 --- a/tests/from_row.rs +++ b/tests/from_row.rs @@ -1,6 +1,6 @@ #![cfg(feature = "migrate")] -use sqlx::{pool::PoolConnection, Executor, FromRow}; +use sqlx_exasol::{pool::PoolConnection, Executor, FromRow}; use sqlx_exasol::Exasol; #[derive(Debug, FromRow, PartialEq, Eq)] @@ -10,7 +10,7 @@ struct TestRow { amount: u64, } -#[sqlx::test] +#[sqlx_exasol::test] async fn test_from_row(mut conn: PoolConnection) -> anyhow::Result<()> { conn.execute( r" @@ -34,14 +34,14 @@ async fn test_from_row(mut conn: PoolConnection) -> anyhow::Result<()> { amount: 43_759_384_749, }; - sqlx::query("INSERT INTO TEST_FROM_ROW VALUES (?, ?, ?)") + sqlx_exasol::query("INSERT INTO TEST_FROM_ROW VALUES (?, ?, ?)") .bind([&test_row1.name, &test_row2.name]) .bind([&test_row1.age, &test_row2.age]) .bind([&test_row1.amount, &test_row2.amount]) .execute(&mut *conn) .await?; - let rows: Vec = sqlx::query_as("SELECT * FROM TEST_FROM_ROW ORDER BY age") + let rows: Vec = sqlx_exasol::query_as("SELECT * FROM TEST_FROM_ROW ORDER BY age") .fetch_all(&mut *conn) .await?; diff --git a/tests/macros.rs b/tests/macros.rs index 5678e6c6..c97d5baf 100644 --- a/tests/macros.rs +++ b/tests/macros.rs @@ -4,11 +4,11 @@ macro_rules! test_type_valid { ($name:ident<$ty:ty>::$datatype:literal::($($unprepared:expr => $prepared:expr),+)) => { paste::item! { - #[sqlx::test] + #[sqlx_exasol::test] async fn [< test_type_valid_ $name >] ( - mut con: sqlx::pool::PoolConnection, - ) -> Result<(), sqlx::error::BoxDynError> { - use sqlx::{Executor, query, query_scalar}; + mut con: sqlx_exasol::pool::PoolConnection, + ) -> Result<(), sqlx_exasol::error::BoxDynError> { + use sqlx_exasol::{Executor, query, query_scalar}; let create_sql = concat!("CREATE TABLE sqlx_test_type ( col ", $datatype, " );"); con.execute(create_sql).await?; @@ -61,11 +61,11 @@ macro_rules! test_type_valid { macro_rules! test_type_array { ($name:ident<$ty:ty>::$datatype:literal::($($prepared:expr),+)) => { paste::item! { - #[sqlx::test] + #[sqlx_exasol::test] async fn [< test_type_array_ $name >] ( - mut con: sqlx::pool::PoolConnection, - ) -> Result<(), sqlx::error::BoxDynError> { - use sqlx::{Executor, query, query_scalar}; + mut con: sqlx_exasol::pool::PoolConnection, + ) -> Result<(), sqlx_exasol::error::BoxDynError> { + use sqlx_exasol::{Executor, query, query_scalar}; let create_sql = concat!("CREATE TABLE sqlx_test_type ( col ", $datatype, " );"); con.execute(create_sql).await?; @@ -95,11 +95,11 @@ macro_rules! test_type_array { macro_rules! test_type_invalid { ($name:ident<$ty:ty>::$datatype:literal::($($prepared:expr),+)) => { paste::item! { - #[sqlx::test] + #[sqlx_exasol::test] async fn [< test_type_invalid_ $name >] ( - mut con: sqlx::pool::PoolConnection, - ) -> Result<(), sqlx::error::BoxDynError> { - use sqlx::{Executor, query, query_scalar}; + mut con: sqlx_exasol::pool::PoolConnection, + ) -> Result<(), sqlx_exasol::error::BoxDynError> { + use sqlx_exasol::{Executor, query, query_scalar}; let create_sql = concat!("CREATE TABLE sqlx_test_type ( col ", $datatype, " );"); con.execute(create_sql).await?; @@ -137,7 +137,7 @@ macro_rules! test_etl { paste::item! { $(#[$attr]),* #[ignore] - #[sqlx::test] + #[sqlx_exasol::test] async fn [< test_etl_ $kind _ $name >](pool_opts: PoolOptions, exa_opts: ExaConnectOptions) -> AnyResult<()> { let pool = pool_opts.min_connections(2).connect_with(exa_opts).await?; @@ -148,7 +148,7 @@ macro_rules! test_etl { .execute(concat!("CREATE TABLE ", $table, " ( col VARCHAR(200) );")) .await?; - sqlx::query(concat!("INSERT INTO ", $table, " VALUES (?)")) + sqlx_exasol::query(concat!("INSERT INTO ", $table, " VALUES (?)")) .bind(vec!["dummy"; NUM_ROWS]) .execute(&mut *conn1) .await?; @@ -164,7 +164,7 @@ macro_rules! test_etl { assert_eq!(NUM_ROWS as u64, export_res.rows_affected(), "exported rows"); assert_eq!(NUM_ROWS as u64, import_res.rows_affected(), "imported rows"); - let num_rows: u64 = sqlx::query_scalar(concat!("SELECT COUNT(*) FROM ", $table)) + let num_rows: u64 = sqlx_exasol::query_scalar(concat!("SELECT COUNT(*) FROM ", $table)) .fetch_one(&mut *conn1) .await?; diff --git a/tests/migrate.rs b/tests/migrate.rs index 8fd5257c..3f56ff53 100644 --- a/tests/migrate.rs +++ b/tests/migrate.rs @@ -2,10 +2,10 @@ use std::path::Path; -use sqlx::{migrate::Migrator, pool::PoolConnection, Executor, Row}; +use sqlx_exasol::{migrate::Migrator, pool::PoolConnection, Executor, Row}; use sqlx_exasol::{ExaConnection, Exasol}; -#[sqlx::test(migrations = false)] +#[sqlx_exasol::test(migrations = false)] async fn simple(mut conn: PoolConnection) -> anyhow::Result<()> { clean_up(&mut conn).await?; @@ -27,7 +27,7 @@ async fn simple(mut conn: PoolConnection) -> anyhow::Result<()> { Ok(()) } -#[sqlx::test(migrations = false)] +#[sqlx_exasol::test(migrations = false)] async fn reversible(mut conn: PoolConnection) -> anyhow::Result<()> { clean_up(&mut conn).await?; diff --git a/tests/migrations/2_post.sql b/tests/migrations/2_post.sql index e4c162ae..1448685d 100644 --- a/tests/migrations/2_post.sql +++ b/tests/migrations/2_post.sql @@ -8,11 +8,4 @@ CREATE TABLE post -- Not meant to do anything, but just test that query separation -- in migrations is done properly. --- We include the semicolon in the where clause because we test based on that. --- NOTE: Putting a semicolon in a comment such as this one will cause the migration --- to fail, because Exasol accepts a statement that is just a comment, but the --- second part of the comment, after the semicolon, will always fail. -DELETE FROM post WHERE ';' = ';' -; - - +DELETE FROM post WHERE ';' = ';'; diff --git a/tests/test-attr.rs b/tests/test-attr.rs index 46549d1c..a98795f9 100644 --- a/tests/test-attr.rs +++ b/tests/test-attr.rs @@ -2,13 +2,13 @@ use sqlx_exasol::ExaPool; -const MIGRATOR: sqlx::migrate::Migrator = sqlx::migrate!("tests/migrations"); +const MIGRATOR: sqlx_exasol::migrate::Migrator = sqlx_exasol::migrate!("tests/migrations"); -#[sqlx::test] -async fn it_gets_a_pool(pool: ExaPool) -> sqlx::Result<()> { +#[sqlx_exasol::test] +async fn it_gets_a_pool(pool: ExaPool) -> sqlx_exasol::Result<()> { let mut conn = pool.acquire().await?; - let db_name: String = sqlx::query_scalar("SELECT CURRENT_SCHEMA") + let db_name: String = sqlx_exasol::query_scalar("SELECT CURRENT_SCHEMA") .fetch_one(&mut *conn) .await?; @@ -18,22 +18,22 @@ async fn it_gets_a_pool(pool: ExaPool) -> sqlx::Result<()> { } // This should apply migrations and then `fixtures/users.sql` -#[sqlx::test(migrations = "tests/migrations", fixtures("users"))] -async fn it_gets_users(pool: ExaPool) -> sqlx::Result<()> { +#[sqlx_exasol::test(migrations = "tests/migrations", fixtures("users"))] +async fn it_gets_users(pool: ExaPool) -> sqlx_exasol::Result<()> { let usernames: Vec = - sqlx::query_scalar(r"SELECT username FROM users ORDER BY username") + sqlx_exasol::query_scalar(r"SELECT username FROM users ORDER BY username") .fetch_all(&pool) .await?; assert_eq!(usernames, ["alice", "bob"]); - let post_exists: bool = sqlx::query_scalar("SELECT exists(SELECT 1 FROM post)") + let post_exists: bool = sqlx_exasol::query_scalar("SELECT exists(SELECT 1 FROM post)") .fetch_one(&pool) .await?; assert!(!post_exists); - let comment_exists: bool = sqlx::query_scalar("SELECT exists(SELECT 1 FROM comment)") + let comment_exists: bool = sqlx_exasol::query_scalar("SELECT exists(SELECT 1 FROM comment)") .fetch_one(&pool) .await?; @@ -42,10 +42,10 @@ async fn it_gets_users(pool: ExaPool) -> sqlx::Result<()> { Ok(()) } -#[sqlx::test(migrations = "tests/migrations", fixtures("users", "posts"))] -async fn it_gets_posts(pool: ExaPool) -> sqlx::Result<()> { +#[sqlx_exasol::test(migrations = "tests/migrations", fixtures("users", "posts"))] +async fn it_gets_posts(pool: ExaPool) -> sqlx_exasol::Result<()> { let post_contents: Vec = - sqlx::query_scalar("SELECT content FROM post ORDER BY created_at") + sqlx_exasol::query_scalar("SELECT content FROM post ORDER BY created_at") .fetch_all(&pool) .await?; @@ -57,7 +57,7 @@ async fn it_gets_posts(pool: ExaPool) -> sqlx::Result<()> { ] ); - let comment_exists: bool = sqlx::query_scalar("SELECT exists(SELECT 1 FROM comment)") + let comment_exists: bool = sqlx_exasol::query_scalar("SELECT exists(SELECT 1 FROM comment)") .fetch_one(&pool) .await?; @@ -67,10 +67,10 @@ async fn it_gets_posts(pool: ExaPool) -> sqlx::Result<()> { } // Try `migrator` -#[sqlx::test(migrator = "MIGRATOR", fixtures("users", "posts", "comments"))] -async fn it_gets_comments(pool: ExaPool) -> sqlx::Result<()> { +#[sqlx_exasol::test(migrator = "MIGRATOR", fixtures("users", "posts", "comments"))] +async fn it_gets_comments(pool: ExaPool) -> sqlx_exasol::Result<()> { let post_1_comments: Vec = - sqlx::query_scalar("SELECT content FROM comment WHERE post_id = ? ORDER BY created_at") + sqlx_exasol::query_scalar("SELECT content FROM comment WHERE post_id = ? ORDER BY created_at") .bind(1) .fetch_all(&pool) .await?; @@ -81,7 +81,7 @@ async fn it_gets_comments(pool: ExaPool) -> sqlx::Result<()> { ); let post_2_comments: Vec = - sqlx::query_scalar("SELECT content FROM comment WHERE post_id = ? ORDER BY created_at") + sqlx_exasol::query_scalar("SELECT content FROM comment WHERE post_id = ? ORDER BY created_at") .bind(2) .fetch_all(&pool) .await?; From bc15bc56d104ebddbe3702d7bc5059e0f837fd55 Mon Sep 17 00:00:00 2001 From: Bogdan Mircea Date: Thu, 17 Jul 2025 19:02:13 +0300 Subject: [PATCH 020/102] tweak CLI and test that it's installable --- .github/workflows/ci.yaml | 11 +++------ sqlx-exasol-cli/Cargo.toml | 19 +++++++++++++-- sqlx-exasol-cli/src/cargo-sqlx-exasol.rs | 23 +++++++++++++++++++ .../src/{main.rs => sqlx-exasol.rs} | 0 4 files changed, 43 insertions(+), 10 deletions(-) create mode 100644 sqlx-exasol-cli/src/cargo-sqlx-exasol.rs rename sqlx-exasol-cli/src/{main.rs => sqlx-exasol.rs} (100%) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index d55e6f39..ef40a5ac 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -200,20 +200,15 @@ jobs: env: DATABASE_URL: ${{ steps.exa-cluster.outputs.no-tls-url }} - - name: ETL without TLS feature but TLS connection (should fail) - run: cargo test --features migrate,etl -- test_etl --ignored --nocapture || true - env: - DATABASE_URL: ${{ steps.exa-cluster.outputs.tls-url }} - - name: Native-TLS ETL tests timeout-minutes: ${{ fromJSON(env.TESTS_TIMEOUT) }} - run: cargo test --features migrate,compression,etl,tls-native-tls -- test_etl --ignored --nocapture + run: cargo test --features tls-native-tls,runtime-tokio,migrate,compression,etl -- test_etl --ignored --nocapture env: DATABASE_URL: ${{ steps.exa-cluster.outputs.tls-url }} - name: Rustls ETL tests timeout-minutes: ${{ fromJSON(env.TESTS_TIMEOUT) }} - run: cargo test --features migrate,compression,etl,tls-rustls-aws-lc-rs -- test_etl --ignored --nocapture + run: cargo test --features tls-rustls-aws-lc-rs,runtime-tokio,migrate,compression,etl -- test_etl --ignored --nocapture env: DATABASE_URL: ${{ steps.exa-cluster.outputs.tls-url }} @@ -245,4 +240,4 @@ jobs: run: cargo run -p sqlx-exasol-cli -- migrate run tests/migrations - name: Run tests - run: cargo test -- test_compile_time --ignored --nocapture \ No newline at end of file + run: cargo test --features runtime-tokio -- test_compile_time --ignored --nocapture \ No newline at end of file diff --git a/sqlx-exasol-cli/Cargo.toml b/sqlx-exasol-cli/Cargo.toml index ecb67d69..e7e8d71f 100644 --- a/sqlx-exasol-cli/Cargo.toml +++ b/sqlx-exasol-cli/Cargo.toml @@ -1,5 +1,6 @@ [package] name = "sqlx-exasol-cli" +description = "Command-line utility for sqlx-exasol." version.workspace = true license.workspace = true edition.workspace = true @@ -8,6 +9,16 @@ repository.workspace = true keywords.workspace = true categories.workspace = true authors.workspace = true +default-run = "sqlx-exasol" + +[[bin]] +name = "sqlx-exasol" +path = "src/sqlx-exasol.rs" + +# enables invocation as `cargo sqlx-exasol`; required for `prepare` subcommand +[[bin]] +name = "cargo-sqlx-exasol" +path = "src/cargo-sqlx-exasol.rs" [features] default = ["native-tls", "completions", "sqlx-toml"] @@ -22,10 +33,14 @@ completions = ["sqlx-cli/completions"] sqlx-toml = ["sqlx-cli/sqlx-toml"] [dependencies] -sqlx-exasol = { workspace = true } -sqlx-cli = { workspace = true } clap = { workspace = true } console = { workspace = true } +sqlx-exasol = { workspace = true, features = [ + "runtime-tokio", + "migrate", + "any", +] } +sqlx-cli = { workspace = true } tokio = { workspace = true } [lints] diff --git a/sqlx-exasol-cli/src/cargo-sqlx-exasol.rs b/sqlx-exasol-cli/src/cargo-sqlx-exasol.rs new file mode 100644 index 00000000..9ef0eb6f --- /dev/null +++ b/sqlx-exasol-cli/src/cargo-sqlx-exasol.rs @@ -0,0 +1,23 @@ +use clap::Parser; +use console::style; +use sqlx_cli::Opt; +use sqlx_exasol::any::DRIVER; + +/// Cargo invokes this binary as `cargo-sqlx-exasol sqlx-exasol ` +#[derive(Parser, Debug)] +#[clap(bin_name = "cargo")] +enum Cli { + SqlxExasol(Opt), +} + +#[tokio::main] +async fn main() { + sqlx_cli::maybe_apply_dotenv(); + sqlx_exasol::any::install_drivers(&[DRIVER]).expect("driver installation failed"); + let Cli::SqlxExasol(opt) = Cli::parse(); + + if let Err(error) = sqlx_cli::run(opt).await { + println!("{} {}", style("error:").bold().red(), error); + std::process::exit(1); + } +} diff --git a/sqlx-exasol-cli/src/main.rs b/sqlx-exasol-cli/src/sqlx-exasol.rs similarity index 100% rename from sqlx-exasol-cli/src/main.rs rename to sqlx-exasol-cli/src/sqlx-exasol.rs From c0fb9ad49aed1dab06f1e4338e9e9f87edba4dbc Mon Sep 17 00:00:00 2001 From: Bogdan Mircea Date: Thu, 17 Jul 2025 20:09:39 +0300 Subject: [PATCH 021/102] various fixes --- ...bf5923d1d6f9d6992535049cdfb0d7b2843bf.json | 30 ---- ...ef249429458b2ef54e46bce72f76a1ef63753.json | 40 ----- ...04ad3cf07b7ccd4f2b3b10e7eed32346b3855.json | 18 --- sqlx-exasol-impl/src/migrate.rs | 2 +- sqlx-exasol-impl/src/type_checking.rs | 2 - tests/compile_time.rs | 147 +++++++++++++++--- tests/migrations/1_user.sql | 2 +- tests/migrations_compile_time/1_setup.sql | 15 ++ 8 files changed, 141 insertions(+), 115 deletions(-) delete mode 100644 .sqlx/query-1798fb8c66823e371b0306d1cefbf5923d1d6f9d6992535049cdfb0d7b2843bf.json delete mode 100644 .sqlx/query-946d756b9e0821ef66916722e44ef249429458b2ef54e46bce72f76a1ef63753.json delete mode 100644 .sqlx/query-ea76fbbc69c1941fe6baba8969b04ad3cf07b7ccd4f2b3b10e7eed32346b3855.json create mode 100644 tests/migrations_compile_time/1_setup.sql diff --git a/.sqlx/query-1798fb8c66823e371b0306d1cefbf5923d1d6f9d6992535049cdfb0d7b2843bf.json b/.sqlx/query-1798fb8c66823e371b0306d1cefbf5923d1d6f9d6992535049cdfb0d7b2843bf.json deleted file mode 100644 index 2de3454b..00000000 --- a/.sqlx/query-1798fb8c66823e371b0306d1cefbf5923d1d6f9d6992535049cdfb0d7b2843bf.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "db_name": "Exasol", - "query": "SELECT user_id as \"user_id!\" FROM users WHERE username = ?", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "user_id!", - "dataType": { - "type": "DECIMAL", - "precision": 18, - "scale": 0 - } - } - ], - "parameters": { - "Left": [ - { - "type": "VARCHAR", - "size": 16, - "characterSet": "UTF8" - } - ] - }, - "nullable": [ - null - ] - }, - "hash": "1798fb8c66823e371b0306d1cefbf5923d1d6f9d6992535049cdfb0d7b2843bf" -} diff --git a/.sqlx/query-946d756b9e0821ef66916722e44ef249429458b2ef54e46bce72f76a1ef63753.json b/.sqlx/query-946d756b9e0821ef66916722e44ef249429458b2ef54e46bce72f76a1ef63753.json deleted file mode 100644 index d79589bf..00000000 --- a/.sqlx/query-946d756b9e0821ef66916722e44ef249429458b2ef54e46bce72f76a1ef63753.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "db_name": "Exasol", - "query": "SELECT user_id as \"user_id!\", username as \"username!\" FROM users WHERE user_id = ?", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "user_id!", - "dataType": { - "type": "DECIMAL", - "precision": 18, - "scale": 0 - } - }, - { - "ordinal": 0, - "name": "username!", - "dataType": { - "type": "VARCHAR", - "size": 16, - "characterSet": "UTF8" - } - } - ], - "parameters": { - "Left": [ - { - "type": "DECIMAL", - "precision": 18, - "scale": 0 - } - ] - }, - "nullable": [ - null, - null - ] - }, - "hash": "946d756b9e0821ef66916722e44ef249429458b2ef54e46bce72f76a1ef63753" -} diff --git a/.sqlx/query-ea76fbbc69c1941fe6baba8969b04ad3cf07b7ccd4f2b3b10e7eed32346b3855.json b/.sqlx/query-ea76fbbc69c1941fe6baba8969b04ad3cf07b7ccd4f2b3b10e7eed32346b3855.json deleted file mode 100644 index e08d7313..00000000 --- a/.sqlx/query-ea76fbbc69c1941fe6baba8969b04ad3cf07b7ccd4f2b3b10e7eed32346b3855.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "db_name": "Exasol", - "query": "INSERT INTO users (username) VALUES(?);", - "describe": { - "columns": [], - "parameters": { - "Left": [ - { - "type": "VARCHAR", - "size": 16, - "characterSet": "UTF8" - } - ] - }, - "nullable": [] - }, - "hash": "ea76fbbc69c1941fe6baba8969b04ad3cf07b7ccd4f2b3b10e7eed32346b3855" -} diff --git a/sqlx-exasol-impl/src/migrate.rs b/sqlx-exasol-impl/src/migrate.rs index b170d4de..61010200 100644 --- a/sqlx-exasol-impl/src/migrate.rs +++ b/sqlx-exasol-impl/src/migrate.rs @@ -72,7 +72,7 @@ impl MigrateDatabase for Exasol { let (options, database) = parse_for_maintenance(url)?; let mut conn = options.connect().await?; - let query = format!(r#"DROP SCHEMA IF EXISTS "{database}""#); + let query = format!(r#"DROP SCHEMA IF EXISTS "{database}" CASCADE;"#); let _ = conn.execute(&*query).await?; Ok(()) diff --git a/sqlx-exasol-impl/src/type_checking.rs b/sqlx-exasol-impl/src/type_checking.rs index 47ecfb00..29447722 100644 --- a/sqlx-exasol-impl/src/type_checking.rs +++ b/sqlx-exasol-impl/src/type_checking.rs @@ -45,8 +45,6 @@ impl_type_checking!( }, ParamChecking::Weak, feature-types: _info => None, - // The expansion of the macro automatically applies the correct feature name - // and checks `[macros.preferred-crates]` datetime-types: { chrono: { sqlx::types::chrono::NaiveDate, diff --git a/tests/compile_time.rs b/tests/compile_time.rs index 65ee886c..e086d4e7 100644 --- a/tests/compile_time.rs +++ b/tests/compile_time.rs @@ -3,39 +3,140 @@ //! cargo run -p sqlx-exasol-cli prepare -- --features runtime-tokio --tests //! ``` -#[sqlx_exasol::test(migrations = "tests/migrations")] +#[sqlx_exasol::test(migrations = "tests/migrations_compile_time")] #[ignore] async fn test_compile_time_queries( mut conn: sqlx_exasol::pool::PoolConnection, ) -> anyhow::Result<()> { - struct User { - user_id: u64, - username: String, - } - - let username = "test"; - - sqlx_exasol::query!("INSERT INTO users (username) VALUES(?);", username) - .execute(&mut *conn) - .await?; - - let user_id: u64 = sqlx_exasol::query_scalar!( - r#"SELECT user_id as "user_id!" FROM users WHERE username = ?"#, - username + sqlx_exasol::query!( + "INSERT INTO compile_time_tests (column_bool) VALUES(?);", + true ) - .fetch_one(&mut *conn) + .execute(&mut *conn) .await?; - let user = sqlx_exasol::query_as!( - User, - r#"SELECT user_id as "user_id!", username as "username!" FROM users WHERE user_id = ?"#, - user_id + sqlx_exasol::query!( + "INSERT INTO compile_time_tests (column_integer) VALUES(?);", + 10i8 + ) + .execute(&mut *conn) + .await?; + + sqlx_exasol::query!( + "INSERT INTO compile_time_tests (column_integer) VALUES(?);", + 10i16 + ) + .execute(&mut *conn) + .await?; + + sqlx_exasol::query!( + "INSERT INTO compile_time_tests (column_integer) VALUES(?);", + 10i32 + ) + .execute(&mut *conn) + .await?; + + sqlx_exasol::query!( + "INSERT INTO compile_time_tests (column_integer) VALUES(?);", + 10i64 + ) + .execute(&mut *conn) + .await?; + + sqlx_exasol::query!( + "INSERT INTO compile_time_tests (column_integer) VALUES(?);", + 10i128 + ) + .execute(&mut *conn) + .await?; + + sqlx_exasol::query!( + "INSERT INTO compile_time_tests (column_integer) VALUES(?);", + 10u8 + ) + .execute(&mut *conn) + .await?; + + sqlx_exasol::query!( + "INSERT INTO compile_time_tests (column_integer) VALUES(?);", + 10u16 ) - .fetch_one(&mut *conn) + .execute(&mut *conn) .await?; + + sqlx_exasol::query!( + "INSERT INTO compile_time_tests (column_integer) VALUES(?);", + 10u32 + ) + .execute(&mut *conn) + .await?; + + sqlx_exasol::query!( + "INSERT INTO compile_time_tests (column_integer) VALUES(?);", + 10u64 + ) + .execute(&mut *conn) + .await?; + + sqlx_exasol::query!( + "INSERT INTO compile_time_tests (column_integer) VALUES(?);", + 10u128 + ) + .execute(&mut *conn) + .await?; + + sqlx_exasol::query!( + "INSERT INTO compile_time_tests (column_float) VALUES(?);", + 15.3f32 + ) + .execute(&mut *conn) + .await?; + + sqlx_exasol::query!( + "INSERT INTO compile_time_tests (column_float) VALUES(?);", + 15.3f64 + ) + .execute(&mut *conn) + .await?; + + #[cfg(feature = "chrono")] + { + sqlx_exasol::query!( + "INSERT INTO compile_time_tests (column_date) VALUES(?);", + sqlx_exasol::types::chrono::NaiveDateTime::default().and_utc() + ) + .execute(&mut *conn) + .await?; + } + + // struct User { + // user_id: u64, + // username: String, + // } + + // let username = "test"; + + // sqlx_exasol::query!("INSERT INTO users (username) VALUES(?);", username) + // .execute(&mut *conn) + // .await?; + + // let user_id: u64 = sqlx_exasol::query_scalar!( + // r#"SELECT user_id as "user_id!" FROM users WHERE username = ?"#, + // username + // ) + // .fetch_one(&mut *conn) + // .await?; + + // let user = sqlx_exasol::query_as!( + // User, + // r#"SELECT user_id as "user_id!", username as "username!" FROM users WHERE user_id = ?"#, + // user_id + // ) + // .fetch_one(&mut *conn) + // .await?; - assert_eq!(user.user_id, user_id); - assert_eq!(user.username, username); + // assert_eq!(user.user_id, user_id); + // assert_eq!(user.username, username); Ok(()) } diff --git a/tests/migrations/1_user.sql b/tests/migrations/1_user.sql index c7019556..69ecc1ab 100644 --- a/tests/migrations/1_user.sql +++ b/tests/migrations/1_user.sql @@ -6,4 +6,4 @@ CREATE TABLE users -- Not meant to do anything, but just test that query separation -- in migrations is done properly. -DELETE FROM users WHERE 1 = 1; \ No newline at end of file +DELETE FROM users WHERE 1 = 1; diff --git a/tests/migrations_compile_time/1_setup.sql b/tests/migrations_compile_time/1_setup.sql new file mode 100644 index 00000000..5fe57455 --- /dev/null +++ b/tests/migrations_compile_time/1_setup.sql @@ -0,0 +1,15 @@ +CREATE TABLE compile_time_tests +( + column_bool BOOLEAN, + column_integer INTEGER, + column_float DOUBLE PRECISION, + column_date DATE, + column_timestamp TIMESTAMP, + column_timestamp_with_timezone TIMESTAMP WITH LOCAL TIME ZONE, + column_duration INTERVAL DAY TO SECOND, + column_months INTERVAL YEAR TO MONTH, + column_geometry GEOMETRY, + column_uuid HASHTYPE(16 BYTE), + column_char CHAR(16), + column_varchar VARCHAR(16) +); From 72b1463581b219761951cfb7377975f274cba35c Mon Sep 17 00:00:00 2001 From: Bogdan Mircea Date: Fri, 18 Jul 2025 12:29:13 +0300 Subject: [PATCH 022/102] compile time query fixes --- sqlx-exasol-cli/src/sqlx-exasol.rs | 1 - .../src/connection/websocket/future.rs | 3 +- sqlx-exasol-impl/src/type_checking.rs | 6 -- sqlx-exasol-impl/src/type_info.rs | 23 ++-- sqlx-exasol-impl/src/types/chrono.rs | 8 -- sqlx-exasol-impl/src/types/float.rs | 8 -- sqlx-exasol-impl/src/types/int.rs | 20 ---- sqlx-exasol-impl/src/types/iter.rs | 33 +----- sqlx-exasol-impl/src/types/mod.rs | 4 +- sqlx-exasol-impl/src/types/rust_decimal.rs | 10 +- sqlx-exasol-impl/src/types/str.rs | 8 -- sqlx-exasol-impl/src/types/text.rs | 4 - sqlx-exasol-impl/src/types/uint.rs | 20 ---- sqlx-exasol-impl/src/types/uuid.rs | 4 - tests/chrono.rs | 4 +- tests/common.rs | 4 +- tests/compile_time.rs | 102 +++++++++--------- tests/describe.rs | 2 +- tests/error.rs | 16 +-- tests/etl.rs | 2 +- tests/float.rs | 3 + tests/from_row.rs | 2 +- tests/int.rs | 5 + tests/migrations_compile_time/1_setup.sql | 8 +- tests/str.rs | 3 + tests/test-attr.rs | 22 ++-- tests/uint.rs | 5 + 27 files changed, 125 insertions(+), 205 deletions(-) diff --git a/sqlx-exasol-cli/src/sqlx-exasol.rs b/sqlx-exasol-cli/src/sqlx-exasol.rs index 562ed2a9..86911bd0 100644 --- a/sqlx-exasol-cli/src/sqlx-exasol.rs +++ b/sqlx-exasol-cli/src/sqlx-exasol.rs @@ -5,7 +5,6 @@ use sqlx_exasol::any::DRIVER; #[tokio::main] async fn main() { - sqlx_cli::maybe_apply_dotenv(); sqlx_exasol::any::install_drivers(&[DRIVER]).expect("driver installation failed"); let opt = Opt::parse(); diff --git a/sqlx-exasol-impl/src/connection/websocket/future.rs b/sqlx-exasol-impl/src/connection/websocket/future.rs index ae929d22..bcd4aa1d 100644 --- a/sqlx-exasol-impl/src/connection/websocket/future.rs +++ b/sqlx-exasol-impl/src/connection/websocket/future.rs @@ -19,6 +19,7 @@ use serde::{ de::{DeserializeOwned, IgnoredAny}, Serialize, }; +use sqlx_core::type_info::TypeInfo; use crate::{ connection::{ @@ -110,7 +111,7 @@ impl WebSocketFuture for ExecutePrepared<'_> { // and the ones expected by the database. let iter = std::iter::zip(prepared.parameters.as_ref(), &self.arguments.types); for (expected, provided) in iter { - if !expected.compatible(provided) { + if !expected.type_compatible(provided) { return Err(ExaProtocolError::DatatypeMismatch( expected.name, provided.name, diff --git a/sqlx-exasol-impl/src/type_checking.rs b/sqlx-exasol-impl/src/type_checking.rs index 29447722..8f53bc0a 100644 --- a/sqlx-exasol-impl/src/type_checking.rs +++ b/sqlx-exasol-impl/src/type_checking.rs @@ -25,17 +25,11 @@ impl DatabaseExt for Exasol { impl_type_checking!( Exasol { bool, - u8, - u16, - u32, - u64, - u128, i8, i16, i32, i64, i128, - f32, f64, String | &str, diff --git a/sqlx-exasol-impl/src/type_info.rs b/sqlx-exasol-impl/src/type_info.rs index 7cd1a98c..6a371f53 100644 --- a/sqlx-exasol-impl/src/type_info.rs +++ b/sqlx-exasol-impl/src/type_info.rs @@ -29,17 +29,6 @@ impl Serialize for ExaTypeInfo { } } -impl ExaTypeInfo { - /// Checks compatibility with other data types. - /// - /// Returns true if the [`ExaTypeInfo`] instance is compatible/bigger/able to - /// accommodate the `other` instance. - #[must_use] - pub fn compatible(&self, other: &Self) -> bool { - self.data_type.compatible(&other.data_type) - } -} - impl From for ExaTypeInfo { fn from(data_type: ExaDataType) -> Self { let name = data_type.full_name(); @@ -73,6 +62,16 @@ impl TypeInfo for ExaTypeInfo { fn name(&self) -> &str { self.name.as_ref() } + + /// Checks compatibility with other data types. + /// + /// Returns true if this [`ExaTypeInfo`] instance is able to accommodate the `other` instance. + fn type_compatible(&self, other: &Self) -> bool + where + Self: Sized, + { + self.data_type.compatible(&other.data_type) + } } /// Datatype definitions enum, as Exasol sees them. @@ -393,7 +392,7 @@ impl PartialOrd for Decimal { let scale_cmp = self.scale.partial_cmp(&other.scale); let diff_cmp = self_diff.partial_cmp(&other_diff); - #[allow(clippy::match_same_arms)] // false positive + #[allow(clippy::match_same_arms, reason = "better readability if split")] match (scale_cmp, diff_cmp) { (Some(Ordering::Greater), Some(Ordering::Greater)) => Some(Ordering::Greater), (Some(Ordering::Greater), Some(Ordering::Equal)) => Some(Ordering::Greater), diff --git a/sqlx-exasol-impl/src/types/chrono.rs b/sqlx-exasol-impl/src/types/chrono.rs index d3483c04..0737daa4 100644 --- a/sqlx-exasol-impl/src/types/chrono.rs +++ b/sqlx-exasol-impl/src/types/chrono.rs @@ -114,10 +114,6 @@ impl Type for Duration { ); ExaDataType::IntervalDayToSecond(ids).into() } - - fn compatible(ty: &ExaTypeInfo) -> bool { - >::type_info().compatible(ty) - } } impl Encode<'_, Exasol> for Duration { @@ -237,10 +233,6 @@ impl Type for Months { let iym = IntervalYearToMonth::new(IntervalYearToMonth::MAX_PRECISION); ExaDataType::IntervalYearToMonth(iym).into() } - - fn compatible(ty: &ExaTypeInfo) -> bool { - >::type_info().compatible(ty) - } } impl Encode<'_, Exasol> for Months { diff --git a/sqlx-exasol-impl/src/types/float.rs b/sqlx-exasol-impl/src/types/float.rs index d3ad1d28..23eaaad9 100644 --- a/sqlx-exasol-impl/src/types/float.rs +++ b/sqlx-exasol-impl/src/types/float.rs @@ -18,10 +18,6 @@ impl Type for f32 { fn type_info() -> ExaTypeInfo { ExaDataType::Double.into() } - - fn compatible(ty: &ExaTypeInfo) -> bool { - >::type_info().compatible(ty) - } } impl Encode<'_, Exasol> for f32 { @@ -57,10 +53,6 @@ impl Type for f64 { fn type_info() -> ExaTypeInfo { ExaDataType::Double.into() } - - fn compatible(ty: &ExaTypeInfo) -> bool { - >::type_info().compatible(ty) - } } impl Encode<'_, Exasol> for f64 { diff --git a/sqlx-exasol-impl/src/types/int.rs b/sqlx-exasol-impl/src/types/int.rs index 81ab28ae..d8b11488 100644 --- a/sqlx-exasol-impl/src/types/int.rs +++ b/sqlx-exasol-impl/src/types/int.rs @@ -30,10 +30,6 @@ impl Type for i8 { fn type_info() -> ExaTypeInfo { ExaDataType::Decimal(Decimal::new(Decimal::MAX_8BIT_PRECISION, 0)).into() } - - fn compatible(ty: &ExaTypeInfo) -> bool { - >::type_info().compatible(ty) - } } impl Encode<'_, Exasol> for i8 { @@ -63,10 +59,6 @@ impl Type for i16 { fn type_info() -> ExaTypeInfo { ExaDataType::Decimal(Decimal::new(Decimal::MAX_16BIT_PRECISION, 0)).into() } - - fn compatible(ty: &ExaTypeInfo) -> bool { - >::type_info().compatible(ty) - } } impl Encode<'_, Exasol> for i16 { @@ -96,10 +88,6 @@ impl Type for i32 { fn type_info() -> ExaTypeInfo { ExaDataType::Decimal(Decimal::new(Decimal::MAX_32BIT_PRECISION, 0)).into() } - - fn compatible(ty: &ExaTypeInfo) -> bool { - >::type_info().compatible(ty) - } } impl Encode<'_, Exasol> for i32 { @@ -129,10 +117,6 @@ impl Type for i64 { fn type_info() -> ExaTypeInfo { ExaDataType::Decimal(Decimal::new(Decimal::MAX_64BIT_PRECISION, 0)).into() } - - fn compatible(ty: &ExaTypeInfo) -> bool { - >::type_info().compatible(ty) - } } impl Encode<'_, Exasol> for i64 { @@ -172,10 +156,6 @@ impl Type for i128 { fn type_info() -> ExaTypeInfo { ExaDataType::Decimal(Decimal::new(Decimal::MAX_128BIT_PRECISION, 0)).into() } - - fn compatible(ty: &ExaTypeInfo) -> bool { - >::type_info().compatible(ty) - } } impl Encode<'_, Exasol> for i128 { diff --git a/sqlx-exasol-impl/src/types/iter.rs b/sqlx-exasol-impl/src/types/iter.rs index bdda9c8e..2d1fe3af 100644 --- a/sqlx-exasol-impl/src/types/iter.rs +++ b/sqlx-exasol-impl/src/types/iter.rs @@ -7,12 +7,11 @@ use sqlx_core::{ use crate::{arguments::ExaBuffer, Exasol}; -/// Adapter allowing any iterator of encodable values to be passed -/// as a parameter set / array to Exasol. +/// Adapter allowing any iterator of encodable values to be passed as a parameter set / array to +/// Exasol. /// -/// Note that the iterator must implement [`Clone`] because -/// it's used in multiple places. Therefore, prefer using iterators over -/// references than owning variants. +/// Note that the iterator must implement [`Clone`] because it's used in multiple places. Therefore, +/// prefer using iterators over references than owning variants. /// /// ```rust /// # use sqlx_exasol_impl as sqlx_exasol; @@ -53,10 +52,6 @@ where fn type_info() -> ::TypeInfo { T::type_info() } - - fn compatible(ty: &::TypeInfo) -> bool { - >::type_info().compatible(ty) - } } impl Encode<'_, Exasol> for ExaIter @@ -94,10 +89,6 @@ where fn type_info() -> ::TypeInfo { T::type_info() } - - fn compatible(ty: &::TypeInfo) -> bool { - >::type_info().compatible(ty) - } } impl Encode<'_, Exasol> for &[T] @@ -127,10 +118,6 @@ where fn type_info() -> ::TypeInfo { T::type_info() } - - fn compatible(ty: &::TypeInfo) -> bool { - >::type_info().compatible(ty) - } } impl Encode<'_, Exasol> for &mut [T] @@ -157,10 +144,6 @@ where fn type_info() -> ::TypeInfo { T::type_info() } - - fn compatible(ty: &::TypeInfo) -> bool { - >::type_info().compatible(ty) - } } impl Encode<'_, Exasol> for [T; N] @@ -187,10 +170,6 @@ where fn type_info() -> ::TypeInfo { T::type_info() } - - fn compatible(ty: &::TypeInfo) -> bool { - >::type_info().compatible(ty) - } } impl Encode<'_, Exasol> for Vec @@ -217,10 +196,6 @@ where fn type_info() -> ::TypeInfo { T::type_info() } - - fn compatible(ty: &::TypeInfo) -> bool { - >::type_info().compatible(ty) - } } impl Encode<'_, Exasol> for Box<[T]> diff --git a/sqlx-exasol-impl/src/types/mod.rs b/sqlx-exasol-impl/src/types/mod.rs index 18f44f23..66c81f13 100644 --- a/sqlx-exasol-impl/src/types/mod.rs +++ b/sqlx-exasol-impl/src/types/mod.rs @@ -13,9 +13,9 @@ mod uint; #[cfg(feature = "uuid")] mod uuid; -#[cfg(feature = "chrono")] -pub use chrono::Months; #[cfg(feature = "chrono")] pub use ::chrono::Duration; +#[cfg(feature = "chrono")] +pub use chrono::Months; pub use iter::ExaIter; diff --git a/sqlx-exasol-impl/src/types/rust_decimal.rs b/sqlx-exasol-impl/src/types/rust_decimal.rs index fb11f35d..b5b9de0a 100644 --- a/sqlx-exasol-impl/src/types/rust_decimal.rs +++ b/sqlx-exasol-impl/src/types/rust_decimal.rs @@ -18,17 +18,13 @@ const RUST_DECIMAL_MAX_SCALE: u32 = 28; impl Type for rust_decimal::Decimal { fn type_info() -> ExaTypeInfo { - // This is not a valid Exasol datatype defintion, - // but defining it like this means that we can accommodate - // almost any DECIMAL value when decoding - // (considering `rust_decimal` scale limitations) + // This is not a valid Exasol datatype defintion, but defining it like this means that we + // can accommodate almost any DECIMAL value when decoding (considering `rust_decimal` scale + // limitations) let precision = Decimal::MAX_PRECISION + RUST_DECIMAL_MAX_SCALE; let decimal = Decimal::new(precision, RUST_DECIMAL_MAX_SCALE); ExaDataType::Decimal(decimal).into() } - fn compatible(ty: &ExaTypeInfo) -> bool { - >::type_info().compatible(ty) - } } impl Encode<'_, Exasol> for rust_decimal::Decimal { diff --git a/sqlx-exasol-impl/src/types/str.rs b/sqlx-exasol-impl/src/types/str.rs index 921217e1..3824df79 100644 --- a/sqlx-exasol-impl/src/types/str.rs +++ b/sqlx-exasol-impl/src/types/str.rs @@ -20,10 +20,6 @@ impl Type for str { let string_like = StringLike::new(StringLike::MAX_VARCHAR_LEN, Charset::Utf8); ExaDataType::Varchar(string_like).into() } - - fn compatible(ty: &ExaTypeInfo) -> bool { - >::type_info().compatible(ty) - } } impl Encode<'_, Exasol> for &'_ str { @@ -58,10 +54,6 @@ impl Type for String { fn type_info() -> ExaTypeInfo { >::type_info() } - - fn compatible(ty: &ExaTypeInfo) -> bool { - >::compatible(ty) - } } impl Encode<'_, Exasol> for String { diff --git a/sqlx-exasol-impl/src/types/text.rs b/sqlx-exasol-impl/src/types/text.rs index e708c62f..80b25b3b 100644 --- a/sqlx-exasol-impl/src/types/text.rs +++ b/sqlx-exasol-impl/src/types/text.rs @@ -13,10 +13,6 @@ impl Type for Text { fn type_info() -> ExaTypeInfo { >::type_info() } - - fn compatible(ty: &ExaTypeInfo) -> bool { - >::compatible(ty) - } } impl Encode<'_, Exasol> for Text diff --git a/sqlx-exasol-impl/src/types/uint.rs b/sqlx-exasol-impl/src/types/uint.rs index 2ddb38ce..6203b66f 100644 --- a/sqlx-exasol-impl/src/types/uint.rs +++ b/sqlx-exasol-impl/src/types/uint.rs @@ -23,10 +23,6 @@ impl Type for u8 { fn type_info() -> ExaTypeInfo { ExaDataType::Decimal(Decimal::new(Decimal::MAX_8BIT_PRECISION, 0)).into() } - - fn compatible(ty: &ExaTypeInfo) -> bool { - >::type_info().compatible(ty) - } } impl Encode<'_, Exasol> for u8 { @@ -55,10 +51,6 @@ impl Type for u16 { fn type_info() -> ExaTypeInfo { ExaDataType::Decimal(Decimal::new(Decimal::MAX_16BIT_PRECISION, 0)).into() } - - fn compatible(ty: &ExaTypeInfo) -> bool { - >::type_info().compatible(ty) - } } impl Encode<'_, Exasol> for u16 { @@ -87,10 +79,6 @@ impl Type for u32 { fn type_info() -> ExaTypeInfo { ExaDataType::Decimal(Decimal::new(Decimal::MAX_32BIT_PRECISION, 0)).into() } - - fn compatible(ty: &ExaTypeInfo) -> bool { - >::type_info().compatible(ty) - } } impl Encode<'_, Exasol> for u32 { @@ -119,10 +107,6 @@ impl Type for u64 { fn type_info() -> ExaTypeInfo { ExaDataType::Decimal(Decimal::new(Decimal::MAX_64BIT_PRECISION, 0)).into() } - - fn compatible(ty: &ExaTypeInfo) -> bool { - >::type_info().compatible(ty) - } } impl Encode<'_, Exasol> for u64 { @@ -161,10 +145,6 @@ impl Type for u128 { fn type_info() -> ExaTypeInfo { ExaDataType::Decimal(Decimal::new(Decimal::MAX_128BIT_PRECISION, 0)).into() } - - fn compatible(ty: &ExaTypeInfo) -> bool { - >::type_info().compatible(ty) - } } impl Encode<'_, Exasol> for u128 { diff --git a/sqlx-exasol-impl/src/types/uuid.rs b/sqlx-exasol-impl/src/types/uuid.rs index fe6249c5..7fc62292 100644 --- a/sqlx-exasol-impl/src/types/uuid.rs +++ b/sqlx-exasol-impl/src/types/uuid.rs @@ -18,10 +18,6 @@ impl Type for Uuid { fn type_info() -> ExaTypeInfo { ExaDataType::HashType(HashType {}).into() } - - fn compatible(ty: &ExaTypeInfo) -> bool { - >::type_info().compatible(ty) - } } impl Encode<'_, Exasol> for Uuid { diff --git a/tests/chrono.rs b/tests/chrono.rs index ec93d272..2ddcbf49 100644 --- a/tests/chrono.rs +++ b/tests/chrono.rs @@ -3,7 +3,9 @@ mod macros; -use sqlx_exasol::types::chrono::{DateTime, Duration, Local, NaiveDate, NaiveDateTime, Utc, Months}; +use sqlx_exasol::types::chrono::{ + DateTime, Duration, Local, Months, NaiveDate, NaiveDateTime, Utc, +}; const TIMESTAMP_FMT: &str = "%Y-%m-%d %H:%M:%S%.6f"; const DATE_FMT: &str = "%Y-%m-%d"; diff --git a/tests/common.rs b/tests/common.rs index 773f0524..0d5cf801 100644 --- a/tests/common.rs +++ b/tests/common.rs @@ -124,7 +124,9 @@ async fn it_drops_results_in_affected_rows(mut conn: PoolConnection) -> #[sqlx_exasol::test] async fn it_selects_null(mut conn: PoolConnection) -> anyhow::Result<()> { - let (val,): (Option,) = sqlx_exasol::query_as("SELECT NULL").fetch_one(&mut *conn).await?; + let (val,): (Option,) = sqlx_exasol::query_as("SELECT NULL") + .fetch_one(&mut *conn) + .await?; assert!(val.is_none()); diff --git a/tests/compile_time.rs b/tests/compile_time.rs index e086d4e7..6a2249f0 100644 --- a/tests/compile_time.rs +++ b/tests/compile_time.rs @@ -3,8 +3,8 @@ //! cargo run -p sqlx-exasol-cli prepare -- --features runtime-tokio --tests //! ``` -#[sqlx_exasol::test(migrations = "tests/migrations_compile_time")] #[ignore] +#[sqlx_exasol::test(migrations = "tests/migrations_compile_time")] async fn test_compile_time_queries( mut conn: sqlx_exasol::pool::PoolConnection, ) -> anyhow::Result<()> { @@ -15,99 +15,99 @@ async fn test_compile_time_queries( .execute(&mut *conn) .await?; - sqlx_exasol::query!( - "INSERT INTO compile_time_tests (column_integer) VALUES(?);", - 10i8 + let _: bool = sqlx_exasol::query_scalar!( + r#"SELECT column_bool as "column_bool!" FROM compile_time_tests WHERE column_bool IS NOT NULL"#, ) - .execute(&mut *conn) + .fetch_one(&mut *conn) .await?; - + sqlx_exasol::query!( - "INSERT INTO compile_time_tests (column_integer) VALUES(?);", - 10i16 + "INSERT INTO compile_time_tests (column_i8) VALUES(?);", + 10i8 ) .execute(&mut *conn) .await?; - sqlx_exasol::query!( - "INSERT INTO compile_time_tests (column_integer) VALUES(?);", - 10i32 + let _: i8 = sqlx_exasol::query_scalar!( + r#"SELECT column_i8 as "column_i8!" FROM compile_time_tests WHERE column_i8 IS NOT NULL"#, ) - .execute(&mut *conn) + .fetch_one(&mut *conn) .await?; - + sqlx_exasol::query!( - "INSERT INTO compile_time_tests (column_integer) VALUES(?);", - 10i64 + "INSERT INTO compile_time_tests (column_i16) VALUES(?);", + 10i16 ) .execute(&mut *conn) .await?; - sqlx_exasol::query!( - "INSERT INTO compile_time_tests (column_integer) VALUES(?);", - 10i128 + let _: i16 = sqlx_exasol::query_scalar!( + r#"SELECT column_i16 as "column_i16!" FROM compile_time_tests WHERE column_i16 IS NOT NULL"#, ) - .execute(&mut *conn) + .fetch_one(&mut *conn) .await?; - + sqlx_exasol::query!( - "INSERT INTO compile_time_tests (column_integer) VALUES(?);", - 10u8 + "INSERT INTO compile_time_tests (column_i32) VALUES(?);", + 10i32 ) .execute(&mut *conn) .await?; - sqlx_exasol::query!( - "INSERT INTO compile_time_tests (column_integer) VALUES(?);", - 10u16 + let _: i32 = sqlx_exasol::query_scalar!( + r#"SELECT column_i32 as "column_i32!" FROM compile_time_tests WHERE column_i32 IS NOT NULL"#, ) - .execute(&mut *conn) + .fetch_one(&mut *conn) .await?; - + sqlx_exasol::query!( - "INSERT INTO compile_time_tests (column_integer) VALUES(?);", - 10u32 + "INSERT INTO compile_time_tests (column_i64) VALUES(?);", + 10i64 ) .execute(&mut *conn) .await?; - - sqlx_exasol::query!( - "INSERT INTO compile_time_tests (column_integer) VALUES(?);", - 10u64 + + let _: i64 = sqlx_exasol::query_scalar!( + r#"SELECT column_i64 as "column_i64!" FROM compile_time_tests WHERE column_i64 IS NOT NULL"#, ) - .execute(&mut *conn) + .fetch_one(&mut *conn) .await?; sqlx_exasol::query!( - "INSERT INTO compile_time_tests (column_integer) VALUES(?);", - 10u128 + "INSERT INTO compile_time_tests (column_i128) VALUES(?);", + 10i128 ) .execute(&mut *conn) .await?; - sqlx_exasol::query!( - "INSERT INTO compile_time_tests (column_float) VALUES(?);", - 15.3f32 + let _: i128 = sqlx_exasol::query_scalar!( + r#"SELECT column_i128 as "column_i128!" FROM compile_time_tests WHERE column_i128 IS NOT NULL"#, ) - .execute(&mut *conn) + .fetch_one(&mut *conn) .await?; - + sqlx_exasol::query!( - "INSERT INTO compile_time_tests (column_float) VALUES(?);", + "INSERT INTO compile_time_tests (column_f64) VALUES(?);", 15.3f64 ) .execute(&mut *conn) .await?; - #[cfg(feature = "chrono")] - { - sqlx_exasol::query!( - "INSERT INTO compile_time_tests (column_date) VALUES(?);", - sqlx_exasol::types::chrono::NaiveDateTime::default().and_utc() - ) - .execute(&mut *conn) - .await?; - } + let _: f64 = sqlx_exasol::query_scalar!( + r#"SELECT column_f64 as "column_f64!" FROM compile_time_tests WHERE column_f64 IS NOT NULL"#, + ) + .fetch_one(&mut *conn) + .await?; + + // #[cfg(feature = "chrono")] + // { + // sqlx_exasol::query!( + // "INSERT INTO compile_time_tests (column_date) VALUES(?);", + // sqlx_exasol::types::chrono::NaiveDateTime::default().and_utc() + // ) + // .execute(&mut *conn) + // .await?; + // } // struct User { // user_id: u64, diff --git a/tests/describe.rs b/tests/describe.rs index b8f3ff2e..e577dd6d 100644 --- a/tests/describe.rs +++ b/tests/describe.rs @@ -1,7 +1,7 @@ #![cfg(feature = "migrate")] -use sqlx_exasol::{pool::PoolConnection, Column, Executor, Type, TypeInfo}; use sqlx_exasol::Exasol; +use sqlx_exasol::{pool::PoolConnection, Column, Executor, Type, TypeInfo}; #[sqlx_exasol::test(migrations = "tests/setup")] async fn it_describes_columns(mut conn: PoolConnection) -> anyhow::Result<()> { diff --git a/tests/error.rs b/tests/error.rs index a69f9278..5fe33417 100644 --- a/tests/error.rs +++ b/tests/error.rs @@ -1,7 +1,7 @@ #![cfg(feature = "migrate")] -use sqlx_exasol::{error::ErrorKind, pool::PoolConnection}; use sqlx_exasol::Exasol; +use sqlx_exasol::{error::ErrorKind, pool::PoolConnection}; #[sqlx_exasol::test(migrations = "tests/setup")] async fn it_fails_with_unique_violation(mut conn: PoolConnection) -> anyhow::Result<()> { @@ -9,9 +9,10 @@ async fn it_fails_with_unique_violation(mut conn: PoolConnection) -> any .execute(&mut *conn) .await?; - let res: Result<_, sqlx_exasol::Error> = sqlx_exasol::query("INSERT INTO tweet VALUES (1, NOW(), 'Foo', 1);") - .execute(&mut *conn) - .await; + let res: Result<_, sqlx_exasol::Error> = + sqlx_exasol::query("INSERT INTO tweet VALUES (1, NOW(), 'Foo', 1);") + .execute(&mut *conn) + .await; let err = res.unwrap_err(); @@ -42,9 +43,10 @@ async fn it_fails_with_foreign_key_violation( #[sqlx_exasol::test(migrations = "tests/setup")] async fn it_fails_with_not_null_violation(mut conn: PoolConnection) -> anyhow::Result<()> { - let res: Result<_, sqlx_exasol::Error> = sqlx_exasol::query("INSERT INTO tweet (text) VALUES (null);") - .execute(&mut *conn) - .await; + let res: Result<_, sqlx_exasol::Error> = + sqlx_exasol::query("INSERT INTO tweet (text) VALUES (null);") + .execute(&mut *conn) + .await; let err = res.unwrap_err(); let err = err.into_database_error().unwrap(); diff --git a/tests/etl.rs b/tests/etl.rs index ed9ce6a3..d3b246c4 100644 --- a/tests/etl.rs +++ b/tests/etl.rs @@ -10,13 +10,13 @@ use futures_util::{ future::{try_join, try_join3, try_join_all}, AsyncReadExt, AsyncWriteExt, TryFutureExt, }; -use sqlx_exasol::{Connection, Executor}; use sqlx_exasol::{ error::BoxDynError, etl::{ExaExport, ExaImport, ExportBuilder, ExportSource, ImportBuilder}, pool::{PoolConnection, PoolOptions}, ExaConnectOptions, Exasol, }; +use sqlx_exasol::{Connection, Executor}; const NUM_ROWS: usize = 1_000_000; diff --git a/tests/float.rs b/tests/float.rs index 5f8d579f..7447b66f 100644 --- a/tests/float.rs +++ b/tests/float.rs @@ -5,9 +5,12 @@ mod macros; test_type_valid!(f32::"DOUBLE PRECISION"::(f32::MIN, f32::MAX)); test_type_valid!(f64::"DOUBLE PRECISION"::(-3.402_823_466_385_29e38_f64, 3.402_823_466_385_29e38_f64)); + test_type_valid!(f32_decimal::"DECIMAL(36, 16)"::(-1005.0456, 1005.0456, -7462.0, 7462.0)); test_type_valid!(f64_decimal::"DECIMAL(36, 16)"::(-1_005_213.045_654_3, 1_005_213.045_654_3, -1005.0456, 1005.0456, -7462.0, 7462.0)); + test_type_valid!(f64_option>::"DOUBLE PRECISION"::("NULL" => None::, -1_005_213.045_654_3 => Some(-1_005_213.045_654_3))); test_type_valid!(f64_decimal_option>::"DECIMAL(36, 16)"::("NULL" => None::, -1_005_213.045_654_3 => Some(-1_005_213.045_654_3))); + test_type_array!(f64_array::"DOUBLE PRECISION"::(vec![-1_005_213.045_654_3, 1_005_213.045_654_3, -1005.0456, 1005.0456, -7462.0, 7462.0])); test_type_array!(f64_decimal_array::"DECIMAL(36, 16)"::(vec![-1_005_213.045_654_3, 1_005_213.045_654_3, -1005.0456, 1005.0456, -7462.0, 7462.0])); diff --git a/tests/from_row.rs b/tests/from_row.rs index 6dfff330..9232bfc1 100644 --- a/tests/from_row.rs +++ b/tests/from_row.rs @@ -1,7 +1,7 @@ #![cfg(feature = "migrate")] -use sqlx_exasol::{pool::PoolConnection, Executor, FromRow}; use sqlx_exasol::Exasol; +use sqlx_exasol::{pool::PoolConnection, Executor, FromRow}; #[derive(Debug, FromRow, PartialEq, Eq)] struct TestRow { diff --git a/tests/int.rs b/tests/int.rs index f99a12c3..af2bd540 100644 --- a/tests/int.rs +++ b/tests/int.rs @@ -9,23 +9,28 @@ const MAX_I128_NUMERIC: i128 = 1_000_000_000_000_000_000; test_type_valid!(i8::"DECIMAL(3, 0)"::(i8::MIN, i8::MAX)); test_type_valid!(i8_into_smaller::"DECIMAL(1, 0)"::(-5, 5)); + test_type_valid!(i16::"DECIMAL(5, 0)"::(i16::MIN, i16::MAX, i16::from(i8::MIN), i16::from(i8::MAX))); test_type_valid!(i16_into_smaller::"DECIMAL(3, 0)"::(-12, 12)); test_type_valid!(i8_in_i16::"DECIMAL(5, 0)"::(i8::MIN => i16::from(i8::MIN), i8::MAX => i16::from(i8::MAX))); + test_type_valid!(i32::"DECIMAL(10, 0)"::(i32::MIN, i32::MAX, i32::from(i8::MIN), i32::from(i8::MAX), i32::from(i16::MIN), i32::from(i16::MAX))); test_type_valid!(i32_into_smaller::"DECIMAL(7, 0)"::(-12345, 12345)); test_type_valid!(i8_in_i32::"DECIMAL(10, 0)"::(i8::MIN => i32::from(i8::MIN), i8::MAX => i32::from(i8::MAX))); test_type_valid!(i16_in_i32::"DECIMAL(10, 0)"::(i16::MIN => i32::from(i16::MIN), i16::MAX => i32::from(i16::MAX))); + test_type_valid!(i64::"DECIMAL(20, 0)"::(i64::MIN, i64::MAX, i64::from(i8::MIN), i64::from(i8::MAX), i64::from(i16::MIN), i64::from(i16::MAX), i64::from(i32::MIN), i64::from(i32::MAX), MIN_I64_NUMERIC, MIN_I64_NUMERIC - 1, MAX_I64_NUMERIC, MAX_I64_NUMERIC - 1)); test_type_valid!(i64_into_smaller::"DECIMAL(15, 0)"::(-1_234_567_890, 1_234_567_890)); test_type_valid!(i8_in_i64::"DECIMAL(20, 0)"::(i8::MIN => i64::from(i8::MIN), i8::MAX => i64::from(i8::MAX))); test_type_valid!(i16_in_i64::"DECIMAL(20, 0)"::(i16::MIN => i64::from(i16::MIN), i16::MAX => i64::from(i16::MAX))); test_type_valid!(i32_in_i64::"DECIMAL(20, 0)"::(i32::MIN => i64::from(i32::MIN), i32::MAX => i64::from(i32::MAX))); + test_type_valid!(i128::"DECIMAL(36, 0)"::(-340_282_366_920_938_463_463_374_607_431_768_211i128, 340_282_366_920_938_463_463_374_607_431_768_211i128, i128::from(i8::MIN), i128::from(i8::MAX), i128::from(i16::MIN), i128::from(i16::MAX), i128::from(i32::MIN), i128::from(i32::MAX), MIN_I128_NUMERIC, MIN_I128_NUMERIC - 1, MAX_I128_NUMERIC, MAX_I128_NUMERIC - 1)); test_type_valid!(i128_into_smaller::"DECIMAL(15, 0)"::(-1_234_567_890i128, 1_234_567_890i128)); test_type_valid!(i8_in_i128::"DECIMAL(36, 0)"::(i8::MIN => i128::from(i8::MIN), i8::MAX => i128::from(i8::MAX))); test_type_valid!(i16_in_i128::"DECIMAL(36, 0)"::(i16::MIN => i128::from(i16::MIN), i16::MAX => i128::from(i16::MAX))); test_type_valid!(i32_in_i128::"DECIMAL(36, 0)"::(i32::MIN => i128::from(i32::MIN), i32::MAX => i128::from(i32::MAX))); test_type_valid!(i64_in_i128::"DECIMAL(36, 0)"::(i64::MIN => i128::from(i64::MIN), i64::MAX => i128::from(i64::MAX))); + test_type_valid!(i64_option>::"DECIMAL(20, 0)"::("NULL" => None::, i64::MAX => Some(i64::MAX))); test_type_array!(i64_array::"DECIMAL(20, 0)"::(vec![i64::MIN, i64::MAX, 1_234_567])); diff --git a/tests/migrations_compile_time/1_setup.sql b/tests/migrations_compile_time/1_setup.sql index 5fe57455..603e6d81 100644 --- a/tests/migrations_compile_time/1_setup.sql +++ b/tests/migrations_compile_time/1_setup.sql @@ -1,8 +1,12 @@ CREATE TABLE compile_time_tests ( column_bool BOOLEAN, - column_integer INTEGER, - column_float DOUBLE PRECISION, + column_i8 DECIMAL(3, 0), + column_i16 DECIMAL(5, 0), + column_i32 DECIMAL(10, 0), + column_i64 DECIMAL(20, 0), + column_i128 DECIMAL(36, 0), + column_f64 DOUBLE PRECISION, column_date DATE, column_timestamp TIMESTAMP, column_timestamp_with_timezone TIMESTAMP WITH LOCAL TIME ZONE, diff --git a/tests/str.rs b/tests/str.rs index b1d295f0..1ce65d56 100644 --- a/tests/str.rs +++ b/tests/str.rs @@ -4,9 +4,12 @@ mod macros; test_type_valid!(varchar_ascii::"VARCHAR(100) ASCII"::("'first value'" => "first value", "'second value'" => "second value")); test_type_valid!(varchar_utf8::"VARCHAR(100) UTF8"::("'first value 🦀'" => "first value 🦀", "'second value 🦀'" => "second value 🦀")); + test_type_valid!(char_ascii::"CHAR(10) ASCII"::("'first '" => "first ", "'second'" => "second ")); test_type_valid!(char_utf8::"CHAR(10) UTF8"::("'first 🦀 '" => "first 🦀 ", "'second 🦀'" => "second 🦀 ")); + test_type_valid!(varchar_option>::"VARCHAR(10) UTF8"::("''" => None::, "NULL" => None::, "'value'" => Some("value".to_owned()))); test_type_valid!(char_option>::"CHAR(10) UTF8"::("''" => None::, "NULL" => None::, "'value'" => Some("value ".to_owned()))); + test_type_array!(varchar_array::"VARCHAR(10) UTF8"::(vec!["abc".to_string(), "cde".to_string()])); test_type_array!(char_array::"CHAR(10) UTF8"::(vec!["abc".to_string(), "cde".to_string()])); diff --git a/tests/test-attr.rs b/tests/test-attr.rs index a98795f9..5dcdf4af 100644 --- a/tests/test-attr.rs +++ b/tests/test-attr.rs @@ -69,22 +69,24 @@ async fn it_gets_posts(pool: ExaPool) -> sqlx_exasol::Result<()> { // Try `migrator` #[sqlx_exasol::test(migrator = "MIGRATOR", fixtures("users", "posts", "comments"))] async fn it_gets_comments(pool: ExaPool) -> sqlx_exasol::Result<()> { - let post_1_comments: Vec = - sqlx_exasol::query_scalar("SELECT content FROM comment WHERE post_id = ? ORDER BY created_at") - .bind(1) - .fetch_all(&pool) - .await?; + let post_1_comments: Vec = sqlx_exasol::query_scalar( + "SELECT content FROM comment WHERE post_id = ? ORDER BY created_at", + ) + .bind(1) + .fetch_all(&pool) + .await?; assert_eq!( post_1_comments, ["lol bet ur still bad, 1v1 me", "you're on!"] ); - let post_2_comments: Vec = - sqlx_exasol::query_scalar("SELECT content FROM comment WHERE post_id = ? ORDER BY created_at") - .bind(2) - .fetch_all(&pool) - .await?; + let post_2_comments: Vec = sqlx_exasol::query_scalar( + "SELECT content FROM comment WHERE post_id = ? ORDER BY created_at", + ) + .bind(2) + .fetch_all(&pool) + .await?; assert_eq!(post_2_comments, ["lol you're just mad you lost :P"]); diff --git a/tests/uint.rs b/tests/uint.rs index 6b17b13c..b230517d 100644 --- a/tests/uint.rs +++ b/tests/uint.rs @@ -7,23 +7,28 @@ const MAX_U128_NUMERIC: u128 = 1_000_000_000_000_000_000; test_type_valid!(u8::"DECIMAL(3, 0)"::(u8::MIN, u8::MAX)); test_type_valid!(u8_into_smaller::"DECIMAL(1, 0)"::(u8::MIN, 5)); + test_type_valid!(u16::"DECIMAL(5, 0)"::(u16::MIN, u16::MAX, u16::from(u8::MAX))); test_type_valid!(u16_into_smaller::"DECIMAL(3, 0)"::(u16::MIN, 20)); test_type_valid!(u8_in_u16::"DECIMAL(5, 0)"::(u8::MIN => u16::from(u8::MIN), u8::MAX => u16::from(u8::MAX))); + test_type_valid!(u32::"DECIMAL(10, 0)"::(u32::MIN, u32::MAX, u32::from(u8::MAX), u32::from(u16::MAX))); test_type_valid!(u32_into_smaller::"DECIMAL(7, 0)"::(u32::MIN, 12345)); test_type_valid!(u8_in_u32::"DECIMAL(10, 0)"::(u8::MIN => u32::from(u8::MIN), u8::MAX => u32::from(u8::MAX))); test_type_valid!(u16_in_u32::"DECIMAL(10, 0)"::(u16::MIN => u32::from(u16::MIN), u16::MAX => u32::from(u16::MAX))); + test_type_valid!(u64::"DECIMAL(20, 0)"::(u64::MIN, u64::MAX, u64::from(u8::MAX), u64::from(u16::MAX), u64::from(u32::MAX), MAX_U64_NUMERIC, MAX_U64_NUMERIC - 1)); test_type_valid!(u64_into_smaller::"DECIMAL(15, 0)"::(u64::MIN, 123_457_890)); test_type_valid!(u8_in_u64::"DECIMAL(20, 0)"::(u8::MIN => u64::from(u8::MIN), u8::MAX => u64::from(u8::MAX))); test_type_valid!(u16_in_u64::"DECIMAL(20, 0)"::(u16::MIN => u64::from(u16::MIN), u16::MAX => u64::from(u16::MAX))); test_type_valid!(u32_in_u64::"DECIMAL(20, 0)"::(u32::MIN => u64::from(u32::MIN), u32::MAX => u64::from(u32::MAX))); + test_type_valid!(u128::"DECIMAL(36, 0)"::(u128::MIN, 340_282_366_920_938_463_463_374_607_431_768_211u128, u128::from(u8::MAX), u128::from(u16::MAX), u128::from(u32::MAX), MAX_U128_NUMERIC, MAX_U128_NUMERIC - 1)); test_type_valid!(u128_into_smaller::"DECIMAL(15, 0)"::(u128::MIN, 123_457_890_u128)); test_type_valid!(u8_in_u128::"DECIMAL(20, 0)"::(u8::MIN => u128::from(u8::MIN), u8::MAX => u128::from(u8::MAX))); test_type_valid!(u16_in_u128::"DECIMAL(20, 0)"::(u16::MIN => u128::from(u16::MIN), u16::MAX => u128::from(u16::MAX))); test_type_valid!(u32_in_u128::"DECIMAL(20, 0)"::(u32::MIN => u128::from(u32::MIN), u32::MAX => u128::from(u32::MAX))); test_type_valid!(u64_in_u128::"DECIMAL(20, 0)"::(u64::MIN => u128::from(u64::MIN), u64::MAX => u128::from(u64::MAX))); + test_type_valid!(u64_option>::"DECIMAL(20, 0)"::("NULL" => None::, u64::MAX => Some(u64::MAX))); test_type_array!(u64_array::"DECIMAL(20, 0)"::(vec![u64::MIN, u64::MAX, 1_234_567])); From f0f0fe48b2d096957409843e246b677bf98bbbda Mon Sep 17 00:00:00 2001 From: Bogdan Mircea Date: Fri, 18 Jul 2025 20:50:34 +0300 Subject: [PATCH 023/102] tweak array/iters impls --- sqlx-exasol-impl/src/type_info.rs | 3 +- sqlx-exasol-impl/src/types/iter.rs | 241 +++++++++-------------------- src/lib.rs | 5 +- src/ty_match.rs | 166 ++++++++++++++++++++ tests/bool.rs | 2 +- tests/compile_time.rs | 28 +++- 6 files changed, 263 insertions(+), 182 deletions(-) create mode 100644 src/ty_match.rs diff --git a/sqlx-exasol-impl/src/type_info.rs b/sqlx-exasol-impl/src/type_info.rs index 6a371f53..3655f811 100644 --- a/sqlx-exasol-impl/src/type_info.rs +++ b/sqlx-exasol-impl/src/type_info.rs @@ -281,8 +281,7 @@ impl StringLike { self.character_set } - /// Strings are complex and ensuring one fits inside - /// a database column would imply a lot of overhead. + /// Strings are complex and ensuring one fits inside a database column would imply a lot of overhead. /// /// So just let the database do its thing and throw an error. #[allow(clippy::unused_self)] diff --git a/sqlx-exasol-impl/src/types/iter.rs b/sqlx-exasol-impl/src/types/iter.rs index 2d1fe3af..a49fe747 100644 --- a/sqlx-exasol-impl/src/types/iter.rs +++ b/sqlx-exasol-impl/src/types/iter.rs @@ -1,3 +1,5 @@ +use std::{rc::Rc, sync::Arc}; + use sqlx_core::{ database::Database, encode::{Encode, IsNull}, @@ -7,210 +9,107 @@ use sqlx_core::{ use crate::{arguments::ExaBuffer, Exasol}; -/// Adapter allowing any iterator of encodable values to be passed as a parameter set / array to -/// Exasol. +/// Adapter allowing any iterator of encodable values to be passed as a parameter array for a column +/// to Exasol in a single query invocation. The adapter is needed because [`Encode`] is still a +/// foreign trait and thus cannot be implemented in a generic manner. /// -/// Note that the iterator must implement [`Clone`] because it's used in multiple places. Therefore, -/// prefer using iterators over references than owning variants. +/// Note that the [`Encode`] trait requires the ability to encode by reference, thus the adapter +/// takes a reference that must implement [`IntoIterator`]. /// /// ```rust /// # use sqlx_exasol_impl as sqlx_exasol; /// use sqlx_exasol::types::ExaIter; /// -/// // Don't do this, as the iterator gets cloned internally. -/// let vector = vec![1, 2, 3]; -/// let owned_iter = ExaIter::from(vector); -/// -/// // Rather, prefer using something cheaper to clone, like: /// let vector = vec![1, 2, 3]; -/// let borrowed_iter = ExaIter::from(vector.as_slice()); +/// let borrowed_iter = ExaIter::new(vector.as_slice()); /// ``` #[derive(Debug)] -pub struct ExaIter +#[repr(transparent)] +pub struct ExaIter<'i, I>(&'i I) where - I: IntoIterator + Clone, - for<'q> T: Encode<'q, Exasol> + Type, -{ - value: I, -} + I: ?Sized, + &'i I: IntoIterator, + <&'i I as IntoIterator>::Item: for<'q> Encode<'q, Exasol> + Type; -impl From for ExaIter +impl<'i, I> ExaIter<'i, I> where - I: IntoIterator + Clone, - for<'q> T: Encode<'q, Exasol> + Type, + I: ?Sized, + &'i I: IntoIterator, + <&'i I as IntoIterator>::Item: for<'q> Encode<'q, Exasol> + Type, { - fn from(value: I) -> Self { - Self { value } + pub fn new(into_iter: &'i I) -> Self { + Self(into_iter) } } -impl Type for ExaIter +impl<'i, I> Type for ExaIter<'i, I> where - I: IntoIterator + Clone, - for<'q> T: Encode<'q, Exasol> + Type, + I: ?Sized, + &'i I: IntoIterator, + <&'i I as IntoIterator>::Item: for<'q> Encode<'q, Exasol> + Type, { fn type_info() -> ::TypeInfo { - T::type_info() + <&'i I as IntoIterator>::Item::type_info() } } -impl Encode<'_, Exasol> for ExaIter +impl<'i, I> Encode<'_, Exasol> for ExaIter<'i, I> where - I: IntoIterator + Clone, - for<'q> T: Encode<'q, Exasol> + Type, + I: ?Sized, + &'i I: IntoIterator, + <&'i I as IntoIterator>::Item: for<'q> Encode<'q, Exasol> + Type, { - fn produces(&self) -> Option<::TypeInfo> { - self.value - .clone() - .into_iter() - .next() - .as_ref() - .and_then(Encode::produces) - .or_else(|| Some(T::type_info())) - } - fn encode_by_ref(&self, buf: &mut ExaBuffer) -> Result { - buf.append_iter(self.value.clone())?; + buf.append_iter(self.0)?; Ok(IsNull::No) } fn size_hint(&self) -> usize { - self.value - .clone() + self.0 .into_iter() .fold(0, |sum, item| sum + item.size_hint()) } } -impl<'a, T> Type for &'a [T] -where - T: Type + 'a, -{ - fn type_info() -> ::TypeInfo { - T::type_info() - } -} - -impl Encode<'_, Exasol> for &[T] -where - for<'q> T: Encode<'q, Exasol> + Type, -{ - fn produces(&self) -> Option<::TypeInfo> { - self.first() - .and_then(Encode::produces) - .or_else(|| Some(T::type_info())) - } - - fn encode_by_ref(&self, buf: &mut ExaBuffer) -> Result { - buf.append_iter(self.iter())?; - Ok(IsNull::No) - } - - fn size_hint(&self) -> usize { - self.iter().fold(0, |sum, item| sum + item.size_hint()) - } -} - -impl<'a, T> Type for &'a mut [T] -where - T: Type + 'a, -{ - fn type_info() -> ::TypeInfo { - T::type_info() - } +macro_rules! impl_for_iterator { + ($ty:ty, deref => { $($deref:tt)* }, bounds => $($bounds:tt)*) => { + impl Type for $ty + where + T: Type, + { + fn type_info() -> ::TypeInfo { + T::type_info() + } + } + + impl Encode<'_, Exasol> for $ty + where + for<'q> T: Encode<'q, Exasol> + Type, + { + fn encode_by_ref(&self, buf: &mut ExaBuffer) -> Result { + ExaIter::new($($deref)* self).encode_by_ref(buf) + } + + fn size_hint(&self) -> usize { + ExaIter::new($($deref)* self).size_hint() + } + } + }; + ($ty:ty, bounds => $($bounds:tt)*) => { + impl_for_iterator!($ty, deref => {}, bounds => $($bounds)*); + }; + ($ty:ty, deref => $($deref:tt)*) => { + impl_for_iterator!($ty, deref => { $($deref)* }, bounds => ); + }; + ($ty:ty) => { + impl_for_iterator!($ty, deref => {}, bounds => ); + }; } -impl Encode<'_, Exasol> for &mut [T] -where - for<'q> T: Encode<'q, Exasol> + Type, -{ - fn produces(&self) -> Option<::TypeInfo> { - (&**self).produces() - } - - fn encode_by_ref(&self, buf: &mut ExaBuffer) -> Result { - (&**self).encode_by_ref(buf) - } - - fn size_hint(&self) -> usize { - (&**self).size_hint() - } -} - -impl Type for [T; N] -where - T: Type, -{ - fn type_info() -> ::TypeInfo { - T::type_info() - } -} - -impl Encode<'_, Exasol> for [T; N] -where - for<'q> T: Encode<'q, Exasol> + Type, -{ - fn produces(&self) -> Option<::TypeInfo> { - self.as_slice().produces() - } - - fn encode_by_ref(&self, buf: &mut ExaBuffer) -> Result { - self.as_slice().encode_by_ref(buf) - } - - fn size_hint(&self) -> usize { - self.as_slice().size_hint() - } -} - -impl Type for Vec -where - T: Type, -{ - fn type_info() -> ::TypeInfo { - T::type_info() - } -} - -impl Encode<'_, Exasol> for Vec -where - for<'q> T: Encode<'q, Exasol> + Type, -{ - fn produces(&self) -> Option<::TypeInfo> { - (&**self).produces() - } - - fn encode_by_ref(&self, buf: &mut ExaBuffer) -> Result { - (&**self).encode_by_ref(buf) - } - - fn size_hint(&self) -> usize { - (&**self).size_hint() - } -} - -impl Type for Box<[T]> -where - T: Type, -{ - fn type_info() -> ::TypeInfo { - T::type_info() - } -} - -impl Encode<'_, Exasol> for Box<[T]> -where - for<'q> T: Encode<'q, Exasol> + Type, -{ - fn produces(&self) -> Option<::TypeInfo> { - (&**self).produces() - } - - fn encode_by_ref(&self, buf: &mut ExaBuffer) -> Result { - (&**self).encode_by_ref(buf) - } - - fn size_hint(&self) -> usize { - (&**self).size_hint() - } -} +impl_for_iterator!(&[T], deref => *); +impl_for_iterator!(&mut [T], deref => *); +impl_for_iterator!([T; N], bounds => const N: usize); +impl_for_iterator!(Vec); +impl_for_iterator!(Box<[T]>, deref => &**); +impl_for_iterator!(Rc<[T]>, deref => &**); +impl_for_iterator!(Arc<[T]>, deref => &**); diff --git a/src/lib.rs b/src/lib.rs index 2917f093..f8269f3a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -240,5 +240,6 @@ pub use sqlx_exasol_macros; #[cfg(feature = "macros")] mod macros; -// TODO: -// - Amend PreparedStatement with a wrapper of ExaTypeInfo +#[cfg(feature = "macros")] +#[doc(hidden)] +pub mod ty_match; diff --git a/src/ty_match.rs b/src/ty_match.rs new file mode 100644 index 00000000..67845b01 --- /dev/null +++ b/src/ty_match.rs @@ -0,0 +1,166 @@ +//! Similar module to `sqlx::ty_match` overridden so that we can allow array types in compile-time +//! statements as well. Most of the code is identical to the original. +use std::marker::PhantomData; + +#[allow(clippy::just_underscores_and_digits)] +pub fn same_type(_1: &T, _2: &T) {} + +pub struct WrapSame(PhantomData, PhantomData); + +impl WrapSame { + pub fn new(_arg: &U) -> Self { + WrapSame(PhantomData, PhantomData) + } +} + +pub trait WrapSameExt: Sized { + type Wrapped; + + fn wrap_same(self) -> Self::Wrapped { + panic!("only for type resolution") + } +} + +impl WrapSameExt for WrapSame> { + type Wrapped = Option; +} + +impl WrapSameExt for &'_ WrapSame { + type Wrapped = T; +} + +pub struct MatchBorrow(PhantomData, PhantomData); + +impl MatchBorrow { + pub fn new(t: T, _u: &U) -> (T, Self) { + (t, MatchBorrow(PhantomData, PhantomData)) + } +} + +pub trait MatchBorrowExt: Sized { + type Matched; + + fn match_borrow(self) -> Self::Matched { + panic!("only for type resolution") + } +} + +// ############################## +// ######## BEGIN CUSTOM ######## +// ############################## + +impl<'a, T> MatchBorrowExt for MatchBorrow, Option>> { + type Matched = Option<&'a T>; +} + +impl<'a, T> MatchBorrowExt for MatchBorrow, Option<&'a Vec>> { + type Matched = Option<&'a T>; +} + +impl MatchBorrowExt for MatchBorrow> { + type Matched = T; +} + +impl<'a, T> MatchBorrowExt for MatchBorrow<&'a T, Vec> { + type Matched = &'a T; +} + +// ############################## +// ######### END CUSTOM ######### +// ############################## + +impl<'a> MatchBorrowExt for MatchBorrow, Option> { + type Matched = Option<&'a str>; +} + +impl<'a> MatchBorrowExt for MatchBorrow, Option<&'a String>> { + type Matched = Option<&'a str>; +} + +impl<'a> MatchBorrowExt for MatchBorrow<&'a str, String> { + type Matched = &'a str; +} + +impl MatchBorrowExt for MatchBorrow<&'_ T, T> { + type Matched = T; +} + +impl MatchBorrowExt for MatchBorrow<&'_ &'_ T, T> { + type Matched = T; +} + +impl MatchBorrowExt for MatchBorrow { + type Matched = T; +} + +impl MatchBorrowExt for MatchBorrow { + type Matched = T; +} + +impl MatchBorrowExt for MatchBorrow, Option> { + type Matched = Option; +} + +impl MatchBorrowExt for MatchBorrow, Option> { + type Matched = Option; +} + +impl MatchBorrowExt for MatchBorrow, Option<&'_ T>> { + type Matched = Option; +} + +impl MatchBorrowExt for MatchBorrow, Option<&'_ &'_ T>> { + type Matched = Option; +} + +impl MatchBorrowExt for &'_ MatchBorrow { + type Matched = U; +} + +pub fn conjure_value() -> T { + panic!() +} + +pub fn dupe_value(_t: &T) -> T { + panic!() +} + +#[test] +fn test_dupe_value() { + let val = &(String::new(),); + + if false { + let _: i32 = dupe_value(&0i32); + let _: String = dupe_value(&String::new()); + let _: String = dupe_value(&val.0); + } +} + +#[test] +fn test_wrap_same() { + if false { + let _: i32 = WrapSame::::new(&0i32).wrap_same(); + let _: i32 = WrapSame::::new(&"hello, world!").wrap_same(); + let _: Option = WrapSame::::new(&Some(String::new())).wrap_same(); + } +} + +#[test] +fn test_match_borrow() { + if false { + let (_, match_borrow) = MatchBorrow::new("", &String::new()); + let _: &str = match_borrow.match_borrow(); + + let (_, match_borrow) = MatchBorrow::new(&&0i64, &0i64); + let _: i64 = match_borrow.match_borrow(); + + let (_, match_borrow) = MatchBorrow::new(&0i64, &0i64); + let _: i64 = match_borrow.match_borrow(); + + let (_, match_borrow) = MatchBorrow::new(0i64, &0i64); + let _: i64 = match_borrow.match_borrow(); + + let (_, match_borrow) = MatchBorrow::new(0i64, &vec![0i64; 1]); + let _: i64 = match_borrow.match_borrow(); + } +} diff --git a/tests/bool.rs b/tests/bool.rs index b5e062ab..781a48ea 100644 --- a/tests/bool.rs +++ b/tests/bool.rs @@ -8,6 +8,6 @@ use sqlx_exasol::types::ExaIter; test_type_valid!(bool::"BOOLEAN"::(false, true)); test_type_valid!(bool_option>::"BOOLEAN"::("NULL" => None::, "true" => Some(true))); -test_type_array!(bool_array::"BOOLEAN"::(vec![true, false], Vec::::new(), Some(vec![true, false]), [false; 4], &[false; 4], vec![true, false].into_boxed_slice(), ExaIter::from(HashSet::from([true, false, true]).iter()))); +test_type_array!(bool_array::"BOOLEAN"::(vec![true, false], Vec::::new(), Some(vec![true, false]), [false; 4], &[false; 4], vec![true, false].into_boxed_slice(), ExaIter::new(&HashSet::from([true, false, true])))); test_type_array!(bool_array_option>::"BOOLEAN"::(vec![Some(true), Some(false), None])); test_type_array!(bool_empty_array>::"BOOLEAN"::(Vec::::new())); diff --git a/tests/compile_time.rs b/tests/compile_time.rs index 6a2249f0..6e14058d 100644 --- a/tests/compile_time.rs +++ b/tests/compile_time.rs @@ -3,6 +3,8 @@ //! cargo run -p sqlx-exasol-cli prepare -- --features runtime-tokio --tests //! ``` +extern crate sqlx_exasol as sqlx; + #[ignore] #[sqlx_exasol::test(migrations = "tests/migrations_compile_time")] async fn test_compile_time_queries( @@ -27,7 +29,7 @@ async fn test_compile_time_queries( ) .execute(&mut *conn) .await?; - + let _: i8 = sqlx_exasol::query_scalar!( r#"SELECT column_i8 as "column_i8!" FROM compile_time_tests WHERE column_i8 IS NOT NULL"#, ) @@ -40,7 +42,7 @@ async fn test_compile_time_queries( ) .execute(&mut *conn) .await?; - + let _: i16 = sqlx_exasol::query_scalar!( r#"SELECT column_i16 as "column_i16!" FROM compile_time_tests WHERE column_i16 IS NOT NULL"#, ) @@ -53,7 +55,7 @@ async fn test_compile_time_queries( ) .execute(&mut *conn) .await?; - + let _: i32 = sqlx_exasol::query_scalar!( r#"SELECT column_i32 as "column_i32!" FROM compile_time_tests WHERE column_i32 IS NOT NULL"#, ) @@ -72,14 +74,14 @@ async fn test_compile_time_queries( ) .fetch_one(&mut *conn) .await?; - + sqlx_exasol::query!( "INSERT INTO compile_time_tests (column_i128) VALUES(?);", 10i128 ) .execute(&mut *conn) .await?; - + let _: i128 = sqlx_exasol::query_scalar!( r#"SELECT column_i128 as "column_i128!" FROM compile_time_tests WHERE column_i128 IS NOT NULL"#, ) @@ -92,7 +94,21 @@ async fn test_compile_time_queries( ) .execute(&mut *conn) .await?; - + + let _: f64 = sqlx_exasol::query_scalar!( + r#"SELECT column_f64 as "column_f64!" FROM compile_time_tests WHERE column_f64 IS NOT NULL"#, + ) + .fetch_one(&mut *conn) + .await?; + + sqlx_exasol::query!( + "INSERT INTO compile_time_tests (column_i8, column_f64) VALUES(?, ?);", + vec![10i8], + 15.3f64 + ) + .execute(&mut *conn) + .await?; + let _: f64 = sqlx_exasol::query_scalar!( r#"SELECT column_f64 as "column_f64!" FROM compile_time_tests WHERE column_f64 IS NOT NULL"#, ) From 7d4e1647e0b095bc7f4f6fcccd2660a708845230 Mon Sep 17 00:00:00 2001 From: Bogdan Mircea Date: Fri, 18 Jul 2025 21:05:59 +0300 Subject: [PATCH 024/102] add array types to compile-time support --- sqlx-exasol-impl/src/types/iter.rs | 1 - src/ty_match.rs | 79 ++++++++++++++++++++++++++++-- 2 files changed, 76 insertions(+), 4 deletions(-) diff --git a/sqlx-exasol-impl/src/types/iter.rs b/sqlx-exasol-impl/src/types/iter.rs index a49fe747..7e736119 100644 --- a/sqlx-exasol-impl/src/types/iter.rs +++ b/sqlx-exasol-impl/src/types/iter.rs @@ -107,7 +107,6 @@ macro_rules! impl_for_iterator { } impl_for_iterator!(&[T], deref => *); -impl_for_iterator!(&mut [T], deref => *); impl_for_iterator!([T; N], bounds => const N: usize); impl_for_iterator!(Vec); impl_for_iterator!(Box<[T]>, deref => &**); diff --git a/src/ty_match.rs b/src/ty_match.rs index 67845b01..dc6d8316 100644 --- a/src/ty_match.rs +++ b/src/ty_match.rs @@ -1,6 +1,7 @@ -//! Similar module to `sqlx::ty_match` overridden so that we can allow array types in compile-time -//! statements as well. Most of the code is identical to the original. -use std::marker::PhantomData; +//! Similar module to `sqlx::ty_match` overridden so that we can allow array like types in +//! compile-time statements as well. Most of the code is identical to the original. + +use std::{marker::PhantomData, rc::Rc, sync::Arc}; #[allow(clippy::just_underscores_and_digits)] pub fn same_type(_1: &T, _2: &T) {} @@ -49,6 +50,30 @@ pub trait MatchBorrowExt: Sized { // ######## BEGIN CUSTOM ######## // ############################## +impl<'a, T> MatchBorrowExt for MatchBorrow, Option<&'a [T]>> { + type Matched = Option<&'a T>; +} + +impl<'a, T> MatchBorrowExt for MatchBorrow<&'a T, &'a [T]> { + type Matched = &'a T; +} + +impl<'a, T, const N: usize> MatchBorrowExt for MatchBorrow, Option<[T; N]>> { + type Matched = Option<&'a T>; +} + +impl<'a, T, const N: usize> MatchBorrowExt for MatchBorrow, Option<&'a [T; N]>> { + type Matched = Option<&'a T>; +} + +impl MatchBorrowExt for MatchBorrow { + type Matched = T; +} + +impl<'a, T, const N: usize> MatchBorrowExt for MatchBorrow<&'a T, [T; N]> { + type Matched = &'a T; +} + impl<'a, T> MatchBorrowExt for MatchBorrow, Option>> { type Matched = Option<&'a T>; } @@ -65,6 +90,54 @@ impl<'a, T> MatchBorrowExt for MatchBorrow<&'a T, Vec> { type Matched = &'a T; } +impl<'a, T> MatchBorrowExt for MatchBorrow, Option>> { + type Matched = Option<&'a T>; +} + +impl<'a, T> MatchBorrowExt for MatchBorrow, Option<&'a Box<[T]>>> { + type Matched = Option<&'a T>; +} + +impl MatchBorrowExt for MatchBorrow> { + type Matched = T; +} + +impl<'a, T> MatchBorrowExt for MatchBorrow<&'a T, Box<[T]>> { + type Matched = &'a T; +} + +impl<'a, T> MatchBorrowExt for MatchBorrow, Option>> { + type Matched = Option<&'a T>; +} + +impl<'a, T> MatchBorrowExt for MatchBorrow, Option<&'a Rc<[T]>>> { + type Matched = Option<&'a T>; +} + +impl MatchBorrowExt for MatchBorrow> { + type Matched = T; +} + +impl<'a, T> MatchBorrowExt for MatchBorrow<&'a T, Rc<[T]>> { + type Matched = &'a T; +} + +impl<'a, T> MatchBorrowExt for MatchBorrow, Option>> { + type Matched = Option<&'a T>; +} + +impl<'a, T> MatchBorrowExt for MatchBorrow, Option<&'a Arc<[T]>>> { + type Matched = Option<&'a T>; +} + +impl MatchBorrowExt for MatchBorrow> { + type Matched = T; +} + +impl<'a, T> MatchBorrowExt for MatchBorrow<&'a T, Arc<[T]>> { + type Matched = &'a T; +} + // ############################## // ######### END CUSTOM ######### // ############################## From 87dd690b00e6f73705ee1f84d203c6c896f1cf2f Mon Sep 17 00:00:00 2001 From: Bogdan Mircea Date: Sat, 19 Jul 2025 17:52:46 +0300 Subject: [PATCH 025/102] improvements on compile time array types support --- sqlx-exasol-impl/src/arguments.rs | 2 +- sqlx-exasol-impl/src/types/iter.rs | 61 +++++++++++++++++------------- sqlx-exasol-impl/src/types/mod.rs | 1 - src/ty_match.rs | 30 +++++++++++++++ tests/compile_time.rs | 4 +- 5 files changed, 69 insertions(+), 29 deletions(-) diff --git a/sqlx-exasol-impl/src/arguments.rs b/sqlx-exasol-impl/src/arguments.rs index 777b52ba..388a9b05 100644 --- a/sqlx-exasol-impl/src/arguments.rs +++ b/sqlx-exasol-impl/src/arguments.rs @@ -77,7 +77,7 @@ impl ExaBuffer { pub fn append_iter<'q, I, T>(&mut self, iter: I) -> Result<(), BoxDynError> where I: IntoIterator, - T: 'q + Encode<'q, Exasol> + Type, + T: 'q + Encode<'q, Exasol>, { let mut iter = iter.into_iter(); diff --git a/sqlx-exasol-impl/src/types/iter.rs b/sqlx-exasol-impl/src/types/iter.rs index 7e736119..e564e7c8 100644 --- a/sqlx-exasol-impl/src/types/iter.rs +++ b/sqlx-exasol-impl/src/types/iter.rs @@ -1,4 +1,4 @@ -use std::{rc::Rc, sync::Arc}; +use std::{marker::PhantomData, rc::Rc, sync::Arc}; use sqlx_core::{ database::Database, @@ -9,12 +9,15 @@ use sqlx_core::{ use crate::{arguments::ExaBuffer, Exasol}; -/// Adapter allowing any iterator of encodable values to be passed as a parameter array for a column -/// to Exasol in a single query invocation. The adapter is needed because [`Encode`] is still a -/// foreign trait and thus cannot be implemented in a generic manner. +/// Adapter allowing any iterator of encodable values to be treated and passed as a one dimensional +/// parameter array for a column to Exasol in a single query invocation. Multi dimensional arrays +/// are not supported. The adapter is needed because [`Encode`] is still a foreign trait and thus +/// cannot be implemented in a generic manner. /// /// Note that the [`Encode`] trait requires the ability to encode by reference, thus the adapter -/// takes a reference that must implement [`IntoIterator`]. +/// takes a reference that implements [`IntoIterator`]. But since iteration requires mutability, +/// the adaptar also requires [`Clone`]. The adapter definition should steer it towards being used +/// with cheaply clonable iterators since it expects the iteration elements to be references. /// /// ```rust /// # use sqlx_exasol_impl as sqlx_exasol; @@ -25,47 +28,53 @@ use crate::{arguments::ExaBuffer, Exasol}; /// ``` #[derive(Debug)] #[repr(transparent)] -pub struct ExaIter<'i, I>(&'i I) +pub struct ExaIter<'i, I, T> where - I: ?Sized, - &'i I: IntoIterator, - <&'i I as IntoIterator>::Item: for<'q> Encode<'q, Exasol> + Type; + I: IntoIterator + Clone, + T: 'i, +{ + into_iter: I, + data_lifetime: PhantomData<&'i ()>, +} -impl<'i, I> ExaIter<'i, I> +impl<'i, 'q, I, T> ExaIter<'i, I, T> where - I: ?Sized, - &'i I: IntoIterator, - <&'i I as IntoIterator>::Item: for<'q> Encode<'q, Exasol> + Type, + I: IntoIterator + Clone, + T: 'i + Type + Encode<'q, Exasol>, + 'i: 'q, { - pub fn new(into_iter: &'i I) -> Self { - Self(into_iter) + pub fn new(into_iter: I) -> Self { + Self { + into_iter, + data_lifetime: PhantomData, + } } } -impl<'i, I> Type for ExaIter<'i, I> +impl<'i, I, T> Type for ExaIter<'i, I, T> where - I: ?Sized, - &'i I: IntoIterator, - <&'i I as IntoIterator>::Item: for<'q> Encode<'q, Exasol> + Type, + I: IntoIterator + Clone, + T: 'i + Type, { fn type_info() -> ::TypeInfo { - <&'i I as IntoIterator>::Item::type_info() + ::Item::type_info() } } -impl<'i, I> Encode<'_, Exasol> for ExaIter<'i, I> +impl<'i, 'q, I, T> Encode<'q, Exasol> for ExaIter<'i, I, T> where - I: ?Sized, - &'i I: IntoIterator, - <&'i I as IntoIterator>::Item: for<'q> Encode<'q, Exasol> + Type, + I: IntoIterator + Clone, + T: 'i + Encode<'q, Exasol>, + 'i: 'q, { fn encode_by_ref(&self, buf: &mut ExaBuffer) -> Result { - buf.append_iter(self.0)?; + buf.append_iter(self.into_iter.clone())?; Ok(IsNull::No) } fn size_hint(&self) -> usize { - self.0 + self.into_iter + .clone() .into_iter() .fold(0, |sum, item| sum + item.size_hint()) } diff --git a/sqlx-exasol-impl/src/types/mod.rs b/sqlx-exasol-impl/src/types/mod.rs index 66c81f13..75aaa45d 100644 --- a/sqlx-exasol-impl/src/types/mod.rs +++ b/sqlx-exasol-impl/src/types/mod.rs @@ -17,5 +17,4 @@ mod uuid; pub use ::chrono::Duration; #[cfg(feature = "chrono")] pub use chrono::Months; - pub use iter::ExaIter; diff --git a/src/ty_match.rs b/src/ty_match.rs index dc6d8316..5fc2c07d 100644 --- a/src/ty_match.rs +++ b/src/ty_match.rs @@ -3,6 +3,8 @@ use std::{marker::PhantomData, rc::Rc, sync::Arc}; +use sqlx_exasol_impl::types::ExaIter; + #[allow(clippy::just_underscores_and_digits)] pub fn same_type(_1: &T, _2: &T) {} @@ -138,6 +140,34 @@ impl<'a, T> MatchBorrowExt for MatchBorrow<&'a T, Arc<[T]>> { type Matched = &'a T; } +impl<'i, I, T> MatchBorrowExt for MatchBorrow>> +where + I: IntoIterator> + Clone, +{ + type Matched = T; +} + +impl<'i, I, T> MatchBorrowExt for MatchBorrow<&'i T, ExaIter<'i, I, Option>> +where + I: IntoIterator> + Clone, +{ + type Matched = &'i T; +} + +impl<'i, I, T> MatchBorrowExt for MatchBorrow> +where + I: IntoIterator + Clone, +{ + type Matched = T; +} + +impl<'i, I, T> MatchBorrowExt for MatchBorrow<&'i T, ExaIter<'i, I, T>> +where + I: IntoIterator + Clone, +{ + type Matched = &'i T; +} + // ############################## // ######### END CUSTOM ######### // ############################## diff --git a/tests/compile_time.rs b/tests/compile_time.rs index 6e14058d..94cf7457 100644 --- a/tests/compile_time.rs +++ b/tests/compile_time.rs @@ -3,6 +3,8 @@ //! cargo run -p sqlx-exasol-cli prepare -- --features runtime-tokio --tests //! ``` +use sqlx_exasol::types::ExaIter; + extern crate sqlx_exasol as sqlx; #[ignore] @@ -103,7 +105,7 @@ async fn test_compile_time_queries( sqlx_exasol::query!( "INSERT INTO compile_time_tests (column_i8, column_f64) VALUES(?, ?);", - vec![10i8], + ExaIter::new([Some(10i8)].iter()), 15.3f64 ) .execute(&mut *conn) From ece06d6ef19cd30924752121b03c16c269ab71ca Mon Sep 17 00:00:00 2001 From: Bogdan Mircea Date: Mon, 21 Jul 2025 16:46:00 +0300 Subject: [PATCH 026/102] add compile time tests for arrays --- sqlx-exasol-impl/src/types/iter.rs | 35 +- src/ty_match.rs | 269 ---------------- src/ty_match/extension.rs | 371 ++++++++++++++++++++++ src/ty_match/mod.rs | 159 ++++++++++ tests/compile_time.rs | 217 ++++--------- tests/etl.rs | 26 +- tests/macros.rs | 114 ++++++- tests/migrations_compile_time/1_setup.sql | 5 +- 8 files changed, 733 insertions(+), 463 deletions(-) delete mode 100644 src/ty_match.rs create mode 100644 src/ty_match/extension.rs create mode 100644 src/ty_match/mod.rs diff --git a/sqlx-exasol-impl/src/types/iter.rs b/sqlx-exasol-impl/src/types/iter.rs index e564e7c8..cbbcb928 100644 --- a/sqlx-exasol-impl/src/types/iter.rs +++ b/sqlx-exasol-impl/src/types/iter.rs @@ -12,36 +12,32 @@ use crate::{arguments::ExaBuffer, Exasol}; /// Adapter allowing any iterator of encodable values to be treated and passed as a one dimensional /// parameter array for a column to Exasol in a single query invocation. Multi dimensional arrays /// are not supported. The adapter is needed because [`Encode`] is still a foreign trait and thus -/// cannot be implemented in a generic manner. +/// cannot be implemented in a generic manner to all types implementing [`IntoIterator`]. /// /// Note that the [`Encode`] trait requires the ability to encode by reference, thus the adapter -/// takes a reference that implements [`IntoIterator`]. But since iteration requires mutability, +/// takes a type that implements [`IntoIterator`]. But since iteration requires mutability, /// the adaptar also requires [`Clone`]. The adapter definition should steer it towards being used /// with cheaply clonable iterators since it expects the iteration elements to be references. +/// However, care should still be taken so as not to clone expensive [`IntoIterator`] types. /// /// ```rust /// # use sqlx_exasol_impl as sqlx_exasol; /// use sqlx_exasol::types::ExaIter; /// /// let vector = vec![1, 2, 3]; -/// let borrowed_iter = ExaIter::new(vector.as_slice()); +/// let borrowed_iter = ExaIter::new(vector.iter().filter(|v| **v % 2 == 0)); /// ``` #[derive(Debug)] #[repr(transparent)] -pub struct ExaIter<'i, I, T> -where - I: IntoIterator + Clone, - T: 'i, -{ +pub struct ExaIter { into_iter: I, - data_lifetime: PhantomData<&'i ()>, + data_lifetime: PhantomData T>, } -impl<'i, 'q, I, T> ExaIter<'i, I, T> +impl ExaIter where - I: IntoIterator + Clone, - T: 'i + Type + Encode<'q, Exasol>, - 'i: 'q, + I: IntoIterator + Clone, + T: for<'q> Encode<'q, Exasol> + Type + Copy, { pub fn new(into_iter: I) -> Self { Self { @@ -51,21 +47,20 @@ where } } -impl<'i, I, T> Type for ExaIter<'i, I, T> +impl Type for ExaIter where - I: IntoIterator + Clone, - T: 'i + Type, + I: IntoIterator + Clone, + T: Type + Copy, { fn type_info() -> ::TypeInfo { ::Item::type_info() } } -impl<'i, 'q, I, T> Encode<'q, Exasol> for ExaIter<'i, I, T> +impl Encode<'_, Exasol> for ExaIter where - I: IntoIterator + Clone, - T: 'i + Encode<'q, Exasol>, - 'i: 'q, + I: IntoIterator + Clone, + T: for<'q> Encode<'q, Exasol> + Copy, { fn encode_by_ref(&self, buf: &mut ExaBuffer) -> Result { buf.append_iter(self.into_iter.clone())?; diff --git a/src/ty_match.rs b/src/ty_match.rs deleted file mode 100644 index 5fc2c07d..00000000 --- a/src/ty_match.rs +++ /dev/null @@ -1,269 +0,0 @@ -//! Similar module to `sqlx::ty_match` overridden so that we can allow array like types in -//! compile-time statements as well. Most of the code is identical to the original. - -use std::{marker::PhantomData, rc::Rc, sync::Arc}; - -use sqlx_exasol_impl::types::ExaIter; - -#[allow(clippy::just_underscores_and_digits)] -pub fn same_type(_1: &T, _2: &T) {} - -pub struct WrapSame(PhantomData, PhantomData); - -impl WrapSame { - pub fn new(_arg: &U) -> Self { - WrapSame(PhantomData, PhantomData) - } -} - -pub trait WrapSameExt: Sized { - type Wrapped; - - fn wrap_same(self) -> Self::Wrapped { - panic!("only for type resolution") - } -} - -impl WrapSameExt for WrapSame> { - type Wrapped = Option; -} - -impl WrapSameExt for &'_ WrapSame { - type Wrapped = T; -} - -pub struct MatchBorrow(PhantomData, PhantomData); - -impl MatchBorrow { - pub fn new(t: T, _u: &U) -> (T, Self) { - (t, MatchBorrow(PhantomData, PhantomData)) - } -} - -pub trait MatchBorrowExt: Sized { - type Matched; - - fn match_borrow(self) -> Self::Matched { - panic!("only for type resolution") - } -} - -// ############################## -// ######## BEGIN CUSTOM ######## -// ############################## - -impl<'a, T> MatchBorrowExt for MatchBorrow, Option<&'a [T]>> { - type Matched = Option<&'a T>; -} - -impl<'a, T> MatchBorrowExt for MatchBorrow<&'a T, &'a [T]> { - type Matched = &'a T; -} - -impl<'a, T, const N: usize> MatchBorrowExt for MatchBorrow, Option<[T; N]>> { - type Matched = Option<&'a T>; -} - -impl<'a, T, const N: usize> MatchBorrowExt for MatchBorrow, Option<&'a [T; N]>> { - type Matched = Option<&'a T>; -} - -impl MatchBorrowExt for MatchBorrow { - type Matched = T; -} - -impl<'a, T, const N: usize> MatchBorrowExt for MatchBorrow<&'a T, [T; N]> { - type Matched = &'a T; -} - -impl<'a, T> MatchBorrowExt for MatchBorrow, Option>> { - type Matched = Option<&'a T>; -} - -impl<'a, T> MatchBorrowExt for MatchBorrow, Option<&'a Vec>> { - type Matched = Option<&'a T>; -} - -impl MatchBorrowExt for MatchBorrow> { - type Matched = T; -} - -impl<'a, T> MatchBorrowExt for MatchBorrow<&'a T, Vec> { - type Matched = &'a T; -} - -impl<'a, T> MatchBorrowExt for MatchBorrow, Option>> { - type Matched = Option<&'a T>; -} - -impl<'a, T> MatchBorrowExt for MatchBorrow, Option<&'a Box<[T]>>> { - type Matched = Option<&'a T>; -} - -impl MatchBorrowExt for MatchBorrow> { - type Matched = T; -} - -impl<'a, T> MatchBorrowExt for MatchBorrow<&'a T, Box<[T]>> { - type Matched = &'a T; -} - -impl<'a, T> MatchBorrowExt for MatchBorrow, Option>> { - type Matched = Option<&'a T>; -} - -impl<'a, T> MatchBorrowExt for MatchBorrow, Option<&'a Rc<[T]>>> { - type Matched = Option<&'a T>; -} - -impl MatchBorrowExt for MatchBorrow> { - type Matched = T; -} - -impl<'a, T> MatchBorrowExt for MatchBorrow<&'a T, Rc<[T]>> { - type Matched = &'a T; -} - -impl<'a, T> MatchBorrowExt for MatchBorrow, Option>> { - type Matched = Option<&'a T>; -} - -impl<'a, T> MatchBorrowExt for MatchBorrow, Option<&'a Arc<[T]>>> { - type Matched = Option<&'a T>; -} - -impl MatchBorrowExt for MatchBorrow> { - type Matched = T; -} - -impl<'a, T> MatchBorrowExt for MatchBorrow<&'a T, Arc<[T]>> { - type Matched = &'a T; -} - -impl<'i, I, T> MatchBorrowExt for MatchBorrow>> -where - I: IntoIterator> + Clone, -{ - type Matched = T; -} - -impl<'i, I, T> MatchBorrowExt for MatchBorrow<&'i T, ExaIter<'i, I, Option>> -where - I: IntoIterator> + Clone, -{ - type Matched = &'i T; -} - -impl<'i, I, T> MatchBorrowExt for MatchBorrow> -where - I: IntoIterator + Clone, -{ - type Matched = T; -} - -impl<'i, I, T> MatchBorrowExt for MatchBorrow<&'i T, ExaIter<'i, I, T>> -where - I: IntoIterator + Clone, -{ - type Matched = &'i T; -} - -// ############################## -// ######### END CUSTOM ######### -// ############################## - -impl<'a> MatchBorrowExt for MatchBorrow, Option> { - type Matched = Option<&'a str>; -} - -impl<'a> MatchBorrowExt for MatchBorrow, Option<&'a String>> { - type Matched = Option<&'a str>; -} - -impl<'a> MatchBorrowExt for MatchBorrow<&'a str, String> { - type Matched = &'a str; -} - -impl MatchBorrowExt for MatchBorrow<&'_ T, T> { - type Matched = T; -} - -impl MatchBorrowExt for MatchBorrow<&'_ &'_ T, T> { - type Matched = T; -} - -impl MatchBorrowExt for MatchBorrow { - type Matched = T; -} - -impl MatchBorrowExt for MatchBorrow { - type Matched = T; -} - -impl MatchBorrowExt for MatchBorrow, Option> { - type Matched = Option; -} - -impl MatchBorrowExt for MatchBorrow, Option> { - type Matched = Option; -} - -impl MatchBorrowExt for MatchBorrow, Option<&'_ T>> { - type Matched = Option; -} - -impl MatchBorrowExt for MatchBorrow, Option<&'_ &'_ T>> { - type Matched = Option; -} - -impl MatchBorrowExt for &'_ MatchBorrow { - type Matched = U; -} - -pub fn conjure_value() -> T { - panic!() -} - -pub fn dupe_value(_t: &T) -> T { - panic!() -} - -#[test] -fn test_dupe_value() { - let val = &(String::new(),); - - if false { - let _: i32 = dupe_value(&0i32); - let _: String = dupe_value(&String::new()); - let _: String = dupe_value(&val.0); - } -} - -#[test] -fn test_wrap_same() { - if false { - let _: i32 = WrapSame::::new(&0i32).wrap_same(); - let _: i32 = WrapSame::::new(&"hello, world!").wrap_same(); - let _: Option = WrapSame::::new(&Some(String::new())).wrap_same(); - } -} - -#[test] -fn test_match_borrow() { - if false { - let (_, match_borrow) = MatchBorrow::new("", &String::new()); - let _: &str = match_borrow.match_borrow(); - - let (_, match_borrow) = MatchBorrow::new(&&0i64, &0i64); - let _: i64 = match_borrow.match_borrow(); - - let (_, match_borrow) = MatchBorrow::new(&0i64, &0i64); - let _: i64 = match_borrow.match_borrow(); - - let (_, match_borrow) = MatchBorrow::new(0i64, &0i64); - let _: i64 = match_borrow.match_borrow(); - - let (_, match_borrow) = MatchBorrow::new(0i64, &vec![0i64; 1]); - let _: i64 = match_borrow.match_borrow(); - } -} diff --git a/src/ty_match/extension.rs b/src/ty_match/extension.rs new file mode 100644 index 00000000..2b7a94d3 --- /dev/null +++ b/src/ty_match/extension.rs @@ -0,0 +1,371 @@ +//! Extension module that houses the array-like types implementations. + +use std::{rc::Rc, sync::Arc}; + +use crate::{ + ty_match::{MatchBorrow, MatchBorrowExt, WrapSame, WrapSameExt}, + types::ExaIter, +}; + +/// Owned types have the highest priorty in autoref specialization. +/// This will take precedence if we're wrapping a type in an slice of [`Option`]. +impl<'a, T, U> WrapSameExt for WrapSame]> +where + T: 'a, +{ + type Wrapped = &'a [Option]; +} + +/// Immutable references have middle priorty in autoref specialization. +/// This will take precedence over the mutalbe reference implementation but not before the owned +/// type implementation. +/// +/// It wraps the type in a slice. +impl<'a, T, U> WrapSameExt for &WrapSame +where + T: 'a, +{ + type Wrapped = &'a [T]; +} + +/// Owned types have the highest priorty in autoref specialization. +/// This will take precedence if we're wrapping a type in a fixed size array of [`Option`]. +impl WrapSameExt for WrapSame; N]> { + type Wrapped = [Option; N]; +} + +/// Immutable references have middle priorty in autoref specialization. +/// This will take precedence over the mutalbe reference implementation but not before the owned +/// type implementation. +/// +/// It wraps the type in a fixed size array. +impl WrapSameExt for &WrapSame { + type Wrapped = [T; N]; +} + +/// Owned types have the highest priorty in autoref specialization. +/// This will take precedence if we're wrapping a type in a [`Vec