use serde::{Deserialize, Serialize};
use std::fmt;
use std::marker::PhantomData;
use crate::orm::query::{Filter, FilterOperator, FilterValue, quote_identifier};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct F {
pub field: String,
}
impl F {
pub fn new(field: impl Into<String>) -> Self {
Self {
field: field.into(),
}
}
pub fn to_sql(&self) -> String {
quote_identifier(&self.field)
}
}
impl fmt::Display for F {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.field)
}
}
#[derive(Debug, Clone, Copy)]
pub struct FieldRef<M, T> {
name: &'static str,
_phantom: PhantomData<(M, T)>,
}
impl<M, T> FieldRef<M, T> {
pub const fn new(name: &'static str) -> Self {
Self {
name,
_phantom: PhantomData,
}
}
pub const fn name(&self) -> &'static str {
self.name
}
pub fn to_sql(&self) -> String {
quote_identifier(self.name)
}
pub fn eq<V: Into<FilterValue>>(&self, value: V) -> Filter {
Filter::new(self.name.to_string(), FilterOperator::Eq, value.into())
}
pub fn ne<V: Into<FilterValue>>(&self, value: V) -> Filter {
Filter::new(self.name.to_string(), FilterOperator::Ne, value.into())
}
pub fn gt<V: Into<FilterValue>>(&self, value: V) -> Filter {
Filter::new(self.name.to_string(), FilterOperator::Gt, value.into())
}
pub fn gte<V: Into<FilterValue>>(&self, value: V) -> Filter {
Filter::new(self.name.to_string(), FilterOperator::Gte, value.into())
}
pub fn lt<V: Into<FilterValue>>(&self, value: V) -> Filter {
Filter::new(self.name.to_string(), FilterOperator::Lt, value.into())
}
pub fn lte<V: Into<FilterValue>>(&self, value: V) -> Filter {
Filter::new(self.name.to_string(), FilterOperator::Lte, value.into())
}
pub fn eq_field<T2>(&self, other: FieldRef<M, T2>) -> Filter {
Filter::new(
self.name.to_string(),
FilterOperator::Eq,
FilterValue::FieldRef(F::new(other.name)),
)
}
pub fn ne_field<T2>(&self, other: FieldRef<M, T2>) -> Filter {
Filter::new(
self.name.to_string(),
FilterOperator::Ne,
FilterValue::FieldRef(F::new(other.name)),
)
}
pub fn gt_field<T2>(&self, other: FieldRef<M, T2>) -> Filter {
Filter::new(
self.name.to_string(),
FilterOperator::Gt,
FilterValue::FieldRef(F::new(other.name)),
)
}
pub fn gte_field<T2>(&self, other: FieldRef<M, T2>) -> Filter {
Filter::new(
self.name.to_string(),
FilterOperator::Gte,
FilterValue::FieldRef(F::new(other.name)),
)
}
pub fn lt_field<T2>(&self, other: FieldRef<M, T2>) -> Filter {
Filter::new(
self.name.to_string(),
FilterOperator::Lt,
FilterValue::FieldRef(F::new(other.name)),
)
}
pub fn lte_field<T2>(&self, other: FieldRef<M, T2>) -> Filter {
Filter::new(
self.name.to_string(),
FilterOperator::Lte,
FilterValue::FieldRef(F::new(other.name)),
)
}
}
impl<M, T> fmt::Display for FieldRef<M, T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.name)
}
}
impl<M, T> From<FieldRef<M, T>> for String {
fn from(field_ref: FieldRef<M, T>) -> Self {
field_ref.name.to_string()
}
}
impl<M, T> From<FieldRef<M, T>> for F {
fn from(field_ref: FieldRef<M, T>) -> Self {
F::new(field_ref.name)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OuterRef {
pub field: String,
}
impl OuterRef {
pub fn new(field: impl Into<String>) -> Self {
Self {
field: field.into(),
}
}
pub fn to_sql(&self) -> String {
self.field.clone()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Subquery {
pub sql: String,
pub template: String,
}
impl Subquery {
pub fn new(sql: impl Into<String>) -> Self {
Self {
sql: sql.into(),
template: "(%(subquery)s)".to_string(),
}
}
pub fn with_template(mut self, template: impl Into<String>) -> Self {
self.template = template.into();
self
}
pub fn to_sql(&self) -> String {
self.template.replace("%(subquery)s", &self.sql)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Exists {
pub subquery: Subquery,
}
impl Exists {
pub fn new(sql: impl Into<String>) -> Self {
Self {
subquery: Subquery {
sql: sql.into(),
template: "%(subquery)s".to_string(),
},
}
}
pub fn to_sql(&self) -> String {
format!("EXISTS({})", self.subquery.to_sql())
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Value {
pub value: ValueType,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ValueType {
String(String),
Integer(i64),
Float(f64),
Bool(bool),
Null,
}
impl Value {
pub fn new<T: Into<ValueType>>(value: T) -> Self {
Self {
value: value.into(),
}
}
pub fn string(s: impl Into<String>) -> Self {
Self {
value: ValueType::String(s.into()),
}
}
pub fn int(i: i64) -> Self {
Self {
value: ValueType::Integer(i),
}
}
pub fn float(f: f64) -> Self {
Self {
value: ValueType::Float(f),
}
}
pub fn bool(b: bool) -> Self {
Self {
value: ValueType::Bool(b),
}
}
pub fn null() -> Self {
Self {
value: ValueType::Null,
}
}
pub fn to_sql(&self) -> String {
match &self.value {
ValueType::String(s) => format!("'{}'", s.replace('\'', "''")),
ValueType::Integer(i) => i.to_string(),
ValueType::Float(f) => f.to_string(),
ValueType::Bool(b) => if *b { "TRUE" } else { "FALSE" }.to_string(),
ValueType::Null => "NULL".to_string(),
}
}
}
impl From<String> for ValueType {
fn from(s: String) -> Self {
ValueType::String(s)
}
}
impl From<&str> for ValueType {
fn from(s: &str) -> Self {
ValueType::String(s.to_string())
}
}
impl From<i64> for ValueType {
fn from(i: i64) -> Self {
ValueType::Integer(i)
}
}
impl From<i32> for ValueType {
fn from(i: i32) -> Self {
ValueType::Integer(i as i64)
}
}
impl From<f64> for ValueType {
fn from(f: f64) -> Self {
ValueType::Float(f)
}
}
impl From<bool> for ValueType {
fn from(b: bool) -> Self {
ValueType::Bool(b)
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum QOperator {
And,
Or,
Not,
}
impl fmt::Display for QOperator {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
QOperator::And => write!(f, "AND"),
QOperator::Or => write!(f, "OR"),
QOperator::Not => write!(f, "NOT"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Q {
Condition {
field: String,
operator: String,
value: String,
},
Combined {
operator: QOperator,
conditions: Vec<Q>,
},
}
impl Q {
pub fn new(
field: impl Into<String>,
operator: impl Into<String>,
value: impl Into<String>,
) -> Self {
Self::Condition {
field: field.into(),
operator: operator.into(),
value: value.into(),
}
}
pub fn from_sql(sql: &str) -> Self {
super::sql_condition_parser::SqlConditionParser::parse(sql)
}
pub fn empty() -> Self {
Self::Combined {
operator: QOperator::And,
conditions: vec![],
}
}
pub fn and(self, other: Q) -> Self {
match self {
Q::Combined {
operator: QOperator::And,
mut conditions,
} => {
conditions.push(other);
Q::Combined {
operator: QOperator::And,
conditions,
}
}
_ => Q::Combined {
operator: QOperator::And,
conditions: vec![self, other],
},
}
}
pub fn or(self, other: Q) -> Self {
match self {
Q::Combined {
operator: QOperator::Or,
mut conditions,
} => {
conditions.push(other);
Q::Combined {
operator: QOperator::Or,
conditions,
}
}
_ => Q::Combined {
operator: QOperator::Or,
conditions: vec![self, other],
},
}
}
#[allow(clippy::should_implement_trait)]
pub fn not(self) -> Self {
Q::Combined {
operator: QOperator::Not,
conditions: vec![self],
}
}
pub fn to_sql(&self) -> String {
match self {
Q::Condition {
field,
operator,
value,
} => {
if field.is_empty() && operator.is_empty() {
return value.clone();
}
let formatted_value = if value.parse::<f64>().is_ok()
|| value.to_uppercase() == "TRUE"
|| value.to_uppercase() == "FALSE"
|| value.to_uppercase() == "NULL"
|| value.starts_with("COUNT(")
|| value.starts_with("SUM(")
|| value.starts_with("AVG(")
|| value.starts_with("MAX(")
|| value.starts_with("MIN(")
|| (value.starts_with('\'') && value.ends_with('\''))
{
value.clone()
} else {
format!("'{}'", value)
};
format!("{} {} {}", field, operator, formatted_value)
}
Q::Combined {
operator,
conditions,
} => {
let sql_conditions: Vec<String> = conditions.iter().map(|q| q.to_sql()).collect();
match operator {
QOperator::Not => {
if conditions.len() == 1 {
format!("NOT ({})", sql_conditions[0])
} else {
format!("NOT ({})", sql_conditions.join(" AND "))
}
}
QOperator::And => {
if sql_conditions.len() == 1 {
sql_conditions[0].clone()
} else {
format!("({})", sql_conditions.join(" AND "))
}
}
QOperator::Or => {
if sql_conditions.len() == 1 {
sql_conditions[0].clone()
} else {
format!("({})", sql_conditions.join(" OR "))
}
}
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[allow(dead_code)]
struct TestUser {
id: i64,
name: String,
}
impl TestUser {
const fn field_id() -> FieldRef<TestUser, i64> {
FieldRef::new("id")
}
const fn field_name() -> FieldRef<TestUser, String> {
FieldRef::new("name")
}
}
#[test]
fn test_field_ref_basic() {
let id_ref = TestUser::field_id();
assert_eq!(id_ref.name(), "id");
assert_eq!(id_ref.to_sql(), "\"id\"");
assert_eq!(format!("{}", id_ref), "id");
}
#[test]
fn test_field_ref_string_field() {
let name_ref = TestUser::field_name();
assert_eq!(name_ref.name(), "name");
assert_eq!(name_ref.to_sql(), "\"name\"");
}
#[test]
fn test_field_ref_to_f_conversion() {
let id_ref = TestUser::field_id();
let f: F = id_ref.into();
assert_eq!(f.to_sql(), "\"id\"");
}
#[test]
fn test_expressions_f_unit() {
let f = F::new("price");
assert_eq!(f.to_sql(), "\"price\"");
assert_eq!(format!("{}", f), "price");
}
#[test]
fn test_q_simple_condition() {
let q = Q::new("age", ">=", "18");
assert_eq!(q.to_sql(), "age >= 18");
}
#[test]
fn test_q_and_operator() {
let q1 = Q::new("age", ">=", "18");
let q2 = Q::new("country", "=", "US");
let q = q1.and(q2);
let sql = q.to_sql();
assert_eq!(
sql, "(age >= 18 AND country = 'US')",
"Expected exact AND query structure, got: {}",
sql
);
}
#[test]
fn test_q_or_operator() {
let q1 = Q::new("status", "=", "active");
let q2 = Q::new("status", "=", "pending");
let q = q1.or(q2);
let sql = q.to_sql();
assert_eq!(
sql, "(status = 'active' OR status = 'pending')",
"Expected exact OR query structure, got: {}",
sql
);
}
#[test]
fn test_q_not_operator() {
let q = Q::new("deleted", "=", "1").not();
assert_eq!(q.to_sql(), "NOT (deleted = 1)");
}
#[test]
fn test_q_complex_query() {
let q1 = Q::new("age", ">=", "18");
let q2 = Q::new("country", "=", "US");
let q3 = Q::new("status", "=", "premium");
let q = q1.and(q2).or(q3);
let sql = q.to_sql();
assert_eq!(
sql, "((age >= 18 AND country = 'US') OR status = 'premium')",
"Expected exact complex query structure, got: {}",
sql
);
}
#[test]
fn test_q_chained_and() {
let q1 = Q::new("a", "=", "1");
let q2 = Q::new("b", "=", "2");
let q3 = Q::new("c", "=", "3");
let q = q1.and(q2).and(q3);
let sql = q.to_sql();
assert_eq!(
sql, "(a = 1 AND b = 2 AND c = 3)",
"Expected exact chained AND query structure, got: {}",
sql
);
}
#[test]
fn test_q_chained_or() {
let q1 = Q::new("x", "=", "1");
let q2 = Q::new("y", "=", "2");
let q3 = Q::new("z", "=", "3");
let q = q1.or(q2).or(q3);
let sql = q.to_sql();
assert_eq!(
sql, "(x = 1 OR y = 2 OR z = 3)",
"Expected exact chained OR query structure, got: {}",
sql
);
}
#[test]
fn test_outer_ref() {
let outer_ref = OuterRef::new("parent_id");
assert_eq!(outer_ref.to_sql(), "parent_id");
}
#[test]
fn test_subquery() {
let subquery = Subquery::new("SELECT id FROM users WHERE active = 1");
let sql = subquery.to_sql();
assert_eq!(
sql, "(SELECT id FROM users WHERE active = 1)",
"Expected exact subquery SQL with parentheses, got: {}",
sql
);
}
#[test]
fn test_subquery_custom_template() {
let subquery =
Subquery::new("SELECT COUNT(*) FROM orders").with_template("COUNT = %(subquery)s");
let sql = subquery.to_sql();
assert_eq!(sql, "COUNT = SELECT COUNT(*) FROM orders");
}
#[test]
fn test_expressions_exists() {
let exists = Exists::new("SELECT 1 FROM orders WHERE user_id = 123");
let sql = exists.to_sql();
assert_eq!(
sql, "EXISTS(SELECT 1 FROM orders WHERE user_id = 123)",
"Expected exact EXISTS SQL structure, got: {}",
sql
);
}
#[test]
fn test_field_ref_to_f_direct_conversion() {
let id_field = TestUser::field_id();
let f: F = id_field.into();
assert_eq!(f.to_sql(), "\"id\"");
assert_eq!(format!("{}", f), "id");
}
#[test]
fn test_field_ref_string_field_to_f() {
let name_field = TestUser::field_name();
let f: F = name_field.into();
assert_eq!(f.to_sql(), "\"name\"");
assert_eq!(format!("{}", f), "name");
}
#[test]
fn test_multiple_field_refs_to_f() {
let id_f: F = TestUser::field_id().into();
let name_f: F = TestUser::field_name().into();
assert_eq!(id_f.to_sql(), "\"id\"");
assert_eq!(name_f.to_sql(), "\"name\"");
assert_ne!(id_f.to_sql(), name_f.to_sql());
}
#[test]
fn test_field_ref_preserves_field_name_in_f() {
let id_field = TestUser::field_id();
let original_name = id_field.name();
let f: F = id_field.into();
assert_eq!(f.to_sql(), quote_identifier(original_name));
}
#[test]
fn test_field_ref_const_to_f_conversion() {
const ID_FIELD: FieldRef<TestUser, i64> = FieldRef::new("id");
let f: F = ID_FIELD.into();
assert_eq!(f.to_sql(), "\"id\"");
}
}
#[cfg(test)]
mod expressions_extended_tests {
use super::*;
use crate::orm::aggregation::*;
use crate::orm::annotation::Value;
use crate::orm::expressions::{F, Q};
#[test]
fn test_values_expression_group_by() {
let val = Value::String("test_group".to_string());
assert_eq!(val.to_sql(), "'test_group'");
}
#[test]
fn test_values_expression_group_by_1() {
let val = Value::Int(42);
assert_eq!(val.to_sql(), "42");
}
#[test]
fn test_aggregate_rawsql_annotation() {
let agg = Aggregate::sum("amount").with_alias("total_amount");
assert_eq!(agg.to_sql(), "SUM(amount) AS total_amount");
}
#[test]
fn test_aggregate_rawsql_annotation_1() {
let agg = Aggregate::max("price").with_alias("max_price");
assert_eq!(agg.to_sql(), "MAX(price) AS max_price");
}
#[test]
fn test_aggregate_subquery_annotation() {
let subquery = Subquery::new("SELECT COUNT(*) FROM orders WHERE status = 'completed'");
let sql = subquery.to_sql();
assert_eq!(
sql, "(SELECT COUNT(*) FROM orders WHERE status = 'completed')",
"Expected exact subquery with aggregate, got: {}",
sql
);
}
#[test]
fn test_aggregate_subquery_annotation_1() {
let subquery = Subquery::new("SELECT AVG(price) FROM products");
let sql = subquery.to_sql();
assert_eq!(
sql, "(SELECT AVG(price) FROM products)",
"Expected exact subquery with AVG aggregate, got: {}",
sql
);
}
#[test]
fn test_aggregates() {
let agg = Aggregate::avg("score");
assert_eq!(agg.to_sql(), "AVG(score)");
}
#[test]
fn test_aggregates_1() {
let agg = Aggregate::min("age");
assert_eq!(agg.to_sql(), "MIN(age)");
}
#[test]
fn test_annotate_by_empty_custom_exists() {
let exists = Exists::new("");
let sql = exists.to_sql();
assert_eq!(sql, "EXISTS()");
}
#[test]
fn test_annotate_by_empty_custom_exists_1() {
let exists = Exists::new("SELECT 1");
let sql = exists.to_sql();
assert_eq!(sql, "EXISTS(SELECT 1)");
}
#[test]
fn test_annotate_values_aggregate() {
let agg = Aggregate::count_all().with_alias("total");
assert_eq!(agg.to_sql(), "COUNT(*) AS total");
}
#[test]
fn test_annotate_values_aggregate_1() {
let agg = Aggregate::sum("quantity").with_alias("total_qty");
assert_eq!(agg.to_sql(), "SUM(quantity) AS total_qty");
}
#[test]
fn test_annotate_values_count() {
let agg = Aggregate::count(Some("id")).with_alias("total");
assert_eq!(agg.to_sql(), "COUNT(id) AS total");
}
#[test]
fn test_annotate_values_count_1() {
let agg = Aggregate::count(Some("id")).with_alias("total");
assert_eq!(agg.to_sql(), "COUNT(id) AS total");
}
#[test]
fn test_annotate_values_filter() {
let q = Q::new("status", "=", "active");
assert_eq!(
q.to_sql(),
"status = 'active'",
"Expected exact Q condition SQL, got: {}",
q.to_sql()
);
}
#[test]
fn test_annotate_values_filter_1() {
let q = Q::new("status", "=", "active");
assert_eq!(
q.to_sql(),
"status = 'active'",
"Expected exact Q condition SQL, got: {}",
q.to_sql()
);
}
#[test]
fn test_annotation_with_deeply_nested_outerref() {
let outer_ref = OuterRef::new("parent.grandparent.id");
assert_eq!(outer_ref.to_sql(), "parent.grandparent.id");
}
#[test]
fn test_annotation_with_deeply_nested_outerref_1() {
let outer_ref = OuterRef::new("root.level1.level2.field");
assert_eq!(outer_ref.to_sql(), "root.level1.level2.field");
}
#[test]
fn test_annotation_with_nested_outerref() {
let outer_ref = OuterRef::new("parent.user_id");
assert_eq!(outer_ref.to_sql(), "parent.user_id");
}
#[test]
fn test_annotation_with_nested_outerref_1() {
let outer_ref = OuterRef::new("outer.category_id");
assert_eq!(outer_ref.to_sql(), "outer.category_id");
}
#[test]
fn test_annotation_with_outerref() {
let outer_ref = OuterRef::new("user_id");
assert_eq!(outer_ref.to_sql(), "user_id");
}
#[test]
fn test_annotation_with_outerref_1() {
let outer_ref = OuterRef::new("category_id");
assert_eq!(outer_ref.to_sql(), "category_id");
}
#[test]
fn test_annotation_with_outerref_and_output_field() {
let outer_ref = OuterRef::new("price");
let f = F::new("product_price");
assert_eq!(outer_ref.to_sql(), "price");
assert_eq!(f.to_sql(), "\"product_price\"");
}
#[test]
fn test_annotation_with_outerref_and_output_field_1() {
let outer_ref = OuterRef::new("amount");
assert_eq!(outer_ref.to_sql(), "amount");
}
#[test]
fn test_annotations_within_subquery() {
let subquery = Subquery::new("SELECT id, COUNT(*) as total FROM items GROUP BY id");
assert_eq!(
subquery.to_sql(),
"(SELECT id, COUNT(*) as total FROM items GROUP BY id)",
"Expected exact subquery with annotations, got: {}",
subquery.to_sql()
);
}
#[test]
fn test_annotations_within_subquery_1() {
let subquery =
Subquery::new("SELECT user_id, SUM(amount) as total FROM orders GROUP BY user_id");
assert_eq!(
subquery.to_sql(),
"(SELECT user_id, SUM(amount) as total FROM orders GROUP BY user_id)",
"Expected exact subquery with SUM aggregate, got: {}",
subquery.to_sql()
);
}
#[test]
fn test_case_in_filter_if_boolean_output_field() {
let q = Q::new("status", "=", "active");
assert_eq!(
q.to_sql(),
"status = 'active'",
"Expected exact Q condition SQL, got: {}",
q.to_sql()
);
}
#[test]
fn test_case_in_filter_if_boolean_output_field_1() {
let q = Q::new("status", "=", "active");
assert_eq!(
q.to_sql(),
"status = 'active'",
"Expected exact Q condition SQL, got: {}",
q.to_sql()
);
}
#[test]
fn test_date_subquery_subtraction() {
let subquery = Subquery::new("SELECT date1 - date2 FROM events");
assert_eq!(
subquery.to_sql(),
"(SELECT date1 - date2 FROM events)",
"Expected exact subquery with date subtraction, got: {}",
subquery.to_sql()
);
}
#[test]
fn test_date_subquery_subtraction_1() {
let subquery = Subquery::new("SELECT end_date - start_date FROM projects");
assert_eq!(
subquery.to_sql(),
"(SELECT end_date - start_date FROM projects)",
"Expected exact subquery with date subtraction, got: {}",
subquery.to_sql()
);
}
#[test]
fn test_datetime_and_duration_field_addition_with_annotate_and_no_output_field() {
let f = F::new("created_at + INTERVAL 7 DAY");
assert_eq!(f.to_sql(), "\"created_at + INTERVAL 7 DAY\"");
}
#[test]
fn test_datetime_and_duration_field_addition_with_annotate_and_no_output_field_1() {
let f = F::new("start_time + duration");
assert_eq!(f.to_sql(), "\"start_time + duration\"");
}
#[test]
fn test_datetime_and_durationfield_addition_with_filter() {
let q = Q::new("status", "=", "active");
assert_eq!(
q.to_sql(),
"status = 'active'",
"Expected exact Q condition SQL, got: {}",
q.to_sql()
);
}
#[test]
fn test_datetime_and_durationfield_addition_with_filter_1() {
let q = Q::new("status", "=", "active");
assert_eq!(
q.to_sql(),
"status = 'active'",
"Expected exact Q condition SQL, got: {}",
q.to_sql()
);
}
#[test]
fn test_datetime_subquery_subtraction() {
let subquery = Subquery::new("SELECT updated_at - created_at FROM records");
assert_eq!(
subquery.to_sql(),
"(SELECT updated_at - created_at FROM records)",
"Expected exact subquery with datetime subtraction, got: {}",
subquery.to_sql()
);
}
#[test]
fn test_datetime_subquery_subtraction_1() {
let subquery = Subquery::new("SELECT NOW() - last_login FROM users");
assert_eq!(
subquery.to_sql(),
"(SELECT NOW() - last_login FROM users)",
"Expected exact subquery with NOW() function, got: {}",
subquery.to_sql()
);
}
#[test]
fn test_datetime_subtraction_with_annotate_and_no_output_field() {
let f = F::new("end_time - start_time");
assert_eq!(f.to_sql(), "\"end_time - start_time\"");
}
#[test]
fn test_datetime_subtraction_with_annotate_and_no_output_field_1() {
let f = F::new("checkout_time - checkin_time");
assert_eq!(f.to_sql(), "\"checkout_time - checkin_time\"");
}
#[test]
fn test_distinct_aggregates() {
let agg = Aggregate::count_distinct("user_id");
assert_eq!(agg.to_sql(), "COUNT(DISTINCT user_id)");
}
#[test]
fn test_distinct_aggregates_1() {
let agg = Aggregate::count_distinct("email");
assert_eq!(agg.to_sql(), "COUNT(DISTINCT email)");
}
#[test]
fn test_empty_group_by() {
let agg = Aggregate::count_all();
assert_eq!(agg.to_sql(), "COUNT(*)");
}
#[test]
fn test_empty_group_by_1() {
let agg = Aggregate::sum("total");
assert_eq!(agg.to_sql(), "SUM(total)");
}
#[test]
fn test_exists_in_filter() {
let q = Q::new("status", "=", "active");
assert_eq!(
q.to_sql(),
"status = 'active'",
"Expected exact Q condition SQL, got: {}",
q.to_sql()
);
}
#[test]
fn test_exists_in_filter_1() {
let q = Q::new("status", "=", "active");
assert_eq!(
q.to_sql(),
"status = 'active'",
"Expected exact Q condition SQL, got: {}",
q.to_sql()
);
}
#[test]
fn test_expressions_range_lookups_join_choice() {
let q1 = Q::new("price", ">=", "10");
let q2 = Q::new("price", "<=", "100");
let q = q1.and(q2);
let sql = q.to_sql();
assert_eq!(
sql, "(price >= 10 AND price <= 100)",
"Expected exact range query with AND, got: {}",
sql
);
}
#[test]
fn test_expressions_range_lookups_join_choice_1() {
let q1 = Q::new("age", ">", "18");
let q2 = Q::new("age", "<", "65");
let q = q1.and(q2);
let sql = q.to_sql();
assert_eq!(
sql, "(age > 18 AND age < 65)",
"Expected exact age range query, got: {}",
sql
);
}
#[test]
fn test_filter() {
let q = Q::new("status", "=", "active");
assert_eq!(
q.to_sql(),
"status = 'active'",
"Expected exact Q condition SQL, got: {}",
q.to_sql()
);
}
#[test]
fn test_filter_1() {
let q = Q::new("status", "=", "active");
assert_eq!(
q.to_sql(),
"status = 'active'",
"Expected exact Q condition SQL, got: {}",
q.to_sql()
);
}
#[test]
fn test_filter_by_empty_exists() {
let q = Q::new("status", "=", "active");
assert_eq!(
q.to_sql(),
"status = 'active'",
"Expected exact Q condition SQL, got: {}",
q.to_sql()
);
}
#[test]
fn test_filter_by_empty_exists_1() {
let q = Q::new("status", "=", "active");
assert_eq!(
q.to_sql(),
"status = 'active'",
"Expected exact Q condition SQL, got: {}",
q.to_sql()
);
}
#[test]
fn test_filter_decimal_expression() {
let q = Q::new("status", "=", "active");
assert_eq!(
q.to_sql(),
"status = 'active'",
"Expected exact Q condition SQL, got: {}",
q.to_sql()
);
}
#[test]
fn test_filter_decimal_expression_1() {
let q = Q::new("status", "=", "active");
assert_eq!(
q.to_sql(),
"status = 'active'",
"Expected exact Q condition SQL, got: {}",
q.to_sql()
);
}
#[test]
fn test_filter_inter_attribute() {
let q = Q::new("status", "=", "active");
assert_eq!(
q.to_sql(),
"status = 'active'",
"Expected exact Q condition SQL, got: {}",
q.to_sql()
);
}
#[test]
fn test_filter_inter_attribute_1() {
let q = Q::new("status", "=", "active");
assert_eq!(
q.to_sql(),
"status = 'active'",
"Expected exact Q condition SQL, got: {}",
q.to_sql()
);
}
#[test]
fn test_filter_not_equals_other_field() {
let q = Q::new("status", "=", "active");
assert_eq!(
q.to_sql(),
"status = 'active'",
"Expected exact Q condition SQL, got: {}",
q.to_sql()
);
}
#[test]
fn test_filter_not_equals_other_field_1() {
let q = Q::new("status", "=", "active");
assert_eq!(
q.to_sql(),
"status = 'active'",
"Expected exact Q condition SQL, got: {}",
q.to_sql()
);
}
#[test]
fn test_filter_with_join() {
let q = Q::new("status", "=", "active");
assert_eq!(
q.to_sql(),
"status = 'active'",
"Expected exact Q condition SQL, got: {}",
q.to_sql()
);
}
#[test]
fn test_filter_with_join_1() {
let q = Q::new("status", "=", "active");
assert_eq!(
q.to_sql(),
"status = 'active'",
"Expected exact Q condition SQL, got: {}",
q.to_sql()
);
}
#[test]
fn test_filtered_aggregates() {
let q = Q::new("status", "=", "active");
assert_eq!(
q.to_sql(),
"status = 'active'",
"Expected exact Q condition SQL, got: {}",
q.to_sql()
);
}
#[test]
fn test_filtered_aggregates_1() {
let q = Q::new("status", "=", "active");
assert_eq!(
q.to_sql(),
"status = 'active'",
"Expected exact Q condition SQL, got: {}",
q.to_sql()
);
}
#[test]
fn test_filtering_on_annotate_that_uses_q() {
let q = Q::new("status", "=", "active");
assert_eq!(
q.to_sql(),
"status = 'active'",
"Expected exact Q condition SQL, got: {}",
q.to_sql()
);
}
#[test]
fn test_filtering_on_annotate_that_uses_q_1() {
let q = Q::new("status", "=", "active");
assert_eq!(
q.to_sql(),
"status = 'active'",
"Expected exact Q condition SQL, got: {}",
q.to_sql()
);
}
#[test]
fn test_filtering_on_q_that_is_boolean() {
let q = Q::new("status", "=", "active");
assert_eq!(
q.to_sql(),
"status = 'active'",
"Expected exact Q condition SQL, got: {}",
q.to_sql()
);
}
#[test]
fn test_filtering_on_q_that_is_boolean_1() {
let q = Q::new("status", "=", "active");
assert_eq!(
q.to_sql(),
"status = 'active'",
"Expected exact Q condition SQL, got: {}",
q.to_sql()
);
}
#[test]
fn test_filtering_on_rawsql_that_is_boolean() {
let q = Q::new("status", "=", "active");
assert_eq!(
q.to_sql(),
"status = 'active'",
"Expected exact Q condition SQL, got: {}",
q.to_sql()
);
}
#[test]
fn test_filtering_on_rawsql_that_is_boolean_1() {
let q = Q::new("status", "=", "active");
assert_eq!(
q.to_sql(),
"status = 'active'",
"Expected exact Q condition SQL, got: {}",
q.to_sql()
);
}
#[test]
fn test_in_lookup_allows_f_expressions_and_expressions_for_integers() {
let f = F::new("category_id");
assert_eq!(f.to_sql(), "\"category_id\"");
}
#[test]
fn test_in_lookup_allows_f_expressions_and_expressions_for_integers_1() {
let q = Q::new("id", "IN", "1,2,3,4,5");
assert_eq!(
q.to_sql(),
"id IN '1,2,3,4,5'",
"Expected exact IN query, got: {}",
q.to_sql()
);
}
#[test]
fn test_in_subquery() {
let subquery = Subquery::new("SELECT id FROM active_users");
assert_eq!(
subquery.to_sql(),
"(SELECT id FROM active_users)",
"Expected exact subquery for IN clause, got: {}",
subquery.to_sql()
);
}
#[test]
fn test_in_subquery_1() {
let subquery = Subquery::new("SELECT category_id FROM featured_categories");
assert_eq!(
subquery.to_sql(),
"(SELECT category_id FROM featured_categories)",
"Expected exact subquery for featured categories, got: {}",
subquery.to_sql()
);
}
#[test]
fn test_incorrect_field_in_f_expression() {
let f = F::new("nonexistent_field");
assert_eq!(f.to_sql(), "\"nonexistent_field\"");
}
#[test]
fn test_incorrect_field_in_f_expression_1() {
let f = F::new("invalid__field__name");
assert_eq!(f.to_sql(), "\"invalid__field__name\"");
}
#[test]
fn test_incorrect_joined_field_in_f_expression() {
let f = F::new("related__invalid_field");
assert_eq!(f.to_sql(), "\"related__invalid_field\"");
}
#[test]
fn test_incorrect_joined_field_in_f_expression_1() {
let f = F::new("user__profile__missing");
assert_eq!(f.to_sql(), "\"user__profile__missing\"");
}
#[test]
fn test_lookups_subquery() {
let subquery = Subquery::new("SELECT MAX(price) FROM products WHERE available = 1");
assert_eq!(
subquery.to_sql(),
"(SELECT MAX(price) FROM products WHERE available = 1)",
"Expected exact subquery with MAX aggregate, got: {}",
subquery.to_sql()
);
}
#[test]
fn test_lookups_subquery_1() {
let subquery = Subquery::new("SELECT MIN(created_at) FROM events");
assert_eq!(
subquery.to_sql(),
"(SELECT MIN(created_at) FROM events)",
"Expected exact subquery with MIN aggregate, got: {}",
subquery.to_sql()
);
}
#[test]
fn test_mixed_char_date_with_annotate() {
let f1 = F::new("name");
let f2 = F::new("created_date");
assert_eq!(f1.to_sql(), "\"name\"");
assert_eq!(f2.to_sql(), "\"created_date\"");
}
#[test]
fn test_mixed_char_date_with_annotate_1() {
let val_str = Value::String("test".to_string());
let f_date = F::new("birth_date");
assert_eq!(val_str.to_sql(), "'test'");
assert_eq!(f_date.to_sql(), "\"birth_date\"");
}
#[test]
fn test_negated_empty_exists() {
let exists = Exists::new("");
let q = Q::new("NOT", "", exists.to_sql());
assert_eq!(
q.to_sql(),
"NOT 'EXISTS()'",
"Expected exact negated EXISTS SQL, got: {}",
q.to_sql()
);
}
#[test]
fn test_negated_empty_exists_1() {
let q = Q::new("id", "NOT IN", "SELECT id FROM deleted");
assert_eq!(
q.to_sql(),
"id NOT IN 'SELECT id FROM deleted'",
"Expected exact NOT IN query, got: {}",
q.to_sql()
);
}
#[test]
fn test_nested_subquery() {
let inner = Subquery::new("SELECT id FROM users WHERE active = 1");
let outer = Subquery::new(format!(
"SELECT * FROM orders WHERE user_id IN {}",
inner.to_sql()
));
assert_eq!(
outer.to_sql(),
"(SELECT * FROM orders WHERE user_id IN (SELECT id FROM users WHERE active = 1))",
"Expected exact nested subquery, got: {}",
outer.to_sql()
);
}
#[test]
fn test_nested_subquery_1() {
let subquery = Subquery::new(
"SELECT category_id FROM (SELECT * FROM products WHERE price > 100) AS expensive",
);
assert_eq!(
subquery.to_sql(),
"(SELECT category_id FROM (SELECT * FROM products WHERE price > 100) AS expensive)",
"Expected exact nested subquery with alias, got: {}",
subquery.to_sql()
);
}
#[test]
fn test_nested_subquery_join_outer_ref() {
let outer_ref = OuterRef::new("parent.id");
let subquery = Subquery::new(format!(
"SELECT COUNT(*) FROM children WHERE parent_id = {}",
outer_ref.to_sql()
));
assert_eq!(
subquery.to_sql(),
"(SELECT COUNT(*) FROM children WHERE parent_id = parent.id)",
"Expected exact subquery with OuterRef, got: {}",
subquery.to_sql()
);
}
#[test]
fn test_nested_subquery_join_outer_ref_1() {
let outer_ref = OuterRef::new("order.user_id");
assert_eq!(outer_ref.to_sql(), "order.user_id");
}
#[test]
fn test_nested_subquery_outer_ref_2() {
let outer_ref = OuterRef::new("main.category_id");
assert_eq!(outer_ref.to_sql(), "main.category_id");
}
#[test]
fn test_nested_subquery_outer_ref_2_1() {
let outer_ref = OuterRef::new("outer_table.field");
assert_eq!(outer_ref.to_sql(), "outer_table.field");
}
#[test]
fn test_nested_subquery_outer_ref_with_autofield() {
let outer_ref = OuterRef::new("id");
assert_eq!(outer_ref.to_sql(), "id");
}
#[test]
fn test_nested_subquery_outer_ref_with_autofield_1() {
let outer_ref = OuterRef::new("pk");
assert_eq!(outer_ref.to_sql(), "pk");
}
#[test]
fn test_non_empty_group_by() {
let f = F::new("category");
let agg = Aggregate::count(Some("id"));
assert_eq!(f.to_sql(), "\"category\"");
assert_eq!(agg.to_sql(), "COUNT(id)");
}
#[test]
fn test_non_empty_group_by_1() {
let f1 = F::new("year");
let f2 = F::new("month");
assert_eq!(f1.to_sql(), "\"year\"");
assert_eq!(f2.to_sql(), "\"month\"");
}
#[test]
fn test_object_create_with_aggregate() {
let agg = Aggregate::max("score");
assert_eq!(agg.to_sql(), "MAX(score)");
}
#[test]
fn test_object_create_with_aggregate_1() {
let agg = Aggregate::avg("rating");
assert_eq!(agg.to_sql(), "AVG(rating)");
}
#[test]
fn test_object_create_with_f_expression_in_subquery() {
let f = F::new("price");
let subquery = Subquery::new(format!("SELECT {} FROM products", f.to_sql()));
assert_eq!(
subquery.to_sql(),
"(SELECT \"price\" FROM products)",
"Expected exact subquery with F expression, got: {}",
subquery.to_sql()
);
}
#[test]
fn test_object_create_with_f_expression_in_subquery_1() {
let f = F::new("quantity");
assert_eq!(f.to_sql(), "\"quantity\"");
}
#[test]
fn test_order_by_exists() {
let exists = Exists::new("SELECT 1 FROM related WHERE related.parent_id = main.id");
assert_eq!(
exists.to_sql(),
"EXISTS(SELECT 1 FROM related WHERE related.parent_id = main.id)",
"Expected exact EXISTS with related join, got: {}",
exists.to_sql()
);
}
#[test]
fn test_order_by_exists_1() {
let exists = Exists::new("SELECT 1 FROM tags WHERE tags.item_id = items.id");
assert_eq!(
exists.to_sql(),
"EXISTS(SELECT 1 FROM tags WHERE tags.item_id = items.id)",
"Expected exact EXISTS with correlation, got: {}",
exists.to_sql()
);
}
#[test]
fn test_order_by_multiline_sql() {
let subquery = Subquery::new(
"SELECT id
FROM users
WHERE active = 1",
);
assert_eq!(
subquery.to_sql(),
"(SELECT id\nFROM users\nWHERE active = 1)",
"Expected exact multiline subquery, got: {}",
subquery.to_sql()
);
}
#[test]
fn test_order_by_multiline_sql_1() {
let subquery = Subquery::new(
"SELECT COUNT(*)
FROM orders
GROUP BY user_id",
);
assert_eq!(
subquery.to_sql(),
"(SELECT COUNT(*)\nFROM orders\nGROUP BY user_id)",
"Expected exact multiline subquery with GROUP BY, got: {}",
subquery.to_sql()
);
}
#[test]
fn test_order_of_operations() {
let q1 = Q::new("a", "=", "1");
let q2 = Q::new("b", "=", "2");
let q3 = Q::new("c", "=", "3");
let q = q1.and(q2).or(q3);
let sql = q.to_sql();
assert_eq!(
sql, "((a = 1 AND b = 2) OR c = 3)",
"Expected exact order of operations with AND/OR, got: {}",
sql
);
}
#[test]
fn test_order_of_operations_1() {
let q1 = Q::new("x", "=", "1");
let q2 = Q::new("y", "=", "2");
let q = q1.or(q2).not();
assert_eq!(
q.to_sql(),
"NOT ((x = 1 OR y = 2))",
"Expected exact NOT with OR operation, got: {}",
q.to_sql()
);
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct When {
pub condition: Q,
then: Box<Expression>,
}
impl When {
pub fn new(condition: Q, then: Expression) -> Self {
Self {
condition,
then: Box::new(then),
}
}
pub fn then(&self) -> &Expression {
&self.then
}
pub fn into_then(self) -> Expression {
*self.then
}
pub fn to_sql(&self) -> String {
format!(
"WHEN {} THEN {}",
self.condition.to_sql(),
self.then.to_sql()
)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Case {
pub when_clauses: Vec<When>,
default: Option<Box<Expression>>,
}
impl Case {
pub fn new() -> Self {
Self {
when_clauses: Vec::new(),
default: None,
}
}
pub fn default_value(&self) -> Option<&Expression> {
self.default.as_deref()
}
pub fn into_default(self) -> Option<Expression> {
self.default.map(|b| *b)
}
pub fn when(mut self, when: When) -> Self {
self.when_clauses.push(when);
self
}
pub fn default(mut self, default: Expression) -> Self {
self.default = Some(Box::new(default));
self
}
pub fn to_sql(&self) -> String {
let when_clauses = self
.when_clauses
.iter()
.map(|w| w.to_sql())
.collect::<Vec<_>>()
.join(" ");
let default_clause = self
.default
.as_ref()
.map(|d| format!(" ELSE {}", d.to_sql()))
.unwrap_or_default();
format!("CASE {}{} END", when_clauses, default_clause)
}
}
impl Default for Case {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Expression {
F(F),
Value(Value),
Case(Case),
}
impl Expression {
pub fn to_sql(&self) -> String {
match self {
Expression::F(f) => f.to_sql(),
Expression::Value(v) => v.to_sql(),
Expression::Case(c) => c.to_sql(),
}
}
}
impl From<F> for Expression {
fn from(f: F) -> Self {
Expression::F(f)
}
}
impl From<Value> for Expression {
fn from(v: Value) -> Self {
Expression::Value(v)
}
}
impl From<Case> for Expression {
fn from(c: Case) -> Self {
Expression::Case(c)
}
}