use std::fmt::Debug;
use crate::{Database, DatabaseError, DatabaseValue, Row};
#[cfg(any(
feature = "sqlite-rusqlite",
feature = "sqlite-sqlx",
feature = "postgres-raw",
feature = "postgres-sqlx",
feature = "mysql-sqlx",
feature = "duckdb",
feature = "turso"
))]
pub(crate) fn render_identifier(value: &str, delimiter: char) -> String {
if cfg!(feature = "raw-sql") {
value.to_owned()
} else {
let escaped = value.replace(delimiter, &format!("{delimiter}{delimiter}"));
format!("{delimiter}{escaped}{delimiter}")
}
}
#[cfg(any(
feature = "sqlite-rusqlite",
feature = "sqlite-sqlx",
feature = "postgres-sqlx",
feature = "postgres-raw",
feature = "mysql-sqlx",
feature = "duckdb",
feature = "turso"
))]
pub(crate) fn render_columns(columns: &[&str], delimiter: char) -> String {
columns
.iter()
.map(|column| {
if *column == "*" {
"*".to_owned()
} else {
render_identifier(column, delimiter)
}
})
.collect::<Vec<_>>()
.join(", ")
}
#[derive(Debug, Clone, Copy)]
pub enum SortDirection {
Asc,
Desc,
}
#[derive(Debug)]
pub struct Sort {
pub expression: Box<dyn Expression>,
pub direction: SortDirection,
}
impl Expression for Sort {
fn expression_type(&self) -> ExpressionType<'_> {
ExpressionType::Sort(self)
}
fn values(&self) -> Option<Vec<&DatabaseValue>> {
self.expression.values()
}
}
#[derive(Debug, Clone)]
pub struct Join<'a> {
pub table_name: &'a str,
pub on: JoinCondition,
pub left: bool,
}
#[cfg_attr(
not(feature = "raw-sql"),
doc = r#"
```compile_fail
use switchy_database::query::JoinCondition;
let _ = JoinCondition::Raw("1 = 1".to_owned());
```
```compile_fail
use switchy_database::query::join;
let _ = join("items", "1 = 1");
```
```compile_fail
use switchy_database::query::select;
let _ = select("items").join("other", "1 = 1");
```
```compile_fail
use switchy_database::query::left_join;
let _ = left_join("items", "1 = 1");
```
```compile_fail
use switchy_database::query::select;
let _ = select("items").left_join("other", "1 = 1");
```
"#
)]
#[derive(Debug, Clone)]
pub enum JoinCondition {
ColumnsEqual(QualifiedColumn, QualifiedColumn),
And(Vec<Self>),
#[cfg(feature = "raw-sql")]
Raw(String),
}
impl From<(QualifiedColumn, QualifiedColumn)> for JoinCondition {
fn from((first, second): (QualifiedColumn, QualifiedColumn)) -> Self {
Self::ColumnsEqual(first, second)
}
}
#[cfg(feature = "raw-sql")]
impl From<&str> for JoinCondition {
fn from(sql: &str) -> Self {
Self::Raw(sql.to_owned())
}
}
#[cfg(feature = "raw-sql")]
impl From<String> for JoinCondition {
fn from(sql: String) -> Self {
Self::Raw(sql)
}
}
impl JoinCondition {
#[cfg(any(
feature = "sqlite-rusqlite",
feature = "sqlite-sqlx",
feature = "postgres-raw",
feature = "postgres-sqlx",
feature = "mysql-sqlx",
feature = "duckdb",
feature = "turso"
))]
pub(crate) fn render(&self, delimiter: char) -> String {
match self {
Self::ColumnsEqual(left, right) => {
format!("{} = {}", left.render(delimiter), right.render(delimiter))
}
Self::And(conditions) => {
if conditions.is_empty() {
return "1 = 1".to_owned();
}
format!(
"({})",
conditions
.iter()
.map(|condition| condition.render(delimiter))
.collect::<Vec<_>>()
.join(" AND ")
)
}
#[cfg(feature = "raw-sql")]
Self::Raw(sql) => sql.clone(),
}
}
}
#[must_use]
pub const fn join_columns(
table_name: &str,
first: QualifiedColumn,
second: QualifiedColumn,
left: bool,
) -> Join<'_> {
Join {
table_name,
on: JoinCondition::ColumnsEqual(first, second),
left,
}
}
impl Join<'_> {
#[cfg(any(
feature = "sqlite-rusqlite",
feature = "sqlite-sqlx",
feature = "postgres-raw",
feature = "postgres-sqlx",
feature = "mysql-sqlx",
feature = "duckdb",
feature = "turso"
))]
pub(crate) fn render_table(&self, delimiter: char) -> String {
#[cfg(feature = "raw-sql")]
if matches!(self.on, JoinCondition::Raw(_)) {
return self.table_name.to_owned();
}
let escaped = self
.table_name
.replace(delimiter, &format!("{delimiter}{delimiter}"));
format!("{delimiter}{escaped}{delimiter}")
}
}
impl Expression for Join<'_> {
fn expression_type(&self) -> ExpressionType<'_> {
ExpressionType::Join(self)
}
}
pub enum ExpressionType<'a> {
JsonTypeIs(&'a JsonTypeIs),
StorageType(&'a StorageType),
Add(&'a Add),
ZeroBlob(&'a ZeroBlob),
StorageCast(&'a StorageCast),
#[cfg(feature = "schema")]
SchemaPredicate(&'a SchemaPredicate),
BoundedText(&'a BoundedText),
GtExpression(&'a GtExpression),
EqExpression(&'a EqExpression),
NotEqExpression(&'a NotEqExpression),
Eq(&'a Eq),
Gt(&'a Gt),
In(&'a In<'a>),
Lt(&'a Lt),
Or(&'a Or),
And(&'a And),
Gte(&'a Gte),
Lte(&'a Lte),
Join(&'a Join<'a>),
Sort(&'a Sort),
NotIn(&'a NotIn<'a>),
NotEq(&'a NotEq),
NotLike(&'a NotLike),
InList(&'a InList),
#[cfg(feature = "raw-sql")]
Literal(&'a Literal),
ByteLength(&'a ByteLength),
Min(&'a Min),
Max(&'a Max),
Count(&'a Count),
Coalesce(&'a Coalesce),
QualifiedColumn(&'a QualifiedColumn),
Identifier(&'a Identifier),
SelectQuery(&'a SelectQuery<'a>),
DatabaseValue(&'a DatabaseValue),
}
pub trait Expression: Send + Sync + Debug {
fn expression_type(&self) -> ExpressionType<'_>;
fn params(&self) -> Option<Vec<&DatabaseValue>> {
self.values().map(|x| {
x.into_iter()
.filter(|value| {
!value.is_null()
&& !matches!(value, DatabaseValue::Now | DatabaseValue::NowPlus(_))
})
.collect::<Vec<_>>()
})
}
fn values(&self) -> Option<Vec<&DatabaseValue>> {
None
}
fn is_null(&self) -> bool {
false
}
}
#[cfg(feature = "raw-sql")]
#[derive(Debug)]
pub struct Literal {
pub value: String,
}
#[cfg(feature = "raw-sql")]
impl From<&str> for Literal {
fn from(val: &str) -> Self {
Self {
value: val.to_string(),
}
}
}
#[cfg(feature = "raw-sql")]
impl From<&String> for Literal {
fn from(val: &String) -> Self {
Self { value: val.clone() }
}
}
#[cfg(feature = "raw-sql")]
impl From<String> for Literal {
fn from(val: String) -> Self {
Self { value: val }
}
}
#[cfg(feature = "raw-sql")]
impl From<Literal> for Box<dyn Expression> {
fn from(val: Literal) -> Self {
Box::new(val)
}
}
#[cfg(feature = "raw-sql")]
impl Expression for Literal {
fn expression_type(&self) -> ExpressionType<'_> {
ExpressionType::Literal(self)
}
}
#[must_use]
#[cfg(feature = "raw-sql")]
pub fn literal(value: &str) -> Literal {
Literal {
value: value.to_string(),
}
}
#[derive(Debug)]
pub struct Min {
pub expression: Box<dyn Expression>,
}
impl Expression for Min {
fn expression_type(&self) -> ExpressionType<'_> {
ExpressionType::Min(self)
}
fn values(&self) -> Option<Vec<&DatabaseValue>> {
self.expression.values()
}
}
impl From<Min> for Box<dyn Expression> {
fn from(value: Min) -> Self {
Box::new(value)
}
}
#[must_use]
pub fn min(expression: impl Into<Box<dyn Expression>>) -> Min {
Min {
expression: expression.into(),
}
}
#[derive(Debug)]
pub struct Max {
pub expression: Box<dyn Expression>,
}
impl Expression for Max {
fn expression_type(&self) -> ExpressionType<'_> {
ExpressionType::Max(self)
}
fn values(&self) -> Option<Vec<&DatabaseValue>> {
self.expression.values()
}
}
impl From<Max> for Box<dyn Expression> {
fn from(value: Max) -> Self {
Box::new(value)
}
}
#[must_use]
pub fn max(expression: impl Into<Box<dyn Expression>>) -> Max {
Max {
expression: expression.into(),
}
}
#[derive(Debug)]
pub struct Count {
pub expression: Option<Box<dyn Expression>>,
}
impl Expression for Count {
fn expression_type(&self) -> ExpressionType<'_> {
ExpressionType::Count(self)
}
fn values(&self) -> Option<Vec<&DatabaseValue>> {
self.expression.as_ref().and_then(|value| value.values())
}
}
impl From<Count> for Box<dyn Expression> {
fn from(value: Count) -> Self {
Box::new(value)
}
}
#[must_use]
pub fn count(expression: impl Into<Box<dyn Expression>>) -> Count {
Count {
expression: Some(expression.into()),
}
}
#[must_use]
pub const fn count_all() -> Count {
Count { expression: None }
}
#[derive(Debug)]
pub struct ByteLength {
pub expression: Box<dyn Expression>,
}
impl Expression for ByteLength {
fn expression_type(&self) -> ExpressionType<'_> {
ExpressionType::ByteLength(self)
}
fn values(&self) -> Option<Vec<&DatabaseValue>> {
self.expression.values()
}
}
impl From<ByteLength> for Box<dyn Expression> {
fn from(value: ByteLength) -> Self {
Box::new(value)
}
}
#[must_use]
pub fn byte_length(expression: impl Into<Box<dyn Expression>>) -> ByteLength {
ByteLength {
expression: expression.into(),
}
}
#[derive(Debug, Clone)]
pub struct QualifiedColumn {
pub table: String,
pub column: String,
}
impl Expression for QualifiedColumn {
fn expression_type(&self) -> ExpressionType<'_> {
ExpressionType::QualifiedColumn(self)
}
}
impl From<QualifiedColumn> for Box<dyn Expression> {
fn from(value: QualifiedColumn) -> Self {
Box::new(value)
}
}
#[must_use]
pub fn qualified_column(table: &str, column: &str) -> QualifiedColumn {
QualifiedColumn {
table: table.to_owned(),
column: column.to_owned(),
}
}
impl QualifiedColumn {
#[cfg(any(
feature = "sqlite-rusqlite",
feature = "sqlite-sqlx",
feature = "postgres-raw",
feature = "postgres-sqlx",
feature = "mysql-sqlx",
feature = "duckdb",
feature = "turso"
))]
pub(crate) fn render(&self, delimiter: char) -> String {
let quote = |name: &str| {
let escaped = name.replace(delimiter, &format!("{delimiter}{delimiter}"));
format!("{delimiter}{escaped}{delimiter}")
};
format!("{}.{}", quote(&self.table), quote(&self.column))
}
}
#[derive(Debug)]
pub struct Identifier {
pub value: String,
}
impl From<&str> for Identifier {
fn from(val: &str) -> Self {
Self {
value: val.to_string(),
}
}
}
impl From<String> for Identifier {
fn from(val: String) -> Self {
Self { value: val }
}
}
impl From<Identifier> for Box<dyn Expression> {
fn from(val: Identifier) -> Self {
Box::new(val)
}
}
impl Expression for Identifier {
fn expression_type(&self) -> ExpressionType<'_> {
ExpressionType::Identifier(self)
}
}
#[must_use]
pub fn identifier(value: &str) -> Identifier {
Identifier {
value: value.to_string(),
}
}
impl Expression for DatabaseValue {
fn expression_type(&self) -> ExpressionType<'_> {
ExpressionType::DatabaseValue(self)
}
fn values(&self) -> Option<Vec<&DatabaseValue>> {
Some(vec![self])
}
fn is_null(&self) -> bool {
#[cfg(all(not(feature = "decimal"), not(feature = "uuid")))]
{
matches!(
self,
Self::Null
| Self::BoolOpt(None)
| Self::Real64Opt(None)
| Self::Real32Opt(None)
| Self::StringOpt(None)
| Self::Int64Opt(None)
| Self::UInt64Opt(None)
)
}
#[cfg(all(feature = "decimal", not(feature = "uuid")))]
{
matches!(
self,
Self::Null
| Self::BoolOpt(None)
| Self::Real64Opt(None)
| Self::Real32Opt(None)
| Self::StringOpt(None)
| Self::Int64Opt(None)
| Self::UInt64Opt(None)
| Self::DecimalOpt(None)
)
}
#[cfg(all(not(feature = "decimal"), feature = "uuid"))]
{
matches!(
self,
Self::Null
| Self::BoolOpt(None)
| Self::Real64Opt(None)
| Self::Real32Opt(None)
| Self::StringOpt(None)
| Self::Int64Opt(None)
| Self::UInt64Opt(None)
| Self::UuidOpt(None)
)
}
#[cfg(all(feature = "decimal", feature = "uuid"))]
{
matches!(
self,
Self::Null
| Self::BoolOpt(None)
| Self::Real64Opt(None)
| Self::Real32Opt(None)
| Self::StringOpt(None)
| Self::Int64Opt(None)
| Self::UInt64Opt(None)
| Self::DecimalOpt(None)
| Self::UuidOpt(None)
)
}
}
}
impl<T: Into<DatabaseValue>> From<T> for Box<dyn Expression> {
fn from(value: T) -> Self {
Box::new(value.into())
}
}
pub trait BooleanExpression: Expression {}
#[derive(Debug)]
pub struct And {
pub(crate) conditions: Vec<Box<dyn BooleanExpression>>,
}
impl BooleanExpression for And {}
impl Expression for And {
fn expression_type(&self) -> ExpressionType<'_> {
ExpressionType::And(self)
}
fn values(&self) -> Option<Vec<&DatabaseValue>> {
let values = self
.conditions
.iter()
.filter_map(|x| x.values())
.collect::<Vec<_>>()
.concat();
if values.is_empty() {
None
} else {
Some(values)
}
}
}
#[derive(Debug)]
pub struct Or {
pub conditions: Vec<Box<dyn BooleanExpression>>,
}
impl BooleanExpression for Or {}
impl Expression for Or {
fn expression_type(&self) -> ExpressionType<'_> {
ExpressionType::Or(self)
}
fn values(&self) -> Option<Vec<&DatabaseValue>> {
let values = self
.conditions
.iter()
.filter_map(|x| x.values())
.collect::<Vec<_>>()
.concat();
if values.is_empty() {
None
} else {
Some(values)
}
}
}
#[derive(Debug)]
pub struct NotLike {
pub left: Identifier,
pub right: DatabaseValue,
}
impl BooleanExpression for NotLike {}
impl Expression for NotLike {
fn expression_type(&self) -> ExpressionType<'_> {
ExpressionType::NotLike(self)
}
fn values(&self) -> Option<Vec<&DatabaseValue>> {
Some(vec![&self.right])
}
}
#[must_use]
pub fn where_not_like(column: impl Into<Identifier>, pattern: impl Into<String>) -> NotLike {
NotLike {
left: column.into(),
right: DatabaseValue::String(pattern.into()),
}
}
#[derive(Debug, Clone)]
pub struct JsonPath {
member: String,
}
impl JsonPath {
#[must_use]
pub fn member_name(&self) -> &str {
&self.member
}
pub fn member(member: impl Into<String>) -> Result<Self, crate::DatabaseError> {
let member = member.into();
if member.is_empty()
|| !member
.bytes()
.all(|byte| byte.is_ascii_alphanumeric() || byte == b'_')
{
return Err(crate::DatabaseError::InvalidQuery(
"Invalid JSON object path segment".into(),
));
}
Ok(Self { member })
}
#[cfg(any(
feature = "sqlite-rusqlite",
feature = "sqlite-sqlx",
feature = "postgres-raw",
feature = "postgres-sqlx",
feature = "mysql-sqlx",
feature = "duckdb",
feature = "turso"
))]
pub(crate) fn sqlite(&self) -> String {
format!("'$.{}'", self.member)
}
}
#[derive(Debug, Clone, Copy)]
pub enum JsonType {
Integer,
Real,
Text,
Null,
Object,
Array,
True,
False,
}
impl JsonType {
#[cfg(any(
feature = "sqlite-rusqlite",
feature = "sqlite-sqlx",
feature = "postgres-raw",
feature = "postgres-sqlx",
feature = "mysql-sqlx",
feature = "duckdb",
feature = "turso"
))]
pub(crate) const fn sqlite(self) -> &'static str {
match self {
Self::Integer => "integer",
Self::Real => "real",
Self::Text => "text",
Self::Null => "null",
Self::Object => "object",
Self::Array => "array",
Self::True => "true",
Self::False => "false",
}
}
}
#[derive(Debug)]
pub struct JsonTypeIs {
pub expression: Box<dyn Expression>,
pub path: JsonPath,
pub kind: JsonType,
}
impl BooleanExpression for JsonTypeIs {}
impl Expression for JsonTypeIs {
fn expression_type(&self) -> ExpressionType<'_> {
ExpressionType::JsonTypeIs(self)
}
fn values(&self) -> Option<Vec<&DatabaseValue>> {
self.expression.values()
}
}
#[must_use]
pub fn json_type_is(
expression: impl Into<Box<dyn Expression>>,
path: JsonPath,
kind: JsonType,
) -> JsonTypeIs {
JsonTypeIs {
expression: expression.into(),
path,
kind,
}
}
#[derive(Debug)]
pub struct StorageType {
pub expression: Box<dyn Expression>,
}
impl Expression for StorageType {
fn expression_type(&self) -> ExpressionType<'_> {
ExpressionType::StorageType(self)
}
fn values(&self) -> Option<Vec<&DatabaseValue>> {
self.expression.values()
}
}
impl From<StorageType> for Box<dyn Expression> {
fn from(value: StorageType) -> Self {
Box::new(value)
}
}
#[must_use]
pub fn storage_type(expression: impl Into<Box<dyn Expression>>) -> StorageType {
StorageType {
expression: expression.into(),
}
}
#[derive(Debug)]
pub struct Add {
pub left: Box<dyn Expression>,
pub right: Box<dyn Expression>,
}
impl Expression for Add {
fn expression_type(&self) -> ExpressionType<'_> {
ExpressionType::Add(self)
}
fn values(&self) -> Option<Vec<&DatabaseValue>> {
Some(
[
self.left.values().unwrap_or_default(),
self.right.values().unwrap_or_default(),
]
.concat(),
)
}
}
impl From<Add> for Box<dyn Expression> {
fn from(value: Add) -> Self {
Box::new(value)
}
}
#[must_use]
pub fn add(left: impl Into<Box<dyn Expression>>, right: impl Into<Box<dyn Expression>>) -> Add {
Add {
left: left.into(),
right: right.into(),
}
}
#[derive(Debug)]
pub struct ZeroBlob {
pub length: u32,
}
impl Expression for ZeroBlob {
fn expression_type(&self) -> ExpressionType<'_> {
ExpressionType::ZeroBlob(self)
}
}
impl From<ZeroBlob> for Box<dyn Expression> {
fn from(value: ZeroBlob) -> Self {
Box::new(value)
}
}
#[must_use]
pub const fn zero_blob(length: u32) -> ZeroBlob {
ZeroBlob { length }
}
#[derive(Debug, Clone, Copy)]
pub enum StorageCastTarget {
Blob,
Text,
}
#[derive(Debug)]
pub struct StorageCast {
pub expression: Box<dyn Expression>,
pub target: StorageCastTarget,
}
impl Expression for StorageCast {
fn expression_type(&self) -> ExpressionType<'_> {
ExpressionType::StorageCast(self)
}
fn values(&self) -> Option<Vec<&DatabaseValue>> {
self.expression.values()
}
}
impl From<StorageCast> for Box<dyn Expression> {
fn from(value: StorageCast) -> Self {
Box::new(value)
}
}
#[must_use]
pub fn cast_blob(expression: impl Into<Box<dyn Expression>>) -> StorageCast {
StorageCast {
expression: expression.into(),
target: StorageCastTarget::Blob,
}
}
#[must_use]
pub fn cast_text(expression: impl Into<Box<dyn Expression>>) -> StorageCast {
StorageCast {
expression: expression.into(),
target: StorageCastTarget::Text,
}
}
#[cfg(feature = "schema")]
#[derive(Debug)]
pub struct SchemaPredicate {
pub(crate) sqlite: String,
#[cfg(feature = "mysql-sqlx")]
pub(crate) mysql: String,
}
#[cfg(feature = "schema")]
impl SchemaPredicate {
#[must_use]
pub fn canonical_sqlite(&self) -> &str {
&self.sqlite
}
}
#[cfg(feature = "schema")]
impl BooleanExpression for SchemaPredicate {}
#[cfg(feature = "schema")]
impl Expression for SchemaPredicate {
fn expression_type(&self) -> ExpressionType<'_> {
ExpressionType::SchemaPredicate(self)
}
}
#[cfg(feature = "schema")]
pub fn schema_predicate(
predicate: &crate::schema::SchemaExpression,
) -> Result<SchemaPredicate, crate::DatabaseError> {
Ok(SchemaPredicate {
sqlite: predicate.render('"')?,
#[cfg(feature = "mysql-sqlx")]
mysql: predicate.render('`')?,
})
}
#[derive(Debug)]
pub struct BoundedText {
pub expression: Box<dyn Expression>,
pub max_bytes: u64,
pub fallback: DatabaseValue,
}
impl Expression for BoundedText {
fn expression_type(&self) -> ExpressionType<'_> {
ExpressionType::BoundedText(self)
}
fn values(&self) -> Option<Vec<&DatabaseValue>> {
let mut values = Vec::new();
for _ in 0..3 {
values.extend(self.expression.values().unwrap_or_default());
}
values.push(&self.fallback);
Some(values)
}
}
impl From<BoundedText> for Box<dyn Expression> {
fn from(value: BoundedText) -> Self {
Box::new(value)
}
}
#[must_use]
pub fn bounded_text(
expression: impl Into<Box<dyn Expression>>,
max_bytes: u64,
fallback: DatabaseValue,
) -> BoundedText {
BoundedText {
expression: expression.into(),
max_bytes,
fallback,
}
}
#[derive(Debug)]
pub struct EqExpression {
pub left: Box<dyn Expression>,
pub right: Box<dyn Expression>,
}
impl BooleanExpression for EqExpression {}
impl Expression for EqExpression {
fn expression_type(&self) -> ExpressionType<'_> {
ExpressionType::EqExpression(self)
}
fn values(&self) -> Option<Vec<&DatabaseValue>> {
Some(
[
self.left.values().unwrap_or_default(),
self.right.values().unwrap_or_default(),
]
.concat(),
)
}
}
#[derive(Debug)]
pub struct NotEqExpression {
pub left: Box<dyn Expression>,
pub right: Box<dyn Expression>,
}
impl BooleanExpression for NotEqExpression {}
impl Expression for NotEqExpression {
fn expression_type(&self) -> ExpressionType<'_> {
ExpressionType::NotEqExpression(self)
}
fn values(&self) -> Option<Vec<&DatabaseValue>> {
Some(
[
self.left.values().unwrap_or_default(),
self.right.values().unwrap_or_default(),
]
.concat(),
)
}
}
#[derive(Debug)]
pub struct GtExpression {
pub left: Box<dyn Expression>,
pub right: Box<dyn Expression>,
}
impl BooleanExpression for GtExpression {}
impl Expression for GtExpression {
fn expression_type(&self) -> ExpressionType<'_> {
ExpressionType::GtExpression(self)
}
fn values(&self) -> Option<Vec<&DatabaseValue>> {
Some(
[
self.left.values().unwrap_or_default(),
self.right.values().unwrap_or_default(),
]
.concat(),
)
}
}
#[must_use]
pub fn where_gt_expression(
left: impl Into<Box<dyn Expression>>,
right: impl Into<Box<dyn Expression>>,
) -> GtExpression {
GtExpression {
left: left.into(),
right: right.into(),
}
}
#[must_use]
pub fn where_eq_expression(
left: impl Into<Box<dyn Expression>>,
right: impl Into<Box<dyn Expression>>,
) -> EqExpression {
EqExpression {
left: left.into(),
right: right.into(),
}
}
#[must_use]
pub fn where_not_eq_expression(
left: impl Into<Box<dyn Expression>>,
right: impl Into<Box<dyn Expression>>,
) -> NotEqExpression {
NotEqExpression {
left: left.into(),
right: right.into(),
}
}
#[derive(Debug)]
pub struct NotEq {
pub left: Identifier,
pub right: Box<dyn Expression>,
}
impl BooleanExpression for NotEq {}
impl Expression for NotEq {
fn expression_type(&self) -> ExpressionType<'_> {
ExpressionType::NotEq(self)
}
fn values(&self) -> Option<Vec<&DatabaseValue>> {
self.right.values()
}
}
#[derive(Debug)]
pub struct Eq {
pub left: Identifier,
pub right: Box<dyn Expression>,
}
impl BooleanExpression for Eq {}
impl Expression for Eq {
fn expression_type(&self) -> ExpressionType<'_> {
ExpressionType::Eq(self)
}
fn values(&self) -> Option<Vec<&DatabaseValue>> {
self.right.values()
}
}
#[derive(Debug)]
pub struct Gt {
pub left: Identifier,
pub right: Box<dyn Expression>,
}
impl BooleanExpression for Gt {}
impl Expression for Gt {
fn expression_type(&self) -> ExpressionType<'_> {
ExpressionType::Gt(self)
}
fn values(&self) -> Option<Vec<&DatabaseValue>> {
self.right.values()
}
}
#[derive(Debug)]
pub struct Gte {
pub left: Identifier,
pub right: Box<dyn Expression>,
}
impl BooleanExpression for Gte {}
impl Expression for Gte {
fn expression_type(&self) -> ExpressionType<'_> {
ExpressionType::Gte(self)
}
fn values(&self) -> Option<Vec<&DatabaseValue>> {
self.right.values()
}
}
#[derive(Debug)]
pub struct Lt {
pub left: Identifier,
pub right: Box<dyn Expression>,
}
impl BooleanExpression for Lt {}
impl Expression for Lt {
fn expression_type(&self) -> ExpressionType<'_> {
ExpressionType::Lt(self)
}
fn values(&self) -> Option<Vec<&DatabaseValue>> {
self.right.values()
}
}
#[derive(Debug)]
pub struct Lte {
pub left: Identifier,
pub right: Box<dyn Expression>,
}
impl BooleanExpression for Lte {}
impl Expression for Lte {
fn expression_type(&self) -> ExpressionType<'_> {
ExpressionType::Lte(self)
}
fn values(&self) -> Option<Vec<&DatabaseValue>> {
self.right.values()
}
}
#[derive(Debug)]
pub struct In<'a> {
pub left: Identifier,
pub values: Box<dyn List + 'a>,
}
impl BooleanExpression for In<'_> {}
impl Expression for In<'_> {
fn expression_type(&self) -> ExpressionType<'_> {
ExpressionType::In(self)
}
fn values(&self) -> Option<Vec<&DatabaseValue>> {
let values = [
self.left.values().unwrap_or_default(),
self.values.values().unwrap_or_default(),
]
.concat();
if values.is_empty() {
None
} else {
Some(values)
}
}
}
#[derive(Debug)]
pub struct NotIn<'a> {
pub left: Identifier,
pub values: Box<dyn List + 'a>,
}
impl BooleanExpression for NotIn<'_> {}
impl Expression for NotIn<'_> {
fn expression_type(&self) -> ExpressionType<'_> {
ExpressionType::NotIn(self)
}
fn values(&self) -> Option<Vec<&DatabaseValue>> {
let values = [
self.left.values().unwrap_or_default(),
self.values.values().unwrap_or_default(),
]
.concat();
if values.is_empty() {
None
} else {
Some(values)
}
}
}
#[must_use]
pub fn sort<T>(expression: T, direction: SortDirection) -> Sort
where
T: Into<Box<dyn Expression>>,
{
Sort {
expression: expression.into(),
direction,
}
}
#[must_use]
pub fn where_eq<L, R>(left: L, right: R) -> Eq
where
L: Into<Identifier>,
R: Into<Box<dyn Expression>>,
{
Eq {
left: left.into(),
right: right.into(),
}
}
#[must_use]
pub fn where_not_eq<L, R>(left: L, right: R) -> NotEq
where
L: Into<Identifier>,
R: Into<Box<dyn Expression>>,
{
NotEq {
left: left.into(),
right: right.into(),
}
}
#[must_use]
pub fn where_gt<L, R>(left: L, right: R) -> Gt
where
L: Into<Identifier>,
R: Into<Box<dyn Expression>>,
{
Gt {
left: left.into(),
right: right.into(),
}
}
#[must_use]
pub fn where_gte<L, R>(left: L, right: R) -> Gte
where
L: Into<Identifier>,
R: Into<Box<dyn Expression>>,
{
Gte {
left: left.into(),
right: right.into(),
}
}
#[must_use]
pub fn where_lt<L, R>(left: L, right: R) -> Lt
where
L: Into<Identifier>,
R: Into<Box<dyn Expression>>,
{
Lt {
left: left.into(),
right: right.into(),
}
}
#[must_use]
pub fn where_lte<L, R>(left: L, right: R) -> Lte
where
L: Into<Identifier>,
R: Into<Box<dyn Expression>>,
{
Lte {
left: left.into(),
right: right.into(),
}
}
#[must_use]
pub fn where_and(conditions: Vec<Box<dyn BooleanExpression>>) -> And {
And { conditions }
}
#[must_use]
pub fn where_or(conditions: Vec<Box<dyn BooleanExpression>>) -> Or {
Or { conditions }
}
#[must_use]
pub fn join(table_name: &str, on: impl Into<JoinCondition>) -> Join<'_> {
Join {
table_name,
on: on.into(),
left: false,
}
}
#[must_use]
pub fn left_join(table_name: &str, on: impl Into<JoinCondition>) -> Join<'_> {
Join {
table_name,
on: on.into(),
left: true,
}
}
#[derive(Debug)]
pub struct Coalesce {
pub values: Vec<Box<dyn Expression>>,
}
impl List for Coalesce {}
impl Expression for Coalesce {
fn expression_type(&self) -> ExpressionType<'_> {
ExpressionType::Coalesce(self)
}
fn values(&self) -> Option<Vec<&DatabaseValue>> {
let values = self
.values
.iter()
.flat_map(|x| x.values().unwrap_or_default())
.collect::<Vec<_>>();
if values.is_empty() {
None
} else {
Some(values)
}
}
}
#[must_use]
pub fn coalesce(values: Vec<Box<dyn Expression>>) -> Coalesce {
Coalesce { values }
}
#[derive(Debug)]
pub struct InList {
pub values: Vec<Box<dyn Expression>>,
}
impl List for InList {}
impl Expression for InList {
fn expression_type(&self) -> ExpressionType<'_> {
ExpressionType::InList(self)
}
fn values(&self) -> Option<Vec<&DatabaseValue>> {
let values = self
.values
.iter()
.flat_map(|x| x.values().unwrap_or_default())
.collect::<Vec<_>>();
if values.is_empty() {
None
} else {
Some(values)
}
}
}
pub trait List: Expression {}
impl<T> From<Vec<T>> for Box<dyn List>
where
T: Into<Box<dyn Expression>> + Send + Sync,
{
fn from(val: Vec<T>) -> Self {
Box::new(InList {
values: val.into_iter().map(std::convert::Into::into).collect(),
})
}
}
#[must_use]
pub fn where_in<'a, L, V>(left: L, values: V) -> In<'a>
where
L: Into<Identifier>,
V: Into<Box<dyn List + 'a>>,
{
In {
left: left.into(),
values: values.into(),
}
}
#[must_use]
pub fn where_not_in<'a, L, V>(left: L, values: V) -> NotIn<'a>
where
L: Into<Identifier>,
V: Into<Box<dyn List + 'a>>,
{
NotIn {
left: left.into(),
values: values.into(),
}
}
#[macro_export]
macro_rules! boxed {
() => (
Vec::new()
);
($($x:expr),+ $(,)?) => (
vec![$(Box::new($x)),+]
);
}
#[allow(clippy::module_name_repetitions)]
pub trait FilterableQuery
where
Self: Sized,
{
#[must_use]
fn where_gt_expression(
self,
left: impl Into<Box<dyn Expression>>,
right: impl Into<Box<dyn Expression>>,
) -> Self {
self.filter(Box::new(where_gt_expression(left, right)))
}
#[must_use]
fn where_eq_expression(
self,
left: impl Into<Box<dyn Expression>>,
right: impl Into<Box<dyn Expression>>,
) -> Self {
self.filter(Box::new(where_eq_expression(left, right)))
}
#[must_use]
fn where_not_eq_expression(
self,
left: impl Into<Box<dyn Expression>>,
right: impl Into<Box<dyn Expression>>,
) -> Self {
self.filter(Box::new(where_not_eq_expression(left, right)))
}
#[must_use]
fn filters(self, filters: Vec<Box<dyn BooleanExpression>>) -> Self {
let mut this = self;
for filter in filters {
this = this.filter(filter);
}
this
}
#[must_use]
fn where_not_like(self, column: impl Into<Identifier>, pattern: impl Into<String>) -> Self {
self.filter(Box::new(where_not_like(column, pattern)))
}
#[must_use]
fn filter(self, filter: Box<dyn BooleanExpression>) -> Self;
#[must_use]
fn filter_if_some<T: BooleanExpression + 'static>(self, filter: Option<T>) -> Self {
if let Some(filter) = filter {
self.filter(Box::new(filter))
} else {
self
}
}
#[must_use]
fn where_in<L, V>(self, left: L, values: V) -> Self
where
L: Into<Identifier>,
V: Into<Box<dyn List>>,
{
self.filter(Box::new(where_in(left, values)))
}
#[must_use]
fn where_not_in<L, V>(self, left: L, values: V) -> Self
where
L: Into<Identifier>,
V: Into<Box<dyn List>>,
{
self.filter(Box::new(where_not_in(left, values)))
}
#[must_use]
fn where_and(self, conditions: Vec<Box<dyn BooleanExpression>>) -> Self {
self.filter(Box::new(where_and(conditions)))
}
#[must_use]
fn where_or(self, conditions: Vec<Box<dyn BooleanExpression>>) -> Self {
self.filter(Box::new(where_or(conditions)))
}
#[must_use]
fn where_eq<L, R>(self, left: L, right: R) -> Self
where
L: Into<Identifier>,
R: Into<Box<dyn Expression>>,
{
self.filter(Box::new(where_eq(left, right)))
}
#[must_use]
fn where_not_eq<L, R>(self, left: L, right: R) -> Self
where
L: Into<Identifier>,
R: Into<Box<dyn Expression>>,
{
self.filter(Box::new(where_not_eq(left, right)))
}
#[must_use]
fn where_gt<L, R>(self, left: L, right: R) -> Self
where
L: Into<Identifier>,
R: Into<Box<dyn Expression>>,
{
self.filter(Box::new(where_gt(left, right)))
}
#[must_use]
fn where_gte<L, R>(self, left: L, right: R) -> Self
where
L: Into<Identifier>,
R: Into<Box<dyn Expression>>,
{
self.filter(Box::new(where_gte(left, right)))
}
#[must_use]
fn where_lt<L, R>(self, left: L, right: R) -> Self
where
L: Into<Identifier>,
R: Into<Box<dyn Expression>>,
{
self.filter(Box::new(where_lt(left, right)))
}
#[must_use]
fn where_lte<L, R>(self, left: L, right: R) -> Self
where
L: Into<Identifier>,
R: Into<Box<dyn Expression>>,
{
self.filter(Box::new(where_lte(left, right)))
}
}
impl<'a> From<SelectQuery<'a>> for Box<dyn List + 'a> {
fn from(val: SelectQuery<'a>) -> Self {
Box::new(val)
}
}
#[derive(Debug)]
pub struct Projection {
pub expression: Box<dyn Expression>,
pub alias: Option<String>,
}
impl Projection {
#[must_use]
pub fn new(expression: impl Into<Box<dyn Expression>>) -> Self {
Self {
expression: expression.into(),
alias: None,
}
}
#[must_use]
pub fn alias(mut self, alias: impl Into<String>) -> Self {
self.alias = Some(alias.into());
self
}
}
#[allow(clippy::module_name_repetitions)]
#[derive(Debug)]
pub struct SelectQuery<'a> {
pub table_alias: Option<&'a str>,
pub indexed_by: Option<&'a str>,
pub table_name: &'a str,
pub distinct: bool,
pub columns: &'a [&'a str],
pub projections: Vec<Projection>,
pub groups: Vec<Box<dyn Expression>>,
pub filters: Option<Vec<Box<dyn BooleanExpression>>>,
pub joins: Option<Vec<Join<'a>>>,
pub sorts: Option<Vec<Sort>>,
pub limit: Option<usize>,
}
impl List for SelectQuery<'_> {}
impl Expression for SelectQuery<'_> {
fn expression_type(&self) -> ExpressionType<'_> {
ExpressionType::SelectQuery(self)
}
fn values(&self) -> Option<Vec<&DatabaseValue>> {
let joins_values = self
.joins
.as_ref()
.map(|x| {
x.iter()
.flat_map(|j| j.values().unwrap_or_default())
.collect::<Vec<_>>()
})
.unwrap_or_default();
let filters_values = self
.filters
.as_ref()
.map(|x| {
x.iter()
.flat_map(|j| j.values().unwrap_or_default())
.collect::<Vec<_>>()
})
.unwrap_or_default();
let sorts_values = self
.sorts
.as_ref()
.map(|x| {
x.iter()
.flat_map(|j| j.values().unwrap_or_default())
.collect::<Vec<_>>()
})
.unwrap_or_default();
let projections_values = self
.projections
.iter()
.flat_map(|p| p.expression.values().unwrap_or_default())
.collect::<Vec<_>>();
let groups_values = self
.groups
.iter()
.flat_map(|p| p.values().unwrap_or_default())
.collect::<Vec<_>>();
let values: Vec<_> = [
projections_values,
joins_values,
filters_values,
groups_values,
sorts_values,
]
.concat();
if values.is_empty() {
None
} else {
Some(values)
}
}
}
#[must_use]
pub fn select(table_name: &str) -> SelectQuery<'_> {
SelectQuery {
table_alias: None,
indexed_by: None,
table_name,
distinct: false,
columns: &["*"],
projections: Vec::new(),
groups: Vec::new(),
filters: None,
joins: None,
sorts: None,
limit: None,
}
}
impl FilterableQuery for SelectQuery<'_> {
fn filter(mut self, filter: Box<dyn BooleanExpression>) -> Self {
if let Some(filters) = &mut self.filters {
filters.push(filter);
} else {
self.filters.replace(vec![filter]);
}
self
}
}
impl<'a> SelectQuery<'a> {
#[must_use]
pub const fn table_alias(mut self, alias: &'a str) -> Self {
self.table_alias = Some(alias);
self
}
#[cfg(any(
feature = "sqlite-rusqlite",
feature = "sqlite-sqlx",
feature = "postgres-raw",
feature = "postgres-sqlx",
feature = "mysql-sqlx",
feature = "duckdb",
feature = "turso"
))]
pub(crate) fn render_table(&self, delimiter: char) -> String {
let table = render_identifier(self.table_name, delimiter);
self.table_alias.map_or_else(
|| table.clone(),
|alias| {
format!(
"{table} AS {delimiter}{}{delimiter}",
alias.replace(delimiter, &format!("{delimiter}{delimiter}"))
)
},
)
}
#[must_use]
pub const fn indexed_by(mut self, index: &'a str) -> Self {
self.indexed_by = Some(index);
self
}
#[must_use]
pub fn projections(mut self, projections: Vec<Projection>) -> Self {
self.projections = projections;
self
}
#[must_use]
pub fn project(mut self, expression: impl Into<Box<dyn Expression>>) -> Self {
self.projections.push(Projection::new(expression));
self
}
#[must_use]
pub fn project_as(
mut self,
expression: impl Into<Box<dyn Expression>>,
alias: impl Into<String>,
) -> Self {
self.projections
.push(Projection::new(expression).alias(alias));
self
}
#[must_use]
pub fn group_by(mut self, expression: impl Into<Box<dyn Expression>>) -> Self {
self.groups.push(expression.into());
self
}
#[must_use]
pub const fn distinct(mut self) -> Self {
self.distinct = true;
self
}
#[must_use]
pub const fn columns(mut self, columns: &'a [&'a str]) -> Self {
self.columns = columns;
self
}
#[must_use]
pub fn joins(mut self, joins: Vec<Join<'a>>) -> Self {
for join in joins {
if let Some(joins) = &mut self.joins {
joins.push(join);
} else {
self.joins.replace(vec![join]);
}
}
self
}
#[must_use]
pub fn join(mut self, table_name: &'a str, on: impl Into<JoinCondition>) -> Self {
if let Some(joins) = &mut self.joins {
joins.push(join(table_name, on));
} else {
self.joins.replace(vec![join(table_name, on)]);
}
self
}
#[must_use]
pub fn left_joins(mut self, left_joins: Vec<Join<'a>>) -> Self {
for left_join in left_joins {
if let Some(left_joins) = &mut self.joins {
left_joins.push(left_join);
} else {
self.joins.replace(vec![left_join]);
}
}
self
}
#[must_use]
pub fn left_join(mut self, table_name: &'a str, on: impl Into<JoinCondition>) -> Self {
if let Some(left_joins) = &mut self.joins {
left_joins.push(left_join(table_name, on));
} else {
self.joins.replace(vec![left_join(table_name, on)]);
}
self
}
#[must_use]
pub fn sorts(mut self, sorts: Vec<Sort>) -> Self {
for sort in sorts {
if let Some(sorts) = &mut self.sorts {
sorts.push(sort);
} else {
self.sorts.replace(vec![sort]);
}
}
self
}
#[must_use]
pub fn sort<T>(mut self, expression: T, direction: SortDirection) -> Self
where
T: Into<Identifier>,
{
if let Some(sorts) = &mut self.sorts {
sorts.push(sort(expression.into(), direction));
} else {
self.sorts.replace(vec![sort(expression.into(), direction)]);
}
self
}
#[must_use]
pub const fn limit(mut self, limit: usize) -> Self {
self.limit.replace(limit);
self
}
pub async fn execute(self, db: &dyn Database) -> Result<Vec<Row>, DatabaseError> {
db.query(&self).await
}
pub async fn execute_first(self, db: &dyn Database) -> Result<Option<Row>, DatabaseError> {
let this = if self.limit.is_none() {
self.limit(1)
} else {
self
};
db.query_first(&this).await
}
}
pub struct UpsertMultiStatement<'a> {
pub table_name: &'a str,
pub values: Vec<Vec<(&'a str, Box<dyn Expression>)>>,
pub unique: Option<Vec<Box<dyn Expression>>>,
}
#[must_use]
pub fn upsert_multi(table_name: &str) -> UpsertMultiStatement<'_> {
UpsertMultiStatement {
table_name,
values: vec![],
unique: None,
}
}
impl<'a> UpsertMultiStatement<'a> {
pub fn values<T: Into<Box<dyn Expression>>>(
&mut self,
values: Vec<Vec<(&'a str, T)>>,
) -> &mut Self {
self.values.extend(values.into_iter().map(|values| {
values
.into_iter()
.map(|(key, value)| (key, value.into()))
.collect()
}));
self
}
pub fn unique(&mut self, unique: Vec<Box<dyn Expression>>) -> &mut Self {
self.unique.replace(unique);
self
}
pub async fn execute(&self, db: &dyn Database) -> Result<Vec<Row>, DatabaseError> {
db.exec_upsert_multi(self).await
}
}
pub struct InsertStatement<'a> {
pub table_name: &'a str,
pub values: Vec<(&'a str, Box<dyn Expression>)>,
}
#[must_use]
pub fn insert(table_name: &str) -> InsertStatement<'_> {
InsertStatement {
table_name,
values: vec![],
}
}
pub struct InsertConflictStatement<'a> {
pub insert: InsertStatement<'a>,
pub columns: Vec<String>,
pub or_ignore: bool,
}
impl InsertConflictStatement<'_> {
pub async fn execute_count(&self, db: &dyn Database) -> Result<u64, DatabaseError> {
db.exec_insert_conflict_count(self).await
}
}
impl<'a> InsertStatement<'a> {
#[must_use]
pub fn on_conflict_do_nothing(
self,
columns: impl IntoIterator<Item = impl Into<String>>,
) -> InsertConflictStatement<'a> {
InsertConflictStatement {
insert: self,
columns: columns.into_iter().map(Into::into).collect(),
or_ignore: false,
}
}
#[must_use]
pub const fn or_ignore(self) -> InsertConflictStatement<'a> {
InsertConflictStatement {
insert: self,
columns: Vec::new(),
or_ignore: true,
}
}
#[must_use]
pub fn values<T: Into<Box<dyn Expression>>>(mut self, values: Vec<(&'a str, T)>) -> Self {
for value in values {
self.values.push((value.0, value.1.into()));
}
self
}
#[must_use]
pub fn value<T: Into<Box<dyn Expression>>>(mut self, name: &'a str, value: T) -> Self {
self.values.push((name, value.into()));
self
}
pub async fn execute(&self, db: &dyn Database) -> Result<Row, DatabaseError> {
db.exec_insert(self).await
}
}
pub struct UpdateStatement<'a> {
pub table_name: &'a str,
pub values: Vec<(&'a str, Box<dyn Expression>)>,
pub filters: Option<Vec<Box<dyn BooleanExpression>>>,
pub unique: Option<&'a [&'a str]>,
pub limit: Option<usize>,
}
#[must_use]
pub fn update(table_name: &str) -> UpdateStatement<'_> {
UpdateStatement {
table_name,
values: vec![],
filters: None,
unique: None,
limit: None,
}
}
impl FilterableQuery for UpdateStatement<'_> {
fn filter(mut self, filter: Box<dyn BooleanExpression>) -> Self {
if let Some(filters) = &mut self.filters {
filters.push(filter);
} else {
self.filters.replace(vec![filter]);
}
self
}
}
impl<'a> UpdateStatement<'a> {
#[must_use]
pub fn values<T: Into<Box<dyn Expression>>>(mut self, values: Vec<(&'a str, T)>) -> Self {
for value in values {
self.values.push((value.0, value.1.into()));
}
self
}
#[must_use]
pub fn value<T: Into<Box<dyn Expression>>>(mut self, name: &'a str, value: T) -> Self {
self.values.push((name, value.into()));
self
}
#[must_use]
pub const fn unique(mut self, unique: &'a [&'a str]) -> Self {
self.unique.replace(unique);
self
}
#[must_use]
pub const fn limit(mut self, limit: usize) -> Self {
self.limit.replace(limit);
self
}
pub async fn execute(&self, db: &dyn Database) -> Result<Vec<Row>, DatabaseError> {
db.exec_update(self).await
}
pub async fn execute_first(&self, db: &dyn Database) -> Result<Option<Row>, DatabaseError> {
db.exec_update_first(self).await
}
}
pub struct UpsertStatement<'a> {
pub table_name: &'a str,
pub values: Vec<(&'a str, Box<dyn Expression>)>,
pub filters: Option<Vec<Box<dyn BooleanExpression>>>,
pub unique: Option<&'a [&'a str]>,
pub limit: Option<usize>,
}
#[must_use]
pub fn upsert(table_name: &str) -> UpsertStatement<'_> {
UpsertStatement {
table_name,
values: vec![],
filters: None,
unique: None,
limit: None,
}
}
impl FilterableQuery for UpsertStatement<'_> {
fn filter(mut self, filter: Box<dyn BooleanExpression>) -> Self {
if let Some(filters) = &mut self.filters {
filters.push(filter);
} else {
self.filters.replace(vec![filter]);
}
self
}
}
impl<'a> UpsertStatement<'a> {
#[must_use]
pub fn values<T: Into<Box<dyn Expression>>>(mut self, values: Vec<(&'a str, T)>) -> Self {
for value in values {
self.values.push((value.0, value.1.into()));
}
self
}
#[must_use]
pub fn value<T: Into<Box<dyn Expression>>>(mut self, name: &'a str, value: T) -> Self {
self.values.push((name, value.into()));
self
}
#[must_use]
pub fn value_opt<T: Into<Box<dyn Expression>>>(
mut self,
name: &'a str,
value: Option<T>,
) -> Self {
if let Some(value) = value {
self.values.push((name, value.into()));
}
self
}
#[must_use]
pub const fn unique(mut self, unique: &'a [&'a str]) -> Self {
self.unique.replace(unique);
self
}
#[must_use]
pub const fn limit(mut self, limit: usize) -> Self {
self.limit.replace(limit);
self
}
pub async fn execute(self, db: &dyn Database) -> Result<Vec<Row>, DatabaseError> {
if self.values.is_empty() {
return db.query(&self.into()).await;
}
db.exec_upsert(&self).await
}
pub async fn execute_first(self, db: &dyn Database) -> Result<Row, DatabaseError> {
if self.values.is_empty() {
return db
.query_first(&self.into())
.await?
.ok_or(DatabaseError::NoRow);
}
db.exec_upsert_first(&self).await
}
}
impl<'a> From<UpsertStatement<'a>> for SelectQuery<'a> {
fn from(value: UpsertStatement<'a>) -> Self {
Self {
table_alias: None,
indexed_by: None,
table_name: value.table_name,
distinct: false,
columns: &["*"],
projections: Vec::new(),
groups: Vec::new(),
filters: value.filters,
joins: None,
sorts: None,
limit: value.limit,
}
}
}
pub struct DeleteStatement<'a> {
pub table_name: &'a str,
pub filters: Option<Vec<Box<dyn BooleanExpression>>>,
pub limit: Option<usize>,
}
#[must_use]
pub fn delete(table_name: &str) -> DeleteStatement<'_> {
DeleteStatement {
table_name,
filters: None,
limit: None,
}
}
impl FilterableQuery for DeleteStatement<'_> {
fn filter(mut self, filter: Box<dyn BooleanExpression>) -> Self {
if let Some(filters) = &mut self.filters {
filters.push(filter);
} else {
self.filters.replace(vec![filter]);
}
self
}
}
impl DeleteStatement<'_> {
#[must_use]
pub const fn limit(mut self, limit: usize) -> Self {
self.limit.replace(limit);
self
}
pub async fn execute(&self, db: &dyn Database) -> Result<Vec<Row>, DatabaseError> {
db.exec_delete(self).await
}
pub async fn execute_first(&self, db: &dyn Database) -> Result<Option<Row>, DatabaseError> {
db.exec_delete_first(self).await
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{DatabaseValue, sql_interval::SqlInterval};
mod expression_params_tests {
use super::*;
#[test_log::test]
fn test_params_filters_null_values() {
let val = DatabaseValue::Null;
let params = val.params();
assert!(params.is_some());
assert_eq!(params.unwrap(), [] as [&DatabaseValue; 0]);
}
#[test_log::test]
fn test_params_filters_now() {
let val = DatabaseValue::Now;
let params = val.params();
assert!(params.is_some());
assert_eq!(params.unwrap(), [] as [&DatabaseValue; 0]);
}
#[test_log::test]
fn test_params_filters_now_plus() {
let val = DatabaseValue::NowPlus(SqlInterval::from_days(1));
let params = val.params();
assert!(params.is_some());
assert_eq!(params.unwrap(), [] as [&DatabaseValue; 0]);
}
#[test_log::test]
fn test_params_includes_regular_values() {
let val = DatabaseValue::Int64(42);
let params = val.params();
assert!(params.is_some());
let params = params.unwrap();
assert_eq!(params.len(), 1);
assert_eq!(params[0], &DatabaseValue::Int64(42));
}
#[test_log::test]
fn test_params_includes_string_values() {
let val = DatabaseValue::String("test".to_string());
let params = val.params();
assert!(params.is_some());
let params = params.unwrap();
assert_eq!(params.len(), 1);
}
}
mod and_expression_tests {
use super::*;
#[test_log::test]
fn test_and_with_no_conditions_returns_none() {
let and_expr = And { conditions: vec![] };
assert!(and_expr.values().is_none());
}
#[test_log::test]
fn test_and_collects_values_from_conditions() {
let eq1 = where_eq("col1", DatabaseValue::Int64(1));
let eq2 = where_eq("col2", DatabaseValue::String("test".to_string()));
let and_expr = where_and(vec![Box::new(eq1), Box::new(eq2)]);
let values = and_expr.values();
assert!(values.is_some());
let values = values.unwrap();
assert_eq!(values.len(), 2);
}
#[test_log::test]
fn test_and_params_filters_non_bindable() {
let eq1 = where_eq("col1", DatabaseValue::Now);
let eq2 = where_eq("col2", DatabaseValue::Int64(42));
let and_expr = where_and(vec![Box::new(eq1), Box::new(eq2)]);
let params = and_expr.params();
assert!(params.is_some());
let params = params.unwrap();
assert_eq!(params.len(), 1);
assert_eq!(params[0], &DatabaseValue::Int64(42));
}
}
mod or_expression_tests {
use super::*;
#[test_log::test]
fn test_or_with_no_conditions_returns_none() {
let or_expr = Or { conditions: vec![] };
assert!(or_expr.values().is_none());
}
#[test_log::test]
fn test_or_collects_values_from_conditions() {
let eq1 = where_eq("status", DatabaseValue::String("active".to_string()));
let eq2 = where_eq("status", DatabaseValue::String("pending".to_string()));
let or_expr = where_or(vec![Box::new(eq1), Box::new(eq2)]);
let values = or_expr.values();
assert!(values.is_some());
let values = values.unwrap();
assert_eq!(values.len(), 2);
}
}
mod in_expression_tests {
use super::*;
#[test_log::test]
fn test_in_values_collection() {
let in_expr = where_in(
"id",
vec![
DatabaseValue::Int64(1),
DatabaseValue::Int64(2),
DatabaseValue::Int64(3),
],
);
let values = in_expr.values();
assert!(values.is_some());
let values = values.unwrap();
assert_eq!(values.len(), 3);
}
#[test_log::test]
fn test_not_in_values_collection() {
let not_in = where_not_in(
"status",
vec![
DatabaseValue::String("deleted".to_string()),
DatabaseValue::String("archived".to_string()),
],
);
let values = not_in.values();
assert!(values.is_some());
let values = values.unwrap();
assert_eq!(values.len(), 2);
}
}
mod coalesce_tests {
use super::*;
#[test_log::test]
fn test_coalesce_empty_returns_none() {
let coal = coalesce(vec![]);
assert!(coal.values().is_none());
}
#[test_log::test]
fn test_coalesce_collects_values() {
let coal = coalesce(vec![
Box::new(DatabaseValue::StringOpt(None)),
Box::new(DatabaseValue::String("default".to_string())),
]);
let values = coal.values();
assert!(values.is_some());
let values = values.unwrap();
assert_eq!(values.len(), 2);
}
}
mod inlist_tests {
use super::*;
#[test_log::test]
fn test_inlist_empty_returns_none() {
let list = InList { values: vec![] };
assert!(list.values().is_none());
}
#[test_log::test]
fn test_inlist_collects_values() {
let list = InList {
values: vec![
Box::new(DatabaseValue::Int64(1)),
Box::new(DatabaseValue::Int64(2)),
],
};
let values = list.values();
assert!(values.is_some());
assert_eq!(values.unwrap().len(), 2);
}
}
mod comparison_expression_tests {
use super::*;
#[test_log::test]
fn test_eq_values() {
let eq = where_eq("col", DatabaseValue::Int64(42));
let values = eq.values();
assert!(values.is_some());
assert_eq!(values.unwrap().len(), 1);
}
#[test_log::test]
fn test_not_eq_values() {
let neq = where_not_eq("col", DatabaseValue::String("test".to_string()));
let values = neq.values();
assert!(values.is_some());
assert_eq!(values.unwrap().len(), 1);
}
#[test_log::test]
fn test_gt_values() {
let gt = where_gt("age", DatabaseValue::Int32(18));
let values = gt.values();
assert!(values.is_some());
assert_eq!(values.unwrap().len(), 1);
}
#[test_log::test]
fn test_gte_values() {
let gte = where_gte("score", DatabaseValue::Int32(100));
let values = gte.values();
assert!(values.is_some());
assert_eq!(values.unwrap().len(), 1);
}
#[test_log::test]
fn test_lt_values() {
let lt = where_lt("quantity", DatabaseValue::Int32(10));
let values = lt.values();
assert!(values.is_some());
assert_eq!(values.unwrap().len(), 1);
}
#[test_log::test]
fn test_lte_values() {
let lte = where_lte("price", DatabaseValue::Real64(99.99));
let values = lte.values();
assert!(values.is_some());
assert_eq!(values.unwrap().len(), 1);
}
}
mod select_query_tests {
use super::*;
#[test_log::test]
fn test_select_query_no_filters_returns_none() {
let query = select("users");
assert!(query.values().is_none());
}
#[test_log::test]
fn test_select_query_with_filter_values() {
let query = select("users").where_eq("id", DatabaseValue::Int64(1));
let values = query.values();
assert!(values.is_some());
assert_eq!(values.unwrap().len(), 1);
}
#[test_log::test]
fn test_select_query_with_multiple_filters() {
let query = select("users")
.where_eq("active", DatabaseValue::Bool(true))
.where_gt("age", DatabaseValue::Int32(18));
let values = query.values();
assert!(values.is_some());
assert_eq!(values.unwrap().len(), 2);
}
}
#[cfg(feature = "raw-sql")]
mod literal_tests {
use super::*;
#[test_log::test]
fn test_literal_from_str() {
let lit: Literal = "COUNT(*)".into();
assert_eq!(lit.value, "COUNT(*)");
}
#[test_log::test]
fn test_literal_from_string() {
let lit: Literal = String::from("SUM(amount)").into();
assert_eq!(lit.value, "SUM(amount)");
}
#[test_log::test]
fn test_literal_function() {
let lit = literal("NOW()");
assert_eq!(lit.value, "NOW()");
}
}
#[cfg(all(test, feature = "sqlite-rusqlite"))]
mod qualified_column_tests {
use super::*;
#[test]
fn qualification_quotes_each_exact_component() {
let column = qualified_column("a.b\"c", "d.e\"f");
assert_eq!(column.render('"'), "\"a.b\"\"c\".\"d.e\"\"f\"");
let column = qualified_column("a.b`c", "d.e`f");
assert_eq!(column.render('`'), "`a.b``c`.`d.e``f`");
}
}
mod identifier_tests {
use super::*;
#[test_log::test]
fn test_identifier_from_str() {
let id: Identifier = "user_id".into();
assert_eq!(id.value, "user_id");
}
#[test_log::test]
fn test_identifier_from_string() {
let id: Identifier = String::from("column_name").into();
assert_eq!(id.value, "column_name");
}
#[test_log::test]
fn test_identifier_function() {
let id = identifier("table.column");
assert_eq!(id.value, "table.column");
}
}
mod typed_join_tests {
use super::*;
#[test_log::test]
fn typed_join_helpers_and_chaining() {
let condition = || {
(
qualified_column("orders", "user_id"),
qualified_column("users", "id"),
)
};
let inner = join("users", condition());
let left = left_join("users", condition());
assert!(!inner.left);
assert!(left.left);
assert!(matches!(inner.on, JoinCondition::ColumnsEqual(..)));
assert!(matches!(left.on, JoinCondition::ColumnsEqual(..)));
let query = select("orders")
.join("users", condition())
.left_join("addresses", JoinCondition::from(condition()))
.join("more", condition());
let joins = query.joins.unwrap();
assert_eq!(joins.len(), 3);
assert_eq!(joins[0].table_name, "users");
assert!(!joins[0].left);
assert_eq!(joins[1].table_name, "addresses");
assert!(joins[1].left);
assert_eq!(joins[2].table_name, "more");
assert!(!joins[2].left);
assert!(
joins
.iter()
.all(|join| matches!(join.on, JoinCondition::ColumnsEqual(..)))
);
}
}
#[cfg(feature = "raw-sql")]
mod join_tests {
use super::*;
#[test_log::test]
fn test_inner_join_creation() {
let j = join("orders", "orders.user_id = users.id");
assert_eq!(j.table_name, "orders");
assert!(matches!(&j.on, JoinCondition::Raw(sql) if sql == "orders.user_id = users.id"));
assert!(!j.left);
}
#[test_log::test]
fn test_left_join_creation() {
let j = left_join("addresses", "users.address_id = addresses.id");
assert_eq!(j.table_name, "addresses");
assert!(j.left);
}
}
mod sort_tests {
use super::*;
#[test_log::test]
fn test_sort_ascending() {
let s = sort(identifier("name"), SortDirection::Asc);
assert!(matches!(s.direction, SortDirection::Asc));
}
#[test_log::test]
fn test_sort_descending() {
let s = sort(identifier("created_at"), SortDirection::Desc);
assert!(matches!(s.direction, SortDirection::Desc));
}
}
mod boxed_macro_tests {
use super::*;
#[test_log::test]
fn test_boxed_empty() {
let v: Vec<Box<dyn BooleanExpression>> = boxed![];
assert!(v.is_empty());
}
#[test_log::test]
fn test_boxed_single() {
let v: Vec<Box<dyn BooleanExpression>> =
boxed![where_eq("id", DatabaseValue::Int64(1))];
assert_eq!(v.len(), 1);
}
#[test_log::test]
fn test_boxed_multiple() {
let v: Vec<Box<dyn BooleanExpression>> = boxed![
where_eq("id", DatabaseValue::Int64(1)),
where_gt("age", DatabaseValue::Int32(18)),
];
assert_eq!(v.len(), 2);
}
}
mod filterable_query_tests {
use super::*;
#[test_log::test]
fn test_filter_if_some_with_value() {
let query =
select("users").filter_if_some(Some(where_eq("id", DatabaseValue::Int64(1))));
assert!(query.filters.is_some());
assert_eq!(query.filters.unwrap().len(), 1);
}
#[test_log::test]
fn test_filter_if_some_with_none() {
let query = select("users").filter_if_some::<Eq>(None);
assert!(query.filters.is_none());
}
#[test_log::test]
fn test_filters_adds_multiple() {
let conditions: Vec<Box<dyn BooleanExpression>> = vec![
Box::new(where_eq("a", DatabaseValue::Int64(1))),
Box::new(where_eq("b", DatabaseValue::Int64(2))),
];
let query = select("table").filters(conditions);
assert!(query.filters.is_some());
assert_eq!(query.filters.unwrap().len(), 2);
}
}
}