From 9bc49f615b24c5ff8507c5759ab1972ff41d3ba4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alja=C5=BE=20Mur=20Er=C5=BEen?= Date: Thu, 21 Mar 2024 14:11:32 +0100 Subject: [PATCH] feat: tiberius initial support --- Cargo.lock | 190 ++++++++++++++++- Justfile | 5 +- README.md | 48 ++--- connector_arrow/Cargo.toml | 12 +- connector_arrow/src/errors.rs | 4 + connector_arrow/src/lib.rs | 2 + connector_arrow/src/mysql/mod.rs | 1 - connector_arrow/src/tiberius/mod.rs | 239 ++++++++++++++++++++++ connector_arrow/src/tiberius/types.rs | 82 ++++++++ connector_arrow/tests/it/main.rs | 2 + connector_arrow/tests/it/test_tiberius.rs | 43 ++++ dbs/docker-compose.yml | 22 +- 12 files changed, 614 insertions(+), 36 deletions(-) create mode 100644 connector_arrow/src/tiberius/mod.rs create mode 100644 connector_arrow/src/tiberius/types.rs create mode 100644 connector_arrow/tests/it/test_tiberius.rs diff --git a/Cargo.lock b/Cargo.lock index c6e96bb..fa4f54b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -298,6 +298,19 @@ dependencies = [ "syn 2.0.48", ] +[[package]] +name = "asynchronous-codec" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4057f2c32adbb2fc158e22fb38433c8e9bbf76b75a4732c7c0cbaf695fb65568" +dependencies = [ + "bytes", + "futures-sink", + "futures-util", + "memchr", + "pin-project-lite", +] + [[package]] name = "autocfg" version = "1.1.0" @@ -546,6 +559,12 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "connection-string" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "510ca239cf13b7f8d16a2b48f263de7b4f8c566f0af58d901031473c76afb1e3" + [[package]] name = "connector_arrow" version = "0.4.0" @@ -559,6 +578,7 @@ dependencies = [ "env_logger", "fallible-iterator 0.2.0", "fallible-streaming-iterator", + "futures", "half", "hex", "itertools", @@ -576,6 +596,10 @@ dependencies = [ "serde_json", "similar-asserts", "thiserror", + "tiberius", + "tokio", + "tokio-util", + "url", ] [[package]] @@ -779,6 +803,90 @@ version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" +[[package]] +name = "encoding" +version = "0.2.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b0d943856b990d12d3b55b359144ff341533e516d94098b1d3fc1ac666d36ec" +dependencies = [ + "encoding-index-japanese", + "encoding-index-korean", + "encoding-index-simpchinese", + "encoding-index-singlebyte", + "encoding-index-tradchinese", +] + +[[package]] +name = "encoding-index-japanese" +version = "1.20141219.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04e8b2ff42e9a05335dbf8b5c6f7567e5591d0d916ccef4e0b1710d32a0d0c91" +dependencies = [ + "encoding_index_tests", +] + +[[package]] +name = "encoding-index-korean" +version = "1.20141219.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dc33fb8e6bcba213fe2f14275f0963fd16f0a02c878e3095ecfdf5bee529d81" +dependencies = [ + "encoding_index_tests", +] + +[[package]] +name = "encoding-index-simpchinese" +version = "1.20141219.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d87a7194909b9118fc707194baa434a4e3b0fb6a5a757c73c3adb07aa25031f7" +dependencies = [ + "encoding_index_tests", +] + +[[package]] +name = "encoding-index-singlebyte" +version = "1.20141219.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3351d5acffb224af9ca265f435b859c7c01537c0849754d3db3fdf2bfe2ae84a" +dependencies = [ + "encoding_index_tests", +] + +[[package]] +name = "encoding-index-tradchinese" +version = "1.20141219.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd0e20d5688ce3cab59eb3ef3a2083a5c77bf496cb798dc6fcdb75f323890c18" +dependencies = [ + "encoding_index_tests", +] + +[[package]] +name = "encoding_index_tests" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a246d82be1c9d791c5dfde9a2bd045fc3cbba3fa2b11ad558f27d01712f00569" + +[[package]] +name = "enumflags2" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3278c9d5fb675e0a51dabcf4c0d355f692b064171535ba72361be1528a9d8e8d" +dependencies = [ + "enumflags2_derive", +] + +[[package]] +name = "enumflags2_derive" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c785274071b1b420972453b306eeca06acf4633829db4223b58a2a8c5953bc4" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + [[package]] name = "env_filter" version = "0.1.0" @@ -880,6 +988,21 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" +[[package]] +name = "futures" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + [[package]] name = "futures-channel" version = "0.3.30" @@ -896,6 +1019,23 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" +[[package]] +name = "futures-executor" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" + [[package]] name = "futures-macro" version = "0.3.30" @@ -925,10 +1065,13 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" dependencies = [ + "futures-channel", "futures-core", + "futures-io", "futures-macro", "futures-sink", "futures-task", + "memchr", "pin-project-lite", "pin-utils", "slab", @@ -1645,6 +1788,12 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "pretty-hex" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6fa0831dd7cc608c38a5e323422a0077678fa5744aa2be4ad91c4ece8eec8d5" + [[package]] name = "proc-macro-crate" version = "3.1.0" @@ -2208,6 +2357,29 @@ dependencies = [ "syn 2.0.48", ] +[[package]] +name = "tiberius" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc6e2bf3e4b5be181a2a2ceff4b9b12e2684010d436a6958bd564fbc8094d44d" +dependencies = [ + "async-trait", + "asynchronous-codec", + "byteorder", + "bytes", + "connection-string", + "encoding", + "enumflags2", + "futures-util", + "num-traits", + "once_cell", + "pin-project-lite", + "pretty-hex", + "thiserror", + "tracing", + "uuid 1.7.0", +] + [[package]] name = "tiny-keccak" version = "2.0.2" @@ -2234,9 +2406,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.35.1" +version = "1.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104" +checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931" dependencies = [ "backtrace", "bytes", @@ -2281,6 +2453,7 @@ checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" dependencies = [ "bytes", "futures-core", + "futures-io", "futures-sink", "pin-project-lite", "tokio", @@ -2310,10 +2483,23 @@ version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ + "log", "pin-project-lite", + "tracing-attributes", "tracing-core", ] +[[package]] +name = "tracing-attributes" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + [[package]] name = "tracing-core" version = "0.1.32" diff --git a/Justfile b/Justfile index 27be1ea..260868e 100644 --- a/Justfile +++ b/Justfile @@ -1,5 +1,6 @@ -export POSTGRES_URL := "postgres://root:root@localhost:5432/dummy" -export MYSQL_URL := "mysql://root:root@localhost:3306/dummy" +export POSTGRES_URL := "postgres://user:pass@localhost:5432/db" +export MYSQL_URL := "mysql://root:pass@localhost:3306/db" +export TIBERIUS_URL := "tds://sa:passwordA1@localhost:1433" default: just --list diff --git a/README.md b/README.md index 8cb0609..5253da3 100644 --- a/README.md +++ b/README.md @@ -30,30 +30,30 @@ without need for dynamic linking of C libraries. ## Support matrix -| | SQLite | DuckDB | PostgreSQL | MySQL | -| --- | --- | --- | --- | --- | -| feature | `src_sqlite` | `src_duckdb` | `src_postgres` | `src_mysql` | -| dependency | [rusqlite](https://crates.io/crates/rusqlite) | [duckdb](https://crates.io/crates/duckdb) | [postgres](https://crates.io/crates/postgres) | [mysql](https://crates.io/crates/mysql) | -| query | x | x | x | x | -| query params | | | x | | -| schema get | x | x | x | | -| schema edit | x | x | x | | -| append | x | x | x | | -| roundtrip: null & bool | x | x | x | | -| roundtrip: int | x | x | x | | -| roundtrip: uint | x | x | x | | -| roundtrip: float | x | x | x | | -| roundtrip: decimal | x | | x | | -| roundtrip: timestamp | x | x | x | | -| roundtrip: date | x | | x | | -| roundtrip: time | x | | x | | -| roundtrip: duration | x | | x | | -| roundtrip: interval | | | | | -| roundtrip: utf8 | x | x | x | | -| roundtrip: binary | x | x | x | | -| roundtrip: empty | | x | x | | -| containers | | | | | -| binary fallback | x | | x | | +| | SQLite | DuckDB | PostgreSQL | MySQL | Microsoft SQL Server | +| --- | --- | --- | --- | --- | --- | +| feature | `src_sqlite` | `src_duckdb` | `src_postgres` | `src_mysql` | `src_tiberius` | +| dependency | [rusqlite](https://crates.io/crates/rusqlite) | [duckdb](https://crates.io/crates/duckdb) | [postgres](https://crates.io/crates/postgres) | [mysql](https://crates.io/crates/mysql) | [tiberius](https://crates.io/crates/tiberius) | +| query | x | x | x | x | x | +| query params | | | x | | | +| schema get | x | x | x | | | +| schema edit | x | x | x | | | +| append | x | x | x | | | +| roundtrip: null & bool | x | x | x | | | +| roundtrip: int | x | x | x | | | +| roundtrip: uint | x | x | x | | | +| roundtrip: float | x | x | x | | | +| roundtrip: decimal | x | | x | | | +| roundtrip: timestamp | x | x | x | | | +| roundtrip: date | x | | x | | | +| roundtrip: time | x | | x | | | +| roundtrip: duration | x | | x | | | +| roundtrip: interval | | | | | | +| roundtrip: utf8 | x | x | x | | | +| roundtrip: binary | x | x | x | | | +| roundtrip: empty | | x | x | | | +| containers | | | | | | +| binary fallback | x | | x | | | None of the sources are enabled by default, use features to enable them. diff --git a/connector_arrow/Cargo.toml b/connector_arrow/Cargo.toml index cf8987e..918faa5 100644 --- a/connector_arrow/Cargo.toml +++ b/connector_arrow/Cargo.toml @@ -33,6 +33,9 @@ byteorder = { version = "1", optional = true } regex = "1.10.3" once_cell = "1.19.0" pac_cell = { git = "https://github.com/aljazerzen/pac_cell.git", optional = true } +tokio = { version = "1.36.0", optional = true } +tokio-util = { version = "0.7.10", optional = true, features = ["compat"]} +futures = { version = "0.3.30", optional = true } [dependencies.postgres] version = "0.19" @@ -61,6 +64,11 @@ default-features = false optional = true features = ["minimal"] +[dependencies.tiberius] +version = "0.12.2" +default-features = false +optional = true + [dev-dependencies] env_logger = "0.11" arrow = { version = "49", features = ["prettyprint"], default-features = false } @@ -69,10 +77,11 @@ half = "2.3.1" rand = { version = "0.8.5", default-features = false } rand_chacha = "0.3.1" rstest = { version = "0.18.2", default-features = false } +url = "2.5.0" [features] -all = ["src_sqlite", "src_duckdb", "src_postgres", "src_mysql"] +all = ["src_sqlite", "src_duckdb", "src_postgres", "src_mysql", "src_tiberius"] src_postgres = [ "postgres", "postgres-protocol", @@ -85,6 +94,7 @@ src_postgres = [ src_sqlite = ["rusqlite"] src_duckdb = ["duckdb", "fallible-streaming-iterator"] src_mysql = ["mysql", "pac_cell"] +src_tiberius = ["tiberius", "tokio", "tokio-util", "futures"] [package.metadata.docs.rs] features = ["all"] diff --git a/connector_arrow/src/errors.rs b/connector_arrow/src/errors.rs index 75d0e8f..e0ebc20 100644 --- a/connector_arrow/src/errors.rs +++ b/connector_arrow/src/errors.rs @@ -54,6 +54,10 @@ pub enum ConnectorError { #[cfg(feature = "src_mysql")] #[error(transparent)] MySQL(#[from] mysql::Error), + + #[cfg(feature = "src_tiberius")] + #[error(transparent)] + Tiberius(#[from] tiberius::error::Error), } #[derive(Error, Debug)] diff --git a/connector_arrow/src/lib.rs b/connector_arrow/src/lib.rs index fb6dd20..1726091 100644 --- a/connector_arrow/src/lib.rs +++ b/connector_arrow/src/lib.rs @@ -51,6 +51,8 @@ pub mod mysql; pub mod postgres; #[cfg(feature = "src_sqlite")] pub mod sqlite; +#[cfg(feature = "src_tiberius")] +pub mod tiberius; pub use arrow; pub use errors::*; diff --git a/connector_arrow/src/mysql/mod.rs b/connector_arrow/src/mysql/mod.rs index 63511f2..43f0d1a 100644 --- a/connector_arrow/src/mysql/mod.rs +++ b/connector_arrow/src/mysql/mod.rs @@ -169,7 +169,6 @@ struct MySQLCellRef<'a> { impl<'r> Produce<'r> for MySQLCellRef<'r> {} -#[macro_export] macro_rules! impl_produce_ty { ($p: ty, ($($t: ty,)+)) => { $( diff --git a/connector_arrow/src/tiberius/mod.rs b/connector_arrow/src/tiberius/mod.rs new file mode 100644 index 0000000..26658a5 --- /dev/null +++ b/connector_arrow/src/tiberius/mod.rs @@ -0,0 +1,239 @@ +mod types; + +use arrow::{datatypes::*, record_batch::RecordBatch}; +use futures::{AsyncRead, AsyncWrite, StreamExt}; +use std::sync::Arc; +use tiberius::QueryStream; +use tokio::runtime::Runtime; + +use crate::api::{unimplemented, Connector, ResultReader, Statement}; +use crate::impl_produce_unsupported; +use crate::types::{ArrowType, FixedSizeBinaryType, NullType}; +use crate::util::transport::ProduceTy; +use crate::util::{self, transport::Produce}; +use crate::ConnectorError; + +pub struct TiberiusConnection { + pub rt: Arc, + pub client: tiberius::Client, +} + +impl TiberiusConnection { + pub fn new(rt: Arc, client: tiberius::Client) -> Self { + TiberiusConnection { rt, client } + } +} + +impl Connector for TiberiusConnection { + type Stmt<'conn> = TiberiusStatement<'conn, S> where Self: 'conn; + + type Append<'conn> = unimplemented::Appender where Self: 'conn; + + fn query<'a>(&'a mut self, query: &str) -> Result, ConnectorError> { + Ok(TiberiusStatement { + conn: self, + query: query.to_string(), + }) + } + + fn append<'a>(&'a mut self, _table_name: &str) -> Result, ConnectorError> { + Ok(unimplemented::Appender {}) + } + + fn type_db_into_arrow(ty: &str) -> Option { + Some(match ty { + "null" | "intn" => DataType::Null, + "bit" => DataType::Boolean, + "tinyint" => DataType::UInt8, + "smallint" => DataType::Int16, + "int" => DataType::Int32, + "bigint" => DataType::Int64, + "float" => DataType::Float32, + "real" => DataType::Float64, + _ => return None, + }) + } + + fn type_arrow_into_db(_ty: &DataType) -> Option { + None + } +} + +pub struct TiberiusStatement<'conn, S: AsyncRead + AsyncWrite + Unpin + Send> { + conn: &'conn mut TiberiusConnection, + query: String, +} + +impl<'conn, S: AsyncRead + AsyncWrite + Unpin + Send> Statement<'conn> + for TiberiusStatement<'conn, S> +{ + type Reader<'stmt> = TiberiusResultReader<'stmt> + where + Self: 'stmt; + + fn start<'p, I>(&mut self, _params: I) -> Result, ConnectorError> + where + I: IntoIterator, + { + // TODO: params + + let mut stream = self + .conn + .rt + .block_on(self.conn.client.query(&self.query, &[]))?; + + // get columns + let columns = self.conn.rt.block_on(stream.columns())?; + let schema = types::get_result_schema(columns)?; + self.conn.rt.block_on(stream.next()); + + Ok(TiberiusResultReader { + schema, + stream: TiberiusStream { + rt: self.conn.rt.clone(), + stream, + }, + }) + } +} + +pub struct TiberiusResultReader<'stmt> { + schema: SchemaRef, + stream: TiberiusStream<'stmt>, +} + +struct TiberiusStream<'stmt> { + rt: Arc, + stream: QueryStream<'stmt>, +} + +impl<'stmt> ResultReader<'stmt> for TiberiusResultReader<'stmt> { + fn get_schema(&mut self) -> Result { + Ok(self.schema.clone()) + } +} + +impl<'stmt> Iterator for TiberiusResultReader<'stmt> { + type Item = Result; + + fn next(&mut self) -> Option { + util::next_batch_from_rows(&self.schema, &mut self.stream, 1024).transpose() + } +} + +impl<'s> util::RowsReader<'s> for TiberiusStream<'s> { + type CellReader<'row> = TiberiusCellReader + where + Self: 'row; + + fn next_row(&mut self) -> Result>, ConnectorError> { + let item = self.rt.block_on(self.stream.next()); + + // are we done? + let Some(item) = item else { return Ok(None) }; + + // are there more result sets? + let row = match item? { + tiberius::QueryItem::Row(row) => row, + tiberius::QueryItem::Metadata(metadata) => { + dbg!(metadata); + // yes, this there are + return Err(ConnectorError::MultipleResultSets); + } + }; + + Ok(Some(TiberiusCellReader { row, cell: 0 })) + } +} + +struct TiberiusCellReader { + row: tiberius::Row, + cell: usize, +} + +impl<'a> util::CellReader<'a> for TiberiusCellReader { + type CellRef<'cell> = TiberiusCellRef<'cell> + where + Self: 'cell; + + fn next_cell(&mut self) -> Option> { + let r = TiberiusCellRef { + row: &mut self.row, + cell: self.cell, + }; + self.cell += 1; + Some(r) + } +} + +#[derive(Debug)] +struct TiberiusCellRef<'a> { + row: &'a mut tiberius::Row, + cell: usize, +} + +impl<'r> Produce<'r> for TiberiusCellRef<'r> {} + +macro_rules! impl_produce_ty { + ($p: ty, ($($t: ty,)+)) => { + $( + impl<'r> ProduceTy<'r, $t> for $p { + fn produce(self) -> Result<<$t as ArrowType>::Native, ConnectorError> { + Ok(self.row.get(self.cell).unwrap()) + } + fn produce_opt(self) -> Result::Native>, ConnectorError> { + Ok(self.row.get(self.cell)) + } + } + )+ + }; +} + +impl_produce_ty!( + TiberiusCellRef<'r>, + ( + BooleanType, + Int16Type, + Int32Type, + Int64Type, + UInt8Type, + Float32Type, + Float64Type, + ) +); + +impl_produce_unsupported!( + TiberiusCellRef<'r>, + ( + NullType, + Int8Type, + UInt16Type, + Float16Type, + UInt32Type, + UInt64Type, + TimestampSecondType, + TimestampMillisecondType, + TimestampMicrosecondType, + TimestampNanosecondType, + Date32Type, + Date64Type, + Time32SecondType, + Time32MillisecondType, + Time64MicrosecondType, + Time64NanosecondType, + IntervalYearMonthType, + IntervalDayTimeType, + IntervalMonthDayNanoType, + DurationSecondType, + DurationMillisecondType, + DurationMicrosecondType, + DurationNanosecondType, + LargeUtf8Type, + LargeBinaryType, + FixedSizeBinaryType, + Decimal128Type, + Decimal256Type, + Utf8Type, + BinaryType, + ) +); diff --git a/connector_arrow/src/tiberius/types.rs b/connector_arrow/src/tiberius/types.rs new file mode 100644 index 0000000..6b43830 --- /dev/null +++ b/connector_arrow/src/tiberius/types.rs @@ -0,0 +1,82 @@ +use std::sync::Arc; + +use arrow::datatypes::*; +use tiberius::{Column, ColumnType}; +use tokio::net::TcpStream; +use tokio_util::compat::Compat; + +use crate::{api::Connector, ConnectorError}; + +pub fn get_result_schema(columns: Option<&[Column]>) -> Result { + let Some(columns) = columns else { + return Err(ConnectorError::NoResultSets); + }; + + let mut fields = Vec::new(); + for column in columns { + let db_ty = get_name_of_column_type(&column.column_type()); + + fields.push(create_field(column.name().to_string(), db_ty, true)); + } + + Ok(Arc::new(Schema::new(fields))) +} + +fn create_field(name: String, db_ty: &str, nullable: bool) -> Field { + let data_type = super::TiberiusConnection::>::type_db_into_arrow(db_ty); + let data_type = data_type.unwrap_or_else(|| todo!("database type: {}", db_ty)); + + Field::new(name, data_type, nullable) +} + +fn get_name_of_column_type(col_ty: &ColumnType) -> &'static str { + use ColumnType::*; + + match col_ty { + Null => "null", + Bit => "bit", + + Int1 => "tinyint", + Int2 => "smallint", + Int4 => "int", + Int8 => "bigint", + + Intn => "intn", + Decimaln => "decimaln", + Numericn => "numericn", + + Float4 => "float4", + Float8 => "float8", + Floatn => "floatn", + + Money => "money", + Money4 => "money4", + + Datetime4 => "datetime4", + Datetime => "datetime", + Datetimen => "datetimen", + Daten => "daten", + Timen => "timen", + Datetime2 => "datetime2", + DatetimeOffsetn => "datetimeoffsetn", + + Guid => "guid", + + Bitn => "bitn", + BigVarBin => "bigvarbin", + BigBinary => "bigbinary", + + BigVarChar => "bigvarchar", + BigChar => "bigchar", + NVarchar => "nvarchar", + NChar => "nchar", + Text => "text", + NText => "ntext", + + Xml => "xml", + Udt => "udt", + Image => "image", + + SSVariant => "ssvariant", + } +} diff --git a/connector_arrow/tests/it/main.rs b/connector_arrow/tests/it/main.rs index 2e42dca..e1939cf 100644 --- a/connector_arrow/tests/it/main.rs +++ b/connector_arrow/tests/it/main.rs @@ -15,3 +15,5 @@ mod test_postgres_extended; mod test_postgres_simple; #[cfg(feature = "src_sqlite")] mod test_sqlite; +#[cfg(feature = "src_tiberius")] +mod test_tiberius; diff --git a/connector_arrow/tests/it/test_tiberius.rs b/connector_arrow/tests/it/test_tiberius.rs new file mode 100644 index 0000000..54dfacd --- /dev/null +++ b/connector_arrow/tests/it/test_tiberius.rs @@ -0,0 +1,43 @@ +use std::sync::Arc; + +use connector_arrow::tiberius::TiberiusConnection; +use tiberius::{AuthMethod, Client, Config}; +use tokio::{net::TcpStream, runtime}; +use tokio_util::compat::{Compat, TokioAsyncWriteCompatExt}; + +fn init() -> TiberiusConnection> { + let _ = env_logger::builder().is_test(true).try_init(); + + let rt = Arc::new( + runtime::Builder::new_current_thread() + .enable_all() + .build() + .unwrap(), + ); + + let url = std::env::var("TIBERIUS_URL").unwrap(); + let url = url::Url::parse(&url).unwrap(); + + let mut config = Config::new(); + config.host(url.host().unwrap()); + config.port(url.port().unwrap()); + config.authentication(AuthMethod::sql_server( + url.username(), + url.password().unwrap(), + )); + + let addr = (url.host_str().unwrap(), url.port().unwrap()); + let tcp = rt.block_on(TcpStream::connect(addr)).unwrap(); + tcp.set_nodelay(true).unwrap(); + + let client = Client::connect(config, tcp.compat_write()); + let client = rt.block_on(client).unwrap(); + + TiberiusConnection::new(rt, client) +} + +#[test] +fn query_01() { + let mut conn = init(); + super::tests::query_01(&mut conn); +} diff --git a/dbs/docker-compose.yml b/dbs/docker-compose.yml index 6191c61..de4a099 100644 --- a/dbs/docker-compose.yml +++ b/dbs/docker-compose.yml @@ -4,11 +4,11 @@ services: image: postgres:16-alpine ports: [5432:5432] environment: - POSTGRES_DB: dummy - POSTGRES_USER: root - POSTGRES_PASSWORD: root + POSTGRES_DB: db + POSTGRES_USER: user + POSTGRES_PASSWORD: pass healthcheck: - test: [CMD-SHELL, pg_isready -U root] + test: [CMD-SHELL, pg_isready -U user db] interval: 5s timeout: 5s retries: 5 @@ -16,6 +16,16 @@ services: image: mariadb:10 ports: [3306:3306] environment: - MYSQL_DATABASE: dummy - MYSQL_ROOT_PASSWORD: root + MYSQL_DATABASE: db + MYSQL_ROOT_PASSWORD: pass command: --secure-file-priv="" + mssql: + image: mcr.microsoft.com/mssql/server + ports: [1433:1433] + platform: linux/amd64 # https://github.com/microsoft/mssql-docker/issues/668#issuecomment-1436802153 + environment: + ACCEPT_EULA: Y + MSSQL_PID: Developer + MSSQL_SA_PASSWORD: passwordA1 + LC_ALL: en_US.UTF-8 + MSSQL_COLLATION: Latin1_General_100_CS_AI_SC_UTF8