WIP: Introduce the zou library
This commit is contained in:
parent
221a740c96
commit
7a8856df10
5 changed files with 805 additions and 0 deletions
4
Cargo.lock
generated
4
Cargo.lock
generated
|
@ -2663,3 +2663,7 @@ dependencies = [
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.95",
|
"syn 2.0.95",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zou"
|
||||||
|
version = "0.1.0"
|
||||||
|
|
|
@ -4,4 +4,5 @@ resolver = "2"
|
||||||
|
|
||||||
members = [
|
members = [
|
||||||
"yorokobot",
|
"yorokobot",
|
||||||
|
"zou"
|
||||||
]
|
]
|
||||||
|
|
14
zou/Cargo.toml
Normal file
14
zou/Cargo.toml
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
[package]
|
||||||
|
name = "zou"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = [ "Victor Mignot <dala@dalaran.fr>" ]
|
||||||
|
license = "BSD-3-Clause"
|
||||||
|
repository = "https://git.dalaran.fr/dala/yorokobot"
|
||||||
|
homepage = "https://git.dalaran.fr/dala/yorokobot/src/branch/main/zou"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
|
||||||
|
[lints.rust]
|
||||||
|
unsafe_code= "forbid"
|
||||||
|
missing_docs = "deny"
|
11
zou/LICENSE
Normal file
11
zou/LICENSE
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
Copyright 2025 Victor Mignot
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
775
zou/src/lib.rs
Normal file
775
zou/src/lib.rs
Normal file
|
@ -0,0 +1,775 @@
|
||||||
|
//! zou is a lightweight ORM for Postgres database.
|
||||||
|
//! zou is licensed under the BSD 3-Clause License.
|
||||||
|
|
||||||
|
// WARN: Delete this
|
||||||
|
#![warn(missing_docs)]
|
||||||
|
|
||||||
|
/// Trait to be implemented for structs to be stored in your postgres Database.
|
||||||
|
pub trait ObjectModel: Sized {
|
||||||
|
/// The name of the table to store this the implementing struct.
|
||||||
|
fn table_name() -> &'static str;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum QueryOperation {
|
||||||
|
Select,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl QueryOperation {
|
||||||
|
fn format(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
QueryOperation::Select => "SELECT $f FROM $t $j $w $o;",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Operand = String;
|
||||||
|
|
||||||
|
enum Filter {
|
||||||
|
Equal {
|
||||||
|
op1: Operand,
|
||||||
|
op2: Operand,
|
||||||
|
},
|
||||||
|
Greater {
|
||||||
|
op1: Operand,
|
||||||
|
op2: Operand,
|
||||||
|
},
|
||||||
|
Less {
|
||||||
|
op1: Operand,
|
||||||
|
op2: Operand,
|
||||||
|
},
|
||||||
|
GreaterOrEqual {
|
||||||
|
op1: Operand,
|
||||||
|
op2: Operand,
|
||||||
|
},
|
||||||
|
LessOrEqual {
|
||||||
|
op1: Operand,
|
||||||
|
op2: Operand,
|
||||||
|
},
|
||||||
|
NotEqual {
|
||||||
|
op1: Operand,
|
||||||
|
op2: Operand,
|
||||||
|
},
|
||||||
|
And {
|
||||||
|
op1: Operand,
|
||||||
|
op2: Operand,
|
||||||
|
},
|
||||||
|
Or {
|
||||||
|
op1: Operand,
|
||||||
|
op2: Operand,
|
||||||
|
},
|
||||||
|
In {
|
||||||
|
value: Operand,
|
||||||
|
valid_range: Vec<Operand>,
|
||||||
|
},
|
||||||
|
Between {
|
||||||
|
value: Operand,
|
||||||
|
lower: Operand,
|
||||||
|
upper: Operand,
|
||||||
|
},
|
||||||
|
Like {
|
||||||
|
value: Operand,
|
||||||
|
pattern: Operand,
|
||||||
|
},
|
||||||
|
IsNull {
|
||||||
|
value: Operand,
|
||||||
|
},
|
||||||
|
IsNotNull {
|
||||||
|
value: Operand,
|
||||||
|
},
|
||||||
|
Not {
|
||||||
|
op: Operand,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToString for Filter {
|
||||||
|
fn to_string(&self) -> String {
|
||||||
|
match self {
|
||||||
|
Filter::Equal { op1, op2 } => format!("{} = {}", op1.to_string(), op2.to_string()),
|
||||||
|
Filter::Greater { op1, op2 } => format!("{} > {}", op1.to_string(), op2.to_string()),
|
||||||
|
Filter::Less { op1, op2 } => format!("{} < {}", op1.to_string(), op2.to_string()),
|
||||||
|
Filter::GreaterOrEqual { op1, op2 } => {
|
||||||
|
format!("{} >= {}", op1.to_string(), op2.to_string())
|
||||||
|
}
|
||||||
|
Filter::LessOrEqual { op1, op2 } => {
|
||||||
|
format!("{} <= {}", op1.to_string(), op2.to_string())
|
||||||
|
}
|
||||||
|
Filter::NotEqual { op1, op2 } => format!("{} != {}", op1.to_string(), op2.to_string()),
|
||||||
|
Filter::And { op1, op2 } => format!("{} AND {}", op1.to_string(), op2.to_string()),
|
||||||
|
Filter::Or { op1, op2 } => format!("{} OR {}", op1.to_string(), op2.to_string()),
|
||||||
|
Filter::In { value, valid_range } => {
|
||||||
|
let mut formatted_range: String = "(".to_string();
|
||||||
|
|
||||||
|
for element in valid_range {
|
||||||
|
formatted_range.push_str(&element.to_string());
|
||||||
|
formatted_range.push(',');
|
||||||
|
formatted_range.push(' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
if valid_range.len() > 0 {
|
||||||
|
formatted_range.pop();
|
||||||
|
formatted_range.pop();
|
||||||
|
};
|
||||||
|
|
||||||
|
formatted_range.push(')');
|
||||||
|
format!("{} in {formatted_range}", value.to_string())
|
||||||
|
}
|
||||||
|
Filter::Between {
|
||||||
|
value,
|
||||||
|
lower,
|
||||||
|
upper,
|
||||||
|
} => format!(
|
||||||
|
"{} BETWEEN {} AND {}",
|
||||||
|
value.to_string(),
|
||||||
|
lower.to_string(),
|
||||||
|
upper.to_string()
|
||||||
|
),
|
||||||
|
Filter::Like { value, pattern } => {
|
||||||
|
format!("{} LIKE {}", value.to_string(), pattern.to_string())
|
||||||
|
}
|
||||||
|
Filter::IsNull { value } => format!("{} IS NULL", value.to_string()),
|
||||||
|
Filter::IsNotNull { value } => format!("{} IS NOT NULL", value.to_string()),
|
||||||
|
Filter::Not { op } => format!("NOT {}", op.to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct FilterData(Option<Filter>);
|
||||||
|
impl FilterData {
|
||||||
|
fn new() -> Self {
|
||||||
|
FilterData(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn equal<T: ToString>(mut self, op1: T, op2: T) -> Self {
|
||||||
|
self.0 = Some(Filter::Equal {
|
||||||
|
op1: op1.to_string(),
|
||||||
|
op2: op2.to_string(),
|
||||||
|
});
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn greater<T: ToString>(mut self, op1: T, op2: T) -> Self {
|
||||||
|
self.0 = Some(Filter::Greater {
|
||||||
|
op1: op1.to_string(),
|
||||||
|
op2: op2.to_string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn less<T: ToString>(mut self, op1: T, op2: T) -> Self {
|
||||||
|
self.0 = Some(Filter::Less {
|
||||||
|
op1: op1.to_string(),
|
||||||
|
op2: op2.to_string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn greater_or_equal<T: ToString>(mut self, op1: T, op2: T) -> Self {
|
||||||
|
self.0 = Some(Filter::GreaterOrEqual {
|
||||||
|
op1: op1.to_string(),
|
||||||
|
op2: op2.to_string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn less_or_equal<T: ToString>(mut self, op1: T, op2: T) -> Self {
|
||||||
|
self.0 = Some(Filter::LessOrEqual {
|
||||||
|
op1: op1.to_string(),
|
||||||
|
op2: op2.to_string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn not_equal<T: ToString>(mut self, op1: T, op2: T) -> Self {
|
||||||
|
self.0 = Some(Filter::NotEqual {
|
||||||
|
op1: op1.to_string(),
|
||||||
|
op2: op2.to_string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn and(
|
||||||
|
mut self,
|
||||||
|
filter1: impl Fn(FilterData) -> FilterData,
|
||||||
|
filter2: impl Fn(FilterData) -> FilterData,
|
||||||
|
) -> Self {
|
||||||
|
let filter_data_1 = filter1(Self::new());
|
||||||
|
let filter_data_2 = filter2(Self::new());
|
||||||
|
|
||||||
|
if filter_data_1.0.is_none() && filter_data_2.0.is_none() {
|
||||||
|
self.0 = None
|
||||||
|
} else if filter_data_1.0.is_none() && filter_data_2.0.is_some() {
|
||||||
|
self.0 = filter_data_2.0;
|
||||||
|
} else if filter_data_1.0.is_some() && filter_data_2.0.is_none() {
|
||||||
|
self.0 = filter_data_1.0;
|
||||||
|
} else {
|
||||||
|
self.0 = Some(Filter::And {
|
||||||
|
op1: filter_data_1.0.unwrap().to_string(),
|
||||||
|
op2: filter_data_2.0.unwrap().to_string(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Refactor with `and`
|
||||||
|
pub fn or(
|
||||||
|
mut self,
|
||||||
|
filter1: impl Fn(FilterData) -> FilterData,
|
||||||
|
filter2: impl Fn(FilterData) -> FilterData,
|
||||||
|
) -> Self {
|
||||||
|
let filter_data_1 = filter1(Self::new());
|
||||||
|
let filter_data_2 = filter2(Self::new());
|
||||||
|
|
||||||
|
if filter_data_1.0.is_none() && filter_data_2.0.is_none() {
|
||||||
|
self.0 = None
|
||||||
|
} else if filter_data_1.0.is_none() && filter_data_2.0.is_some() {
|
||||||
|
self.0 = filter_data_2.0;
|
||||||
|
} else if filter_data_1.0.is_some() && filter_data_2.0.is_none() {
|
||||||
|
self.0 = filter_data_1.0;
|
||||||
|
} else {
|
||||||
|
self.0 = Some(Filter::Or {
|
||||||
|
op1: filter_data_1.0.unwrap().to_string(),
|
||||||
|
op2: filter_data_2.0.unwrap().to_string(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn in_values<T: ToString>(mut self, value: T, valid_values: Vec<T>) -> Self {
|
||||||
|
self.0 = Some(Filter::In {
|
||||||
|
value: value.to_string(),
|
||||||
|
valid_range: valid_values.iter().map(|v| v.to_string()).collect(),
|
||||||
|
});
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn between<T: ToString>(mut self, value: T, lower: T, upper: T) -> Self {
|
||||||
|
self.0 = Some(Filter::Between {
|
||||||
|
value: value.to_string(),
|
||||||
|
lower: lower.to_string(),
|
||||||
|
upper: upper.to_string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn like<T: ToString>(mut self, value: T, pattern: T) -> Self {
|
||||||
|
self.0 = Some(Filter::Like {
|
||||||
|
value: value.to_string(),
|
||||||
|
pattern: pattern.to_string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_null<T: ToString>(mut self, value: T) -> Self {
|
||||||
|
self.0 = Some(Filter::IsNull {
|
||||||
|
value: value.to_string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_not_null<T: ToString>(mut self, value: T) -> Self {
|
||||||
|
self.0 = Some(Filter::IsNotNull {
|
||||||
|
value: value.to_string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Handle case whern NOT is an expression with a nice interface
|
||||||
|
pub fn not<T: ToString>(mut self, value: T) -> Self {
|
||||||
|
self.0 = Some(Filter::Not {
|
||||||
|
op: value.to_string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Order {
|
||||||
|
Asc,
|
||||||
|
Desc,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToString for Order {
|
||||||
|
fn to_string(&self) -> String {
|
||||||
|
match self {
|
||||||
|
Order::Asc => "ASC",
|
||||||
|
Order::Desc => "DESC",
|
||||||
|
}
|
||||||
|
.to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct OrderingClause {
|
||||||
|
field: String,
|
||||||
|
order: Order,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToString for OrderingClause {
|
||||||
|
fn to_string(&self) -> String {
|
||||||
|
format!("{} {}", self.field, self.order.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum JoinKind {
|
||||||
|
Inner,
|
||||||
|
Left,
|
||||||
|
Right,
|
||||||
|
Full,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToString for JoinKind {
|
||||||
|
fn to_string(&self) -> String {
|
||||||
|
match self {
|
||||||
|
JoinKind::Inner => "INNER JOIN",
|
||||||
|
JoinKind::Left => "LEFT JOIN",
|
||||||
|
JoinKind::Right => "RIGHT JOIN",
|
||||||
|
JoinKind::Full => "FULL JOIN",
|
||||||
|
}
|
||||||
|
.to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Join {
|
||||||
|
kind: JoinKind,
|
||||||
|
dest_table: String,
|
||||||
|
join_columns: (String, String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToString for Join {
|
||||||
|
fn to_string(&self) -> String {
|
||||||
|
format!(
|
||||||
|
" {} {} ON {} = {}",
|
||||||
|
self.kind.to_string(),
|
||||||
|
self.dest_table,
|
||||||
|
self.join_columns.0,
|
||||||
|
self.join_columns.1
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Query {
|
||||||
|
operation: QueryOperation,
|
||||||
|
table: String,
|
||||||
|
filter: Option<Filter>,
|
||||||
|
ordering: Vec<OrderingClause>,
|
||||||
|
join: Option<Join>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Query {
|
||||||
|
pub fn new<T: ObjectModel>(operation: QueryOperation) -> Self {
|
||||||
|
Query {
|
||||||
|
operation,
|
||||||
|
table: T::table_name().to_string(),
|
||||||
|
filter: None,
|
||||||
|
ordering: vec![],
|
||||||
|
join: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn filter<F: Fn(FilterData) -> FilterData>(mut self, filter_data_callback: F) -> Self {
|
||||||
|
let filter_data = FilterData::new();
|
||||||
|
self.filter = filter_data_callback(filter_data).0;
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn order(mut self, field: String, order: Order) -> Self {
|
||||||
|
self.ordering.push(OrderingClause { field, order });
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn join(
|
||||||
|
mut self,
|
||||||
|
kind: JoinKind,
|
||||||
|
dest_table: String,
|
||||||
|
join_columns: (String, String),
|
||||||
|
) -> Self {
|
||||||
|
self.join = Some(Join {
|
||||||
|
kind,
|
||||||
|
dest_table,
|
||||||
|
join_columns,
|
||||||
|
});
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build(self) -> String {
|
||||||
|
let where_clause = if let Some(f) = self.filter {
|
||||||
|
format!(" WHERE {}", f.to_string())
|
||||||
|
} else {
|
||||||
|
"".to_string()
|
||||||
|
};
|
||||||
|
|
||||||
|
let order_clause = if self.ordering.len() != 0 {
|
||||||
|
let mut clause_str = String::from(" ORDER BY ");
|
||||||
|
|
||||||
|
for o in self.ordering {
|
||||||
|
clause_str.push_str(&o.to_string());
|
||||||
|
clause_str.push(',');
|
||||||
|
clause_str.push(' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
clause_str.pop();
|
||||||
|
clause_str.pop();
|
||||||
|
|
||||||
|
clause_str
|
||||||
|
} else {
|
||||||
|
"".to_string()
|
||||||
|
};
|
||||||
|
|
||||||
|
let join_clause = if let Some(j) = self.join {
|
||||||
|
j.to_string()
|
||||||
|
} else {
|
||||||
|
"".to_string()
|
||||||
|
};
|
||||||
|
|
||||||
|
self.operation
|
||||||
|
.format()
|
||||||
|
.replace("$f", "*")
|
||||||
|
.replace("$t", &self.table)
|
||||||
|
.replace(" $j", &join_clause)
|
||||||
|
.replace(" $w", &where_clause)
|
||||||
|
.replace(" $o", &order_clause)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::ObjectModel;
|
||||||
|
|
||||||
|
use super::Query;
|
||||||
|
|
||||||
|
struct TestModel;
|
||||||
|
impl ObjectModel for TestModel {
|
||||||
|
fn table_name() -> &'static str {
|
||||||
|
"test_objects"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn select_all() {
|
||||||
|
let query = Query::new::<TestModel>(super::QueryOperation::Select).build();
|
||||||
|
|
||||||
|
assert_eq!(query, "SELECT * FROM test_objects;")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn select_join_inner() {
|
||||||
|
let query = Query::new::<TestModel>(super::QueryOperation::Select)
|
||||||
|
.join(
|
||||||
|
crate::JoinKind::Inner,
|
||||||
|
"test_join".to_owned(),
|
||||||
|
("a".to_owned(), "b".to_owned()),
|
||||||
|
)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
query,
|
||||||
|
"SELECT * FROM test_objects INNER JOIN test_join ON a = b;",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn select_join_left() {
|
||||||
|
let query = Query::new::<TestModel>(super::QueryOperation::Select)
|
||||||
|
.join(
|
||||||
|
crate::JoinKind::Left,
|
||||||
|
"test_join".to_owned(),
|
||||||
|
("a".to_owned(), "b".to_owned()),
|
||||||
|
)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
query,
|
||||||
|
"SELECT * FROM test_objects LEFT JOIN test_join ON a = b;",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn select_join_right() {
|
||||||
|
let query = Query::new::<TestModel>(super::QueryOperation::Select)
|
||||||
|
.join(
|
||||||
|
crate::JoinKind::Right,
|
||||||
|
"test_join".to_owned(),
|
||||||
|
("a".to_owned(), "b".to_owned()),
|
||||||
|
)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
query,
|
||||||
|
"SELECT * FROM test_objects RIGHT JOIN test_join ON a = b;",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn select_join_full() {
|
||||||
|
let query = Query::new::<TestModel>(super::QueryOperation::Select)
|
||||||
|
.join(
|
||||||
|
crate::JoinKind::Full,
|
||||||
|
"test_join".to_owned(),
|
||||||
|
("a".to_owned(), "b".to_owned()),
|
||||||
|
)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
query,
|
||||||
|
"SELECT * FROM test_objects FULL JOIN test_join ON a = b;",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn select_ordering_single_asc() {
|
||||||
|
let query = Query::new::<TestModel>(super::QueryOperation::Select)
|
||||||
|
.order("a".to_owned(), crate::Order::Asc)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
assert_eq!(query, "SELECT * FROM test_objects ORDER BY a ASC;");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn select_ordering_single_desc() {
|
||||||
|
let query = Query::new::<TestModel>(super::QueryOperation::Select)
|
||||||
|
.order("a".to_owned(), crate::Order::Desc)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
assert_eq!(query, "SELECT * FROM test_objects ORDER BY a DESC;");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn select_ordering_multiple() {
|
||||||
|
let query = Query::new::<TestModel>(super::QueryOperation::Select)
|
||||||
|
.order("a".to_owned(), crate::Order::Desc)
|
||||||
|
.order("b".to_owned(), crate::Order::Asc)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
assert_eq!(query, "SELECT * FROM test_objects ORDER BY a DESC, b ASC;");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn select_where_equal() {
|
||||||
|
let query = Query::new::<TestModel>(super::QueryOperation::Select)
|
||||||
|
.filter(|f| f.equal(1, 1))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
assert_eq!(query, "SELECT * FROM test_objects WHERE 1 = 1;");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn select_where_not_equal() {
|
||||||
|
let query = Query::new::<TestModel>(super::QueryOperation::Select)
|
||||||
|
.filter(|f| f.not_equal(1, 2))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
assert_eq!(query, "SELECT * FROM test_objects WHERE 1 != 2;")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn select_where_greater() {
|
||||||
|
let query = Query::new::<TestModel>(super::QueryOperation::Select)
|
||||||
|
.filter(|f| f.greater(2, 1))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
assert_eq!(query, "SELECT * FROM test_objects WHERE 2 > 1;")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn select_where_greater_or_equal() {
|
||||||
|
let query = Query::new::<TestModel>(super::QueryOperation::Select)
|
||||||
|
.filter(|f| f.greater_or_equal(1, 1))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
assert_eq!(query, "SELECT * FROM test_objects WHERE 1 >= 1;")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn select_where_less() {
|
||||||
|
let query = Query::new::<TestModel>(super::QueryOperation::Select)
|
||||||
|
.filter(|f| f.less(1, 2))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
assert_eq!(query, "SELECT * FROM test_objects WHERE 1 < 2;")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn select_where_less_or_equal() {
|
||||||
|
let query = Query::new::<TestModel>(super::QueryOperation::Select)
|
||||||
|
.filter(|f| f.less_or_equal(2, 2))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
assert_eq!(query, "SELECT * FROM test_objects WHERE 2 <= 2;")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn select_where_and_two_valid_operand() {
|
||||||
|
let query = Query::new::<TestModel>(super::QueryOperation::Select)
|
||||||
|
.filter(|f| f.and(|f1| f1.equal(1, 1), |f2| f2.equal(2, 2)))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
assert_eq!(query, "SELECT * FROM test_objects WHERE 1 = 1 AND 2 = 2;")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn select_where_and_one_valid_operand_only() {
|
||||||
|
let mut query = Query::new::<TestModel>(super::QueryOperation::Select)
|
||||||
|
.filter(|f| f.and(|f1| f1.equal(1, 1), |f2| f2))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
assert_eq!(query, "SELECT * FROM test_objects WHERE 1 = 1;");
|
||||||
|
|
||||||
|
query = Query::new::<TestModel>(super::QueryOperation::Select)
|
||||||
|
.filter(|f| f.and(|f1| f1, |f2| f2.equal(1, 1)))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
assert_eq!(query, "SELECT * FROM test_objects WHERE 1 = 1;");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn select_where_and_no_valid_operand() {
|
||||||
|
let query = Query::new::<TestModel>(super::QueryOperation::Select)
|
||||||
|
.filter(|f| f.and(|f1| f1, |f2| f2))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
assert_eq!(query, "SELECT * FROM test_objects;");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn select_where_and_nested() {
|
||||||
|
let query = Query::new::<TestModel>(super::QueryOperation::Select)
|
||||||
|
.filter(|f| {
|
||||||
|
f.and(
|
||||||
|
|f1| f1.and(|n1| n1.equal(1, 1), |n2| n2.equal(2, 2)),
|
||||||
|
|f2| f2.equal(3, 3),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.build();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
query,
|
||||||
|
"SELECT * FROM test_objects WHERE 1 = 1 AND 2 = 2 AND 3 = 3;"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn select_where_or_two_valid_operand() {
|
||||||
|
let query = Query::new::<TestModel>(super::QueryOperation::Select)
|
||||||
|
.filter(|f| f.or(|f1| f1.equal(1, 1), |f2| f2.equal(2, 2)))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
assert_eq!(query, "SELECT * FROM test_objects WHERE 1 = 1 OR 2 = 2;");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn select_where_or_one_valid_operand() {
|
||||||
|
let mut query = Query::new::<TestModel>(super::QueryOperation::Select)
|
||||||
|
.filter(|f| f.or(|f1| f1, |f2| f2.equal(2, 2)))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
assert_eq!(query, "SELECT * FROM test_objects WHERE 2 = 2;");
|
||||||
|
|
||||||
|
query = Query::new::<TestModel>(super::QueryOperation::Select)
|
||||||
|
.filter(|f| f.or(|f1| f1.equal(1, 1), |f2| f2))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
assert_eq!(query, "SELECT * FROM test_objects WHERE 1 = 1;");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn select_where_or_no_valid_operand() {
|
||||||
|
let query = Query::new::<TestModel>(super::QueryOperation::Select)
|
||||||
|
.filter(|f| f.or(|f1| f1, |f2| f2))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
assert_eq!(query, "SELECT * FROM test_objects;");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn select_where_or_nested() {
|
||||||
|
let query = Query::new::<TestModel>(super::QueryOperation::Select)
|
||||||
|
.filter(|f| {
|
||||||
|
f.or(
|
||||||
|
|f1| f1.and(|n1| n1.equal(1, 1), |n2| n2.equal(2, 2)),
|
||||||
|
|f2| f2.equal(3, 3),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.build();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
query,
|
||||||
|
"SELECT * FROM test_objects WHERE 1 = 1 AND 2 = 2 OR 3 = 3;"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn select_where_in_values() {
|
||||||
|
let query = Query::new::<TestModel>(super::QueryOperation::Select)
|
||||||
|
.filter(|f| f.in_values(1, vec![1, 2, 3]))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
assert_eq!(query, "SELECT * FROM test_objects WHERE 1 in (1, 2, 3);")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn select_where_in_empty_values() {
|
||||||
|
let query = Query::new::<TestModel>(super::QueryOperation::Select)
|
||||||
|
.filter(|f| f.in_values(1, vec![]))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
assert_eq!(query, "SELECT * FROM test_objects WHERE 1 in ();")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn select_where_between() {
|
||||||
|
let query = Query::new::<TestModel>(super::QueryOperation::Select)
|
||||||
|
.filter(|f| f.between(1, 0, 5))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
assert_eq!(query, "SELECT * FROM test_objects WHERE 1 BETWEEN 0 AND 5;");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn select_where_like() {
|
||||||
|
let query = Query::new::<TestModel>(super::QueryOperation::Select)
|
||||||
|
.filter(|f| f.like("test", "'test'"))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
assert_eq!(query, "SELECT * FROM test_objects WHERE test LIKE 'test';");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn select_where_is_null() {
|
||||||
|
let query = Query::new::<TestModel>(super::QueryOperation::Select)
|
||||||
|
.filter(|f| f.is_null("null"))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
assert_eq!(query, "SELECT * FROM test_objects WHERE null IS NULL;");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn select_where_is_not_null() {
|
||||||
|
let query = Query::new::<TestModel>(super::QueryOperation::Select)
|
||||||
|
.filter(|f| f.is_not_null("null"))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
assert_eq!(query, "SELECT * FROM test_objects WHERE null IS NOT NULL;");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn select_where_not_primitive() {
|
||||||
|
let query = Query::new::<TestModel>(super::QueryOperation::Select)
|
||||||
|
.filter(|f| f.not("null"))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
assert_eq!(query, "SELECT * FROM test_objects WHERE NOT null;")
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue