use std::marker::PhantomData;
use crate::config::DatabaseType;
use crate::error::{Error, Result};
use crate::model::Model;
use crate::tide_debug;
use crate::internal::{
EntityTrait, QueryFilter, QueryOrder, QuerySelect, Condition,
Expr, translate_error, FromQueryResult, Asterisk, ConnectionTrait, Statement, DbBackend,
ExprTrait,
};
mod db_sql {
use crate::config::DatabaseType;
pub fn quote_char(db_type: DatabaseType) -> char {
match db_type {
DatabaseType::Postgres | DatabaseType::SQLite => '"',
DatabaseType::MySQL | DatabaseType::MariaDB => '`',
}
}
pub fn quote_ident(db_type: DatabaseType, name: &str) -> String {
let q = quote_char(db_type);
format!("{}{}{}", q, name, q)
}
pub fn json_contains(db_type: DatabaseType, column: &str, value: &str) -> String {
let escaped_value = value.replace("'", "''");
match db_type {
DatabaseType::Postgres => {
format!("\"{}\" @> '{}'", column, escaped_value)
}
DatabaseType::MySQL | DatabaseType::MariaDB => {
format!("JSON_CONTAINS(`{}`, '{}')", column, escaped_value)
}
DatabaseType::SQLite => {
format!(
"EXISTS (SELECT 1 FROM json_each(\"{}\") WHERE value = '{}')",
column, escaped_value.trim_matches('"')
)
}
}
}
pub fn json_contained_by(db_type: DatabaseType, column: &str, value: &str) -> String {
let escaped_value = value.replace("'", "''");
match db_type {
DatabaseType::Postgres => {
format!("\"{}\" <@ '{}'", column, escaped_value)
}
DatabaseType::MySQL | DatabaseType::MariaDB => {
format!("JSON_CONTAINS('{}', `{}`)", escaped_value, column)
}
DatabaseType::SQLite => {
format!(
"json_type(\"{}\") IS NOT NULL AND '{}' LIKE '%' || \"{}\" || '%'",
column, escaped_value, column
)
}
}
}
pub fn json_key_exists(db_type: DatabaseType, column: &str, key: &str) -> String {
let escaped_key = key.replace("'", "''");
match db_type {
DatabaseType::Postgres => {
format!("\"{}\" ? '{}'", column, escaped_key)
}
DatabaseType::MySQL | DatabaseType::MariaDB => {
format!("JSON_CONTAINS_PATH(`{}`, 'one', '$.{}')", column, escaped_key)
}
DatabaseType::SQLite => {
format!("json_extract(\"{}\", '$.{}') IS NOT NULL", column, escaped_key)
}
}
}
pub fn json_key_not_exists(db_type: DatabaseType, column: &str, key: &str) -> String {
let escaped_key = key.replace("'", "''");
match db_type {
DatabaseType::Postgres => {
format!("NOT (\"{}\" ? '{}')", column, escaped_key)
}
DatabaseType::MySQL | DatabaseType::MariaDB => {
format!("NOT JSON_CONTAINS_PATH(`{}`, 'one', '$.{}')", column, escaped_key)
}
DatabaseType::SQLite => {
format!("json_extract(\"{}\", '$.{}') IS NULL", column, escaped_key)
}
}
}
pub fn json_path_exists(db_type: DatabaseType, column: &str, path: &str) -> String {
let escaped_path = path.replace("'", "''");
match db_type {
DatabaseType::Postgres => {
format!("\"{}\" @? '{}'", column, escaped_path)
}
DatabaseType::MySQL | DatabaseType::MariaDB => {
format!("JSON_CONTAINS_PATH(`{}`, 'one', '{}')", column, escaped_path)
}
DatabaseType::SQLite => {
format!("json_extract(\"{}\", '{}') IS NOT NULL", column, escaped_path)
}
}
}
pub fn json_path_not_exists(db_type: DatabaseType, column: &str, path: &str) -> String {
let escaped_path = path.replace("'", "''");
match db_type {
DatabaseType::Postgres => {
format!("NOT (\"{}\" @? '{}')", column, escaped_path)
}
DatabaseType::MySQL | DatabaseType::MariaDB => {
format!("NOT JSON_CONTAINS_PATH(`{}`, 'one', '{}')", column, escaped_path)
}
DatabaseType::SQLite => {
format!("json_extract(\"{}\", '{}') IS NULL", column, escaped_path)
}
}
}
pub fn array_contains(db_type: DatabaseType, column: &str, values: &[String]) -> String {
match db_type {
DatabaseType::Postgres => {
format!("\"{}\" @> ARRAY[{}]", column, values.join(","))
}
DatabaseType::MySQL | DatabaseType::MariaDB => {
let json_array = format!("[{}]", values.iter()
.map(|v| if v.starts_with("'") { v.clone() } else { format!("\"{}\"", v.trim_matches('\'')) })
.collect::<Vec<_>>()
.join(","));
format!("JSON_CONTAINS(`{}`, '{}')", column, json_array.replace("'", "''"))
}
DatabaseType::SQLite => {
let conditions: Vec<String> = values.iter()
.map(|v| {
let clean_val = v.trim_matches('\'');
format!(
"EXISTS (SELECT 1 FROM json_each(\"{}\") WHERE value = '{}')",
column, clean_val.replace("'", "''")
)
})
.collect();
format!("({})", conditions.join(" AND "))
}
}
}
pub fn array_contained_by(db_type: DatabaseType, column: &str, values: &[String]) -> String {
match db_type {
DatabaseType::Postgres => {
format!("\"{}\" <@ ARRAY[{}]", column, values.join(","))
}
DatabaseType::MySQL | DatabaseType::MariaDB => {
let json_array = format!("[{}]", values.iter()
.map(|v| if v.starts_with("'") { v.clone() } else { format!("\"{}\"", v.trim_matches('\'')) })
.collect::<Vec<_>>()
.join(","));
format!("JSON_CONTAINS('{}', `{}`)", json_array.replace("'", "''"), column)
}
DatabaseType::SQLite => {
let value_list = values.iter()
.map(|v| format!("'{}'", v.trim_matches('\'').replace("'", "''")))
.collect::<Vec<_>>()
.join(",");
format!(
"NOT EXISTS (SELECT 1 FROM json_each(\"{}\") WHERE value NOT IN ({}))",
column, value_list
)
}
}
}
pub fn array_overlaps(db_type: DatabaseType, column: &str, values: &[String]) -> String {
match db_type {
DatabaseType::Postgres => {
format!("\"{}\" && ARRAY[{}]", column, values.join(","))
}
DatabaseType::MySQL | DatabaseType::MariaDB => {
let conditions: Vec<String> = values.iter()
.map(|v| {
let clean_val = v.trim_matches('\'');
format!("JSON_CONTAINS(`{}`, '\"{}\"')", column, clean_val.replace("'", "''"))
})
.collect();
format!("({})", conditions.join(" OR "))
}
DatabaseType::SQLite => {
let conditions: Vec<String> = values.iter()
.map(|v| {
let clean_val = v.trim_matches('\'');
format!(
"EXISTS (SELECT 1 FROM json_each(\"{}\") WHERE value = '{}')",
column, clean_val.replace("'", "''")
)
})
.collect();
format!("({})", conditions.join(" OR "))
}
}
}
pub fn format_column(db_type: DatabaseType, column: &str) -> String {
if column.contains('(') || column.contains('*') {
column.to_string()
} else if column.contains('.') {
let parts: Vec<&str> = column.split('.').collect();
if parts.len() == 2 {
let q = quote_char(db_type);
format!("{0}{1}{0}.{0}{2}{0}", q, parts[0], parts[1])
} else {
column.to_string()
}
} else {
quote_ident(db_type, column)
}
}
#[allow(dead_code)]
pub fn format_array_literal(db_type: DatabaseType, values: &[serde_json::Value]) -> String {
let elements: Vec<String> = values.iter()
.map(|v| match v {
serde_json::Value::String(s) => format!("'{}'", s.replace("'", "''")),
serde_json::Value::Number(n) => n.to_string(),
serde_json::Value::Bool(b) => b.to_string(),
_ => v.to_string(),
})
.collect();
match db_type {
DatabaseType::Postgres => format!("ARRAY[{}]", elements.join(",")),
DatabaseType::MySQL | DatabaseType::MariaDB | DatabaseType::SQLite => {
format!("'[{}]'", elements.iter()
.map(|e| if e.starts_with("'") {
format!("\"{}\"", e.trim_matches('\''))
} else {
e.clone()
})
.collect::<Vec<_>>()
.join(","))
}
}
}
pub fn cast_to_float(db_type: DatabaseType, expr: &str) -> String {
match db_type {
DatabaseType::Postgres => format!("CAST({} AS FLOAT8)", expr),
DatabaseType::MySQL | DatabaseType::MariaDB => format!("CAST({} AS DOUBLE)", expr),
DatabaseType::SQLite => format!("CAST({} AS REAL)", expr),
}
}
pub fn eq_any(db_type: DatabaseType, column: &str, values: &[String]) -> String {
match db_type {
DatabaseType::Postgres => {
format!("{} = ANY(ARRAY[{}])", column, values.join(","))
}
DatabaseType::MySQL | DatabaseType::MariaDB | DatabaseType::SQLite => {
format!("{} IN ({})", column, values.join(","))
}
}
}
pub fn ne_all(db_type: DatabaseType, column: &str, values: &[String]) -> String {
match db_type {
DatabaseType::Postgres => {
format!("{} <> ALL(ARRAY[{}])", column, values.join(","))
}
DatabaseType::MySQL | DatabaseType::MariaDB | DatabaseType::SQLite => {
format!("{} NOT IN ({})", column, values.join(","))
}
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Order {
Asc,
Desc,
}
impl Order {
pub fn as_str(&self) -> &'static str {
match self {
Order::Asc => "ASC",
Order::Desc => "DESC",
}
}
}
#[derive(Debug, Clone)]
pub enum Operator {
Eq,
NotEq,
Gt,
Gte,
Lt,
Lte,
Like,
NotLike,
In,
NotIn,
IsNull,
IsNotNull,
Between,
JsonContains,
JsonContainedBy,
JsonKeyExists,
JsonKeyNotExists,
JsonPathExists,
JsonPathNotExists,
ArrayContains,
ArrayContainedBy,
ArrayOverlaps,
ArrayContainsAny,
ArrayContainsAll,
SubqueryIn,
SubqueryNotIn,
Raw,
EqAny,
NeAll,
}
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct WhereCondition {
pub column: String,
pub operator: Operator,
pub value: ConditionValue,
}
#[derive(Debug, Clone)]
pub enum ConditionValue {
Single(serde_json::Value),
List(Vec<serde_json::Value>),
Range(serde_json::Value, serde_json::Value),
None,
Subquery(String),
RawExpr(String),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LogicalOp {
And,
Or,
}
impl LogicalOp {
pub fn as_sql(&self) -> &'static str {
match self {
LogicalOp::And => "AND",
LogicalOp::Or => "OR",
}
}
}
#[derive(Debug, Clone)]
pub struct OrGroup {
pub conditions: Vec<WhereCondition>,
pub nested_groups: Vec<OrGroup>,
pub combine_with: LogicalOp,
}
impl OrGroup {
pub fn new() -> Self {
Self {
conditions: Vec::new(),
nested_groups: Vec::new(),
combine_with: LogicalOp::Or,
}
}
pub fn where_eq(mut self, column: &str, value: impl Into<serde_json::Value>) -> Self {
self.conditions.push(WhereCondition {
column: column.to_string(),
operator: Operator::Eq,
value: ConditionValue::Single(value.into()),
});
self
}
pub fn where_not(mut self, column: &str, value: impl Into<serde_json::Value>) -> Self {
self.conditions.push(WhereCondition {
column: column.to_string(),
operator: Operator::NotEq,
value: ConditionValue::Single(value.into()),
});
self
}
pub fn where_gt(mut self, column: &str, value: impl Into<serde_json::Value>) -> Self {
self.conditions.push(WhereCondition {
column: column.to_string(),
operator: Operator::Gt,
value: ConditionValue::Single(value.into()),
});
self
}
pub fn where_gte(mut self, column: &str, value: impl Into<serde_json::Value>) -> Self {
self.conditions.push(WhereCondition {
column: column.to_string(),
operator: Operator::Gte,
value: ConditionValue::Single(value.into()),
});
self
}
pub fn where_lt(mut self, column: &str, value: impl Into<serde_json::Value>) -> Self {
self.conditions.push(WhereCondition {
column: column.to_string(),
operator: Operator::Lt,
value: ConditionValue::Single(value.into()),
});
self
}
pub fn where_lte(mut self, column: &str, value: impl Into<serde_json::Value>) -> Self {
self.conditions.push(WhereCondition {
column: column.to_string(),
operator: Operator::Lte,
value: ConditionValue::Single(value.into()),
});
self
}
pub fn where_like(mut self, column: &str, pattern: &str) -> Self {
self.conditions.push(WhereCondition {
column: column.to_string(),
operator: Operator::Like,
value: ConditionValue::Single(serde_json::Value::String(pattern.to_string())),
});
self
}
pub fn where_not_like(mut self, column: &str, pattern: &str) -> Self {
self.conditions.push(WhereCondition {
column: column.to_string(),
operator: Operator::NotLike,
value: ConditionValue::Single(serde_json::Value::String(pattern.to_string())),
});
self
}
pub fn where_in<V: Into<serde_json::Value>>(mut self, column: &str, values: Vec<V>) -> Self {
self.conditions.push(WhereCondition {
column: column.to_string(),
operator: Operator::In,
value: ConditionValue::List(values.into_iter().map(|v| v.into()).collect()),
});
self
}
pub fn where_not_in<V: Into<serde_json::Value>>(mut self, column: &str, values: Vec<V>) -> Self {
self.conditions.push(WhereCondition {
column: column.to_string(),
operator: Operator::NotIn,
value: ConditionValue::List(values.into_iter().map(|v| v.into()).collect()),
});
self
}
pub fn where_null(mut self, column: &str) -> Self {
self.conditions.push(WhereCondition {
column: column.to_string(),
operator: Operator::IsNull,
value: ConditionValue::None,
});
self
}
pub fn where_not_null(mut self, column: &str) -> Self {
self.conditions.push(WhereCondition {
column: column.to_string(),
operator: Operator::IsNotNull,
value: ConditionValue::None,
});
self
}
pub fn where_between(
mut self,
column: &str,
min: impl Into<serde_json::Value>,
max: impl Into<serde_json::Value>,
) -> Self {
self.conditions.push(WhereCondition {
column: column.to_string(),
operator: Operator::Between,
value: ConditionValue::Range(min.into(), max.into()),
});
self
}
pub fn where_raw(mut self, raw_sql: &str) -> Self {
self.conditions.push(WhereCondition {
column: String::new(),
operator: Operator::Raw,
value: ConditionValue::RawExpr(raw_sql.to_string()),
});
self
}
pub fn nested_or<F>(mut self, f: F) -> Self
where
F: FnOnce(OrGroup) -> OrGroup,
{
let mut nested = OrGroup::new();
nested.combine_with = LogicalOp::Or;
nested = f(nested);
self.nested_groups.push(nested);
self
}
pub fn nested_and<F>(mut self, f: F) -> Self
where
F: FnOnce(OrGroup) -> OrGroup,
{
let mut nested = OrGroup::new();
nested.combine_with = LogicalOp::And;
nested = f(nested);
self.nested_groups.push(nested);
self
}
pub fn is_empty(&self) -> bool {
self.conditions.is_empty() && self.nested_groups.is_empty()
}
pub fn condition_count(&self) -> usize {
let nested_count: usize = self.nested_groups.iter()
.map(|g| g.condition_count())
.sum();
self.conditions.len() + nested_count
}
}
impl Default for OrGroup {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct OrBranch {
pub conditions: Vec<WhereCondition>,
}
impl OrBranch {
pub fn new() -> Self {
Self {
conditions: Vec::new(),
}
}
pub fn where_eq(mut self, column: impl crate::columns::IntoColumnName, value: impl Into<serde_json::Value>) -> Self {
self.conditions.push(WhereCondition {
column: column.column_name().to_string(),
operator: Operator::Eq,
value: ConditionValue::Single(value.into()),
});
self
}
pub fn where_not(mut self, column: impl crate::columns::IntoColumnName, value: impl Into<serde_json::Value>) -> Self {
self.conditions.push(WhereCondition {
column: column.column_name().to_string(),
operator: Operator::NotEq,
value: ConditionValue::Single(value.into()),
});
self
}
pub fn where_gt(mut self, column: impl crate::columns::IntoColumnName, value: impl Into<serde_json::Value>) -> Self {
self.conditions.push(WhereCondition {
column: column.column_name().to_string(),
operator: Operator::Gt,
value: ConditionValue::Single(value.into()),
});
self
}
pub fn where_gte(mut self, column: impl crate::columns::IntoColumnName, value: impl Into<serde_json::Value>) -> Self {
self.conditions.push(WhereCondition {
column: column.column_name().to_string(),
operator: Operator::Gte,
value: ConditionValue::Single(value.into()),
});
self
}
pub fn where_lt(mut self, column: impl crate::columns::IntoColumnName, value: impl Into<serde_json::Value>) -> Self {
self.conditions.push(WhereCondition {
column: column.column_name().to_string(),
operator: Operator::Lt,
value: ConditionValue::Single(value.into()),
});
self
}
pub fn where_lte(mut self, column: impl crate::columns::IntoColumnName, value: impl Into<serde_json::Value>) -> Self {
self.conditions.push(WhereCondition {
column: column.column_name().to_string(),
operator: Operator::Lte,
value: ConditionValue::Single(value.into()),
});
self
}
pub fn where_like(mut self, column: impl crate::columns::IntoColumnName, pattern: &str) -> Self {
self.conditions.push(WhereCondition {
column: column.column_name().to_string(),
operator: Operator::Like,
value: ConditionValue::Single(serde_json::Value::String(pattern.to_string())),
});
self
}
pub fn where_not_like(mut self, column: impl crate::columns::IntoColumnName, pattern: &str) -> Self {
self.conditions.push(WhereCondition {
column: column.column_name().to_string(),
operator: Operator::NotLike,
value: ConditionValue::Single(serde_json::Value::String(pattern.to_string())),
});
self
}
pub fn where_in<V: Into<serde_json::Value>>(mut self, column: impl crate::columns::IntoColumnName, values: Vec<V>) -> Self {
self.conditions.push(WhereCondition {
column: column.column_name().to_string(),
operator: Operator::In,
value: ConditionValue::List(values.into_iter().map(|v| v.into()).collect()),
});
self
}
pub fn where_not_in<V: Into<serde_json::Value>>(mut self, column: impl crate::columns::IntoColumnName, values: Vec<V>) -> Self {
self.conditions.push(WhereCondition {
column: column.column_name().to_string(),
operator: Operator::NotIn,
value: ConditionValue::List(values.into_iter().map(|v| v.into()).collect()),
});
self
}
pub fn where_null(mut self, column: impl crate::columns::IntoColumnName) -> Self {
self.conditions.push(WhereCondition {
column: column.column_name().to_string(),
operator: Operator::IsNull,
value: ConditionValue::None,
});
self
}
pub fn where_not_null(mut self, column: impl crate::columns::IntoColumnName) -> Self {
self.conditions.push(WhereCondition {
column: column.column_name().to_string(),
operator: Operator::IsNotNull,
value: ConditionValue::None,
});
self
}
pub fn where_between(
mut self,
column: impl crate::columns::IntoColumnName,
min: impl Into<serde_json::Value>,
max: impl Into<serde_json::Value>,
) -> Self {
self.conditions.push(WhereCondition {
column: column.column_name().to_string(),
operator: Operator::Between,
value: ConditionValue::Range(min.into(), max.into()),
});
self
}
pub fn where_raw(mut self, raw_sql: &str) -> Self {
self.conditions.push(WhereCondition {
column: String::new(),
operator: Operator::Raw,
value: ConditionValue::RawExpr(raw_sql.to_string()),
});
self
}
pub fn is_empty(&self) -> bool {
self.conditions.is_empty()
}
pub fn len(&self) -> usize {
self.conditions.len()
}
}
impl Default for OrBranch {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug)]
pub struct OrBranchBuilder<M: Model> {
query: QueryBuilder<M>,
branches: Vec<OrBranch>,
current_branch: OrBranch,
}
impl<M: Model> OrBranchBuilder<M> {
pub fn new(query: QueryBuilder<M>) -> Self {
Self {
query,
branches: Vec::new(),
current_branch: OrBranch::new(),
}
}
pub fn or_where_eq(mut self, column: impl crate::columns::IntoColumnName, value: impl Into<serde_json::Value>) -> Self {
if !self.current_branch.is_empty() {
self.branches.push(self.current_branch);
}
self.current_branch = OrBranch::new().where_eq(column, value);
self
}
pub fn or_where_not(mut self, column: impl crate::columns::IntoColumnName, value: impl Into<serde_json::Value>) -> Self {
if !self.current_branch.is_empty() {
self.branches.push(self.current_branch);
}
self.current_branch = OrBranch::new().where_not(column, value);
self
}
pub fn or_where_gt(mut self, column: impl crate::columns::IntoColumnName, value: impl Into<serde_json::Value>) -> Self {
if !self.current_branch.is_empty() {
self.branches.push(self.current_branch);
}
self.current_branch = OrBranch::new().where_gt(column, value);
self
}
pub fn or_where_gte(mut self, column: impl crate::columns::IntoColumnName, value: impl Into<serde_json::Value>) -> Self {
if !self.current_branch.is_empty() {
self.branches.push(self.current_branch);
}
self.current_branch = OrBranch::new().where_gte(column, value);
self
}
pub fn or_where_lt(mut self, column: impl crate::columns::IntoColumnName, value: impl Into<serde_json::Value>) -> Self {
if !self.current_branch.is_empty() {
self.branches.push(self.current_branch);
}
self.current_branch = OrBranch::new().where_lt(column, value);
self
}
pub fn or_where_lte(mut self, column: impl crate::columns::IntoColumnName, value: impl Into<serde_json::Value>) -> Self {
if !self.current_branch.is_empty() {
self.branches.push(self.current_branch);
}
self.current_branch = OrBranch::new().where_lte(column, value);
self
}
pub fn or_where_like(mut self, column: impl crate::columns::IntoColumnName, pattern: &str) -> Self {
if !self.current_branch.is_empty() {
self.branches.push(self.current_branch);
}
self.current_branch = OrBranch::new().where_like(column, pattern);
self
}
pub fn or_where_in<V: Into<serde_json::Value>>(mut self, column: impl crate::columns::IntoColumnName, values: Vec<V>) -> Self {
if !self.current_branch.is_empty() {
self.branches.push(self.current_branch);
}
self.current_branch = OrBranch::new().where_in(column, values);
self
}
pub fn or_where_not_in<V: Into<serde_json::Value>>(mut self, column: impl crate::columns::IntoColumnName, values: Vec<V>) -> Self {
if !self.current_branch.is_empty() {
self.branches.push(self.current_branch);
}
self.current_branch = OrBranch::new().where_not_in(column, values);
self
}
pub fn or_where_null(mut self, column: impl crate::columns::IntoColumnName) -> Self {
if !self.current_branch.is_empty() {
self.branches.push(self.current_branch);
}
self.current_branch = OrBranch::new().where_null(column);
self
}
pub fn or_where_not_null(mut self, column: impl crate::columns::IntoColumnName) -> Self {
if !self.current_branch.is_empty() {
self.branches.push(self.current_branch);
}
self.current_branch = OrBranch::new().where_not_null(column);
self
}
pub fn or_where_between(
mut self,
column: impl crate::columns::IntoColumnName,
min: impl Into<serde_json::Value>,
max: impl Into<serde_json::Value>,
) -> Self {
if !self.current_branch.is_empty() {
self.branches.push(self.current_branch);
}
self.current_branch = OrBranch::new().where_between(column, min, max);
self
}
pub fn or_where_raw(mut self, raw_sql: &str) -> Self {
if !self.current_branch.is_empty() {
self.branches.push(self.current_branch);
}
self.current_branch = OrBranch::new().where_raw(raw_sql);
self
}
pub fn and_where_eq(mut self, column: impl crate::columns::IntoColumnName, value: impl Into<serde_json::Value>) -> Self {
self.current_branch = self.current_branch.where_eq(column, value);
self
}
pub fn and_where_not(mut self, column: impl crate::columns::IntoColumnName, value: impl Into<serde_json::Value>) -> Self {
self.current_branch = self.current_branch.where_not(column, value);
self
}
pub fn and_where_gt(mut self, column: impl crate::columns::IntoColumnName, value: impl Into<serde_json::Value>) -> Self {
self.current_branch = self.current_branch.where_gt(column, value);
self
}
pub fn and_where_gte(mut self, column: impl crate::columns::IntoColumnName, value: impl Into<serde_json::Value>) -> Self {
self.current_branch = self.current_branch.where_gte(column, value);
self
}
pub fn and_where_lt(mut self, column: impl crate::columns::IntoColumnName, value: impl Into<serde_json::Value>) -> Self {
self.current_branch = self.current_branch.where_lt(column, value);
self
}
pub fn and_where_lte(mut self, column: impl crate::columns::IntoColumnName, value: impl Into<serde_json::Value>) -> Self {
self.current_branch = self.current_branch.where_lte(column, value);
self
}
pub fn and_where_like(mut self, column: impl crate::columns::IntoColumnName, pattern: &str) -> Self {
self.current_branch = self.current_branch.where_like(column, pattern);
self
}
pub fn and_where_not_like(mut self, column: impl crate::columns::IntoColumnName, pattern: &str) -> Self {
self.current_branch = self.current_branch.where_not_like(column, pattern);
self
}
pub fn and_where_in<V: Into<serde_json::Value>>(mut self, column: impl crate::columns::IntoColumnName, values: Vec<V>) -> Self {
self.current_branch = self.current_branch.where_in(column, values);
self
}
pub fn and_where_not_in<V: Into<serde_json::Value>>(mut self, column: impl crate::columns::IntoColumnName, values: Vec<V>) -> Self {
self.current_branch = self.current_branch.where_not_in(column, values);
self
}
pub fn and_where_null(mut self, column: impl crate::columns::IntoColumnName) -> Self {
self.current_branch = self.current_branch.where_null(column);
self
}
pub fn and_where_not_null(mut self, column: impl crate::columns::IntoColumnName) -> Self {
self.current_branch = self.current_branch.where_not_null(column);
self
}
pub fn and_where_between(
mut self,
column: impl crate::columns::IntoColumnName,
min: impl Into<serde_json::Value>,
max: impl Into<serde_json::Value>,
) -> Self {
self.current_branch = self.current_branch.where_between(column, min, max);
self
}
pub fn and_where_raw(mut self, raw_sql: &str) -> Self {
self.current_branch = self.current_branch.where_raw(raw_sql);
self
}
pub fn end_or(mut self) -> QueryBuilder<M> {
if !self.current_branch.is_empty() {
self.branches.push(self.current_branch);
}
if !self.branches.is_empty() {
let mut or_group = OrGroup::new();
for branch in self.branches {
if branch.conditions.len() == 1 {
if let Some(condition) = branch.conditions.into_iter().next() {
or_group.conditions.push(condition);
}
} else {
let mut nested = OrGroup::new();
nested.combine_with = LogicalOp::And;
nested.conditions = branch.conditions;
or_group.nested_groups.push(nested);
}
}
self.query.or_groups.push(or_group);
}
self.query
}
pub fn branch_count(&self) -> usize {
let current = if self.current_branch.is_empty() { 0 } else { 1 };
self.branches.len() + current
}
pub fn total_conditions(&self) -> usize {
let mut total: usize = self.branches.iter().map(|b| b.len()).sum();
total += self.current_branch.len();
total
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum JoinType {
Inner,
Left,
Right,
}
impl JoinType {
pub fn as_sql(&self) -> &'static str {
match self {
JoinType::Inner => "INNER JOIN",
JoinType::Left => "LEFT JOIN",
JoinType::Right => "RIGHT JOIN",
}
}
}
#[derive(Debug, Clone)]
pub struct JoinClause {
pub join_type: JoinType,
pub table: String,
pub alias: Option<String>,
pub left_column: String,
pub right_column: String,
}
#[derive(Debug, Clone)]
pub enum AggregateFunction {
Count,
CountDistinct(String),
Sum(String),
Avg(String),
Min(String),
Max(String),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum UnionType {
Union,
UnionAll,
}
impl UnionType {
pub fn as_sql(&self) -> &'static str {
match self {
UnionType::Union => "UNION",
UnionType::UnionAll => "UNION ALL",
}
}
}
#[derive(Debug, Clone)]
pub struct UnionClause {
pub union_type: UnionType,
pub query_sql: String,
}
#[derive(Debug, Clone)]
pub enum FrameBound {
UnboundedPreceding,
UnboundedFollowing,
CurrentRow,
Preceding(u64),
Following(u64),
}
impl FrameBound {
pub fn as_sql(&self) -> String {
match self {
FrameBound::UnboundedPreceding => "UNBOUNDED PRECEDING".to_string(),
FrameBound::UnboundedFollowing => "UNBOUNDED FOLLOWING".to_string(),
FrameBound::CurrentRow => "CURRENT ROW".to_string(),
FrameBound::Preceding(n) => format!("{} PRECEDING", n),
FrameBound::Following(n) => format!("{} FOLLOWING", n),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FrameType {
Rows,
Range,
Groups,
}
impl FrameType {
pub fn as_sql(&self) -> &'static str {
match self {
FrameType::Rows => "ROWS",
FrameType::Range => "RANGE",
FrameType::Groups => "GROUPS",
}
}
}
#[derive(Debug, Clone)]
pub enum WindowFunctionType {
RowNumber,
Rank,
DenseRank,
Ntile(u32),
Lag(String, Option<i32>, Option<String>),
Lead(String, Option<i32>, Option<String>),
FirstValue(String),
LastValue(String),
NthValue(String, u32),
Sum(String),
Avg(String),
Count(Option<String>),
Min(String),
Max(String),
Custom(String),
}
impl WindowFunctionType {
pub fn as_sql(&self) -> String {
match self {
WindowFunctionType::RowNumber => "ROW_NUMBER()".to_string(),
WindowFunctionType::Rank => "RANK()".to_string(),
WindowFunctionType::DenseRank => "DENSE_RANK()".to_string(),
WindowFunctionType::Ntile(n) => format!("NTILE({})", n),
WindowFunctionType::Lag(col, offset, default) => {
let mut s = format!("LAG(\"{}\"", col);
if let Some(o) = offset {
s.push_str(&format!(", {}", o));
if let Some(d) = default {
s.push_str(&format!(", {}", d));
}
}
s.push(')');
s
}
WindowFunctionType::Lead(col, offset, default) => {
let mut s = format!("LEAD(\"{}\"", col);
if let Some(o) = offset {
s.push_str(&format!(", {}", o));
if let Some(d) = default {
s.push_str(&format!(", {}", d));
}
}
s.push(')');
s
}
WindowFunctionType::FirstValue(col) => format!("FIRST_VALUE(\"{}\")", col),
WindowFunctionType::LastValue(col) => format!("LAST_VALUE(\"{}\")", col),
WindowFunctionType::NthValue(col, n) => format!("NTH_VALUE(\"{}\", {})", col, n),
WindowFunctionType::Sum(col) => format!("SUM(\"{}\")", col),
WindowFunctionType::Avg(col) => format!("AVG(\"{}\")", col),
WindowFunctionType::Count(col) => match col {
Some(c) => format!("COUNT(\"{}\")", c),
None => "COUNT(*)".to_string(),
},
WindowFunctionType::Min(col) => format!("MIN(\"{}\")", col),
WindowFunctionType::Max(col) => format!("MAX(\"{}\")", col),
WindowFunctionType::Custom(expr) => expr.clone(),
}
}
}
#[derive(Debug, Clone)]
pub struct WindowFunction {
pub function: WindowFunctionType,
pub partition_by: Vec<String>,
pub order_by: Vec<(String, Order)>,
pub frame_type: Option<FrameType>,
pub frame_start: Option<FrameBound>,
pub frame_end: Option<FrameBound>,
pub alias: String,
}
impl WindowFunction {
pub fn new(function: WindowFunctionType, alias: &str) -> Self {
Self {
function,
partition_by: Vec::new(),
order_by: Vec::new(),
frame_type: None,
frame_start: None,
frame_end: None,
alias: alias.to_string(),
}
}
pub fn partition_by(mut self, column: impl crate::columns::IntoColumnName) -> Self {
self.partition_by.push(column.column_name().to_string());
self
}
pub fn order_by(mut self, column: impl crate::columns::IntoColumnName, direction: Order) -> Self {
self.order_by.push((column.column_name().to_string(), direction));
self
}
pub fn frame(mut self, frame_type: FrameType, start: FrameBound, end: FrameBound) -> Self {
self.frame_type = Some(frame_type);
self.frame_start = Some(start);
self.frame_end = Some(end);
self
}
pub fn to_sql(&self) -> String {
let mut sql = self.function.as_sql();
sql.push_str(" OVER (");
let mut clauses = Vec::new();
if !self.partition_by.is_empty() {
let cols: Vec<String> = self.partition_by.iter()
.map(|c| format!("\"{}\"", c))
.collect();
clauses.push(format!("PARTITION BY {}", cols.join(", ")));
}
if !self.order_by.is_empty() {
let orders: Vec<String> = self.order_by.iter()
.map(|(col, dir)| format!("\"{}\" {}", col, dir.as_str()))
.collect();
clauses.push(format!("ORDER BY {}", orders.join(", ")));
}
if let (Some(frame_type), Some(start)) = (&self.frame_type, &self.frame_start) {
let frame_sql = if let Some(end) = &self.frame_end {
format!("{} BETWEEN {} AND {}", frame_type.as_sql(), start.as_sql(), end.as_sql())
} else {
format!("{} {}", frame_type.as_sql(), start.as_sql())
};
clauses.push(frame_sql);
}
sql.push_str(&clauses.join(" "));
sql.push_str(&format!(") AS \"{}\"", self.alias));
sql
}
}
#[derive(Debug, Clone)]
pub struct CTE {
pub name: String,
pub columns: Option<Vec<String>>,
pub query_sql: String,
pub recursive: bool,
}
impl CTE {
pub fn new(name: &str, query_sql: String) -> Self {
Self {
name: name.to_string(),
columns: None,
query_sql,
recursive: false,
}
}
pub fn with_columns(name: &str, columns: Vec<&str>, query_sql: String) -> Self {
Self {
name: name.to_string(),
columns: Some(columns.into_iter().map(|s| s.to_string()).collect()),
query_sql,
recursive: false,
}
}
pub fn recursive(mut self) -> Self {
self.recursive = true;
self
}
pub fn to_sql(&self) -> String {
let mut sql = format!("\"{}\"", self.name);
if let Some(ref cols) = self.columns {
let col_list: Vec<String> = cols.iter()
.map(|c| format!("\"{}\"", c))
.collect();
sql.push_str(&format!(" ({})", col_list.join(", ")));
}
sql.push_str(&format!(" AS ({})", self.query_sql));
sql
}
}
#[derive(Debug, Clone)]
pub struct QueryFragment<M: Model> {
_marker: PhantomData<M>,
pub conditions: Vec<WhereCondition>,
pub order_by: Vec<(String, Order)>,
pub group_by: Vec<String>,
pub having_conditions: Vec<String>,
pub joins: Vec<JoinClause>,
pub include_trashed: bool,
pub only_trashed: bool,
}
impl<M: Model> Default for QueryFragment<M> {
fn default() -> Self {
Self::new()
}
}
impl<M: Model> QueryFragment<M> {
pub fn new() -> Self {
Self {
_marker: PhantomData,
conditions: Vec::new(),
order_by: Vec::new(),
group_by: Vec::new(),
having_conditions: Vec::new(),
joins: Vec::new(),
include_trashed: false,
only_trashed: false,
}
}
pub fn is_empty(&self) -> bool {
self.conditions.is_empty()
&& self.order_by.is_empty()
&& self.group_by.is_empty()
&& self.having_conditions.is_empty()
&& self.joins.is_empty()
}
pub fn condition_count(&self) -> usize {
self.conditions.len()
}
}
pub struct JoinResultConsolidator;
impl JoinResultConsolidator {
pub fn consolidate_two<A, B, K, F>(items: Vec<(A, B)>, key_fn: F) -> Vec<(A, Vec<B>)>
where
A: Clone,
K: Eq + std::hash::Hash,
F: Fn(&A) -> K,
{
use std::collections::HashMap;
let mut groups: HashMap<K, (A, Vec<B>)> = HashMap::new();
let mut order: Vec<K> = Vec::new();
for (a, b) in items {
let key = key_fn(&a);
if let Some((_, bs)) = groups.get_mut(&key) {
bs.push(b);
} else {
order.push(key_fn(&a));
groups.insert(key, (a, vec![b]));
}
}
order.into_iter()
.filter_map(|k| groups.remove(&k))
.collect()
}
pub fn consolidate_two_optional<A, B, K, F>(items: Vec<(A, Option<B>)>, key_fn: F) -> Vec<(A, Vec<B>)>
where
A: Clone,
K: Eq + std::hash::Hash,
F: Fn(&A) -> K,
{
use std::collections::HashMap;
let mut groups: HashMap<K, (A, Vec<B>)> = HashMap::new();
let mut order: Vec<K> = Vec::new();
for (a, maybe_b) in items {
let key = key_fn(&a);
if let Some((_, bs)) = groups.get_mut(&key) {
if let Some(b) = maybe_b {
bs.push(b);
}
} else {
order.push(key_fn(&a));
let bs = maybe_b.into_iter().collect();
groups.insert(key, (a, bs));
}
}
order.into_iter()
.filter_map(|k| groups.remove(&k))
.collect()
}
#[allow(clippy::type_complexity)]
pub fn consolidate_three<A, B, C, KA, KB, FA, FB>(
items: Vec<(A, B, C)>,
key_a: FA,
key_b: FB,
) -> Vec<(A, Vec<(B, Vec<C>)>)>
where
A: Clone,
B: Clone,
KA: Eq + std::hash::Hash + Clone,
KB: Eq + std::hash::Hash + Clone,
FA: Fn(&A) -> KA,
FB: Fn(&B) -> KB,
{
use std::collections::HashMap;
let mut a_groups: HashMap<KA, (A, HashMap<KB, (B, Vec<C>)>, Vec<KB>)> = HashMap::new();
let mut a_order: Vec<KA> = Vec::new();
for (a, b, c) in items {
let ka = key_a(&a);
let kb = key_b(&b);
if let Some((_, b_groups, b_order)) = a_groups.get_mut(&ka) {
if let Some((_, cs)) = b_groups.get_mut(&kb) {
cs.push(c);
} else {
b_order.push(kb.clone());
b_groups.insert(kb, (b, vec![c]));
}
} else {
a_order.push(ka.clone());
let mut b_groups = HashMap::new();
let b_order = vec![kb.clone()];
b_groups.insert(kb, (b, vec![c]));
a_groups.insert(ka, (a, b_groups, b_order));
}
}
a_order.into_iter()
.filter_map(|ka| {
a_groups.remove(&ka).map(|(a, mut b_groups, b_order)| {
let bs: Vec<(B, Vec<C>)> = b_order.into_iter()
.filter_map(|kb| b_groups.remove(&kb))
.collect();
(a, bs)
})
})
.collect()
}
#[allow(clippy::type_complexity)]
pub fn consolidate_three_optional<A, B, C, KA, KB, FA, FB>(
items: Vec<(A, B, Option<C>)>,
key_a: FA,
key_b: FB,
) -> Vec<(A, Vec<(B, Vec<C>)>)>
where
A: Clone,
B: Clone,
KA: Eq + std::hash::Hash + Clone,
KB: Eq + std::hash::Hash + Clone,
FA: Fn(&A) -> KA,
FB: Fn(&B) -> KB,
{
use std::collections::HashMap;
let mut a_groups: HashMap<KA, (A, HashMap<KB, (B, Vec<C>)>, Vec<KB>)> = HashMap::new();
let mut a_order: Vec<KA> = Vec::new();
for (a, b, maybe_c) in items {
let ka = key_a(&a);
let kb = key_b(&b);
if let Some((_, b_groups, b_order)) = a_groups.get_mut(&ka) {
if let Some((_, cs)) = b_groups.get_mut(&kb) {
if let Some(c) = maybe_c {
cs.push(c);
}
} else {
b_order.push(kb.clone());
let cs = maybe_c.into_iter().collect();
b_groups.insert(kb, (b, cs));
}
} else {
a_order.push(ka.clone());
let mut b_groups = HashMap::new();
let b_order = vec![kb.clone()];
let cs = maybe_c.into_iter().collect();
b_groups.insert(kb, (b, cs));
a_groups.insert(ka, (a, b_groups, b_order));
}
}
a_order.into_iter()
.filter_map(|ka| {
a_groups.remove(&ka).map(|(a, mut b_groups, b_order)| {
let bs: Vec<(B, Vec<C>)> = b_order.into_iter()
.filter_map(|kb| b_groups.remove(&kb))
.collect();
(a, bs)
})
})
.collect()
}
}
#[derive(Debug, Clone)]
pub struct QueryBuilder<M: Model> {
_marker: PhantomData<M>,
pub conditions: Vec<WhereCondition>,
pub or_groups: Vec<OrGroup>,
order_by: Vec<(String, Order)>,
limit_value: Option<u64>,
offset_value: Option<u64>,
select_columns: Option<Vec<String>>,
raw_select_expressions: Vec<String>,
include_trashed: bool,
only_trashed: bool,
joins: Vec<JoinClause>,
group_by: Vec<String>,
having_conditions: Vec<String>,
unions: Vec<UnionClause>,
window_functions: Vec<WindowFunction>,
ctes: Vec<CTE>,
cache_options: Option<crate::cache::CacheOptions>,
cache_key: Option<String>,
}
impl<M: Model> QueryBuilder<M> {
pub fn new() -> Self {
Self {
_marker: PhantomData,
conditions: Vec::new(),
or_groups: Vec::new(),
order_by: Vec::new(),
limit_value: None,
offset_value: None,
select_columns: None,
raw_select_expressions: Vec::new(),
include_trashed: false,
only_trashed: false,
joins: Vec::new(),
group_by: Vec::new(),
having_conditions: Vec::new(),
unions: Vec::new(),
window_functions: Vec::new(),
ctes: Vec::new(),
cache_options: None,
cache_key: None,
}
}
pub fn consolidate(&self) -> QueryFragment<M> {
QueryFragment {
_marker: PhantomData,
conditions: self.conditions.clone(),
order_by: self.order_by.clone(),
group_by: self.group_by.clone(),
having_conditions: self.having_conditions.clone(),
joins: self.joins.clone(),
include_trashed: self.include_trashed,
only_trashed: self.only_trashed,
}
}
pub fn apply(mut self, fragment: &QueryFragment<M>) -> Self {
self.conditions.extend(fragment.conditions.clone());
if self.order_by.is_empty() {
self.order_by.extend(fragment.order_by.clone());
}
self.group_by.extend(fragment.group_by.clone());
self.having_conditions.extend(fragment.having_conditions.clone());
self.joins.extend(fragment.joins.clone());
if fragment.include_trashed {
self.include_trashed = true;
}
if fragment.only_trashed {
self.only_trashed = true;
}
self
}
pub fn from_fragment(fragment: &QueryFragment<M>) -> Self {
Self::new().apply(fragment)
}
pub fn where_eq(mut self, column: impl crate::columns::IntoColumnName, value: impl Into<serde_json::Value>) -> Self {
self.conditions.push(WhereCondition {
column: column.column_name().to_string(),
operator: Operator::Eq,
value: ConditionValue::Single(value.into()),
});
self
}
pub fn where_not(mut self, column: impl crate::columns::IntoColumnName, value: impl Into<serde_json::Value>) -> Self {
self.conditions.push(WhereCondition {
column: column.column_name().to_string(),
operator: Operator::NotEq,
value: ConditionValue::Single(value.into()),
});
self
}
pub fn where_gt(mut self, column: impl crate::columns::IntoColumnName, value: impl Into<serde_json::Value>) -> Self {
self.conditions.push(WhereCondition {
column: column.column_name().to_string(),
operator: Operator::Gt,
value: ConditionValue::Single(value.into()),
});
self
}
pub fn where_gte(mut self, column: impl crate::columns::IntoColumnName, value: impl Into<serde_json::Value>) -> Self {
self.conditions.push(WhereCondition {
column: column.column_name().to_string(),
operator: Operator::Gte,
value: ConditionValue::Single(value.into()),
});
self
}
pub fn where_lt(mut self, column: impl crate::columns::IntoColumnName, value: impl Into<serde_json::Value>) -> Self {
self.conditions.push(WhereCondition {
column: column.column_name().to_string(),
operator: Operator::Lt,
value: ConditionValue::Single(value.into()),
});
self
}
pub fn where_lte(mut self, column: impl crate::columns::IntoColumnName, value: impl Into<serde_json::Value>) -> Self {
self.conditions.push(WhereCondition {
column: column.column_name().to_string(),
operator: Operator::Lte,
value: ConditionValue::Single(value.into()),
});
self
}
pub fn where_like(mut self, column: impl crate::columns::IntoColumnName, pattern: &str) -> Self {
self.conditions.push(WhereCondition {
column: column.column_name().to_string(),
operator: Operator::Like,
value: ConditionValue::Single(serde_json::Value::String(pattern.to_string())),
});
self
}
pub fn where_not_like(mut self, column: impl crate::columns::IntoColumnName, pattern: &str) -> Self {
self.conditions.push(WhereCondition {
column: column.column_name().to_string(),
operator: Operator::NotLike,
value: ConditionValue::Single(serde_json::Value::String(pattern.to_string())),
});
self
}
pub fn where_in<V: Into<serde_json::Value>>(mut self, column: impl crate::columns::IntoColumnName, values: Vec<V>) -> Self {
self.conditions.push(WhereCondition {
column: column.column_name().to_string(),
operator: Operator::In,
value: ConditionValue::List(values.into_iter().map(|v| v.into()).collect()),
});
self
}
pub fn where_not_in<V: Into<serde_json::Value>>(mut self, column: impl crate::columns::IntoColumnName, values: Vec<V>) -> Self {
self.conditions.push(WhereCondition {
column: column.column_name().to_string(),
operator: Operator::NotIn,
value: ConditionValue::List(values.into_iter().map(|v| v.into()).collect()),
});
self
}
pub fn or_where<F>(mut self, f: F) -> Self
where
F: FnOnce(OrGroup) -> OrGroup,
{
let group = f(OrGroup::new());
if !group.is_empty() {
self.or_groups.push(group);
}
self
}
pub fn or_where_eq(mut self, column: impl crate::columns::IntoColumnName, value: impl Into<serde_json::Value>) -> Self {
let mut group = OrGroup::new();
group.conditions.push(WhereCondition {
column: column.column_name().to_string(),
operator: Operator::Eq,
value: ConditionValue::Single(value.into()),
});
self.or_groups.push(group);
self
}
pub fn or_where_not(mut self, column: impl crate::columns::IntoColumnName, value: impl Into<serde_json::Value>) -> Self {
let mut group = OrGroup::new();
group.conditions.push(WhereCondition {
column: column.column_name().to_string(),
operator: Operator::NotEq,
value: ConditionValue::Single(value.into()),
});
self.or_groups.push(group);
self
}
pub fn or_where_gt(mut self, column: impl crate::columns::IntoColumnName, value: impl Into<serde_json::Value>) -> Self {
let mut group = OrGroup::new();
group.conditions.push(WhereCondition {
column: column.column_name().to_string(),
operator: Operator::Gt,
value: ConditionValue::Single(value.into()),
});
self.or_groups.push(group);
self
}
pub fn or_where_gte(mut self, column: impl crate::columns::IntoColumnName, value: impl Into<serde_json::Value>) -> Self {
let mut group = OrGroup::new();
group.conditions.push(WhereCondition {
column: column.column_name().to_string(),
operator: Operator::Gte,
value: ConditionValue::Single(value.into()),
});
self.or_groups.push(group);
self
}
pub fn or_where_lt(mut self, column: impl crate::columns::IntoColumnName, value: impl Into<serde_json::Value>) -> Self {
let mut group = OrGroup::new();
group.conditions.push(WhereCondition {
column: column.column_name().to_string(),
operator: Operator::Lt,
value: ConditionValue::Single(value.into()),
});
self.or_groups.push(group);
self
}
pub fn or_where_lte(mut self, column: impl crate::columns::IntoColumnName, value: impl Into<serde_json::Value>) -> Self {
let mut group = OrGroup::new();
group.conditions.push(WhereCondition {
column: column.column_name().to_string(),
operator: Operator::Lte,
value: ConditionValue::Single(value.into()),
});
self.or_groups.push(group);
self
}
pub fn or_where_like(mut self, column: impl crate::columns::IntoColumnName, pattern: &str) -> Self {
let mut group = OrGroup::new();
group.conditions.push(WhereCondition {
column: column.column_name().to_string(),
operator: Operator::Like,
value: ConditionValue::Single(serde_json::Value::String(pattern.to_string())),
});
self.or_groups.push(group);
self
}
pub fn or_where_in<V: Into<serde_json::Value>>(mut self, column: impl crate::columns::IntoColumnName, values: Vec<V>) -> Self {
let mut group = OrGroup::new();
group.conditions.push(WhereCondition {
column: column.column_name().to_string(),
operator: Operator::In,
value: ConditionValue::List(values.into_iter().map(|v| v.into()).collect()),
});
self.or_groups.push(group);
self
}
pub fn or_where_not_in<V: Into<serde_json::Value>>(mut self, column: impl crate::columns::IntoColumnName, values: Vec<V>) -> Self {
let mut group = OrGroup::new();
group.conditions.push(WhereCondition {
column: column.column_name().to_string(),
operator: Operator::NotIn,
value: ConditionValue::List(values.into_iter().map(|v| v.into()).collect()),
});
self.or_groups.push(group);
self
}
pub fn or_where_null(mut self, column: impl crate::columns::IntoColumnName) -> Self {
let mut group = OrGroup::new();
group.conditions.push(WhereCondition {
column: column.column_name().to_string(),
operator: Operator::IsNull,
value: ConditionValue::None,
});
self.or_groups.push(group);
self
}
pub fn or_where_not_null(mut self, column: impl crate::columns::IntoColumnName) -> Self {
let mut group = OrGroup::new();
group.conditions.push(WhereCondition {
column: column.column_name().to_string(),
operator: Operator::IsNotNull,
value: ConditionValue::None,
});
self.or_groups.push(group);
self
}
pub fn or_where_between(
mut self,
column: impl crate::columns::IntoColumnName,
min: impl Into<serde_json::Value>,
max: impl Into<serde_json::Value>,
) -> Self {
let mut group = OrGroup::new();
group.conditions.push(WhereCondition {
column: column.column_name().to_string(),
operator: Operator::Between,
value: ConditionValue::Range(min.into(), max.into()),
});
self.or_groups.push(group);
self
}
pub fn or_where_raw(mut self, raw_sql: &str) -> Self {
let mut group = OrGroup::new();
group.conditions.push(WhereCondition {
column: String::new(),
operator: Operator::Raw,
value: ConditionValue::RawExpr(raw_sql.to_string()),
});
self.or_groups.push(group);
self
}
pub fn begin_or(self) -> OrBranchBuilder<M> {
OrBranchBuilder::new(self)
}
pub fn begin_or_where_eq(self, column: &str, value: impl Into<serde_json::Value>) -> OrBranchBuilder<M> {
OrBranchBuilder::new(self).or_where_eq(column, value)
}
pub fn begin_or_where_gt(self, column: &str, value: impl Into<serde_json::Value>) -> OrBranchBuilder<M> {
OrBranchBuilder::new(self).or_where_gt(column, value)
}
pub fn begin_or_where_gte(self, column: &str, value: impl Into<serde_json::Value>) -> OrBranchBuilder<M> {
OrBranchBuilder::new(self).or_where_gte(column, value)
}
pub fn begin_or_where_lt(self, column: &str, value: impl Into<serde_json::Value>) -> OrBranchBuilder<M> {
OrBranchBuilder::new(self).or_where_lt(column, value)
}
pub fn begin_or_where_lte(self, column: &str, value: impl Into<serde_json::Value>) -> OrBranchBuilder<M> {
OrBranchBuilder::new(self).or_where_lte(column, value)
}
pub fn begin_or_where_like(self, column: &str, pattern: &str) -> OrBranchBuilder<M> {
OrBranchBuilder::new(self).or_where_like(column, pattern)
}
pub fn begin_or_where_in<V: Into<serde_json::Value>>(self, column: &str, values: Vec<V>) -> OrBranchBuilder<M> {
OrBranchBuilder::new(self).or_where_in(column, values)
}
pub fn begin_or_where_null(self, column: &str) -> OrBranchBuilder<M> {
OrBranchBuilder::new(self).or_where_null(column)
}
pub fn begin_or_where_not_null(self, column: &str) -> OrBranchBuilder<M> {
OrBranchBuilder::new(self).or_where_not_null(column)
}
pub fn begin_or_where_between(
self,
column: &str,
min: impl Into<serde_json::Value>,
max: impl Into<serde_json::Value>,
) -> OrBranchBuilder<M> {
OrBranchBuilder::new(self).or_where_between(column, min, max)
}
pub fn eq_any<V: Into<serde_json::Value>>(mut self, column: &str, values: Vec<V>) -> Self {
self.conditions.push(WhereCondition {
column: column.to_string(),
operator: Operator::EqAny,
value: ConditionValue::List(values.into_iter().map(|v| v.into()).collect()),
});
self
}
pub fn ne_all<V: Into<serde_json::Value>>(mut self, column: &str, values: Vec<V>) -> Self {
self.conditions.push(WhereCondition {
column: column.to_string(),
operator: Operator::NeAll,
value: ConditionValue::List(values.into_iter().map(|v| v.into()).collect()),
});
self
}
pub fn where_col(mut self, condition: crate::columns::ColumnCondition) -> Self {
let operator = match condition.operator {
crate::columns::ColumnOperator::Eq => Operator::Eq,
crate::columns::ColumnOperator::NotEq => Operator::NotEq,
crate::columns::ColumnOperator::Gt => Operator::Gt,
crate::columns::ColumnOperator::Gte => Operator::Gte,
crate::columns::ColumnOperator::Lt => Operator::Lt,
crate::columns::ColumnOperator::Lte => Operator::Lte,
crate::columns::ColumnOperator::Like => Operator::Like,
crate::columns::ColumnOperator::NotLike => Operator::NotLike,
crate::columns::ColumnOperator::In => Operator::In,
crate::columns::ColumnOperator::NotIn => Operator::NotIn,
crate::columns::ColumnOperator::IsNull => Operator::IsNull,
crate::columns::ColumnOperator::IsNotNull => Operator::IsNotNull,
crate::columns::ColumnOperator::Between => Operator::Between,
};
let value = match condition.operator {
crate::columns::ColumnOperator::IsNull | crate::columns::ColumnOperator::IsNotNull => {
ConditionValue::None
}
crate::columns::ColumnOperator::In | crate::columns::ColumnOperator::NotIn => {
if let serde_json::Value::Array(arr) = condition.value {
ConditionValue::List(arr)
} else {
ConditionValue::List(vec![condition.value])
}
}
crate::columns::ColumnOperator::Between => {
if let serde_json::Value::Array(arr) = condition.value {
if arr.len() >= 2 {
ConditionValue::Range(arr[0].clone(), arr[1].clone())
} else {
ConditionValue::Single(serde_json::Value::Null)
}
} else {
ConditionValue::Single(condition.value)
}
}
_ => ConditionValue::Single(condition.value),
};
self.conditions.push(WhereCondition {
column: condition.column,
operator,
value,
});
self
}
pub fn where_in_subquery<N: Model>(mut self, column: &str, subquery: QueryBuilder<N>) -> Self {
let subquery_sql = subquery.to_subquery_sql();
self.conditions.push(WhereCondition {
column: column.to_string(),
operator: Operator::SubqueryIn,
value: ConditionValue::Subquery(subquery_sql),
});
self
}
pub fn where_not_in_subquery<N: Model>(mut self, column: &str, subquery: QueryBuilder<N>) -> Self {
let subquery_sql = subquery.to_subquery_sql();
self.conditions.push(WhereCondition {
column: column.to_string(),
operator: Operator::SubqueryNotIn,
value: ConditionValue::Subquery(subquery_sql),
});
self
}
pub fn where_exists<N: Model>(mut self, subquery: QueryBuilder<N>) -> Self {
let subquery_sql = subquery.to_subquery_sql();
self.conditions.push(WhereCondition {
column: String::new(),
operator: Operator::Raw,
value: ConditionValue::RawExpr(format!("EXISTS ({})", subquery_sql)),
});
self
}
pub fn where_not_exists<N: Model>(mut self, subquery: QueryBuilder<N>) -> Self {
let subquery_sql = subquery.to_subquery_sql();
self.conditions.push(WhereCondition {
column: String::new(),
operator: Operator::Raw,
value: ConditionValue::RawExpr(format!("NOT EXISTS ({})", subquery_sql)),
});
self
}
pub fn has_related(
mut self,
related_table: &str,
foreign_key: &str,
local_key: &str,
condition_column: &str,
condition_value: impl Into<serde_json::Value>,
) -> Self {
let table = M::table_name();
let value = condition_value.into();
let value_sql = match &value {
serde_json::Value::String(s) => format!("'{}'", s.replace("'", "''")),
serde_json::Value::Number(n) => n.to_string(),
serde_json::Value::Bool(b) => b.to_string(),
serde_json::Value::Null => "NULL".to_string(),
_ => value.to_string(),
};
let exists_sql = format!(
"EXISTS (SELECT 1 FROM \"{}\" WHERE \"{}\".\"{}\" = \"{}\".\"{}\" AND \"{}\".\"{}\" = {})",
related_table,
related_table, foreign_key,
table, local_key,
related_table, condition_column, value_sql
);
self.conditions.push(WhereCondition {
column: String::new(),
operator: Operator::Raw,
value: ConditionValue::RawExpr(exists_sql),
});
self
}
pub fn has_no_related(
mut self,
related_table: &str,
foreign_key: &str,
local_key: &str,
condition_column: &str,
condition_value: impl Into<serde_json::Value>,
) -> Self {
let table = M::table_name();
let value = condition_value.into();
let value_sql = match &value {
serde_json::Value::String(s) => format!("'{}'", s.replace("'", "''")),
serde_json::Value::Number(n) => n.to_string(),
serde_json::Value::Bool(b) => b.to_string(),
serde_json::Value::Null => "NULL".to_string(),
_ => value.to_string(),
};
let not_exists_sql = format!(
"NOT EXISTS (SELECT 1 FROM \"{}\" WHERE \"{}\".\"{}\" = \"{}\".\"{}\" AND \"{}\".\"{}\" = {})",
related_table,
related_table, foreign_key,
table, local_key,
related_table, condition_column, value_sql
);
self.conditions.push(WhereCondition {
column: String::new(),
operator: Operator::Raw,
value: ConditionValue::RawExpr(not_exists_sql),
});
self
}
pub fn has_any_related(
mut self,
related_table: &str,
foreign_key: &str,
local_key: &str,
) -> Self {
let table = M::table_name();
let exists_sql = format!(
"EXISTS (SELECT 1 FROM \"{}\" WHERE \"{}\".\"{}\" = \"{}\".\"{}\")",
related_table,
related_table, foreign_key,
table, local_key
);
self.conditions.push(WhereCondition {
column: String::new(),
operator: Operator::Raw,
value: ConditionValue::RawExpr(exists_sql),
});
self
}
pub fn has_no_related_at_all(
mut self,
related_table: &str,
foreign_key: &str,
local_key: &str,
) -> Self {
let table = M::table_name();
let not_exists_sql = format!(
"NOT EXISTS (SELECT 1 FROM \"{}\" WHERE \"{}\".\"{}\" = \"{}\".\"{}\")",
related_table,
related_table, foreign_key,
table, local_key
);
self.conditions.push(WhereCondition {
column: String::new(),
operator: Operator::Raw,
value: ConditionValue::RawExpr(not_exists_sql),
});
self
}
pub fn to_subquery_sql(&self) -> String {
self.build_select_sql()
}
pub fn where_raw(mut self, raw_sql: &str) -> Self {
self.conditions.push(WhereCondition {
column: String::new(),
operator: Operator::Raw,
value: ConditionValue::RawExpr(raw_sql.to_string()),
});
self
}
pub fn where_column_raw(mut self, column: &str, raw_expr: &str) -> Self {
self.conditions.push(WhereCondition {
column: column.to_string(),
operator: Operator::Raw,
value: ConditionValue::RawExpr(raw_expr.to_string()),
});
self
}
pub fn select_raw(mut self, raw_select: &str) -> Self {
self.raw_select_expressions.push(raw_select.to_string());
self
}
pub fn select_subquery<N: Model>(mut self, subquery: QueryBuilder<N>, alias: &str) -> Self {
let subquery_sql = subquery.to_subquery_sql();
self.raw_select_expressions.push(format!("({}) AS \"{}\"", subquery_sql, alias));
self
}
pub fn where_null(mut self, column: impl crate::columns::IntoColumnName) -> Self {
self.conditions.push(WhereCondition {
column: column.column_name().to_string(),
operator: Operator::IsNull,
value: ConditionValue::None,
});
self
}
pub fn where_not_null(mut self, column: impl crate::columns::IntoColumnName) -> Self {
self.conditions.push(WhereCondition {
column: column.column_name().to_string(),
operator: Operator::IsNotNull,
value: ConditionValue::None,
});
self
}
pub fn where_between(
mut self,
column: impl crate::columns::IntoColumnName,
low: impl Into<serde_json::Value>,
high: impl Into<serde_json::Value>,
) -> Self {
self.conditions.push(WhereCondition {
column: column.column_name().to_string(),
operator: Operator::Between,
value: ConditionValue::Range(low.into(), high.into()),
});
self
}
pub fn where_json_contains(mut self, column: &str, value: impl Into<serde_json::Value>) -> Self {
self.conditions.push(WhereCondition {
column: column.to_string(),
operator: Operator::JsonContains,
value: ConditionValue::Single(value.into()),
});
self
}
pub fn where_json_contained_by(mut self, column: &str, value: impl Into<serde_json::Value>) -> Self {
self.conditions.push(WhereCondition {
column: column.to_string(),
operator: Operator::JsonContainedBy,
value: ConditionValue::Single(value.into()),
});
self
}
pub fn where_json_key_exists(mut self, column: &str, key: &str) -> Self {
self.conditions.push(WhereCondition {
column: column.to_string(),
operator: Operator::JsonKeyExists,
value: ConditionValue::Single(serde_json::Value::String(key.to_string())),
});
self
}
pub fn where_json_key_not_exists(mut self, column: &str, key: &str) -> Self {
self.conditions.push(WhereCondition {
column: column.to_string(),
operator: Operator::JsonKeyNotExists,
value: ConditionValue::Single(serde_json::Value::String(key.to_string())),
});
self
}
pub fn where_json_path_exists(mut self, column: &str, path: &str) -> Self {
self.conditions.push(WhereCondition {
column: column.to_string(),
operator: Operator::JsonPathExists,
value: ConditionValue::Single(serde_json::Value::String(path.to_string())),
});
self
}
pub fn where_json_path_not_exists(mut self, column: &str, path: &str) -> Self {
self.conditions.push(WhereCondition {
column: column.to_string(),
operator: Operator::JsonPathNotExists,
value: ConditionValue::Single(serde_json::Value::String(path.to_string())),
});
self
}
pub fn where_array_contains<V: Into<serde_json::Value>>(mut self, column: &str, value: Vec<V>) -> Self {
self.conditions.push(WhereCondition {
column: column.to_string(),
operator: Operator::ArrayContains,
value: ConditionValue::List(value.into_iter().map(|v| v.into()).collect()),
});
self
}
pub fn where_array_contained_by<V: Into<serde_json::Value>>(mut self, column: &str, value: Vec<V>) -> Self {
self.conditions.push(WhereCondition {
column: column.to_string(),
operator: Operator::ArrayContainedBy,
value: ConditionValue::List(value.into_iter().map(|v| v.into()).collect()),
});
self
}
pub fn where_array_overlaps<V: Into<serde_json::Value>>(mut self, column: &str, value: Vec<V>) -> Self {
self.conditions.push(WhereCondition {
column: column.to_string(),
operator: Operator::ArrayOverlaps,
value: ConditionValue::List(value.into_iter().map(|v| v.into()).collect()),
});
self
}
pub fn where_array_contains_any<V: Into<serde_json::Value>>(mut self, column: &str, value: Vec<V>) -> Self {
self.conditions.push(WhereCondition {
column: column.to_string(),
operator: Operator::ArrayContainsAny,
value: ConditionValue::List(value.into_iter().map(|v| v.into()).collect()),
});
self
}
pub fn where_array_contains_all<V: Into<serde_json::Value>>(mut self, column: &str, value: Vec<V>) -> Self {
self.conditions.push(WhereCondition {
column: column.to_string(),
operator: Operator::ArrayContainsAll,
value: ConditionValue::List(value.into_iter().map(|v| v.into()).collect()),
});
self
}
pub fn order_by(mut self, column: impl crate::columns::IntoColumnName, direction: Order) -> Self {
self.order_by.push((column.column_name().to_string(), direction));
self
}
pub fn order_asc(self, column: impl crate::columns::IntoColumnName) -> Self {
self.order_by(column, Order::Asc)
}
pub fn order_desc(self, column: impl crate::columns::IntoColumnName) -> Self {
self.order_by(column, Order::Desc)
}
pub fn latest(self) -> Self {
self.order_desc("created_at")
}
pub fn oldest(self) -> Self {
self.order_asc("created_at")
}
pub fn limit(mut self, n: u64) -> Self {
self.limit_value = Some(n);
self
}
pub fn offset(mut self, n: u64) -> Self {
self.offset_value = Some(n);
self
}
pub fn page(self, page: u64, per_page: u64) -> Self {
let offset = (page.saturating_sub(1)) * per_page;
self.limit(per_page).offset(offset)
}
pub fn take(self, n: u64) -> Self {
self.limit(n)
}
pub fn skip(self, n: u64) -> Self {
self.offset(n)
}
pub fn select(mut self, columns: Vec<&str>) -> Self {
self.select_columns = Some(columns.into_iter().map(|s| s.to_string()).collect());
self
}
pub fn select_with_linked(
mut self,
local_columns: Vec<&str>,
linked_table: &str,
local_fk: &str,
remote_pk: &str,
linked_columns: Vec<&str>,
) -> Self {
let table_name = M::table_name();
let mut all_columns: Vec<String> = local_columns
.iter()
.map(|c| format!("{}.{}", table_name, c))
.collect();
for col in linked_columns {
all_columns.push(format!("{}.{}", linked_table, col));
}
self.select_columns = Some(all_columns);
self.joins.push(JoinClause {
join_type: JoinType::Left,
table: linked_table.to_string(),
alias: None,
left_column: format!("{}.{}", table_name, local_fk),
right_column: format!("{}.{}", linked_table, remote_pk),
});
self
}
pub fn select_also_linked(
mut self,
linked_table: &str,
local_pk: &str,
remote_fk: &str,
linked_columns: Vec<&str>,
) -> Self {
let table_name = M::table_name();
let local_cols: Vec<String> = M::column_names()
.iter()
.map(|c| format!("{}.{}", table_name, c))
.collect();
let mut all_columns = local_cols;
for col in linked_columns {
all_columns.push(format!("{}.{}", linked_table, col));
}
self.select_columns = Some(all_columns);
self.joins.push(JoinClause {
join_type: JoinType::Left,
table: linked_table.to_string(),
alias: None,
left_column: format!("{}.{}", table_name, local_pk),
right_column: format!("{}.{}", linked_table, remote_fk),
});
self
}
pub fn inner_join(self, table: &str, left_column: &str, right_column: &str) -> Self {
self.join(JoinType::Inner, table, None, left_column, right_column)
}
pub fn inner_join_as(self, table: &str, alias: &str, left_column: &str, right_column: &str) -> Self {
self.join(JoinType::Inner, table, Some(alias), left_column, right_column)
}
pub fn left_join(self, table: &str, left_column: &str, right_column: &str) -> Self {
self.join(JoinType::Left, table, None, left_column, right_column)
}
pub fn left_join_as(self, table: &str, alias: &str, left_column: &str, right_column: &str) -> Self {
self.join(JoinType::Left, table, Some(alias), left_column, right_column)
}
pub fn right_join(self, table: &str, left_column: &str, right_column: &str) -> Self {
self.join(JoinType::Right, table, None, left_column, right_column)
}
pub fn right_join_as(self, table: &str, alias: &str, left_column: &str, right_column: &str) -> Self {
self.join(JoinType::Right, table, Some(alias), left_column, right_column)
}
fn join(
mut self,
join_type: JoinType,
table: &str,
alias: Option<&str>,
left_column: &str,
right_column: &str,
) -> Self {
self.joins.push(JoinClause {
join_type,
table: table.to_string(),
alias: alias.map(|s| s.to_string()),
left_column: left_column.to_string(),
right_column: right_column.to_string(),
});
self
}
pub fn group_by(mut self, column: impl crate::columns::IntoColumnName) -> Self {
self.group_by.push(column.column_name().to_string());
self
}
pub fn group_by_columns(mut self, columns: Vec<&str>) -> Self {
for col in columns {
self.group_by.push(col.to_string());
}
self
}
pub fn having(mut self, condition: &str) -> Self {
self.having_conditions.push(condition.to_string());
self
}
pub fn having_count_gt(self, value: i64) -> Self {
self.having(&format!("COUNT(*) > {}", value))
}
pub fn having_count_gte(self, value: i64) -> Self {
self.having(&format!("COUNT(*) >= {}", value))
}
pub fn having_count_lt(self, value: i64) -> Self {
self.having(&format!("COUNT(*) < {}", value))
}
pub fn having_count_lte(self, value: i64) -> Self {
self.having(&format!("COUNT(*) <= {}", value))
}
pub fn having_sum_gt(self, column: impl crate::columns::IntoColumnName, value: f64) -> Self {
let db_type = crate::database::try_db()
.map(|db| db.backend())
.unwrap_or(DatabaseType::Postgres);
let col = db_sql::quote_ident(db_type, column.column_name());
self.having(&format!("SUM({}) > {}", col, value))
}
pub fn having_avg_gt(self, column: impl crate::columns::IntoColumnName, value: f64) -> Self {
let db_type = crate::database::try_db()
.map(|db| db.backend())
.unwrap_or(DatabaseType::Postgres);
let col = db_sql::quote_ident(db_type, column.column_name());
self.having(&format!("AVG({}) > {}", col, value))
}
pub async fn sum(self, column: impl crate::columns::IntoColumnName) -> Result<f64> {
let db_type = crate::database::try_db()
.map(|db| db.backend())
.unwrap_or(DatabaseType::Postgres);
let col = db_sql::quote_ident(db_type, column.column_name());
let expr = db_sql::cast_to_float(db_type, &format!("SUM({})", col));
self.aggregate_f64(&expr, "sum_result").await
}
pub async fn avg(self, column: impl crate::columns::IntoColumnName) -> Result<f64> {
let db_type = crate::database::try_db()
.map(|db| db.backend())
.unwrap_or(DatabaseType::Postgres);
let col = db_sql::quote_ident(db_type, column.column_name());
let expr = db_sql::cast_to_float(db_type, &format!("AVG({})", col));
self.aggregate_f64(&expr, "avg_result").await
}
pub async fn min(self, column: impl crate::columns::IntoColumnName) -> Result<f64> {
let db_type = crate::database::try_db()
.map(|db| db.backend())
.unwrap_or(DatabaseType::Postgres);
let col = db_sql::quote_ident(db_type, column.column_name());
let expr = db_sql::cast_to_float(db_type, &format!("MIN({})", col));
self.aggregate_f64(&expr, "min_result").await
}
pub async fn max(self, column: impl crate::columns::IntoColumnName) -> Result<f64> {
let db_type = crate::database::try_db()
.map(|db| db.backend())
.unwrap_or(DatabaseType::Postgres);
let col = db_sql::quote_ident(db_type, column.column_name());
let expr = db_sql::cast_to_float(db_type, &format!("MAX({})", col));
self.aggregate_f64(&expr, "max_result").await
}
pub async fn count_distinct(self, column: impl crate::columns::IntoColumnName) -> Result<u64> {
#[derive(Debug, FromQueryResult)]
struct CountResult {
count_result: i64,
}
let db_type = crate::database::try_db()
.map(|db| db.backend())
.unwrap_or(DatabaseType::Postgres);
let col = db_sql::quote_ident(db_type, column.column_name());
let conn = crate::database::require_db()?.__internal_connection();
let mut select = M::Entity::find();
if !self.conditions.is_empty() || !self.or_groups.is_empty() || M::soft_delete_enabled() {
let condition = self.build_sea_condition();
select = select.filter(condition);
}
let count_expr = Expr::cust(format!("COUNT(DISTINCT {})", col));
let result: Option<CountResult> = select
.select_only()
.column_as(count_expr, "count_result")
.into_model::<CountResult>()
.one(conn)
.await
.map_err(translate_error)?;
Ok(result.map(|r| r.count_result as u64).unwrap_or(0))
}
async fn aggregate_f64(self, expr_sql: &str, _alias: &str) -> Result<f64> {
#[derive(Debug, FromQueryResult)]
struct AggResult {
agg_result: Option<f64>,
}
let conn = crate::database::require_db()?.__internal_connection();
let mut select = M::Entity::find();
if !self.conditions.is_empty() || !self.or_groups.is_empty() || M::soft_delete_enabled() {
let condition = self.build_sea_condition();
select = select.filter(condition);
}
let agg_expr = Expr::cust(expr_sql.to_string());
let result: Option<AggResult> = select
.select_only()
.column_as(agg_expr, "agg_result")
.into_model::<AggResult>()
.one(conn)
.await
.map_err(translate_error)?;
Ok(result.and_then(|r| r.agg_result).unwrap_or(0.0))
}
pub fn union<N: Model>(mut self, other: QueryBuilder<N>) -> Self {
self.unions.push(UnionClause {
union_type: UnionType::Union,
query_sql: other.build_base_select_sql(),
});
self
}
pub fn union_all<N: Model>(mut self, other: QueryBuilder<N>) -> Self {
self.unions.push(UnionClause {
union_type: UnionType::UnionAll,
query_sql: other.build_base_select_sql(),
});
self
}
pub fn union_raw(mut self, sql: &str) -> Self {
self.unions.push(UnionClause {
union_type: UnionType::Union,
query_sql: sql.to_string(),
});
self
}
pub fn union_all_raw(mut self, sql: &str) -> Self {
self.unions.push(UnionClause {
union_type: UnionType::UnionAll,
query_sql: sql.to_string(),
});
self
}
pub fn window(mut self, window_fn: WindowFunction) -> Self {
self.window_functions.push(window_fn);
self
}
pub fn row_number(
mut self,
alias: &str,
partition_by: Option<&str>,
order_by: &str,
order: Order,
) -> Self {
let mut wf = WindowFunction::new(WindowFunctionType::RowNumber, alias)
.order_by(order_by, order);
if let Some(partition) = partition_by {
wf = wf.partition_by(partition);
}
self.window_functions.push(wf);
self
}
pub fn rank(
mut self,
alias: &str,
partition_by: Option<&str>,
order_by: &str,
order: Order,
) -> Self {
let mut wf = WindowFunction::new(WindowFunctionType::Rank, alias)
.order_by(order_by, order);
if let Some(partition) = partition_by {
wf = wf.partition_by(partition);
}
self.window_functions.push(wf);
self
}
pub fn dense_rank(
mut self,
alias: &str,
partition_by: Option<&str>,
order_by: &str,
order: Order,
) -> Self {
let mut wf = WindowFunction::new(WindowFunctionType::DenseRank, alias)
.order_by(order_by, order);
if let Some(partition) = partition_by {
wf = wf.partition_by(partition);
}
self.window_functions.push(wf);
self
}
#[allow(clippy::too_many_arguments)]
pub fn lag(
mut self,
alias: &str,
column: &str,
offset: i32,
default: Option<&str>,
partition_by: &str,
order_by: &str,
order: Order,
) -> Self {
let wf = WindowFunction::new(
WindowFunctionType::Lag(column.to_string(), Some(offset), default.map(|s| s.to_string())),
alias,
)
.partition_by(partition_by)
.order_by(order_by, order);
self.window_functions.push(wf);
self
}
#[allow(clippy::too_many_arguments)]
pub fn lead(
mut self,
alias: &str,
column: &str,
offset: i32,
default: Option<&str>,
partition_by: &str,
order_by: &str,
order: Order,
) -> Self {
let wf = WindowFunction::new(
WindowFunctionType::Lead(column.to_string(), Some(offset), default.map(|s| s.to_string())),
alias,
)
.partition_by(partition_by)
.order_by(order_by, order);
self.window_functions.push(wf);
self
}
pub fn running_sum(
mut self,
alias: &str,
column: &str,
order_by: &str,
order: Order,
) -> Self {
let wf = WindowFunction::new(WindowFunctionType::Sum(column.to_string()), alias)
.order_by(order_by, order)
.frame(FrameType::Rows, FrameBound::UnboundedPreceding, FrameBound::CurrentRow);
self.window_functions.push(wf);
self
}
pub fn running_avg(
mut self,
alias: &str,
column: &str,
order_by: &str,
order: Order,
) -> Self {
let wf = WindowFunction::new(WindowFunctionType::Avg(column.to_string()), alias)
.order_by(order_by, order)
.frame(FrameType::Rows, FrameBound::UnboundedPreceding, FrameBound::CurrentRow);
self.window_functions.push(wf);
self
}
pub fn ntile(
mut self,
alias: &str,
buckets: u32,
order_by: &str,
order: Order,
) -> Self {
let wf = WindowFunction::new(WindowFunctionType::Ntile(buckets), alias)
.order_by(order_by, order);
self.window_functions.push(wf);
self
}
pub fn first_value(
mut self,
alias: &str,
column: &str,
partition_by: &str,
order_by: &str,
order: Order,
) -> Self {
let wf = WindowFunction::new(WindowFunctionType::FirstValue(column.to_string()), alias)
.partition_by(partition_by)
.order_by(order_by, order);
self.window_functions.push(wf);
self
}
pub fn last_value(
mut self,
alias: &str,
column: &str,
partition_by: &str,
order_by: &str,
order: Order,
) -> Self {
let wf = WindowFunction::new(WindowFunctionType::LastValue(column.to_string()), alias)
.partition_by(partition_by)
.order_by(order_by, order)
.frame(FrameType::Rows, FrameBound::UnboundedPreceding, FrameBound::UnboundedFollowing);
self.window_functions.push(wf);
self
}
pub fn with_cte(mut self, cte: CTE) -> Self {
self.ctes.push(cte);
self
}
pub fn with_query<N: Model>(mut self, name: &str, query: QueryBuilder<N>) -> Self {
self.ctes.push(CTE::new(name, query.build_base_select_sql()));
self
}
pub fn with_cte_columns(mut self, name: &str, columns: Vec<&str>, sql: &str) -> Self {
self.ctes.push(CTE::with_columns(name, columns, sql.to_string()));
self
}
pub fn with_recursive_cte(
mut self,
name: &str,
columns: Vec<&str>,
base_case: &str,
recursive_case: &str,
) -> Self {
let full_sql = format!("{} UNION ALL {}", base_case, recursive_case);
let cte = CTE::with_columns(name, columns, full_sql).recursive();
self.ctes.push(cte);
self
}
pub fn with_trashed(mut self) -> Self {
self.include_trashed = true;
self.only_trashed = false;
self
}
pub fn only_trashed(mut self) -> Self {
self.only_trashed = true;
self.include_trashed = false;
self
}
pub fn without_trashed(mut self) -> Self {
self.include_trashed = false;
self.only_trashed = false;
self
}
pub fn scope<F>(self, f: F) -> Self
where
F: FnOnce(Self) -> Self,
{
f(self)
}
pub fn when<F>(self, condition: bool, f: F) -> Self
where
F: FnOnce(Self) -> Self,
{
if condition {
f(self)
} else {
self
}
}
pub fn when_some<T, F>(self, option: Option<T>, f: F) -> Self
where
F: FnOnce(Self, T) -> Self,
{
match option {
Some(value) => f(self, value),
None => self,
}
}
fn json_to_sea_value(value: &serde_json::Value) -> crate::internal::Value {
use crate::internal::Value;
match value {
serde_json::Value::Null => Value::String(None),
serde_json::Value::Bool(b) => Value::Bool(Some(*b)),
serde_json::Value::Number(n) => {
if let Some(i) = n.as_i64() {
Value::BigInt(Some(i))
} else if let Some(f) = n.as_f64() {
Value::Double(Some(f))
} else {
Value::String(Some(n.to_string()))
}
}
serde_json::Value::String(s) => Value::String(Some(s.clone())),
serde_json::Value::Array(_) | serde_json::Value::Object(_) => {
Value::String(Some(value.to_string()))
}
}
}
fn build_sea_condition(&self) -> Condition {
use sea_orm::sea_query::{Alias, SimpleExpr};
let db_type = crate::database::try_db()
.map(|db| db.backend())
.unwrap_or(DatabaseType::Postgres);
let mut condition = Condition::all();
for where_cond in &self.conditions {
let col = Expr::col(Alias::new(&where_cond.column));
let expr: SimpleExpr = match &where_cond.operator {
Operator::Eq => {
if let ConditionValue::Single(val) = &where_cond.value {
col.eq(Self::json_to_sea_value(val))
} else {
continue;
}
}
Operator::NotEq => {
if let ConditionValue::Single(val) = &where_cond.value {
col.ne(Self::json_to_sea_value(val))
} else {
continue;
}
}
Operator::Gt => {
if let ConditionValue::Single(val) = &where_cond.value {
col.gt(Self::json_to_sea_value(val))
} else {
continue;
}
}
Operator::Gte => {
if let ConditionValue::Single(val) = &where_cond.value {
col.gte(Self::json_to_sea_value(val))
} else {
continue;
}
}
Operator::Lt => {
if let ConditionValue::Single(val) = &where_cond.value {
col.lt(Self::json_to_sea_value(val))
} else {
continue;
}
}
Operator::Lte => {
if let ConditionValue::Single(val) = &where_cond.value {
col.lte(Self::json_to_sea_value(val))
} else {
continue;
}
}
Operator::Like => {
if let ConditionValue::Single(serde_json::Value::String(pattern)) = &where_cond.value {
col.like(pattern.as_str())
} else {
continue;
}
}
Operator::NotLike => {
if let ConditionValue::Single(serde_json::Value::String(pattern)) = &where_cond.value {
col.not_like(pattern.as_str())
} else {
continue;
}
}
Operator::In => {
if let ConditionValue::List(values) = &where_cond.value {
let sea_values: Vec<_> = values.iter().map(Self::json_to_sea_value).collect();
col.is_in(sea_values)
} else {
continue;
}
}
Operator::NotIn => {
if let ConditionValue::List(values) = &where_cond.value {
let sea_values: Vec<_> = values.iter().map(Self::json_to_sea_value).collect();
col.is_not_in(sea_values)
} else {
continue;
}
}
Operator::IsNull => {
col.is_null()
}
Operator::IsNotNull => {
col.is_not_null()
}
Operator::Between => {
if let ConditionValue::Range(low, high) = &where_cond.value {
col.between(Self::json_to_sea_value(low), Self::json_to_sea_value(high))
} else {
continue;
}
}
Operator::JsonContains => {
if let ConditionValue::Single(val) = &where_cond.value {
let value_str = val.to_string();
Expr::cust(db_sql::json_contains(db_type, &where_cond.column, &value_str))
} else {
continue;
}
}
Operator::JsonContainedBy => {
if let ConditionValue::Single(val) = &where_cond.value {
let value_str = val.to_string();
Expr::cust(db_sql::json_contained_by(db_type, &where_cond.column, &value_str))
} else {
continue;
}
}
Operator::JsonKeyExists => {
if let ConditionValue::Single(serde_json::Value::String(key)) = &where_cond.value {
Expr::cust(db_sql::json_key_exists(db_type, &where_cond.column, key))
} else {
continue;
}
}
Operator::JsonKeyNotExists => {
if let ConditionValue::Single(serde_json::Value::String(key)) = &where_cond.value {
Expr::cust(db_sql::json_key_not_exists(db_type, &where_cond.column, key))
} else {
continue;
}
}
Operator::JsonPathExists => {
if let ConditionValue::Single(serde_json::Value::String(path)) = &where_cond.value {
Expr::cust(db_sql::json_path_exists(db_type, &where_cond.column, path))
} else {
continue;
}
}
Operator::JsonPathNotExists => {
if let ConditionValue::Single(serde_json::Value::String(path)) = &where_cond.value {
Expr::cust(db_sql::json_path_not_exists(db_type, &where_cond.column, path))
} else {
continue;
}
}
Operator::ArrayContains => {
if let ConditionValue::List(values) = &where_cond.value {
let array_vals: Vec<String> = values.iter()
.map(|v| match v {
serde_json::Value::String(s) => format!("'{}'", s.replace("'", "''")),
serde_json::Value::Number(n) => n.to_string(),
serde_json::Value::Bool(b) => b.to_string(),
_ => v.to_string(),
})
.collect();
Expr::cust(db_sql::array_contains(db_type, &where_cond.column, &array_vals))
} else {
continue;
}
}
Operator::ArrayContainedBy => {
if let ConditionValue::List(values) = &where_cond.value {
let array_vals: Vec<String> = values.iter()
.map(|v| match v {
serde_json::Value::String(s) => format!("'{}'", s.replace("'", "''")),
serde_json::Value::Number(n) => n.to_string(),
serde_json::Value::Bool(b) => b.to_string(),
_ => v.to_string(),
})
.collect();
Expr::cust(db_sql::array_contained_by(db_type, &where_cond.column, &array_vals))
} else {
continue;
}
}
Operator::ArrayOverlaps | Operator::ArrayContainsAny => {
if let ConditionValue::List(values) = &where_cond.value {
let array_vals: Vec<String> = values.iter()
.map(|v| match v {
serde_json::Value::String(s) => format!("'{}'", s.replace("'", "''")),
serde_json::Value::Number(n) => n.to_string(),
serde_json::Value::Bool(b) => b.to_string(),
_ => v.to_string(),
})
.collect();
Expr::cust(db_sql::array_overlaps(db_type, &where_cond.column, &array_vals))
} else {
continue;
}
}
Operator::ArrayContainsAll => {
if let ConditionValue::List(values) = &where_cond.value {
let array_vals: Vec<String> = values.iter()
.map(|v| match v {
serde_json::Value::String(s) => format!("'{}'", s.replace("'", "''")),
serde_json::Value::Number(n) => n.to_string(),
serde_json::Value::Bool(b) => b.to_string(),
_ => v.to_string(),
})
.collect();
Expr::cust(db_sql::array_contains(db_type, &where_cond.column, &array_vals))
} else {
continue;
}
}
Operator::SubqueryIn => {
if let ConditionValue::Subquery(subquery_sql) = &where_cond.value {
let col_quoted = db_sql::quote_ident(db_type, &where_cond.column);
Expr::cust(format!("{} IN ({})", col_quoted, subquery_sql))
} else {
continue;
}
}
Operator::SubqueryNotIn => {
if let ConditionValue::Subquery(subquery_sql) = &where_cond.value {
let col_quoted = db_sql::quote_ident(db_type, &where_cond.column);
Expr::cust(format!("{} NOT IN ({})", col_quoted, subquery_sql))
} else {
continue;
}
}
Operator::Raw => {
if let ConditionValue::RawExpr(raw_sql) = &where_cond.value {
if where_cond.column.is_empty() {
Expr::cust(raw_sql.clone())
} else {
let col_quoted = db_sql::quote_ident(db_type, &where_cond.column);
Expr::cust(format!("{} {}", col_quoted, raw_sql))
}
} else {
continue;
}
}
Operator::EqAny => {
if let ConditionValue::List(values) = &where_cond.value {
let array_vals: Vec<String> = values.iter()
.map(|v| match v {
serde_json::Value::String(s) => format!("'{}'", s.replace("'", "''")),
serde_json::Value::Number(n) => n.to_string(),
serde_json::Value::Bool(b) => b.to_string(),
serde_json::Value::Null => "NULL".to_string(),
_ => v.to_string(),
})
.collect();
let col_quoted = db_sql::quote_ident(db_type, &where_cond.column);
Expr::cust(db_sql::eq_any(db_type, &col_quoted, &array_vals))
} else {
continue;
}
}
Operator::NeAll => {
if let ConditionValue::List(values) = &where_cond.value {
let array_vals: Vec<String> = values.iter()
.map(|v| match v {
serde_json::Value::String(s) => format!("'{}'", s.replace("'", "''")),
serde_json::Value::Number(n) => n.to_string(),
serde_json::Value::Bool(b) => b.to_string(),
serde_json::Value::Null => "NULL".to_string(),
_ => v.to_string(),
})
.collect();
let col_quoted = db_sql::quote_ident(db_type, &where_cond.column);
Expr::cust(db_sql::ne_all(db_type, &col_quoted, &array_vals))
} else {
continue;
}
}
};
condition = condition.add(expr);
}
for or_group in &self.or_groups {
let or_condition = self.build_or_group_condition(or_group, db_type);
condition = condition.add(or_condition);
}
if M::soft_delete_enabled() {
use sea_orm::sea_query::Alias;
let deleted_at_col = Expr::col(Alias::new("deleted_at"));
if self.only_trashed {
condition = condition.add(deleted_at_col.is_not_null());
} else if !self.include_trashed {
condition = condition.add(deleted_at_col.is_null());
}
}
condition
}
#[allow(clippy::only_used_in_recursion)]
fn build_or_group_condition(&self, group: &OrGroup, db_type: DatabaseType) -> Condition {
use sea_orm::sea_query::{Alias, SimpleExpr};
let mut or_condition = match group.combine_with {
LogicalOp::Or => Condition::any(),
LogicalOp::And => Condition::all(),
};
for where_cond in &group.conditions {
let col = Expr::col(Alias::new(&where_cond.column));
let expr: SimpleExpr = match &where_cond.operator {
Operator::Eq => {
if let ConditionValue::Single(val) = &where_cond.value {
col.eq(Self::json_to_sea_value(val))
} else { continue; }
}
Operator::NotEq => {
if let ConditionValue::Single(val) = &where_cond.value {
col.ne(Self::json_to_sea_value(val))
} else { continue; }
}
Operator::Gt => {
if let ConditionValue::Single(val) = &where_cond.value {
col.gt(Self::json_to_sea_value(val))
} else { continue; }
}
Operator::Gte => {
if let ConditionValue::Single(val) = &where_cond.value {
col.gte(Self::json_to_sea_value(val))
} else { continue; }
}
Operator::Lt => {
if let ConditionValue::Single(val) = &where_cond.value {
col.lt(Self::json_to_sea_value(val))
} else { continue; }
}
Operator::Lte => {
if let ConditionValue::Single(val) = &where_cond.value {
col.lte(Self::json_to_sea_value(val))
} else { continue; }
}
Operator::Like => {
if let ConditionValue::Single(serde_json::Value::String(pattern)) = &where_cond.value {
col.like(pattern.as_str())
} else { continue; }
}
Operator::NotLike => {
if let ConditionValue::Single(serde_json::Value::String(pattern)) = &where_cond.value {
col.not_like(pattern.as_str())
} else { continue; }
}
Operator::In => {
if let ConditionValue::List(values) = &where_cond.value {
let sea_values: Vec<_> = values.iter().map(Self::json_to_sea_value).collect();
col.is_in(sea_values)
} else { continue; }
}
Operator::NotIn => {
if let ConditionValue::List(values) = &where_cond.value {
let sea_values: Vec<_> = values.iter().map(Self::json_to_sea_value).collect();
col.is_not_in(sea_values)
} else { continue; }
}
Operator::IsNull => col.is_null(),
Operator::IsNotNull => col.is_not_null(),
Operator::Between => {
if let ConditionValue::Range(low, high) = &where_cond.value {
col.between(Self::json_to_sea_value(low), Self::json_to_sea_value(high))
} else { continue; }
}
Operator::Raw => {
if let ConditionValue::RawExpr(raw_sql) = &where_cond.value {
if where_cond.column.is_empty() {
Expr::cust(raw_sql.clone())
} else {
let col_quoted = db_sql::quote_ident(db_type, &where_cond.column);
Expr::cust(format!("{} {}", col_quoted, raw_sql))
}
} else { continue; }
}
Operator::JsonContains => {
if let ConditionValue::Single(val) = &where_cond.value {
Expr::cust(db_sql::json_contains(db_type, &where_cond.column, &val.to_string()))
} else { continue; }
}
Operator::JsonContainedBy => {
if let ConditionValue::Single(val) = &where_cond.value {
Expr::cust(db_sql::json_contained_by(db_type, &where_cond.column, &val.to_string()))
} else { continue; }
}
Operator::JsonKeyExists => {
if let ConditionValue::Single(serde_json::Value::String(key)) = &where_cond.value {
Expr::cust(db_sql::json_key_exists(db_type, &where_cond.column, key))
} else { continue; }
}
Operator::JsonKeyNotExists => {
if let ConditionValue::Single(serde_json::Value::String(key)) = &where_cond.value {
Expr::cust(db_sql::json_key_not_exists(db_type, &where_cond.column, key))
} else { continue; }
}
Operator::JsonPathExists => {
if let ConditionValue::Single(serde_json::Value::String(path)) = &where_cond.value {
Expr::cust(db_sql::json_path_exists(db_type, &where_cond.column, path))
} else { continue; }
}
Operator::JsonPathNotExists => {
if let ConditionValue::Single(serde_json::Value::String(path)) = &where_cond.value {
Expr::cust(db_sql::json_path_not_exists(db_type, &where_cond.column, path))
} else { continue; }
}
Operator::ArrayContains | Operator::ArrayContainsAll => {
if let ConditionValue::List(values) = &where_cond.value {
let array_vals: Vec<String> = values.iter()
.map(|v| match v {
serde_json::Value::String(s) => format!("'{}'", s.replace("'", "''")),
serde_json::Value::Number(n) => n.to_string(),
serde_json::Value::Bool(b) => b.to_string(),
_ => v.to_string(),
})
.collect();
Expr::cust(db_sql::array_contains(db_type, &where_cond.column, &array_vals))
} else { continue; }
}
Operator::ArrayContainedBy => {
if let ConditionValue::List(values) = &where_cond.value {
let array_vals: Vec<String> = values.iter()
.map(|v| match v {
serde_json::Value::String(s) => format!("'{}'", s.replace("'", "''")),
serde_json::Value::Number(n) => n.to_string(),
serde_json::Value::Bool(b) => b.to_string(),
_ => v.to_string(),
})
.collect();
Expr::cust(db_sql::array_contained_by(db_type, &where_cond.column, &array_vals))
} else { continue; }
}
Operator::ArrayOverlaps | Operator::ArrayContainsAny => {
if let ConditionValue::List(values) = &where_cond.value {
let array_vals: Vec<String> = values.iter()
.map(|v| match v {
serde_json::Value::String(s) => format!("'{}'", s.replace("'", "''")),
serde_json::Value::Number(n) => n.to_string(),
serde_json::Value::Bool(b) => b.to_string(),
_ => v.to_string(),
})
.collect();
Expr::cust(db_sql::array_overlaps(db_type, &where_cond.column, &array_vals))
} else { continue; }
}
Operator::SubqueryIn => {
if let ConditionValue::Subquery(subquery_sql) = &where_cond.value {
let col_quoted = db_sql::quote_ident(db_type, &where_cond.column);
Expr::cust(format!("{} IN ({})", col_quoted, subquery_sql))
} else { continue; }
}
Operator::SubqueryNotIn => {
if let ConditionValue::Subquery(subquery_sql) = &where_cond.value {
let col_quoted = db_sql::quote_ident(db_type, &where_cond.column);
Expr::cust(format!("{} NOT IN ({})", col_quoted, subquery_sql))
} else { continue; }
}
Operator::EqAny => {
if let ConditionValue::List(values) = &where_cond.value {
let array_vals: Vec<String> = values.iter()
.map(|v| match v {
serde_json::Value::String(s) => format!("'{}'", s.replace("'", "''")),
serde_json::Value::Number(n) => n.to_string(),
serde_json::Value::Bool(b) => b.to_string(),
serde_json::Value::Null => "NULL".to_string(),
_ => v.to_string(),
})
.collect();
let col_quoted = db_sql::quote_ident(db_type, &where_cond.column);
Expr::cust(db_sql::eq_any(db_type, &col_quoted, &array_vals))
} else { continue; }
}
Operator::NeAll => {
if let ConditionValue::List(values) = &where_cond.value {
let array_vals: Vec<String> = values.iter()
.map(|v| match v {
serde_json::Value::String(s) => format!("'{}'", s.replace("'", "''")),
serde_json::Value::Number(n) => n.to_string(),
serde_json::Value::Bool(b) => b.to_string(),
serde_json::Value::Null => "NULL".to_string(),
_ => v.to_string(),
})
.collect();
let col_quoted = db_sql::quote_ident(db_type, &where_cond.column);
Expr::cust(db_sql::ne_all(db_type, &col_quoted, &array_vals))
} else { continue; }
}
};
or_condition = or_condition.add(expr);
}
for nested_group in &group.nested_groups {
let nested_condition = self.build_or_group_condition(nested_group, db_type);
or_condition = or_condition.add(nested_condition);
}
or_condition
}
#[allow(dead_code)]
fn log_query(&self, sql: &str) {
if std::env::var("TIDE_LOG_QUERIES")
.map(|v| v.to_lowercase() == "true" || v == "1")
.unwrap_or(false)
{
tide_debug!("Query: {}", sql);
}
if crate::logging::QueryLogger::is_enabled() {
let entry = crate::logging::QueryLogEntry::new(sql)
.with_table(M::table_name());
crate::logging::QueryLogger::log(entry);
}
}
pub fn debug(&self) -> crate::logging::QueryDebugInfo {
use crate::logging::QueryDebugInfo;
let mut info = QueryDebugInfo::new(M::table_name());
for condition in &self.conditions {
let op_str = match &condition.operator {
Operator::Eq => "=",
Operator::NotEq => "!=",
Operator::Gt => ">",
Operator::Gte => ">=",
Operator::Lt => "<",
Operator::Lte => "<=",
Operator::Like => "LIKE",
Operator::NotLike => "NOT LIKE",
Operator::In => "IN",
Operator::NotIn => "NOT IN",
Operator::IsNull => "IS NULL",
Operator::IsNotNull => "IS NOT NULL",
Operator::Between => "BETWEEN",
Operator::JsonContains => "@>",
Operator::JsonContainedBy => "<@",
Operator::JsonKeyExists => "?",
Operator::JsonKeyNotExists => "?!",
Operator::JsonPathExists => "@?",
Operator::JsonPathNotExists => "NOT @?",
Operator::ArrayContains => "@>",
Operator::ArrayContainedBy => "<@",
Operator::ArrayOverlaps => "&&",
Operator::ArrayContainsAny => "&& ANY",
Operator::ArrayContainsAll => "&& ALL",
Operator::SubqueryIn => "IN (subquery)",
Operator::SubqueryNotIn => "NOT IN (subquery)",
Operator::Raw => "RAW",
Operator::EqAny => "= ANY(array)",
Operator::NeAll => "<> ALL(array)",
};
let value_str = match &condition.value {
ConditionValue::Single(v) => format!("{:?}", v),
ConditionValue::List(list) => format!("{:?}", list),
ConditionValue::Range(start, end) => format!("{:?}..{:?}", start, end),
ConditionValue::None => "NULL".to_string(),
ConditionValue::Subquery(sub) => format!("({})", sub),
ConditionValue::RawExpr(expr) => expr.clone(),
};
info.add_condition(format!("{} {} {}", condition.column, op_str, value_str));
}
for (column, order) in &self.order_by {
info.add_order_by(format!("{} {}", column, order.as_str()));
}
info.group_by = self.group_by.clone();
info.limit = self.limit_value;
info.offset = self.offset_value;
if let Some(ref cols) = self.select_columns {
info.select = cols.clone();
}
for join in &self.joins {
info.joins.push(format!(
"{:?} JOIN {} ON {} = {}",
join.join_type,
join.table,
join.left_column,
join.right_column
));
}
info.sql = self.build_sql_preview();
info
}
pub fn build_sql_preview(&self) -> String {
let mut sql = String::new();
match &self.select_columns {
Some(cols) if !cols.is_empty() => {
sql.push_str("SELECT ");
sql.push_str(&cols.join(", "));
sql.push_str(" FROM ");
}
_ => {
sql.push_str("SELECT * FROM ");
}
}
sql.push_str(M::table_name());
for join in &self.joins {
sql.push_str(&format!(
" {:?} JOIN {} ON {} = {}",
join.join_type,
join.table,
join.left_column,
join.right_column
));
}
if !self.conditions.is_empty() {
sql.push_str(" WHERE ");
let conditions: Vec<String> = self.conditions.iter()
.map(|cond| {
let op_str = match &cond.operator {
Operator::Eq => "= ?",
Operator::NotEq => "!= ?",
Operator::Gt => "> ?",
Operator::Gte => ">= ?",
Operator::Lt => "< ?",
Operator::Lte => "<= ?",
Operator::Like | Operator::NotLike => "LIKE ?",
Operator::In | Operator::NotIn => "IN (?)",
Operator::IsNull => "IS NULL",
Operator::IsNotNull => "IS NOT NULL",
Operator::Between => "BETWEEN ? AND ?",
Operator::JsonContains | Operator::ArrayContains => "@> ?",
Operator::JsonContainedBy | Operator::ArrayContainedBy => "<@ ?",
Operator::JsonKeyExists => "? ?",
Operator::JsonKeyNotExists => "?! ?",
Operator::JsonPathExists => "@? ?",
Operator::JsonPathNotExists => "NOT @? ?",
Operator::ArrayOverlaps => "&& ?",
Operator::ArrayContainsAny => "&& ANY(?)",
Operator::ArrayContainsAll => "&& ALL(?)",
Operator::SubqueryIn => "IN (SELECT ...)",
Operator::SubqueryNotIn => "NOT IN (SELECT ...)",
Operator::Raw => "...",
Operator::EqAny => "= ANY(ARRAY[?])",
Operator::NeAll => "<> ALL(ARRAY[?])",
};
format!("{} {}", cond.column, op_str)
})
.collect();
sql.push_str(&conditions.join(" AND "));
if !self.or_groups.is_empty() {
for or_group in &self.or_groups {
let or_preview = self.build_or_group_preview(or_group);
if !or_preview.is_empty() {
sql.push_str(&format!(" AND ({})", or_preview));
}
}
}
} else if !self.or_groups.is_empty() {
sql.push_str(" WHERE ");
let or_previews: Vec<String> = self.or_groups.iter()
.map(|g| format!("({})", self.build_or_group_preview(g)))
.filter(|s| s != "()")
.collect();
sql.push_str(&or_previews.join(" AND "));
}
if !self.group_by.is_empty() {
sql.push_str(" GROUP BY ");
sql.push_str(&self.group_by.join(", "));
}
if !self.order_by.is_empty() {
sql.push_str(" ORDER BY ");
let orders: Vec<String> = self.order_by.iter()
.map(|(col, ord)| format!("{} {}", col, ord.as_str()))
.collect();
sql.push_str(&orders.join(", "));
}
if let Some(limit) = self.limit_value {
sql.push_str(&format!(" LIMIT {}", limit));
}
if let Some(offset) = self.offset_value {
sql.push_str(&format!(" OFFSET {}", offset));
}
sql
}
#[allow(clippy::only_used_in_recursion)]
fn build_or_group_preview(&self, group: &OrGroup) -> String {
let mut parts: Vec<String> = Vec::new();
for cond in &group.conditions {
let op_str = match &cond.operator {
Operator::Eq => "= ?",
Operator::NotEq => "!= ?",
Operator::Gt => "> ?",
Operator::Gte => ">= ?",
Operator::Lt => "< ?",
Operator::Lte => "<= ?",
Operator::Like | Operator::NotLike => "LIKE ?",
Operator::In | Operator::NotIn => "IN (?)",
Operator::IsNull => "IS NULL",
Operator::IsNotNull => "IS NOT NULL",
Operator::Between => "BETWEEN ? AND ?",
Operator::Raw => "...",
_ => "?",
};
parts.push(format!("{} {}", cond.column, op_str));
}
for nested in &group.nested_groups {
let nested_preview = self.build_or_group_preview(nested);
if !nested_preview.is_empty() {
parts.push(format!("({})", nested_preview));
}
}
let joiner = match group.combine_with {
LogicalOp::Or => " OR ",
LogicalOp::And => " AND ",
};
parts.join(joiner)
}
pub fn cache(mut self, ttl: std::time::Duration) -> Self {
self.cache_options = Some(crate::cache::CacheOptions::new(ttl));
self
}
pub fn cache_with_key(mut self, key: &str, ttl: std::time::Duration) -> Self {
self.cache_key = Some(key.to_string());
self.cache_options = Some(crate::cache::CacheOptions::new(ttl));
self
}
pub fn cache_with_options(mut self, options: crate::cache::CacheOptions) -> Self {
self.cache_options = Some(options);
self
}
pub fn no_cache(mut self) -> Self {
self.cache_options = None;
self.cache_key = None;
self
}
fn generate_cache_key(&self) -> String {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
if let Some(ref key) = self.cache_key {
return key.clone();
}
let mut hasher = DefaultHasher::new();
M::table_name().hash(&mut hasher);
for cond in &self.conditions {
cond.column.hash(&mut hasher);
format!("{:?}", cond.operator).hash(&mut hasher);
format!("{:?}", cond.value).hash(&mut hasher);
}
for (col, ord) in &self.order_by {
col.hash(&mut hasher);
ord.as_str().hash(&mut hasher);
}
self.limit_value.hash(&mut hasher);
self.offset_value.hash(&mut hasher);
for join in &self.joins {
join.table.hash(&mut hasher);
}
for col in &self.group_by {
col.hash(&mut hasher);
}
let hash = hasher.finish();
crate::cache::QueryCache::global().generate_key(M::table_name(), hash)
}
pub async fn get(self) -> Result<Vec<M>> {
use sea_orm::sea_query::Alias;
let cache_key = if self.cache_options.is_some() {
let key = self.generate_cache_key();
if let Some(cached) = crate::cache::QueryCache::global().get::<Vec<M>>(&key) {
return Ok(cached);
}
Some(key)
} else {
None
};
let conn = crate::database::require_db()?.__internal_connection();
if !self.joins.is_empty() || !self.group_by.is_empty() {
let results = self.clone().get_with_joins().await?;
if let (Some(key), Some(options)) = (&cache_key, &self.cache_options) {
let _ = crate::cache::QueryCache::global().set(
key,
&results,
Some(options.ttl),
M::table_name(),
);
}
return Ok(results);
}
let mut select = M::Entity::find();
if !self.conditions.is_empty() || !self.or_groups.is_empty() || M::soft_delete_enabled() {
let condition = self.build_sea_condition();
select = select.filter(condition);
}
for (column, direction) in &self.order_by {
let col_expr = Expr::col(Alias::new(column));
select = match direction {
Order::Asc => select.order_by_asc(col_expr),
Order::Desc => select.order_by_desc(col_expr),
};
}
if let Some(limit) = self.limit_value {
select = select.limit(limit);
}
if let Some(offset) = self.offset_value {
select = select.offset(offset);
}
let results = select
.all(conn)
.await
.map_err(translate_error)?;
let results: Vec<M> = results.into_iter().map(M::from_sea_model).collect();
if let (Some(key), Some(options)) = (cache_key, &self.cache_options) {
let _ = crate::cache::QueryCache::global().set(
&key,
&results,
Some(options.ttl),
M::table_name(),
);
}
Ok(results)
}
async fn get_with_joins(self) -> Result<Vec<M>> {
let sql = self.build_select_sql();
let results = crate::database::Database::raw::<M>(&sql).await?;
Ok(results)
}
fn build_base_select_sql(&self) -> String {
let table = M::table_name();
let mut sql = String::new();
if !self.raw_select_expressions.is_empty() {
let mut exprs = self.raw_select_expressions.clone();
for wf in &self.window_functions {
exprs.push(wf.to_sql());
}
sql.push_str(&format!("SELECT {} ", exprs.join(", ")));
} else if let Some(ref columns) = self.select_columns {
let mut cols: Vec<String> = columns.iter()
.map(|c| {
if c.contains('.') || c.contains('(') || c.contains('*') {
c.clone()
} else {
format!("\"{}\".\"{}\"" , table, c)
}
})
.collect();
for wf in &self.window_functions {
cols.push(wf.to_sql());
}
sql.push_str(&format!("SELECT {} ", cols.join(", ")));
} else {
let mut select_parts = vec![format!("\"{}\".*", table)];
for wf in &self.window_functions {
select_parts.push(wf.to_sql());
}
sql.push_str(&format!("SELECT {} ", select_parts.join(", ")));
}
sql.push_str(&format!("FROM \"{}\" ", table));
for join in &self.joins {
let join_table = if let Some(ref alias) = join.alias {
format!("\"{}\" AS \"{}\"", join.table, alias)
} else {
format!("\"{}\"", join.table)
};
sql.push_str(&format!(
"{} {} ON {} = {} ",
join.join_type.as_sql(),
join_table,
self.format_column(&join.left_column),
self.format_column(&join.right_column)
));
}
let where_sql = self.build_where_sql();
if !where_sql.is_empty() {
sql.push_str(&format!("WHERE {} ", where_sql));
}
if !self.group_by.is_empty() {
let group_cols: Vec<String> = self.group_by.iter()
.map(|c| self.format_column(c))
.collect();
sql.push_str(&format!("GROUP BY {} ", group_cols.join(", ")));
}
if !self.having_conditions.is_empty() {
sql.push_str(&format!("HAVING {} ", self.having_conditions.join(" AND ")));
}
sql.trim().to_string()
}
fn build_select_sql(&self) -> String {
let mut sql = String::new();
if !self.ctes.is_empty() {
let has_recursive = self.ctes.iter().any(|c| c.recursive);
if has_recursive {
sql.push_str("WITH RECURSIVE ");
} else {
sql.push_str("WITH ");
}
let cte_parts: Vec<String> = self.ctes.iter()
.map(|c| c.to_sql())
.collect();
sql.push_str(&cte_parts.join(", "));
sql.push(' ');
}
sql.push_str(&self.build_base_select_sql());
for union in &self.unions {
sql.push_str(&format!(" {} {}", union.union_type.as_sql(), union.query_sql));
}
if !self.order_by.is_empty() {
let order_parts: Vec<String> = self.order_by.iter()
.map(|(col, dir)| format!("{} {}", self.format_column(col), dir.as_str()))
.collect();
sql.push_str(&format!(" ORDER BY {}", order_parts.join(", ")));
}
if let Some(limit) = self.limit_value {
sql.push_str(&format!(" LIMIT {}", limit));
}
if let Some(offset) = self.offset_value {
sql.push_str(&format!(" OFFSET {}", offset));
}
sql.trim().to_string()
}
fn format_column(&self, column: &str) -> String {
if column.contains('(') || column.contains('*') || column.contains('"') {
column.to_string()
} else if column.contains('.') {
let parts: Vec<&str> = column.split('.').collect();
if parts.len() == 2 {
format!("\"{}\".\"{}\"", parts[0], parts[1])
} else {
column.to_string()
}
} else {
format!("\"{}\"", column)
}
}
fn build_where_sql(&self) -> String {
let db_type = crate::database::try_db()
.map(|db| db.backend())
.unwrap_or(DatabaseType::Postgres);
self.build_where_sql_for_db(db_type)
}
fn build_where_sql_for_db(&self, db_type: DatabaseType) -> String {
let mut conditions = Vec::new();
for cond in &self.conditions {
let col = db_sql::format_column(db_type, &cond.column);
let expr = match &cond.operator {
Operator::Eq => {
if let ConditionValue::Single(val) = &cond.value {
format!("{} = {}", col, self.format_value(val))
} else { continue; }
}
Operator::NotEq => {
if let ConditionValue::Single(val) = &cond.value {
format!("{} != {}", col, self.format_value(val))
} else { continue; }
}
Operator::Gt => {
if let ConditionValue::Single(val) = &cond.value {
format!("{} > {}", col, self.format_value(val))
} else { continue; }
}
Operator::Gte => {
if let ConditionValue::Single(val) = &cond.value {
format!("{} >= {}", col, self.format_value(val))
} else { continue; }
}
Operator::Lt => {
if let ConditionValue::Single(val) = &cond.value {
format!("{} < {}", col, self.format_value(val))
} else { continue; }
}
Operator::Lte => {
if let ConditionValue::Single(val) = &cond.value {
format!("{} <= {}", col, self.format_value(val))
} else { continue; }
}
Operator::Like => {
if let ConditionValue::Single(serde_json::Value::String(pattern)) = &cond.value {
format!("{} LIKE '{}'", col, pattern.replace("'", "''"))
} else { continue; }
}
Operator::NotLike => {
if let ConditionValue::Single(serde_json::Value::String(pattern)) = &cond.value {
format!("{} NOT LIKE '{}'", col, pattern.replace("'", "''"))
} else { continue; }
}
Operator::In => {
if let ConditionValue::List(values) = &cond.value {
let vals: Vec<String> = values.iter().map(|v| self.format_value(v)).collect();
format!("{} IN ({})", col, vals.join(", "))
} else { continue; }
}
Operator::NotIn => {
if let ConditionValue::List(values) = &cond.value {
let vals: Vec<String> = values.iter().map(|v| self.format_value(v)).collect();
format!("{} NOT IN ({})", col, vals.join(", "))
} else { continue; }
}
Operator::IsNull => format!("{} IS NULL", col),
Operator::IsNotNull => format!("{} IS NOT NULL", col),
Operator::Between => {
if let ConditionValue::Range(low, high) = &cond.value {
format!("{} BETWEEN {} AND {}", col, self.format_value(low), self.format_value(high))
} else { continue; }
}
Operator::JsonContains => {
if let ConditionValue::Single(val) = &cond.value {
let value_str = val.to_string();
db_sql::json_contains(db_type, &cond.column, &value_str)
} else { continue; }
}
Operator::JsonContainedBy => {
if let ConditionValue::Single(val) = &cond.value {
let value_str = val.to_string();
db_sql::json_contained_by(db_type, &cond.column, &value_str)
} else { continue; }
}
Operator::JsonKeyExists => {
if let ConditionValue::Single(serde_json::Value::String(key)) = &cond.value {
db_sql::json_key_exists(db_type, &cond.column, key)
} else { continue; }
}
Operator::JsonKeyNotExists => {
if let ConditionValue::Single(serde_json::Value::String(key)) = &cond.value {
db_sql::json_key_not_exists(db_type, &cond.column, key)
} else { continue; }
}
Operator::JsonPathExists => {
if let ConditionValue::Single(serde_json::Value::String(path)) = &cond.value {
db_sql::json_path_exists(db_type, &cond.column, path)
} else { continue; }
}
Operator::JsonPathNotExists => {
if let ConditionValue::Single(serde_json::Value::String(path)) = &cond.value {
db_sql::json_path_not_exists(db_type, &cond.column, path)
} else { continue; }
}
Operator::ArrayContains | Operator::ArrayContainsAll => {
if let ConditionValue::List(values) = &cond.value {
let array_vals: Vec<String> = values.iter()
.map(|v| match v {
serde_json::Value::String(s) => format!("'{}'", s.replace("'", "''")),
serde_json::Value::Number(n) => n.to_string(),
serde_json::Value::Bool(b) => b.to_string(),
_ => v.to_string(),
})
.collect();
db_sql::array_contains(db_type, &cond.column, &array_vals)
} else { continue; }
}
Operator::ArrayContainedBy => {
if let ConditionValue::List(values) = &cond.value {
let array_vals: Vec<String> = values.iter()
.map(|v| match v {
serde_json::Value::String(s) => format!("'{}'", s.replace("'", "''")),
serde_json::Value::Number(n) => n.to_string(),
serde_json::Value::Bool(b) => b.to_string(),
_ => v.to_string(),
})
.collect();
db_sql::array_contained_by(db_type, &cond.column, &array_vals)
} else { continue; }
}
Operator::ArrayOverlaps | Operator::ArrayContainsAny => {
if let ConditionValue::List(values) = &cond.value {
let array_vals: Vec<String> = values.iter()
.map(|v| match v {
serde_json::Value::String(s) => format!("'{}'", s.replace("'", "''")),
serde_json::Value::Number(n) => n.to_string(),
serde_json::Value::Bool(b) => b.to_string(),
_ => v.to_string(),
})
.collect();
db_sql::array_overlaps(db_type, &cond.column, &array_vals)
} else { continue; }
}
Operator::SubqueryIn => {
if let ConditionValue::Subquery(subquery_sql) = &cond.value {
format!("{} IN ({})", col, subquery_sql)
} else { continue; }
}
Operator::SubqueryNotIn => {
if let ConditionValue::Subquery(subquery_sql) = &cond.value {
format!("{} NOT IN ({})", col, subquery_sql)
} else { continue; }
}
Operator::Raw => {
if let ConditionValue::RawExpr(raw_sql) = &cond.value {
if cond.column.is_empty() {
raw_sql.clone()
} else {
format!("{} {}", col, raw_sql)
}
} else { continue; }
}
Operator::EqAny => {
if let ConditionValue::List(values) = &cond.value {
let array_vals: Vec<String> = values.iter()
.map(|v| match v {
serde_json::Value::String(s) => format!("'{}'", s.replace("'", "''")),
serde_json::Value::Number(n) => n.to_string(),
serde_json::Value::Bool(b) => b.to_string(),
serde_json::Value::Null => "NULL".to_string(),
_ => v.to_string(),
})
.collect();
db_sql::eq_any(db_type, &col, &array_vals)
} else { continue; }
}
Operator::NeAll => {
if let ConditionValue::List(values) = &cond.value {
let array_vals: Vec<String> = values.iter()
.map(|v| match v {
serde_json::Value::String(s) => format!("'{}'", s.replace("'", "''")),
serde_json::Value::Number(n) => n.to_string(),
serde_json::Value::Bool(b) => b.to_string(),
serde_json::Value::Null => "NULL".to_string(),
_ => v.to_string(),
})
.collect();
db_sql::ne_all(db_type, &col, &array_vals)
} else { continue; }
}
};
conditions.push(expr);
}
if M::soft_delete_enabled() {
let deleted_col = db_sql::quote_ident(db_type, "deleted_at");
if self.only_trashed {
conditions.push(format!("{} IS NOT NULL", deleted_col));
} else if !self.include_trashed {
conditions.push(format!("{} IS NULL", deleted_col));
}
}
conditions.join(" AND ")
}
fn format_value(&self, value: &serde_json::Value) -> String {
match value {
serde_json::Value::Null => "NULL".to_string(),
serde_json::Value::Bool(b) => b.to_string(),
serde_json::Value::Number(n) => n.to_string(),
serde_json::Value::String(s) => format!("'{}'", s.replace("'", "''")),
serde_json::Value::Array(_) | serde_json::Value::Object(_) => {
format!("'{}'", value.to_string().replace("'", "''"))
}
}
}
#[allow(dead_code)]
fn format_array_literal(&self, values: &[serde_json::Value]) -> String {
let db_type = crate::database::try_db()
.map(|db| db.backend())
.unwrap_or(DatabaseType::Postgres);
db_sql::format_array_literal(db_type, values)
}
pub async fn first(self) -> Result<Option<M>> {
use sea_orm::sea_query::Alias;
let conn = crate::database::require_db()?.__internal_connection();
if !self.joins.is_empty() || !self.group_by.is_empty() {
let results = self.limit(1).get_with_joins().await?;
return Ok(results.into_iter().next());
}
let mut select = M::Entity::find();
if !self.conditions.is_empty() || !self.or_groups.is_empty() || M::soft_delete_enabled() {
let condition = self.build_sea_condition();
select = select.filter(condition);
}
for (column, direction) in &self.order_by {
let col_expr = Expr::col(Alias::new(column));
select = match direction {
Order::Asc => select.order_by_asc(col_expr),
Order::Desc => select.order_by_desc(col_expr),
};
}
select = select.limit(1);
if let Some(offset) = self.offset_value {
select = select.offset(offset);
}
let result = select
.one(conn)
.await
.map_err(translate_error)?;
Ok(result.map(M::from_sea_model))
}
pub async fn first_or_fail(self) -> Result<M> {
self.first().await?.ok_or_else(|| {
crate::error::Error::not_found(format!(
"No {} found matching query",
M::table_name()
))
})
}
pub async fn count(self) -> Result<u64> {
#[derive(Debug, FromQueryResult)]
struct CountResult {
count: i64,
}
let conn = crate::database::require_db()?.__internal_connection();
let mut select = M::Entity::find();
if !self.conditions.is_empty() || !self.or_groups.is_empty() || M::soft_delete_enabled() {
let condition = self.build_sea_condition();
select = select.filter(condition);
}
let result: Option<CountResult> = select
.select_only()
.column_as(Expr::col(Asterisk).count(), "count")
.into_model::<CountResult>()
.one(conn)
.await
.map_err(translate_error)?;
Ok(result.map(|r| r.count as u64).unwrap_or(0))
}
pub async fn exists(self) -> Result<bool> {
Ok(self.count().await? > 0)
}
pub async fn delete(self) -> Result<u64> {
let conn = crate::database::require_db()?.__internal_connection();
let mut delete = M::Entity::delete_many();
if !self.conditions.is_empty() || !self.or_groups.is_empty() || M::soft_delete_enabled() {
let condition = self.build_sea_condition();
delete = delete.filter(condition);
}
let result = delete
.exec(conn)
.await
.map_err(translate_error)?;
Ok(result.rows_affected)
}
pub async fn soft_delete(self) -> Result<u64> {
if !M::soft_delete_enabled() {
return Err(Error::invalid_query(
"soft_delete() can only be used on models with soft delete enabled".to_string()
));
}
let table = M::table_name();
let where_sql = self.build_where_sql();
let sql = if where_sql.is_empty() {
format!(
"UPDATE \"{}\" SET \"deleted_at\" = NOW()",
table
)
} else {
format!(
"UPDATE \"{}\" SET \"deleted_at\" = NOW() WHERE {}",
table, where_sql
)
};
let conn = crate::database::require_db()?.__internal_connection();
let stmt = Statement::from_string(
DbBackend::Postgres,
sql,
);
let result = conn
.execute_raw(stmt)
.await
.map_err(translate_error)?;
Ok(result.rows_affected())
}
pub async fn restore(self) -> Result<u64> {
if !M::soft_delete_enabled() {
return Err(Error::invalid_query(
"restore() can only be used on models with soft delete enabled".to_string()
));
}
let table = M::table_name();
let where_sql = self.build_where_sql();
let sql = if where_sql.is_empty() {
format!(
"UPDATE \"{}\" SET \"deleted_at\" = NULL WHERE \"deleted_at\" IS NOT NULL",
table
)
} else {
format!(
"UPDATE \"{}\" SET \"deleted_at\" = NULL WHERE {} AND \"deleted_at\" IS NOT NULL",
table, where_sql
)
};
let conn = crate::database::require_db()?.__internal_connection();
let stmt = Statement::from_string(
DbBackend::Postgres,
sql,
);
let result = conn
.execute_raw(stmt)
.await
.map_err(translate_error)?;
Ok(result.rows_affected())
}
pub async fn force_delete(self) -> Result<u64> {
let table = M::table_name();
let where_sql = self.build_where_sql();
let sql = if where_sql.is_empty() {
format!("DELETE FROM \"{}\"", table)
} else {
format!("DELETE FROM \"{}\" WHERE {}", table, where_sql)
};
let conn = crate::database::require_db()?.__internal_connection();
let stmt = Statement::from_string(
DbBackend::Postgres,
sql,
);
let result = conn
.execute_raw(stmt)
.await
.map_err(translate_error)?;
Ok(result.rows_affected())
}
pub async fn get_json(self) -> Result<Vec<serde_json::Value>> {
let sql = self.build_select_sql();
crate::database::Database::raw_json(&sql).await
}
}
impl<M: Model> Default for QueryBuilder<M> {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::db_sql;
use super::{
Order, UnionType, UnionClause, FrameBound, FrameType,
WindowFunction, WindowFunctionType, CTE,
};
use crate::config::DatabaseType;
#[test]
fn test_quote_char() {
assert_eq!(db_sql::quote_char(DatabaseType::Postgres), '"');
assert_eq!(db_sql::quote_char(DatabaseType::MySQL), '`');
assert_eq!(db_sql::quote_char(DatabaseType::MariaDB), '`');
assert_eq!(db_sql::quote_char(DatabaseType::SQLite), '"');
}
#[test]
fn test_quote_ident() {
assert_eq!(db_sql::quote_ident(DatabaseType::Postgres, "column"), "\"column\"");
assert_eq!(db_sql::quote_ident(DatabaseType::MySQL, "column"), "`column`");
assert_eq!(db_sql::quote_ident(DatabaseType::MariaDB, "column"), "`column`");
assert_eq!(db_sql::quote_ident(DatabaseType::SQLite, "column"), "\"column\"");
}
#[test]
fn test_json_contains_postgres() {
let sql = db_sql::json_contains(DatabaseType::Postgres, "metadata", r#"{"key": "value"}"#);
assert!(sql.contains("@>"));
assert!(sql.contains("\"metadata\""));
}
#[test]
fn test_json_contains_mysql() {
let sql = db_sql::json_contains(DatabaseType::MySQL, "metadata", r#"{"key": "value"}"#);
assert!(sql.contains("JSON_CONTAINS"));
assert!(sql.contains("`metadata`"));
let sql = db_sql::json_contains(DatabaseType::MariaDB, "metadata", r#"{"key": "value"}"#);
assert!(sql.contains("JSON_CONTAINS"));
assert!(sql.contains("`metadata`"));
}
#[test]
fn test_json_contains_sqlite() {
let sql = db_sql::json_contains(DatabaseType::SQLite, "metadata", "test_value");
assert!(sql.contains("json_each"));
assert!(sql.contains("\"metadata\""));
}
#[test]
fn test_json_key_exists_postgres() {
let sql = db_sql::json_key_exists(DatabaseType::Postgres, "data", "email");
assert_eq!(sql, "\"data\" ? 'email'");
}
#[test]
fn test_json_key_exists_mysql() {
let sql = db_sql::json_key_exists(DatabaseType::MySQL, "data", "email");
assert!(sql.contains("JSON_CONTAINS_PATH"));
assert!(sql.contains("$.email"));
let sql = db_sql::json_key_exists(DatabaseType::MariaDB, "data", "email");
assert!(sql.contains("JSON_CONTAINS_PATH"));
assert!(sql.contains("$.email"));
}
#[test]
fn test_json_key_exists_sqlite() {
let sql = db_sql::json_key_exists(DatabaseType::SQLite, "data", "email");
assert!(sql.contains("json_extract"));
assert!(sql.contains("$.email"));
assert!(sql.contains("IS NOT NULL"));
}
#[test]
fn test_json_path_exists_postgres() {
let sql = db_sql::json_path_exists(DatabaseType::Postgres, "data", "$.user.name");
assert!(sql.contains("@?"));
}
#[test]
fn test_json_path_exists_mysql() {
let sql = db_sql::json_path_exists(DatabaseType::MySQL, "data", "$.user.name");
assert!(sql.contains("JSON_CONTAINS_PATH"));
let sql = db_sql::json_path_exists(DatabaseType::MariaDB, "data", "$.user.name");
assert!(sql.contains("JSON_CONTAINS_PATH"));
}
#[test]
fn test_json_path_exists_sqlite() {
let sql = db_sql::json_path_exists(DatabaseType::SQLite, "data", "$.user.name");
assert!(sql.contains("json_extract"));
}
#[test]
fn test_array_contains_postgres() {
let values = vec!["'admin'".to_string(), "'user'".to_string()];
let sql = db_sql::array_contains(DatabaseType::Postgres, "roles", &values);
assert!(sql.contains("@>"));
assert!(sql.contains("ARRAY["));
}
#[test]
fn test_array_contains_mysql() {
let values = vec!["'admin'".to_string(), "'user'".to_string()];
let sql = db_sql::array_contains(DatabaseType::MySQL, "roles", &values);
assert!(sql.contains("JSON_CONTAINS"));
let sql = db_sql::array_contains(DatabaseType::MariaDB, "roles", &values);
assert!(sql.contains("JSON_CONTAINS"));
}
#[test]
fn test_array_contains_sqlite() {
let values = vec!["'admin'".to_string(), "'user'".to_string()];
let sql = db_sql::array_contains(DatabaseType::SQLite, "roles", &values);
assert!(sql.contains("json_each"));
}
#[test]
fn test_array_overlaps_postgres() {
let values = vec!["'a'".to_string(), "'b'".to_string()];
let sql = db_sql::array_overlaps(DatabaseType::Postgres, "tags", &values);
assert!(sql.contains("&&"));
assert!(sql.contains("ARRAY["));
}
#[test]
fn test_array_overlaps_mysql() {
let values = vec!["'a'".to_string(), "'b'".to_string()];
let sql = db_sql::array_overlaps(DatabaseType::MySQL, "tags", &values);
assert!(sql.contains(" OR "));
let sql = db_sql::array_overlaps(DatabaseType::MariaDB, "tags", &values);
assert!(sql.contains(" OR "));
}
#[test]
fn test_array_overlaps_sqlite() {
let values = vec!["'a'".to_string(), "'b'".to_string()];
let sql = db_sql::array_overlaps(DatabaseType::SQLite, "tags", &values);
assert!(sql.contains(" OR "));
}
#[test]
fn test_format_column_simple() {
assert_eq!(
db_sql::format_column(DatabaseType::Postgres, "name"),
"\"name\""
);
assert_eq!(
db_sql::format_column(DatabaseType::MySQL, "name"),
"`name`"
);
assert_eq!(
db_sql::format_column(DatabaseType::MariaDB, "name"),
"`name`"
);
}
#[test]
fn test_format_column_dotted() {
assert_eq!(
db_sql::format_column(DatabaseType::Postgres, "users.name"),
"\"users\".\"name\""
);
assert_eq!(
db_sql::format_column(DatabaseType::MySQL, "users.name"),
"`users`.`name`"
);
assert_eq!(
db_sql::format_column(DatabaseType::MariaDB, "users.name"),
"`users`.`name`"
);
}
#[test]
fn test_format_column_expression() {
assert_eq!(
db_sql::format_column(DatabaseType::Postgres, "COUNT(*)"),
"COUNT(*)"
);
}
#[test]
fn test_cast_to_float() {
assert_eq!(
db_sql::cast_to_float(DatabaseType::Postgres, "value"),
"CAST(value AS FLOAT8)"
);
assert_eq!(
db_sql::cast_to_float(DatabaseType::MySQL, "value"),
"CAST(value AS DOUBLE)"
);
assert_eq!(
db_sql::cast_to_float(DatabaseType::MariaDB, "value"),
"CAST(value AS DOUBLE)"
);
assert_eq!(
db_sql::cast_to_float(DatabaseType::SQLite, "value"),
"CAST(value AS REAL)"
);
}
#[test]
fn test_sql_injection_prevention() {
let sql = db_sql::json_contains(DatabaseType::Postgres, "data", "O'Brien");
assert!(sql.contains("O''Brien"));
let sql = db_sql::json_key_exists(DatabaseType::MySQL, "data", "key'; DROP TABLE--");
assert!(sql.contains("key''; DROP TABLE--"));
let sql = db_sql::json_key_exists(DatabaseType::MariaDB, "data", "key'; DROP TABLE--");
assert!(sql.contains("key''; DROP TABLE--"));
}
#[test]
fn test_union_type_sql() {
assert_eq!(UnionType::Union.as_sql(), "UNION");
assert_eq!(UnionType::UnionAll.as_sql(), "UNION ALL");
}
#[test]
fn test_union_clause_creation() {
let clause = UnionClause {
union_type: UnionType::Union,
query_sql: "SELECT * FROM users WHERE active = true".to_string(),
};
assert_eq!(clause.union_type, UnionType::Union);
assert!(clause.query_sql.contains("active = true"));
}
#[test]
fn test_frame_bound_sql() {
assert_eq!(FrameBound::UnboundedPreceding.as_sql(), "UNBOUNDED PRECEDING");
assert_eq!(FrameBound::UnboundedFollowing.as_sql(), "UNBOUNDED FOLLOWING");
assert_eq!(FrameBound::CurrentRow.as_sql(), "CURRENT ROW");
assert_eq!(FrameBound::Preceding(5).as_sql(), "5 PRECEDING");
assert_eq!(FrameBound::Following(3).as_sql(), "3 FOLLOWING");
}
#[test]
fn test_frame_type_sql() {
assert_eq!(FrameType::Rows.as_sql(), "ROWS");
assert_eq!(FrameType::Range.as_sql(), "RANGE");
assert_eq!(FrameType::Groups.as_sql(), "GROUPS");
}
#[test]
fn test_window_function_type_row_number() {
let wft = WindowFunctionType::RowNumber;
assert_eq!(wft.as_sql(), "ROW_NUMBER()");
}
#[test]
fn test_window_function_type_rank() {
let wft = WindowFunctionType::Rank;
assert_eq!(wft.as_sql(), "RANK()");
}
#[test]
fn test_window_function_type_dense_rank() {
let wft = WindowFunctionType::DenseRank;
assert_eq!(wft.as_sql(), "DENSE_RANK()");
}
#[test]
fn test_window_function_type_ntile() {
let wft = WindowFunctionType::Ntile(4);
assert_eq!(wft.as_sql(), "NTILE(4)");
}
#[test]
fn test_window_function_type_lag() {
let wft = WindowFunctionType::Lag("price".to_string(), Some(1), Some("0".to_string()));
let sql = wft.as_sql();
assert!(sql.contains("LAG"));
assert!(sql.contains("\"price\""));
assert!(sql.contains("1"));
}
#[test]
fn test_window_function_type_lead() {
let wft = WindowFunctionType::Lead("date".to_string(), Some(1), None);
let sql = wft.as_sql();
assert!(sql.contains("LEAD"));
assert!(sql.contains("\"date\""));
}
#[test]
fn test_window_function_type_first_value() {
let wft = WindowFunctionType::FirstValue("amount".to_string());
assert_eq!(wft.as_sql(), "FIRST_VALUE(\"amount\")");
}
#[test]
fn test_window_function_type_last_value() {
let wft = WindowFunctionType::LastValue("total".to_string());
assert_eq!(wft.as_sql(), "LAST_VALUE(\"total\")");
}
#[test]
fn test_window_function_type_sum() {
let wft = WindowFunctionType::Sum("amount".to_string());
assert_eq!(wft.as_sql(), "SUM(\"amount\")");
}
#[test]
fn test_window_function_type_count() {
let wft1 = WindowFunctionType::Count(None);
assert_eq!(wft1.as_sql(), "COUNT(*)");
let wft2 = WindowFunctionType::Count(Some("id".to_string()));
assert_eq!(wft2.as_sql(), "COUNT(\"id\")");
}
#[test]
fn test_window_function_basic() {
let wf = WindowFunction::new(WindowFunctionType::RowNumber, "row_num");
let sql = wf.to_sql();
assert!(sql.contains("ROW_NUMBER()"));
assert!(sql.contains("OVER"));
assert!(sql.contains("AS \"row_num\""));
}
#[test]
fn test_window_function_with_partition() {
let wf = WindowFunction::new(WindowFunctionType::RowNumber, "row_num")
.partition_by("category");
let sql = wf.to_sql();
assert!(sql.contains("PARTITION BY \"category\""));
}
#[test]
fn test_window_function_with_order() {
let wf = WindowFunction::new(WindowFunctionType::Rank, "rank")
.order_by("score", Order::Desc);
let sql = wf.to_sql();
assert!(sql.contains("ORDER BY \"score\" DESC"));
}
#[test]
fn test_window_function_with_frame() {
let wf = WindowFunction::new(WindowFunctionType::Sum("amount".to_string()), "running_total")
.order_by("date", Order::Asc)
.frame(FrameType::Rows, FrameBound::UnboundedPreceding, FrameBound::CurrentRow);
let sql = wf.to_sql();
assert!(sql.contains("ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW"));
}
#[test]
fn test_window_function_full() {
let wf = WindowFunction::new(WindowFunctionType::Sum("sales".to_string()), "total_sales")
.partition_by("region")
.order_by("month", Order::Asc)
.frame(FrameType::Range, FrameBound::UnboundedPreceding, FrameBound::CurrentRow);
let sql = wf.to_sql();
assert!(sql.contains("SUM(\"sales\")"));
assert!(sql.contains("PARTITION BY \"region\""));
assert!(sql.contains("ORDER BY \"month\" ASC"));
assert!(sql.contains("RANGE BETWEEN"));
assert!(sql.contains("AS \"total_sales\""));
}
#[test]
fn test_cte_basic() {
let cte = CTE::new("active_users", "SELECT * FROM users WHERE active = true".to_string());
let sql = cte.to_sql();
assert!(sql.contains("\"active_users\""));
assert!(sql.contains("AS ("));
assert!(sql.contains("active = true"));
}
#[test]
fn test_cte_with_columns() {
let cte = CTE::with_columns(
"user_stats",
vec!["user_id", "total", "count"],
"SELECT user_id, SUM(amount), COUNT(*) FROM orders GROUP BY user_id".to_string()
);
let sql = cte.to_sql();
assert!(sql.contains("\"user_stats\""));
assert!(sql.contains("(\"user_id\", \"total\", \"count\")"));
assert!(sql.contains("GROUP BY"));
}
#[test]
fn test_cte_recursive() {
let cte = CTE::new(
"tree",
"SELECT 1 UNION ALL SELECT 2".to_string()
).recursive();
assert!(cte.recursive);
}
#[test]
fn test_cte_name_quoting() {
let cte = CTE::new("my_cte", "SELECT 1".to_string());
let sql = cte.to_sql();
assert!(sql.starts_with("\"my_cte\""));
}
}