use crate::connection::Connection;
use crate::error::SqliteError;
use crate::types::{Param, Row, Rows};
pub struct SelectBuilder<'a> {
conn: &'a Connection,
table: String,
columns: Vec<String>,
where_clause: Option<String>,
where_params: Vec<Param>,
order_by: Option<String>,
limit: Option<usize>,
offset: Option<usize>,
}
impl<'a> SelectBuilder<'a> {
pub fn new(conn: &'a Connection, table: impl Into<String>) -> Self {
Self {
conn,
table: table.into(),
columns: vec!["*".to_string()],
where_clause: None,
where_params: Vec::new(),
order_by: None,
limit: None,
offset: None,
}
}
pub fn columns(mut self, cols: &[&str]) -> Self {
self.columns = cols.iter().map(|s| s.to_string()).collect();
self
}
pub fn filter(mut self, condition: &str, params: impl IntoIterator<Item = Param>) -> Self {
self.where_clause = Some(condition.to_string());
self.where_params = params.into_iter().collect();
self
}
pub fn filter_eq(self, column: &str, value: impl Into<Param>) -> Self {
self.filter(&format!("{} = ?", column), [value.into()])
}
pub fn order_by(mut self, column: &str, desc: bool) -> Self {
let direction = if desc { "DESC" } else { "ASC" };
self.order_by = Some(format!("{} {}", column, direction));
self
}
pub fn limit(mut self, limit: usize) -> Self {
self.limit = Some(limit);
self
}
pub fn offset(mut self, offset: usize) -> Self {
self.offset = Some(offset);
self
}
pub fn build(&self) -> (String, Vec<Param>) {
let mut sql = format!("SELECT {} FROM {}", self.columns.join(", "), self.table);
let params = self.where_params.clone();
if let Some(ref where_clause) = self.where_clause {
sql.push_str(" WHERE ");
sql.push_str(where_clause);
}
if let Some(ref order) = self.order_by {
sql.push_str(" ORDER BY ");
sql.push_str(order);
}
if let Some(limit) = self.limit {
sql.push_str(&format!(" LIMIT {}", limit));
}
if let Some(offset) = self.offset {
sql.push_str(&format!(" OFFSET {}", offset));
}
(sql, params)
}
pub fn fetch_all(self) -> Result<Rows, SqliteError> {
let (sql, params) = self.build();
self.conn.query(&sql, ¶ms)
}
pub fn fetch_one(self) -> Result<Option<Row>, SqliteError> {
let (sql, params) = self.build();
self.conn.query_row(&sql, ¶ms)
}
pub fn count(self) -> Result<i64, SqliteError> {
let sql = format!(
"SELECT COUNT(*) as cnt FROM {}{}",
self.table,
self.where_clause
.as_ref()
.map(|w| format!(" WHERE {}", w))
.unwrap_or_default()
);
let row = self.conn.query_row(&sql, &self.where_params)?;
Ok(row.and_then(|r| r.get_i64("cnt")).unwrap_or(0))
}
}
pub struct InsertBuilder<'a> {
conn: &'a Connection,
table: String,
columns: Vec<String>,
values: Vec<Param>,
}
impl<'a> InsertBuilder<'a> {
pub fn new(conn: &'a Connection, table: impl Into<String>) -> Self {
Self {
conn,
table: table.into(),
columns: Vec::new(),
values: Vec::new(),
}
}
pub fn set(mut self, column: &str, value: impl Into<Param>) -> Self {
self.columns.push(column.to_string());
self.values.push(value.into());
self
}
pub fn build(&self) -> (String, Vec<Param>) {
let placeholders = vec!["?"; self.columns.len()].join(", ");
let sql = format!(
"INSERT INTO {} ({}) VALUES ({})",
self.table,
self.columns.join(", "),
placeholders
);
(sql, self.values.clone())
}
pub fn execute(self) -> Result<i64, SqliteError> {
let (sql, params) = self.build();
self.conn.execute(&sql, ¶ms)?;
self.conn.last_insert_rowid()
}
}
pub struct UpdateBuilder<'a> {
conn: &'a Connection,
table: String,
sets: Vec<(String, Param)>,
where_clause: Option<String>,
where_params: Vec<Param>,
}
impl<'a> UpdateBuilder<'a> {
pub fn new(conn: &'a Connection, table: impl Into<String>) -> Self {
Self {
conn,
table: table.into(),
sets: Vec::new(),
where_clause: None,
where_params: Vec::new(),
}
}
pub fn set(mut self, column: &str, value: impl Into<Param>) -> Self {
self.sets.push((column.to_string(), value.into()));
self
}
pub fn filter(mut self, condition: &str, params: impl IntoIterator<Item = Param>) -> Self {
self.where_clause = Some(condition.to_string());
self.where_params = params.into_iter().collect();
self
}
pub fn filter_eq(self, column: &str, value: impl Into<Param>) -> Self {
self.filter(&format!("{} = ?", column), [value.into()])
}
pub fn build(&self) -> (String, Vec<Param>) {
let set_clause: Vec<String> = self.sets.iter().map(|(col, _)| format!("{} = ?", col)).collect();
let mut sql = format!("UPDATE {} SET {}", self.table, set_clause.join(", "));
let mut params: Vec<Param> = self.sets.iter().map(|(_, v)| v.clone()).collect();
if let Some(ref where_clause) = self.where_clause {
sql.push_str(" WHERE ");
sql.push_str(where_clause);
params.extend(self.where_params.clone());
}
(sql, params)
}
pub fn execute(self) -> Result<usize, SqliteError> {
let (sql, params) = self.build();
self.conn.execute(&sql, ¶ms)
}
}
pub struct DeleteBuilder<'a> {
conn: &'a Connection,
table: String,
where_clause: Option<String>,
where_params: Vec<Param>,
}
impl<'a> DeleteBuilder<'a> {
pub fn new(conn: &'a Connection, table: impl Into<String>) -> Self {
Self {
conn,
table: table.into(),
where_clause: None,
where_params: Vec::new(),
}
}
pub fn filter(mut self, condition: &str, params: impl IntoIterator<Item = Param>) -> Self {
self.where_clause = Some(condition.to_string());
self.where_params = params.into_iter().collect();
self
}
pub fn filter_eq(self, column: &str, value: impl Into<Param>) -> Self {
self.filter(&format!("{} = ?", column), [value.into()])
}
pub fn build(&self) -> (String, Vec<Param>) {
let mut sql = format!("DELETE FROM {}", self.table);
if let Some(ref where_clause) = self.where_clause {
sql.push_str(" WHERE ");
sql.push_str(where_clause);
}
(sql, self.where_params.clone())
}
pub fn execute(self) -> Result<usize, SqliteError> {
let (sql, params) = self.build();
self.conn.execute(&sql, ¶ms)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn setup_test_db() -> Connection {
let conn = Connection::open_in_memory().unwrap();
conn.execute_batch(
"CREATE TABLE users (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
age INTEGER
)",
)
.unwrap();
conn
}
#[test]
fn test_insert() {
let conn = setup_test_db();
let id = InsertBuilder::new(&conn, "users")
.set("name", "Alice")
.set("age", 30)
.execute()
.unwrap();
assert_eq!(id, 1);
}
#[test]
fn test_select() {
let conn = setup_test_db();
InsertBuilder::new(&conn, "users")
.set("name", "Alice")
.set("age", 30)
.execute()
.unwrap();
InsertBuilder::new(&conn, "users")
.set("name", "Bob")
.set("age", 25)
.execute()
.unwrap();
let rows = SelectBuilder::new(&conn, "users").fetch_all().unwrap();
assert_eq!(rows.len(), 2);
let rows = SelectBuilder::new(&conn, "users")
.filter_eq("name", "Alice")
.fetch_all()
.unwrap();
assert_eq!(rows.len(), 1);
let rows = SelectBuilder::new(&conn, "users")
.order_by("age", false)
.limit(1)
.fetch_all()
.unwrap();
assert_eq!(rows.len(), 1);
assert_eq!(rows[0].get_str("name"), Some("Bob"));
}
#[test]
fn test_update() {
let conn = setup_test_db();
InsertBuilder::new(&conn, "users")
.set("name", "Alice")
.set("age", 30)
.execute()
.unwrap();
let affected = UpdateBuilder::new(&conn, "users")
.set("age", 31)
.filter_eq("name", "Alice")
.execute()
.unwrap();
assert_eq!(affected, 1);
let row = SelectBuilder::new(&conn, "users")
.filter_eq("name", "Alice")
.fetch_one()
.unwrap()
.unwrap();
assert_eq!(row.get_i64("age"), Some(31));
}
#[test]
fn test_delete() {
let conn = setup_test_db();
InsertBuilder::new(&conn, "users")
.set("name", "Alice")
.set("age", 30)
.execute()
.unwrap();
InsertBuilder::new(&conn, "users")
.set("name", "Bob")
.set("age", 25)
.execute()
.unwrap();
let affected = DeleteBuilder::new(&conn, "users")
.filter_eq("name", "Alice")
.execute()
.unwrap();
assert_eq!(affected, 1);
let count = SelectBuilder::new(&conn, "users").count().unwrap();
assert_eq!(count, 1);
}
#[test]
fn test_count() {
let conn = setup_test_db();
for i in 0..5 {
InsertBuilder::new(&conn, "users")
.set("name", format!("User{}", i))
.set("age", 20 + i)
.execute()
.unwrap();
}
let count = SelectBuilder::new(&conn, "users").count().unwrap();
assert_eq!(count, 5);
let count = SelectBuilder::new(&conn, "users")
.filter("age >= ?", [22i32.into()])
.count()
.unwrap();
assert_eq!(count, 3);
}
}