sql-middleware
Sql-middleware is a lightweight async wrapper for tokio-postgres and rusqlite, with deadpool connection pooling, and an async api (via deadpool-sqlite and tokio-postgres). A slim alternative to SQLx; fewer features, but striving toward a consistent api regardless of database backend.
Motivated from trying SQLx, not liking some issue others already noted, and wanting an alternative.
Goals
- Convenience functions for common sql query patterns
- Keep underlying flexibility of deadpool-sqlite and deadpool-postgres.
- Minimal overhead (just syntax convenience/wrapper fns).
Examples
More examples available in the tests dir, and this is in-use with a tiny little website app, rusty-golf.
Importing
Use the prelude to import everything you need, or import stuff item by item:
use *;
Get a connection from the pool
Similar api regardless of db backend.
let mut cfg = deadpool_postgres
new;
cfg.dbname = Some;
cfg.host = Some;
cfg.port = Some;
cfg.user = Some;
cfg.password = Some;
let c = ConfigAndPool
new_postgres
.await?;
let conn = MiddlewarePool
get_connection
.await?;
let cfg =
"file::memory:?cache=shared"
.to_string;
// same api for connection
// sqlite just has fewer required
// config items (no port, etc.)
let c =
new_sqlite
.await?;
let conn = MiddlewarePool
get_connection
.await?;
Batch query w/o params
Same api regardless of db backend.
// simple api for batch queries
let ddl_query =
include_str!;
conn.execute_batch.await?;
// same api
let ddl_query =
include_str!;
conn.execute_batch.await?;
Parameterized Queries
Consistent api for running parametrized queries using the QueryAndParams
struct. Database-specific parameter syntax is handled by the middleware.
// Create query with parameters
// tokio-postgres uses $-style params
let q = new;
// Convert params using the ParamConverter trait
let converted_params =
?;
// Execute the query
conn.execute_dml
.await?;
// Create query with parameters
// rusqlite uses ?-style params
let q = new;
// Similar API for parameter conversion
let converted_params =
?;
// Execute the query
conn.execute_dml
.await?;
Queries without parameters
You can create queries without parameters using new_without_params
, same whether using sqlite or postgres:
let query = new_without_params;
let results = conn.execute_select.await?;
Transactions with custom logic
Here, the APIs differ, because the underlying database's transaction approach differs. It doesn't appear easy to make these consistent. But this is the way to do queries if you need custom app logic between connection.transaction()
and connection.commit()
.
// Get PostgreSQL-specific connection
let pg_conn = match &conn ;
// Get client
let pg_client = &pg_conn.client;
let tx = pg_client.transaction.await?;
// could run custom logic anywhere between stmts
// Prepare statement
let stmt = tx.prepare.await?;
// Convert parameters
let converted_params =
?;
// Execute query
let rows = tx.execute.await?;
tx.commit.await?;
// Get SQLite-specific connection
let sqlite_conn = match &conn ;
// Use interact for async tx
let rows = sqlite_conn
.interact
.await?;
Using the AsyncDatabaseExecutor trait
The AsyncDatabaseExecutor
trait provides a consistent interface for database operations:
// This works for both PostgreSQL and SQLite connections
async