Expand description
exasol
Database connector for Exasol implemented using the Websocket protocol. Messages are sent and received in the JSON format.
Errors
Most, if not all, of the public API is implemented to return a Result due to the nature of the library. Mentioning this here to avoid repeating the same note on all public functions.
Examples
Using the connect function to conveniently create a connection.
use exasol::error::Result;
use exasol::{connect, bind, QueryResult};
use std::env;
// Credentials can be retrieved however you like
let dsn = env::var("EXA_DSN").unwrap();
let schema = env::var("EXA_SCHEMA").unwrap();
let user = env::var("EXA_USER").unwrap();
let password = env::var("EXA_PASSWORD").unwrap();
// Quick and convenient way to create a connection
let mut exa_con = connect(&dsn, &schema, &user, &password).unwrap();
// Executing a statement
let result = exa_con.execute("SELECT '1', '2', '3' UNION ALL SELECT '4', '5', '6'").unwrap();
// The ResultSet is a lazy iterator, fetching rows as needed.
// For that reason, it can only be iterated over when owned,
// as it gets consumed in the process.
if let QueryResult::ResultSet(r) = result {
// Each row is a Result<Vec<serde_json::Value>>
for row in r {
for col_val in row.unwrap() {
// Columns values are of type serde_json::Value
println!("{}", col_val);
}
}
}Keeping in mind that the result set is an iterator when owned, the full iterator toolset can be used.
Additionally, TryFrom is implemented for QueryResult to make
ResultSet retrieval more convenient, instead of having to match
against it (the same can be done for the u32 representing the affected rows count).
use exasol::error::Result;
use exasol::{connect, bind, QueryResult, ResultSet};
use std::env;
use serde_json::Value;
let dsn = env::var("EXA_DSN").unwrap();
let schema = env::var("EXA_SCHEMA").unwrap();
let user = env::var("EXA_USER").unwrap();
let password = env::var("EXA_PASSWORD").unwrap();
let mut exa_con = connect(&dsn, &schema, &user, &password).unwrap();
let result = exa_con.execute("SELECT OBJECT_NAME FROM EXA_ALL_OBJECTS LIMIT 5000;").unwrap();
ResultSet::try_from(result)
.unwrap()
.take(500)
.collect::<Result<Vec<Vec<Value>>>>()
.unwrap()
.into_iter()
.map(|row| row[0].as_str().unwrap().to_owned())
.collect::<Vec<String>>();In the example above, note the collect::<Result<Vec<Row>>>() call.
When iterating over a result set, because fetch requests may fail, every row
(which by default is a Vec<serde_json::Value>) is in the form of a Result.
collect can handle that for us and convert a Vec<Result
Custom Row Type
The crate implements a custom deserializer, which is a trimmed down specialized version
of the serde_json deserializer. Database row deserialization can be attempted to any
type implementing Deserialize.
This is thanks to the magic of serde. In addition, regular serde features are supported,
such as flattening or renaming. Note that implicitly, column names are converted to lowercase
for easier deserialization. This can be changed through the Connection::set_lowercase_columns
method.
Map-like and sequence-like types are natively deserialized. For enum sequence-like variants,
see deserialize_as_seq.
use exasol::{connect, QueryResult, ResultSet};
use exasol::error::Result;
use serde_json::Value;
use serde::Deserialize;
use std::env;
let dsn = env::var("EXA_DSN").unwrap();
let schema = env::var("EXA_SCHEMA").unwrap();
let user = env::var("EXA_USER").unwrap();
let password = env::var("EXA_PASSWORD").unwrap();
let mut exa_con = connect(&dsn, &schema, &user, &password).unwrap();
let result = exa_con.execute("SELECT 1, 2 UNION ALL SELECT 1, 2;").unwrap();
// Change the expected row type with the turbofish notation
let mut result_set = ResultSet::try_from(result).unwrap().with_row_type::<(String, String)>();
let row1 = result_set.next();
// Nothing stops you from changing row types
// on the same result set, even while iterating through it
let mut result_set = result_set.with_row_type::<Vec<String>>();
let row2 = result_set.next();
let result = exa_con.execute("SELECT 1 as col1, 2 as col2, 3 as col3 \
UNION ALL \
SELECT 4 as col1, 5 as col2, 6 as col3;").unwrap();
#[derive(Debug, Deserialize)]
struct Test {
col1: u8,
col2: u8,
col3: u8,
}
let result_set = ResultSet::try_from(result).unwrap().with_row_type::<Test>();
for row in result_set.with_row_type::<Test>() {
let ok_row = row.unwrap();
// do stuff with row
}Parameter binding
Queries can be composed by binding named or positional parameters to them through the bind function. The function takes a string and a type implementing the Serialize trait. The second argument must serialize to a sequence or map.
Named parameter values behaviour:
- single value type get parsed to their SQL representation
- sequence-like types get parsed to a parenthesized list with elements in SQL representation
- map-like types get their values parsed to a parenthesized list with elements in SQL representation
use exasol::bind;
use serde::Serialize;
#[derive(Serialize)]
struct Parameters {
col1: String,
col2: u16,
col3: Vec<String>
}
let params = Parameters {
col1: "test".to_owned(),
col2: 10,
col3: vec!["a".to_owned(), "b".to_owned(), "c".to_owned()]
};
let query = "\
SELECT * FROM TEST_TABLE \
WHERE NAME = :col1 \
AND ID = :col2 \
AND VALUE IN :col3;";
let new_query = bind(query, params).unwrap();
assert_eq!(new_query, "\
SELECT * FROM TEST_TABLE \
WHERE NAME = 'test' \
AND ID = 10 \
AND VALUE IN ('a', 'b', 'c');");Custom Connection
The Connection struct can be directly instantiated with the use of ConOpts.
use exasol::{ConOpts, Connection};
use std::env;
let dsn = env::var("EXA_DSN").unwrap();
let schema = env::var("EXA_SCHEMA").unwrap();
let user = env::var("EXA_USER").unwrap();
let password = env::var("EXA_PASSWORD").unwrap();
// Only providing fields for which we want custom values
let mut opts = ConOpts::new();
opts.set_dsn(dsn);
opts.set_user(user);
opts.set_password(password);
opts.set_schema(schema);
let exa_con = Connection::new(opts).unwrap();Features
native-tls- (disabled by default) enablestungsteniteWSS support through native-tlsrustls- (disabled by default) enablestungsteniteWSS support through rustlsflate2- (disabled by default) enables support for requests and responses compression
Enabling these features allows changing additional settings in ConOpts instances.
Panics
Attempting to use these methods without their respective features enabled results in panics.
let mut opts = ConOpts::new();
opts.set_dsn(dsn);
opts.set_user(user);
opts.set_password(password);
opts.set_schema(schema);
opts.set_encryption(true);
opts.set_compression(true);Batch Execution
Faster query execution can be achieved through either Connection::execute_batch or PreparedStatement.
Re-exports
pub use crate::con_opts::ConOpts;pub use crate::con_opts::ProtocolVersion;pub use crate::connection::connect;pub use crate::connection::Connection;pub use crate::params::bind;pub use crate::query::PreparedStatement;pub use crate::query::QueryResult;pub use crate::query::ResultSet;pub use crate::response::Column;pub use crate::response::DataType;pub use crate::row::deserialize_as_seq;