A database driver for Exasol to be used with the Rust sqlx framework, based on the Exasol Websocket API.
Inspired by Py-Exasol and based on the (now archived) rust-exasol sync driver.
NOTE: The driver is currently in its alpha
stage. This will change when it's seen enough usage to be declared stable.
Please find the documentation here.
MSRV: 1.70
etl
- enables the usage ETL jobs without TLS encryption.etl_native_tls
- enables the etl
feature and adds TLS encryption through native-tls
1etl_rustls
- enables the etl
feature and adds TLS encryption through rustls
1compression
- enables compression support (for both connections and ETL jobs)uuid
- enables support for the uuid
cratechrono
- enables support for the chrono
crate typesrust_decimal
- enables support for the rust_decimal
typemigrate
- enables the use of migrations and testing (just like in other sqlx
drivers).Since the driver is used through sqlx
and it implements the interfaces there, it can do all the drivers
shipped with sqlx
do, with some caveats:
- Limitations
- no compile-time query check support2
- no sqlx-cli
support2
- no locking migrations support3
- no column nullability checks4
- apart from migrations, only a single query per statement is allowed (including in fixtures)5
Using the driver for regular database interactions: ```rust use std::env; use sqlx_exasol::*;
let pool = ExaPool::connect(&env::var("DATABASE_URL").unwrap()).await?; let mut con = pool.acquire().await?;
sqlx::query("CREATE SCHEMA RUSTDOCTEST")
.execute(&mut con)
.await?;
Array-like parameter binding, also featuring the [`ExaIter`] adapter.
An important thing to note is that the parameter sets must be of equal length,
otherwise an error is thrown:
rust
use std::{collections::HashSet, env};
use sqlx_exasol::;
let pool = ExaPool::connect(&env::var("DATABASE_URL").unwrap()).await?; let mut con = pool.acquire().await?;
let params1 = vec![1, 2, 3]; let params2 = HashSet::from([1, 2, 3]);
sqlx::query("INSERT INTO MYTABLE VALUES (?, ?)")
.bind(¶ms1)
.bind(ExaIter::from(¶ms2))
.execute(&mut *con)
.await?;
An EXPORT - IMPORT ETL data pipe. The data is always in `CSV` format and some configuration can
be done through the `ImportBuilder` and `ExportBuilder` structs:
rust
use std::env;
use futuresutil::{
future::{tryjoin, tryjoin3, tryjoinall},
AsyncReadExt, AsyncWriteExt, TryFutureExt,
};
use sqlx_exasol::{etl::*, *};
async fn pipe(mut reader: ExaExport, mut writer: ExaImport) -> anyhow::Result<()> { let mut buf = vec![0; 5120].intoboxedslice(); let mut read = 1;
while read > 0 {
// Readers return EOF when there's no more data.
read = reader.read(&mut buf).await?;
// Write data to Exasol
writer.write_all(&buf[..read]).await?;
}
// Writes, unlike readers, MUST be closed to signal we won't send more data to Exasol
writer.close().await?;
Ok(())
}
let pool = ExaPool::connect(&env::var("DATABASE_URL").unwrap()).await?; let mut con1 = pool.acquire().await?; let mut con2 = pool.acquire().await?;
// Build EXPORT job let (exportfut, readers) = ExportBuilder::new(ExportSource::Table("TESTETL")) .build(&mut con1) .await?;
// Build IMPORT job let (importfut, writers) = ImportBuilder::new("TESTETL").build(&mut con2).await?;
// Use readers and writers in some futures let transport_futs = std::iter::zip(readers, writers).map(|(r, w)| pipe(r, w));
// Execute the EXPORT and IMPORT query futures along with the worker futures let (exportres, importres, ) = tryjoin3( exportfut.maperr(From::from), importfut.maperr(From::from), tryjoinall(transport_futs), ) .await?;
asserteq!(exportres.rowsaffected(), importres.rows_affected()); ```
Licensed under either of
at your option.
Contributions to this repository, unless explicitly stated otherwise, will be considered dual-licensed under MIT and Apache 2.0.
Bugs/issues encountered can be opened here
1: There is unfortunately no way to automagically choose a crate's feature flags based on its dependencies feature flags, so the TLS backend has
to be manually selected. While nothing prevents you from using, say native-tls
with sqlx
and rustls
with Exasol ETL jobs, it might be best to avoid compiling
two different TLS backends. Therefore, consider choosing the sqlx
and sqlx-exasol
feature flags in a consistent manner.
2: The sqlx
API powering the compile-time query checks and the sqlx-cli
tool is not public. Even if it were, the drivers that are incorporated into sqlx
are hardcoded in the part of the code that handles the compile-time driver decision logic.
The main problem from what I can gather is that there's no easy way of defining a plugin system in Rust at the moment, hence the hardcoding.
3: Exasol has no advisory or database locks and simple, unnested, transactions are unfortunately not enough to define a mechanism so that concurrent migrations do not collide. This does not pose a problem when migrations are run sequentially or do not act on the same database objects.
4: Exasol does not provide the information of whether a column is 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.
5: I didn't even know this (as I never even thought of doing it), but sqlx
allows running multiple queries in a single statement. Due to limitations with the websocket API this driver is based on, sqlx-exasol
can only run one query at a time.
This is only circumvented in migrations through a somewhat limited, convoluted and possibly costly workaround that tries to split queries by ;
, which does not make it applicable for runtime queries at all.