use crate::filters::FilterValue;
use crate::{
Aggregate, Database, FilterOperator, Operator, PaginatedResult, Pagination, Result, Sort, Value,
};
use std::collections::HashMap;
pub struct QueryResult<T> {
pub data: Vec<T>,
pub total: Option<u64>,
}
impl<T> QueryResult<T> {
pub fn new(data: Vec<T>) -> Self {
Self { data, total: None }
}
pub fn with_total(data: Vec<T>, total: u64) -> Self {
Self {
data,
total: Some(total),
}
}
}
pub struct QueryBuilder {
table: String,
select_columns: Vec<String>,
joins: Vec<JoinClause>,
where_clauses: Vec<FilterOperator>,
group_by: Vec<String>,
having: Vec<FilterOperator>,
order_by: Vec<Sort>,
limit: Option<u32>,
offset: Option<u32>,
distinct: bool,
aggregate: Option<AggregateClause>,
}
struct JoinClause {
join_type: crate::JoinType,
table: String,
alias: Option<String>,
condition: String,
}
struct AggregateClause {
function: Aggregate,
column: String,
alias: Option<String>,
}
impl QueryBuilder {
pub fn new(table: impl Into<String>) -> Self {
Self {
table: table.into(),
select_columns: vec!["*".to_string()],
joins: Vec::new(),
where_clauses: Vec::new(),
group_by: Vec::new(),
having: Vec::new(),
order_by: Vec::new(),
limit: None,
offset: None,
distinct: false,
aggregate: None,
}
}
pub fn select(mut self, columns: Vec<impl Into<String>>) -> Self {
self.select_columns = columns.into_iter().map(|c| c.into()).collect();
self
}
pub fn join(
mut self,
join_type: crate::JoinType,
table: impl Into<String>,
condition: impl Into<String>,
) -> Self {
self.joins.push(JoinClause {
join_type,
table: table.into(),
alias: None,
condition: condition.into(),
});
self
}
pub fn join_as(
mut self,
join_type: crate::JoinType,
table: impl Into<String>,
alias: impl Into<String>,
condition: impl Into<String>,
) -> Self {
self.joins.push(JoinClause {
join_type,
table: table.into(),
alias: Some(alias.into()),
condition: condition.into(),
});
self
}
pub fn r#where(mut self, filter: FilterOperator) -> Self {
self.where_clauses.push(filter);
self
}
pub fn group_by(mut self, columns: Vec<impl Into<String>>) -> Self {
self.group_by = columns.into_iter().map(|c| c.into()).collect();
self
}
pub fn having(mut self, filter: FilterOperator) -> Self {
self.having.push(filter);
self
}
pub fn order_by(mut self, sort: Sort) -> Self {
self.order_by.push(sort);
self
}
pub fn order_by_multiple(mut self, sorts: Vec<Sort>) -> Self {
self.order_by.extend(sorts);
self
}
pub fn limit(mut self, limit: u32) -> Self {
self.limit = Some(limit);
self
}
pub fn offset(mut self, offset: u32) -> Self {
self.offset = Some(offset);
self
}
pub fn distinct(mut self, distinct: bool) -> Self {
self.distinct = distinct;
self
}
pub fn aggregate(
mut self,
function: Aggregate,
column: impl Into<String>,
alias: Option<impl Into<String>>,
) -> Self {
self.aggregate = Some(AggregateClause {
function,
column: column.into(),
alias: alias.map(|a| a.into()),
});
self
}
pub fn select_all(mut self) -> Self {
self.select_columns = vec!["*".to_string()];
self
}
pub fn select_columns(mut self, columns: &[&str]) -> Self {
self.select_columns = columns.iter().map(|&c| c.to_string()).collect();
self
}
pub fn select_column(mut self, column: &str) -> Self {
self.select_columns = vec![column.to_string()];
self
}
pub fn select_count(mut self) -> Self {
self.select_columns = vec!["COUNT(*)".to_string()];
self
}
pub fn select_aggregate(mut self, aggregate: &str) -> Self {
self.select_columns = vec![aggregate.to_string()];
self
}
pub fn select_distinct(mut self, column: &str) -> Self {
self.select_columns = vec![column.to_string()];
self.distinct = true;
self
}
pub fn where_condition(
mut self,
condition: &str,
_params: impl Into<Vec<crate::compat::LibsqlValue>>,
) -> Self {
self.where_clauses
.push(FilterOperator::Custom(condition.to_string()));
self
}
pub fn search(mut self, field: &str, query: &str) -> Self {
let condition = format!("{field} LIKE '%{query}%'");
self.where_clauses.push(FilterOperator::Custom(condition));
self
}
pub fn with_filter(mut self, filter: crate::Filter) -> Self {
self.where_clauses.push(FilterOperator::Single(filter));
self
}
pub fn with_filters(mut self, filters: Vec<crate::Filter>) -> Self {
for filter in filters {
self = self.with_filter(filter);
}
self
}
pub fn with_sorts(mut self, sorts: Vec<crate::Sort>) -> Self {
for sort in sorts {
self = self.order_by(sort);
}
self
}
pub fn having_condition(
mut self,
condition: &str,
_params: impl Into<Vec<crate::compat::LibsqlValue>>,
) -> Self {
self.having
.push(FilterOperator::Custom(condition.to_string()));
self
}
pub fn where_in(mut self, field: &str, subquery: QueryBuilder) -> Self {
let (subquery_sql, _) = subquery.build().unwrap_or_default();
let condition = format!("{field} IN ({subquery_sql})");
self.where_clauses.push(FilterOperator::Custom(condition));
self
}
pub async fn execute_count(&self, db: &Database) -> Result<u64> {
let (sql, params) = self.build_count()?;
let mut rows = db.query(&sql, params).await?;
if let Some(row) = rows.next().await? {
row.get_value(0)
.ok()
.and_then(|v| match v {
crate::compat::LibsqlValue::Integer(i) => Some(i as u64),
_ => None,
})
.ok_or_else(|| crate::Error::Query("Failed to get count".to_string()))
} else {
Err(crate::Error::Query("No count result".to_string()))
}
}
pub async fn execute_aggregate(&self, db: &Database) -> Result<Vec<crate::compat::LibsqlRow>> {
let (sql, params) = self.build()?;
let mut rows = db.query(&sql, params).await?;
let mut results = Vec::new();
while let Some(row) = rows.next().await? {
results.push(row);
}
Ok(results)
}
pub fn build(&self) -> Result<(String, Vec<crate::compat::LibsqlValue>)> {
let mut sql = String::new();
let mut params = Vec::new();
sql.push_str("SELECT ");
if self.distinct {
sql.push_str("DISTINCT ");
}
if let Some(agg) = &self.aggregate {
sql.push_str(&format!("{}({})", agg.function, agg.column));
if let Some(alias) = &agg.alias {
sql.push_str(&format!(" AS {alias}"));
}
} else {
sql.push_str(&self.select_columns.join(", "));
}
sql.push_str(&format!(" FROM {}", self.table));
for join in &self.joins {
sql.push_str(&format!(" {} {}", join.join_type, join.table));
if let Some(alias) = &join.alias {
sql.push_str(&format!(" AS {alias}"));
}
sql.push_str(&format!(" ON {}", join.condition));
}
if !self.where_clauses.is_empty() {
sql.push_str(" WHERE ");
let (where_sql, where_params) = self.build_where_clause(&self.where_clauses)?;
sql.push_str(&where_sql);
params.extend(where_params);
}
if !self.group_by.is_empty() {
sql.push_str(&format!(" GROUP BY {}", self.group_by.join(", ")));
}
if !self.having.is_empty() {
sql.push_str(" HAVING ");
let (having_sql, having_params) = self.build_where_clause(&self.having)?;
sql.push_str(&having_sql);
params.extend(having_params);
}
if !self.order_by.is_empty() {
sql.push_str(" ORDER BY ");
let order_clauses: Vec<String> = self
.order_by
.iter()
.map(|sort| format!("{} {}", sort.column, sort.order))
.collect();
sql.push_str(&order_clauses.join(", "));
}
if let Some(limit) = self.limit {
sql.push_str(&format!(" LIMIT {limit}"));
}
if let Some(offset) = self.offset {
sql.push_str(&format!(" OFFSET {offset}"));
}
Ok((sql, params))
}
pub fn build_count(&self) -> Result<(String, Vec<crate::compat::LibsqlValue>)> {
let mut sql = String::new();
let mut params = Vec::new();
sql.push_str("SELECT COUNT(*)");
sql.push_str(&format!(" FROM {}", self.table));
for join in &self.joins {
sql.push_str(&format!(" {} {}", join.join_type, join.table));
if let Some(alias) = &join.alias {
sql.push_str(&format!(" AS {alias}"));
}
sql.push_str(&format!(" ON {}", join.condition));
}
if !self.where_clauses.is_empty() {
sql.push_str(" WHERE ");
let (where_sql, where_params) = self.build_where_clause(&self.where_clauses)?;
sql.push_str(&where_sql);
params.extend(where_params);
}
if !self.group_by.is_empty() {
sql.push_str(&format!(" GROUP BY {}", self.group_by.join(", ")));
}
if !self.having.is_empty() {
sql.push_str(" HAVING ");
let (having_sql, having_params) = self.build_where_clause(&self.having)?;
sql.push_str(&having_sql);
params.extend(having_params);
}
Ok((sql, params))
}
fn build_where_clause(
&self,
filters: &[FilterOperator],
) -> Result<(String, Vec<crate::compat::LibsqlValue>)> {
let mut sql = String::new();
let mut params = Vec::new();
for (i, filter) in filters.iter().enumerate() {
if i > 0 {
sql.push_str(" AND ");
}
let (filter_sql, filter_params) = self.build_filter_operator(filter)?;
sql.push_str(&filter_sql);
params.extend(filter_params);
}
Ok((sql, params))
}
fn build_filter_operator(
&self,
filter: &FilterOperator,
) -> Result<(String, Vec<crate::compat::LibsqlValue>)> {
match filter {
FilterOperator::Single(filter) => self.build_filter(filter),
FilterOperator::And(filters) => {
let mut sql = String::new();
let mut params = Vec::new();
sql.push('(');
for (i, filter) in filters.iter().enumerate() {
if i > 0 {
sql.push_str(" AND ");
}
let (filter_sql, filter_params) = self.build_filter_operator(filter)?;
sql.push_str(&filter_sql);
params.extend(filter_params);
}
sql.push(')');
Ok((sql, params))
}
FilterOperator::Or(filters) => {
let mut sql = String::new();
let mut params = Vec::new();
sql.push('(');
for (i, filter) in filters.iter().enumerate() {
if i > 0 {
sql.push_str(" OR ");
}
let (filter_sql, filter_params) = self.build_filter_operator(filter)?;
sql.push_str(&filter_sql);
params.extend(filter_params);
}
sql.push(')');
Ok((sql, params))
}
FilterOperator::Not(filter) => {
let (filter_sql, filter_params) = self.build_filter_operator(filter)?;
Ok((format!("NOT ({filter_sql})"), filter_params))
}
FilterOperator::Custom(condition) => Ok((condition.clone(), vec![])),
}
}
fn build_filter(
&self,
filter: &crate::Filter,
) -> Result<(String, Vec<crate::compat::LibsqlValue>)> {
let mut sql = String::new();
let mut params = Vec::new();
match &filter.operator {
Operator::IsNull => {
sql.push_str(&format!("{} IS NULL", filter.column));
}
Operator::IsNotNull => {
sql.push_str(&format!("{} IS NOT NULL", filter.column));
}
_ => {
sql.push_str(&format!("{} {} ", filter.column, filter.operator));
match &filter.value {
FilterValue::Single(value) => {
sql.push('?');
params.push(self.value_to_libsql_value(value));
}
FilterValue::Multiple(values) => {
sql.push('(');
for (i, value) in values.iter().enumerate() {
if i > 0 {
sql.push_str(", ");
}
sql.push('?');
params.push(self.value_to_libsql_value(value));
}
sql.push(')');
}
FilterValue::Range(min, max) => {
sql.push_str("? AND ?");
params.push(self.value_to_libsql_value(min));
params.push(self.value_to_libsql_value(max));
}
}
}
}
Ok((sql, params))
}
fn value_to_libsql_value(&self, value: &Value) -> crate::compat::LibsqlValue {
match value {
Value::Null => crate::compat::LibsqlValue::Null,
Value::Integer(i) => crate::compat::LibsqlValue::Integer(*i),
Value::Real(f) => crate::compat::LibsqlValue::Real(*f),
Value::Text(s) => crate::compat::LibsqlValue::Text(s.clone()),
Value::Blob(b) => crate::compat::LibsqlValue::Blob(b.clone()),
Value::Boolean(b) => crate::compat::LibsqlValue::Integer(if *b { 1 } else { 0 }),
}
}
pub async fn execute<T>(&self, db: &Database) -> Result<Vec<T>>
where
T: serde::de::DeserializeOwned,
{
let (sql, params) = self.build()?;
let mut rows = db.query(&sql, params).await?;
let mut results = Vec::new();
while let Some(row) = rows.next().await? {
let mut map = HashMap::new();
for i in 0..row.column_count() {
if let Some(column_name) = row.column_name(i) {
let value = row.get_value(i).unwrap_or(crate::compat::LibsqlValue::Null);
map.insert(
column_name.to_string(),
self.libsql_value_to_json_value(&value),
);
}
}
let json_value = serde_json::to_value(map)?;
let result: T = serde_json::from_value(json_value)?;
results.push(result);
}
Ok(results)
}
pub async fn execute_paginated<T>(
&self,
db: &Database,
pagination: &Pagination,
) -> Result<PaginatedResult<T>>
where
T: serde::de::DeserializeOwned,
{
let count_builder = QueryBuilder::new(&self.table).select(vec!["COUNT(*) as count"]);
let (count_sql, count_params) = count_builder.build_count()?;
let mut count_rows = db.query(&count_sql, count_params).await?;
let total: u64 = if let Some(row) = count_rows.next().await? {
row.get_value(0)
.ok()
.and_then(|v| match v {
crate::compat::LibsqlValue::Integer(i) => Some(i as u64),
_ => None,
})
.unwrap_or(0)
} else {
0
};
let data_builder = self
.clone()
.limit(pagination.limit())
.offset(pagination.offset());
let data = data_builder.execute::<T>(db).await?;
Ok(PaginatedResult::with_total(data, pagination.clone(), total))
}
fn libsql_value_to_json_value(&self, value: &crate::compat::LibsqlValue) -> serde_json::Value {
match value {
crate::compat::LibsqlValue::Null => serde_json::Value::Null,
crate::compat::LibsqlValue::Integer(i) => {
serde_json::Value::Number(serde_json::Number::from(*i))
}
crate::compat::LibsqlValue::Real(f) => {
if let Some(n) = serde_json::Number::from_f64(*f) {
serde_json::Value::Number(n)
} else {
serde_json::Value::Null
}
}
crate::compat::LibsqlValue::Text(s) => serde_json::Value::String(s.clone()),
crate::compat::LibsqlValue::Blob(b) => serde_json::Value::Array(
b.iter()
.map(|&byte| serde_json::Value::Number(serde_json::Number::from(byte)))
.collect(),
),
}
}
}
impl Clone for QueryBuilder {
fn clone(&self) -> Self {
Self {
table: self.table.clone(),
select_columns: self.select_columns.clone(),
joins: self.joins.clone(),
where_clauses: self.where_clauses.clone(),
group_by: self.group_by.clone(),
having: self.having.clone(),
order_by: self.order_by.clone(),
limit: self.limit,
offset: self.offset,
distinct: self.distinct,
aggregate: self.aggregate.clone(),
}
}
}
impl Clone for JoinClause {
fn clone(&self) -> Self {
Self {
join_type: self.join_type,
table: self.table.clone(),
alias: self.alias.clone(),
condition: self.condition.clone(),
}
}
}
impl Clone for AggregateClause {
fn clone(&self) -> Self {
Self {
function: self.function,
column: self.column.clone(),
alias: self.alias.clone(),
}
}
}