Skip to content

Commit

Permalink
Carry Expr's instead of ColumnRef's to make queries more flexible
Browse files Browse the repository at this point in the history
This is enables the querying of JSON columns via the LHS syntax of
`"table.column"->>'key'`

Signed-off-by: Jim Crossley <[email protected]>
  • Loading branch information
jcrossley3 authored and ctron committed Nov 7, 2024
1 parent 6d6433c commit ba0cce2
Show file tree
Hide file tree
Showing 3 changed files with 28 additions and 31 deletions.
11 changes: 6 additions & 5 deletions common/src/db/query/columns.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::fmt::{Display, Formatter};

use sea_orm::entity::ColumnDef;
use sea_orm::{sea_query, ColumnTrait, ColumnType, EntityTrait, IntoIdentity, Iterable};
use sea_query::{Alias, ColumnRef, IntoColumnRef, IntoIden};
use sea_query::{Alias, ColumnRef, Expr, IntoColumnRef, IntoIden};

/// Context of columns which can be used for filtering and sorting.
#[derive(Default, Debug, Clone)]
Expand Down Expand Up @@ -128,17 +128,18 @@ impl Columns {
}

/// Look up the column context for a given simple field name.
pub(crate) fn for_field(&self, field: &str) -> Option<(ColumnRef, ColumnDef)> {
/// Look up the column context for a given simple field name.
pub(crate) fn for_field(&self, field: &str) -> Option<(Expr, ColumnDef)> {
self.columns
.iter()
.find(|(col_ref, _)| {
matches!( col_ref,
.find(|(col, _)| {
matches!(col,
ColumnRef::Column(name)
| ColumnRef::TableColumn(_, name)
| ColumnRef::SchemaTableColumn(_, _, name)
if name.to_string().eq_ignore_ascii_case(field))
})
.cloned()
.map(|(r, d)| (Expr::col(r.clone()), d.clone()))
}

pub(crate) fn translate(&self, field: &str, op: &str, value: &str) -> Option<String> {
Expand Down
42 changes: 19 additions & 23 deletions common/src/db/query/filter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use super::{q, Columns, Error};
use human_date_parser::{from_human_time, ParseResult};
use sea_orm::sea_query::{extension::postgres::PgExpr, ConditionExpression, IntoCondition};
use sea_orm::{sea_query, ColumnType, Condition, IntoSimpleExpr, Value as SeaValue};
use sea_query::{BinOper, ColumnRef, Expr, Keyword, SimpleExpr};
use sea_query::{BinOper, Expr, Keyword, SimpleExpr};
use std::fmt::{Display, Formatter};
use std::str::FromStr;
use time::format_description::well_known::Rfc3339;
Expand Down Expand Up @@ -30,7 +30,7 @@ impl TryFrom<(&str, Operator, &Vec<String>, &Columns)> for Filter {
type Error = Error;
fn try_from(tuple: (&str, Operator, &Vec<String>, &Columns)) -> Result<Self, Self::Error> {
let (ref field, operator, values, columns) = tuple;
let (col_ref, col_def) = columns.for_field(field).ok_or(Error::SearchSyntax(format!(
let (expr, col_def) = columns.for_field(field).ok_or(Error::SearchSyntax(format!(
"Invalid field name for filter: '{field}'"
)))?;
Ok(Filter {
Expand All @@ -48,7 +48,7 @@ impl TryFrom<(&str, Operator, &Vec<String>, &Columns)> for Filter {
|(s, v)| match columns.translate(field, &operator.to_string(), s) {
Some(x) => q(&x).filter_for(columns),
None => Ok(Filter {
operands: Operand::Simple(col_ref.clone(), v),
operands: Operand::Simple(expr.clone(), v),
operator,
}),
},
Expand All @@ -71,12 +71,12 @@ impl TryFrom<(&Vec<String>, &Columns)> for Filter {
.iter()
.flat_map(|s| {
// Create a LIKE filter for all the string-ish columns
columns.iter().filter_map(|(col_ref, col_def)| {
columns.iter().filter_map(move |(col_ref, col_def)| {
match col_def.get_column_type() {
ColumnType::String(_) | ColumnType::Text => Some(Filter {
operands: Operand::Simple(
col_ref.clone(),
Arg::Value(SeaValue::String(Some(s.clone().into()))),
Expr::col(col_ref.clone()),
Arg::Value(SeaValue::from(s)),
),
operator: Operator::Like,
}),
Expand All @@ -93,26 +93,22 @@ impl TryFrom<(&Vec<String>, &Columns)> for Filter {
impl IntoCondition for Filter {
fn into_condition(self) -> Condition {
match self.operands {
Operand::Simple(col, v) => match self.operator {
Operand::Simple(expr, v) => match self.operator {
Operator::Equal => match v {
Arg::Null => Expr::col(col).is_null(),
v => Expr::col(col).binary(BinOper::Equal, v.into_simple_expr()),
Arg::Null => expr.is_null(),
v => expr.binary(BinOper::Equal, v.into_simple_expr()),
},
Operator::NotEqual => match v {
Arg::Null => Expr::col(col).is_not_null(),
v => Expr::col(col).binary(BinOper::NotEqual, v.into_simple_expr()),
Arg::Null => expr.is_not_null(),
v => expr.binary(BinOper::NotEqual, v.into_simple_expr()),
},
Operator::GreaterThan => {
Expr::col(col).binary(BinOper::GreaterThan, v.into_simple_expr())
}
Operator::GreaterThan => expr.binary(BinOper::GreaterThan, v.into_simple_expr()),
Operator::GreaterThanOrEqual => {
Expr::col(col).binary(BinOper::GreaterThanOrEqual, v.into_simple_expr())
}
Operator::LessThan => {
Expr::col(col).binary(BinOper::SmallerThan, v.into_simple_expr())
expr.binary(BinOper::GreaterThanOrEqual, v.into_simple_expr())
}
Operator::LessThan => expr.binary(BinOper::SmallerThan, v.into_simple_expr()),
Operator::LessThanOrEqual => {
Expr::col(col).binary(BinOper::SmallerThanOrEqual, v.into_simple_expr())
expr.binary(BinOper::SmallerThanOrEqual, v.into_simple_expr())
}
op @ (Operator::Like | Operator::NotLike) => {
if let Arg::Value(v) = v {
Expand All @@ -121,12 +117,12 @@ impl IntoCondition for Filter {
v.unwrap::<String>().replace('%', r"\%").replace('_', r"\_")
);
if op == Operator::Like {
SimpleExpr::Column(col).ilike(v)
expr.ilike(v)
} else {
SimpleExpr::Column(col).not_ilike(v)
expr.not_ilike(v)
}
} else {
SimpleExpr::Column(col)
expr.into()
}
}
_ => unreachable!(),
Expand Down Expand Up @@ -214,7 +210,7 @@ impl Arg {

#[derive(Debug)]
enum Operand {
Simple(ColumnRef, Arg),
Simple(Expr, Arg),
Composite(Vec<Filter>),
}

Expand Down
6 changes: 3 additions & 3 deletions common/src/db/query/sort.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
use super::{Columns, Error};
use sea_orm::{Order, QueryOrder};
use sea_query::{ColumnRef, SimpleExpr};
use sea_query::Expr;

pub(crate) struct Sort {
field: ColumnRef,
field: Expr,
order: Order,
}

impl Sort {
pub(crate) fn order_by<T: QueryOrder>(self, stmt: T) -> T {
stmt.order_by(SimpleExpr::Column(self.field), self.order)
stmt.order_by(self.field, self.order)
}
pub(crate) fn parse(s: &str, columns: &Columns) -> Result<Self, Error> {
let (field, order) = match s.split(':').collect::<Vec<_>>()[..] {
Expand Down

0 comments on commit ba0cce2

Please sign in to comment.