Batiskaf - библиотека, предназначенная для избавления от бойлерплейта при работе с rusqlite. Идея заключается в том, что программист пишет SQL-код, а преобразования структур в параметры для запросов и результаты запросов в структуры выполняет магия библиотеки. Идея честно позаимствована из замечательного фреймворка MyBatis.
Добавить в Cargo.toml
:
batiskaf = { git = "https://github.com/yakov-bakhmatov/batiskaf", features = ["derive"] }
Batiskaf зависит от rusqlite версии 0.17
use rusqlite::{Connection, NO_PARAMS};
use batiskaf::*;
#[derive(Debug, SqlParam, SqlResult)]
struct Person {
pub id: i64,
pub name: String,
pub age: Option<u32>,
}
fn main() {
let conn = Connection::open_in_memory().unwrap();
conn.execute(
"create table person (id integer primary key, name text not null, age integer)",
NO_PARAMS,
)
.unwrap();
let person = Person {
id: 0,
name: "Bob".to_string(),
age: Some(30),
};
let mut stmt = conn
.prepare("insert into person (name, age) values (:name, :age)")
.unwrap();
let params = person.to_named_params(&stmt);
stmt.execute_named(¶ms).unwrap();
let mut select = conn.prepare("select id, name, age from person").unwrap();
let mut rows = select.query(NO_PARAMS).unwrap();
let row = rows.next().unwrap().unwrap();
let bob = Person::from_row(&row).unwrap();
println!("{:?}", bob); // Person { id: 1, name: "Bob", age: Some(30) }
}
fn to_named_params(&self, stmt: &Statement) -> Vec<(&str, &dyn ToSql)>;
Функция предназначена для преобразования структуры в именованные параметры SQL-запроса.
fn from_row(row: &Row) -> rusqlite::Result<Self>;
Функция предназначена для преобразования строки результата запроса в структуру.
fn insert_statement(table: &str) -> String;
Вспомогательный trait для уменьшения бойлерплейта; его единственная функция предназначена для генерации SQL-запроса INSERT для создания записи в указанной таблице.
fn update_statement(table: &str) -> String;
Функция генерирует SQL-запрос обновления записи в указанной таблице.
fn delete_statement(table: &str) -> String;
Функция возвращает SQL-запрос удаления записи из указанной таблицы.
Дополняет структуру rusqlite::Connection
следующими функциями:
fn select_one<T: SqlResult>(&self, sql: &str, params: &[(&str, &dyn ToSql)]) -> rusqlite::Result<T>;
Функция-обёртка над rusqlite::Connection::query_row_named
, преобразующая результат запроса в тип T
.
fn select_many<T: SqlResult>(&self, sql: &str, params: &[(&str, &dyn ToSql)]) -> rusqlite::Result<Vec<T>>;
Функция-обёртка над rusqlite::Connection::query_named
, преобразующая все строки результата запроса в тип T
.
fn insert<T: SqlInsert + SqlParam>(&self, table: &str, value: &T) -> rusqlite::Result<i64>;
Функция вставляет строку в таблицу table
. SQL-код выражения INSERT
генерируется функцией T::insert_statement
, параметром для запроса является аргумент value
. Функция возвращает rowid
только что вставленной строки.
fn update<T: SqlUpdate + SqlParam>(&self, table: &str, value: &T) -> rusqlite::Result<usize>;
Функция изменяет строки в таблице table
на основании запроса T::insert_statement
с параметрами value
и возвращает количество изменённых строк.
fn delete<T: SqlDelete + SqlParam>(&self, table: &str, value: &T) -> rusqlite::Result<usize>;
Функция удаляет строки из таблицы table
при помощи запроса T::delete_statement
и значения value
и возвращает количество удалённых строк.
Библиотека batiskaf сама по себе довольно бесполезная. Эту ситуацию исправляет библиотека batiskaf_derive, избавляя программиста от кучи бойлерплейта при помощи магии процедурных макросов.
Библиотека умеет выводить реализацию трейтов batiskaf для структур с именованными полями.
use rusqlite::{Connection, NO_PARAMS};
use batiskaf::*;
#[derive(Debug, SqlParam, SqlResult, SqlInsert, SqlUpdate, SqlDelete)]
struct Person {
#[batiskaf(primary_key, autogenerated)]
pub id: i64,
#[batiskaf(column = "full_name")]
pub name: String,
pub age: Option<u32>,
#[batiskaf(skip, default)]
pub hobby: Option<String>,
}
fn main() {
let conn = Connection::open_in_memory().unwrap();
conn.execute(
"create table person (\
id integer primary key, \
full_name text not null, \
age integer\
)",
NO_PARAMS,
)
.unwrap();
let mut bob = Person {
id: 0,
name: "Bob".to_string(),
age: Some(30),
hobby: Some("wood carving".to_string()),
};
bob.id = conn.insert("person", &bob).unwrap();
bob.age = Some(31);
conn.update("person", &bob).unwrap();
let stored_bob: Person = conn
.select_one("select id, full_name, age from person where id = :id", &[(":id", &bob.id)])
.unwrap();
println!("{:?}", stored_bob); // Person { id: 1, name: "Bob", age: Some(31), hobby: None }
conn.delete("person", &bob).unwrap();
let nobody: Vec<Person> = conn
.select_many("select id, full_name, age from person", &[])
.unwrap();
println!("{}", nobody.len()); // 0
}
Переименование столбца. По-умолчанию название столбца в БД совпадает с названием поля структуры. Атрибут column
задаёт другое название для соответствующего столбца в таблице.
Применяется во всех пяти трейтах.
Поле (и соответствующий столбец) является первичным ключом. В том случае, когда этот атрибут указан для нескольких полей, соответствующие столбцы образуют составной первичный ключ. Первичные ключи используются при выводе SqlUpdate
и SqlDelete
для идентификации изменяемой (удаляемой) строки.
Учитывается при генерации SqlUpdate
и SqlDelete
. Если ни одно поле не будет иметь этот атрибут, будет ошибка компиляции.
Если все поля имеют атрибут primary_key
, компиляция трейта SqlUpdate
завершится с ошибкой.
Значение соответствуюего столбца является автогенерируемым и пропускается при генерации SQL-кода выражения INSERT
, возвращаемого функцией SqlInsert::insert_statement
.
Учитывается при выводе SqlInsert
.
Поле не используется в SQL-выражениях. Применяется ко всем пяти трейтам.
Если поле пропущено или его нет в результатах запроса (SqlResult
), использовать значение по-умолчанию.
Может быть применено к структуре в целом; в таком случае каждое поле получает этот атрибут.
При автогенерации реализации SqlResult
этот атрибут должен быть указан, если указан атрибут skip
.
При выводе трейта SqlResult
все generic-типы в объявлении структуры получают дополнительные ограничения: ::std::default::Default + ::rusqlite::types::FromSql
.