Crate derive_sql
source ·Expand description
The crate derive_sql
is articulated around:
- traits that allow SQL database drivers (
rusqlite
andmysql
) to provide defined set of functionalities; - traits that scaffold on these common functionalities to wrap common SQL statement functions into typed interfaces.
The trait Connection
provides a uniform interface for interaction with SQL
database drivers via query
calls where all elements of the query are encompassed
in the statement; and execute
calls where the statement refers to parameters
to be provided.
use derive_sql::traits;
// Function to create table, populate table with an entry, and retrieve the first entry field name
fn run<S, R>(s: &mut S) -> String
where S: traits::Connection<R>,
R: traits::Row,
{
s.query_drop("DROP TABLE IF EXISTS example_table").unwrap();
s.query_drop("CREATE TABLE example_table (
id INTEGER,
name TEXT
)").unwrap();
s.execute_with_params("INSERT INTO example_table (id, name) VALUES (?, ?)",
&(1i64, "Jane Doe".to_string())
).unwrap();
let r: String = s.query_first("SELECT name FROM example_table").unwrap()
.map(|r| r.get(0).unwrap().unwrap())
.unwrap();
r
}
// Called using a SQLite connection via rusqlite
let mut conn = rusqlite::Connection::open_in_memory().unwrap();
assert!(run(&mut conn).eq("Jane Doe"));
// Call using a MySQL connection
let mut mysql_conn = mysql::Conn::new(
mysql::Opts::from_url("mysql://test@localhost/simpledb").unwrap()
).unwrap();
assert!(run(&mut mysql_conn).eq("Jane Doe"));
The following is showing the same test using a set of traits provided to standardize the interaction with SQL databases:
use derive_sql::traits::{self, Table, TableStatement, SelectV2, SelectStatement, Insert, InsertStatement,
Delete, DeleteStatement,
Update, UpdateStatement,
Param, Params, Row, TryFromRefRow,
ToParam,
};
use derive_sql::structs::{filter, Field};
use derive_sql::Result;
// Define a `struct` representing the data to be stored
/* #[derive(Clone)] */
struct People {
id: i64,
name: String,
}
// Implement traits to convert `People` to and from
impl Params for People {
fn as_vec_params(&self) -> Result<Vec<Param>> {
Ok(vec![self.id.to_param()?, self.name.to_param()?])
}
}
impl<R> TryFromRefRow<R> for People
where R: Row,
{
fn try_from(r: &R) -> Result<Self> {
Ok(People {
id: r.get(0).ok_or(derive_sql::Error::RowItemNotFound(0))??,
name: r.get(1).ok_or(derive_sql::Error::RowItemNotFound(1))??,
})
}
}
#[derive(Default)]
struct SqlPeople {}
// Implement traits to manipulate the data using struct `SqlPeople`
impl TableStatement for SqlPeople {
fn create_stmt(&self) -> Result<String> { Ok(format!("CREATE TABLE example2_table ( id INTEGER, name TEXT )")) }
fn create_if_not_exist_stmt(&self) -> Result<String> { Ok(format!("CREATE TABLE IF NOT EXISTS example2_table ( id INTEGER, name TEXT )")) }
fn drop_stmt(&self) -> Result<String> { Ok(format!("DROP TABLE IF EXISTS example2_table")) }
}
impl SelectStatement for SqlPeople {
fn select_stmt(&self) -> Result<String> { Ok(format!("SELECT id,name FROM example2_table")) }
}
impl InsertStatement for SqlPeople {
fn insert_stmt(&self) -> Result<String> { Ok(format!("INSERT INTO example2_table (id, name) VALUES (?, ?)")) }
}
impl UpdateStatement for SqlPeople {
fn update_stmt(&self) -> Result<String> { Ok(format!("UPDATE example2_table SET `id` = ?, `name` = ?")) }
}
impl DeleteStatement for SqlPeople {
fn delete_stmt(&self) -> Result<String> { Ok(format!("DELETE FROM example2_table")) }
}
fn run<S, R>(s: &mut S) -> String
where S: traits::Connection<R>,
R: traits::Row,
{
SqlPeople::default().drop(s).unwrap();
SqlPeople::default().create(s).unwrap();
SqlPeople::default().insert(s, &People { id: 1, name: "Jane Doe".to_string() }).unwrap();
SqlPeople::default().insert(s, &People { id: 2, name: "Jane Foe".to_string() }).unwrap();
SqlPeople::default().insert(s, &People { id: 3, name: "Jane Goe".to_string() }).unwrap();
let r: Vec<People> = SqlPeople::default().select(s).unwrap();
assert!(r.len() == 3);
let r: Vec<People> = SqlPeople::default().select_with_filter(s,
&filter::Or::from((Field::from("id").eq(1), Field::from("id").eq(3)))
).unwrap();
assert!(r.len() == 2);
let r: Vec<People> = SqlPeople::default().select_with_filter(s,
&filter::And::from((Field::from("id").eq(1), Field::from("name").ne("Jane Doe")))
).unwrap();
assert!(r.len() == 0);
let r: Vec<People> = SqlPeople::default().select_with_filter_order_limit_offset(s,
&filter::None::default(),
&Field::from("id").descending(),
1, // Limit
0, // Offset
).unwrap();
assert!(r[0].id == 3);
SqlPeople::default().update_with_filter(s,
&Field::from("id").eq(3),
&People { id: 3, name: "Francis".to_string() },
).unwrap();
let r: Vec<People> = SqlPeople::default().select_with_filter(s,
&Field::from("id").eq(3),
).unwrap();
assert!(r[0].name.eq("Francis"));
SqlPeople::default().delete_with_filter(s, &Field::from("name").eq("Francis")).unwrap();
let r: Vec<People> = SqlPeople::default().select(s).unwrap();
assert!(r.len() == 2);
let r: Vec<People> = SqlPeople::default().select_with_filter(s, &Field::from("id").eq(1)).unwrap();
assert!(r.len() == 1);
r[0].name.clone()
}
// Called using a SQLite connection via rusqlite
let mut conn = rusqlite::Connection::open_in_memory().unwrap();
assert!(run(&mut conn).eq("Jane Doe"));
// Call using a MySQL connection
let mut mysql_conn = mysql::Conn::new(
mysql::Opts::from_url("mysql://test@localhost/simpledb").unwrap()
).unwrap();
assert!(run(&mut mysql_conn).eq("Jane Doe"));
This replicates the same test using the derive macro DeriveSqlStatement
:
use derive_sql::{DeriveSqlStatement};
use derive_sql::traits::{Table, Insert, Delete, Update, SelectV2};
use derive_sql::{traits, structs::filter, structs::Field, Result};
// Define a `struct` representing the data to be stored
#[derive(DeriveSqlStatement)]
struct People {
id: i64,
name: String,
}
fn run<S, R>(s: &mut S) -> String
where S: traits::Connection<R>,
R: traits::Row,
{
SqlPeople::default().drop(s).unwrap();
SqlPeople::default().create(s).unwrap();
SqlPeople::default().insert(s, &People { id: 1, name: "Jane Doe".to_string() }).unwrap();
SqlPeople::default().insert(s, &People { id: 2, name: "Jane Foe".to_string() }).unwrap();
SqlPeople::default().insert(s, &People { id: 3, name: "Jane Goe".to_string() }).unwrap();
let r: Vec<People> = SqlPeople::default().select(s).unwrap();
assert!(r.len() == 3);
let r: Vec<People> = SqlPeople::default().select_with_filter(s,
&filter::Or::from((Field::from("id").eq(1), Field::from("id").eq(3)))
).unwrap();
assert!(r.len() == 2);
let r: Vec<People> = SqlPeople::default().select_with_filter(s,
&filter::And::from((Field::from("id").eq(1), Field::from("name").ne("Jane Doe")))
).unwrap();
assert!(r.len() == 0);
let r: Vec<People> = SqlPeople::default().select_with_filter_order_limit_offset(s,
&filter::None::default(),
&Field::from("id").descending(),
1, // Limit
0, // Offset
).unwrap();
assert!(r[0].id == 3);
SqlPeople::default().update_with_filter(s,
&Field::from("id").eq(3),
&People { id: 3, name: "Francis".to_string() },
).unwrap();
let r: Vec<People> = SqlPeople::default().select_with_filter(s,
&Field::from("id").eq(3),
).unwrap();
assert!(r[0].name.eq("Francis"));
SqlPeople::default().delete_with_filter(s, &Field::from("name").eq("Francis")).unwrap();
let r: Vec<People> = SqlPeople::default().select(s).unwrap();
assert!(r.len() == 2);
let r: Vec<People> = SqlPeople::default().select_with_filter(s, &Field::from("id").eq(1)).unwrap();
assert!(r.len() == 1);
r[0].name.clone()
}
// Called using a SQLite connection via rusqlite
let mut conn = rusqlite::Connection::open_in_memory().unwrap();
assert!(run(&mut conn).eq("Jane Doe"));
// Call using a MySQL connection
let mut mysql_conn = mysql::Conn::new(
mysql::Opts::from_url("mysql://test@localhost/simpledb").unwrap()
).unwrap();
assert!(run(&mut mysql_conn).eq("Jane Doe"));
§Legacy v0.10 feature:
Available by activating feature compability_v0_10
The trait Sqlable
that defines a set of operation for
interacting with SQL tables:
count
to provide a count of the number of items in the table.select
to return an array of the items in the table.insert
to insert a new item in the table.update
to update an existing item(s) with the values of the provided item.delete
to delete items in the table.delete_table
to drop the table.
Implementation of the trait should allow the user of the trait to interact with the table via the above interface…
The trait Selectable
provides a possible interface for selector queries
used in the Sqlable
trait. It is a possible option - but not limited
to it as the Sqlable
trait uses an associated type for Selector
.
This crate includes:
- the derive macro
DeriveSqlite
[when compiled with the feature--features sqlite
] which provides an implementation of theSqlable
trait for SQLite as a wrapper around therusqlite
crate; - the derive macro
DeriveMysql
[when compiled with the feature--features mysql
] which provides an implementation of theSqlable
trait for MySQL as a wrapper around themysql
crate;
Please see examples here and the DeriveSqlite
documentation.
§Features:
sqlite
provides a derive macro that implements theSqlable
trait for SQLite database (implemented as a wrapper around therusqlite
crate);mysql
provides a derive macro that implements theSqlable
trait for MySQL database (implemented as a wrapper around themysql
crate);
§Mocking:
The example of code below shows how the trait can be mocked using mockall
for unit testing purposes. The
example uses mockall
external trait functionality - ie works in a code using this crate as a dependency.
Note: one has to explicitely nominates the associated type in the method definitions.
mockall::mock! {
SqlableStruct {}
impl derive_sql::Sqlable for SqlableStruct {
type Item = String;
type Error = Box<dyn std::error::Error>;
type Selector = ();
fn count(&self, s: ()) -> Result<usize, Box<dyn std::error::Error>>;
fn select(&self, s: ()) -> Result<Vec<String>, Box<dyn std::error::Error>>;
fn insert(&mut self, item: String) -> Result<String, Box<dyn std::error::Error>>;
fn update(&mut self, s: (), item: String) -> Result<String, Box<dyn std::error::Error>>;
fn delete(&mut self, s: ()) -> Result<(), Box<dyn std::error::Error>>;
fn delete_table(&mut self) -> Result<(), Box<dyn std::error::Error>>;
}
}
fn my_function<S>(s: &mut S) -> Result<usize, Box<dyn std::error::Error>>
where S: derive_sql::Sqlable<Selector = (), Item = String, Error = Box<dyn std::error::Error>>,
{
let _ = s.insert("an item".to_string())?;
Ok(s.count(())?)
}
// Create mock
let mut mock = MockSqlableStruct::new();
// Configure mock
mock.expect_insert()
.with(mockall::predicate::eq("an item".to_string()))
.returning(|s| Ok(s));
mock.expect_count().returning(|_| Ok(11));
// Check result
assert!(matches!(my_function(&mut mock), Ok(11)));
Re-exports§
Modules§
- Implementation of generic approach to
WHERE
clauses filtering. Provides a generic operator for single clause andAnd
andOr
clauses combinator; - Proxy to manipulate SQL queries
- traits underlying the implementation of SQL functionalities. What some other crates call
prelude
Structs§
- Convenient struct for implementing a simple filter, ie a struct that generates the content of a simple
WHERE a = value
clause - Convenient struct for implementing a limit, ie a struct that generates the content of a
LIMIT value
clause - Convenient struct for implementing an offset, ie a struct that generates the content of an
OFFSET value
clause - Convenient struct for implementing an order by, ie a struct that generates the content of an
ORDER BY value ASC|DESC
clause
Enums§
- Convenient struct for implementing an order by, ie a struct that generates the content of an
ORDER BY value ASC|DESC
clause Nominate whether the order is ascending (ie A to Z) or descending (ie Z to A)
Traits§
- Definition of trait
Selectable
. This trait outputs filter, limit and offset statement. - Definition of
Sqlable
trait to be implemented to allow interaction with SQL tables.
Type Aliases§
Derive Macros§
- Derive macro to implement the
Sqlable
trait for a struct with named fields so that instances of the struct can be saved, queried, stored to/from a MySQL database. Usesmysql
. Requires--features mysql
. - Derive macro to implement the
Sqlable
trait for a struct with named fields so that instances of the struct can be saved, queried, stored to/from an SQLite database. Usesrusqlite
. Requires--features sqlite
.