use super::*;
#[allow(missing_docs)]
impl<M: Model> QueryBuilder<M> {
pub(crate) fn build_null_check_expression(
&self,
column_expr: SimpleExpr,
negated: bool,
) -> SimpleExpr {
if negated {
column_expr.is_not_null()
} else {
column_expr.is_null()
}
}
pub(crate) fn build_null_check_sql(&self, column: &str, negated: bool) -> String {
format!("{} IS {}NULL", column, if negated { "NOT " } else { "" })
}
pub(crate) fn build_between_expression(
&self,
column_expr: SimpleExpr,
low: &serde_json::Value,
high: &serde_json::Value,
) -> SimpleExpr {
column_expr.between(
crate::internal::json_to_db_value(low),
crate::internal::json_to_db_value(high),
)
}
pub(crate) fn build_between_sql(
&self,
db_type: DatabaseType,
column: &str,
low: &serde_json::Value,
high: &serde_json::Value,
) -> String {
format!(
"{} BETWEEN {} AND {}",
column,
self.format_preview_value(db_type, low),
self.format_preview_value(db_type, high)
)
}
pub(in crate::query::sql) fn build_json_value_sql(
&self,
db_type: DatabaseType,
column: &str,
operator: JsonValueOperator,
value: &serde_json::Value,
) -> String {
match operator {
JsonValueOperator::Contains => {
db_sql::preview_json_contains(db_type, column, &value.to_string())
}
JsonValueOperator::ContainedBy => {
db_sql::preview_json_contained_by(db_type, column, &value.to_string())
}
}
}
pub(in crate::query::sql) fn build_json_value_expression(
&self,
db_type: DatabaseType,
column_sql: &str,
operator: JsonValueOperator,
value: &serde_json::Value,
) -> SimpleExpr {
let bound = match operator {
JsonValueOperator::Contains => db_sql::json_contains_bound(db_type, column_sql, value),
JsonValueOperator::ContainedBy => {
db_sql::json_contained_by_bound(db_type, column_sql, value)
}
};
self.build_custom_expression(bound.sql, bound.values)
}
pub(in crate::query::sql) fn build_json_string_sql(
&self,
db_type: DatabaseType,
column: &str,
operator: JsonStringOperator,
value: &str,
) -> String {
match operator {
JsonStringOperator::KeyPresent => {
db_sql::preview_json_key_exists(db_type, column, value)
}
JsonStringOperator::KeyAbsent => {
db_sql::preview_json_key_not_exists(db_type, column, value)
}
JsonStringOperator::PathPresent => {
db_sql::preview_json_path_exists(db_type, column, value)
}
JsonStringOperator::PathAbsent => {
db_sql::preview_json_path_not_exists(db_type, column, value)
}
}
}
pub(in crate::query::sql) fn build_json_string_expression(
&self,
db_type: DatabaseType,
column_sql: &str,
operator: JsonStringOperator,
value: &str,
) -> SimpleExpr {
let maybe_bound = match operator {
JsonStringOperator::KeyPresent => {
Some(db_sql::json_key_exists_bound(db_type, column_sql, value))
}
JsonStringOperator::KeyAbsent => Some(db_sql::json_key_not_exists_bound(
db_type, column_sql, value,
)),
JsonStringOperator::PathPresent => {
db_sql::json_path_exists_bound(db_type, column_sql, value)
}
JsonStringOperator::PathAbsent => {
db_sql::json_path_not_exists_bound(db_type, column_sql, value)
}
};
let Some(bound) = maybe_bound else {
return Expr::cust(db_sql::invalid_json_path_predicate(matches!(
operator,
JsonStringOperator::PathPresent
)));
};
self.build_custom_expression(bound.sql, bound.values)
}
pub(in crate::query::sql) fn build_array_sql(
&self,
db_type: DatabaseType,
column: &str,
operator: ArrayOperator,
values: &[serde_json::Value],
) -> String {
let rendered = self.render_array_values(values);
match operator {
ArrayOperator::Contains => db_sql::array_contains(db_type, column, &rendered),
ArrayOperator::ContainedBy => db_sql::array_contained_by(db_type, column, &rendered),
ArrayOperator::Overlaps => db_sql::array_overlaps(db_type, column, &rendered),
}
}
pub(in crate::query::sql) fn build_array_expression(
&self,
db_type: DatabaseType,
column_expr: SimpleExpr,
column_sql: &str,
operator: ArrayOperator,
values: &[serde_json::Value],
) -> SimpleExpr {
match db_type {
DatabaseType::Postgres => {
let _ = column_expr;
let rendered = self.render_array_values(values);
let sql = match operator {
ArrayOperator::Contains => {
format!("{} @> ARRAY[{}]", column_sql, rendered.join(","))
}
ArrayOperator::ContainedBy => {
format!("{} <@ ARRAY[{}]", column_sql, rendered.join(","))
}
ArrayOperator::Overlaps => {
format!("{} && ARRAY[{}]", column_sql, rendered.join(","))
}
};
Expr::cust(sql)
}
DatabaseType::MySQL | DatabaseType::MariaDB => match operator {
ArrayOperator::Contains => self.build_custom_expression(
format!("JSON_CONTAINS({}, CAST(? AS JSON))", column_sql),
vec![Self::json_array_parameter(values)],
),
ArrayOperator::ContainedBy => self.build_custom_expression(
format!("JSON_CONTAINS(CAST(? AS JSON), {})", column_sql),
vec![Self::json_array_parameter(values)],
),
ArrayOperator::Overlaps => {
if values.is_empty() {
Expr::cust("0 = 1".to_string())
} else {
let sql = std::iter::repeat_n(
format!("JSON_CONTAINS({}, CAST(? AS JSON))", column_sql),
values.len(),
)
.collect::<Vec<_>>()
.join(" OR ");
let params = values.iter().map(Self::json_scalar_parameter).collect();
self.build_custom_expression(format!("({})", sql), params)
}
}
},
DatabaseType::SQLite => match operator {
ArrayOperator::Contains => {
if values.is_empty() {
Expr::cust("1 = 1".to_string())
} else {
let sql = std::iter::repeat_n(
format!(
"EXISTS (SELECT 1 FROM json_each({}) WHERE value = ?)",
column_sql
),
values.len(),
)
.collect::<Vec<_>>()
.join(" AND ");
self.build_custom_expression(
format!("({})", sql),
Self::sea_value_list(values),
)
}
}
ArrayOperator::ContainedBy => {
if values.is_empty() {
Expr::cust(format!(
"NOT EXISTS (SELECT 1 FROM json_each({}))",
column_sql
))
} else {
self.build_custom_expression(
format!(
"NOT EXISTS (SELECT 1 FROM json_each({}) WHERE value NOT IN ({}))",
column_sql,
Self::placeholder_list(values.len())
),
Self::sea_value_list(values),
)
}
}
ArrayOperator::Overlaps => {
if values.is_empty() {
Expr::cust("0 = 1".to_string())
} else {
let sql = std::iter::repeat_n(
format!(
"EXISTS (SELECT 1 FROM json_each({}) WHERE value = ?)",
column_sql
),
values.len(),
)
.collect::<Vec<_>>()
.join(" OR ");
self.build_custom_expression(
format!("({})", sql),
Self::sea_value_list(values),
)
}
}
},
}
}
}