pub mod es;
pub mod mongo;
pub mod sql;
use crate::config::QueryConfig;
use crate::query::error::QueryError;
use crate::query::ir::{self, RelRef};
use crate::query::spec::{QuerySpec, SortDir, SortKey};
use crate::query::write::{ConflictAction, ResolvedConflict, WriteError};
use crate::storage::DbBackend;
pub(crate) fn resolve_limit(
requested: Option<u64>,
limits: &QueryConfig,
) -> Result<u64, QueryError> {
match requested {
Some(l) if l > limits.max_limit => Err(QueryError::LimitExceeded {
requested: l,
max: limits.max_limit,
}),
Some(l) => Ok(l),
None => Ok(limits.default_limit.min(limits.max_limit)),
}
}
pub(crate) fn resolve_skip(
requested: Option<u64>,
limits: &QueryConfig,
) -> Result<Option<u64>, QueryError> {
match requested {
Some(s) if s > limits.max_skip => Err(QueryError::SkipExceeded {
requested: s,
max: limits.max_skip,
}),
other => Ok(other),
}
}
pub(crate) fn plan_projection(fields: &[String]) -> Option<&[String]> {
if fields.is_empty() {
None
} else {
Some(fields)
}
}
pub(crate) struct SortPlan<'a> {
pub field: &'a str,
pub ascending: bool,
pub nulls_first: bool,
}
pub(crate) fn plan_sort(sort: &[SortKey]) -> Vec<SortPlan<'_>> {
sort.iter()
.map(|k| {
let ascending = matches!(k.dir, SortDir::Asc);
SortPlan {
field: &k.field,
ascending,
nulls_first: ascending,
}
})
.collect()
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum UpsertMode {
Replace,
SetWithInsertDefaults,
InsertOnly,
}
pub(crate) struct UpsertPlan<'a> {
pub row: &'a [ir::Value],
pub mode: UpsertMode,
pub on_conflict: Vec<(&'a str, &'a ir::Value)>,
pub insert_only: Vec<(&'a str, &'a ir::Value)>,
}
pub(crate) fn plan_upsert<'a>(
columns: &'a [String],
rows: &'a [Vec<ir::Value>],
set: &'a [(String, ir::Value)],
conflict: &'a ResolvedConflict,
target_backend: &str,
) -> Result<UpsertPlan<'a>, WriteError> {
if rows.len() != 1 {
return Err(QueryError::FeatureUnsupportedByTarget {
feature: "bulk upsert".to_string(),
target: target_backend.to_string(),
}
.into());
}
let row = &rows[0];
let non_target = |col: &String| !conflict.targets.contains(col);
let (mode, on_conflict, insert_only) = match conflict.action {
ConflictAction::Update if set.is_empty() => {
let on_conflict = columns
.iter()
.zip(row)
.filter(|(c, _)| non_target(c))
.map(|(c, v)| (c.as_str(), v))
.collect();
(UpsertMode::Replace, on_conflict, Vec::new())
}
ConflictAction::Update => {
let on_conflict = set.iter().map(|(c, v)| (c.as_str(), v)).collect();
let insert_only = columns
.iter()
.zip(row)
.filter(|(c, _)| non_target(c) && !set.iter().any(|(s, _)| s == *c))
.map(|(c, v)| (c.as_str(), v))
.collect();
(UpsertMode::SetWithInsertDefaults, on_conflict, insert_only)
}
ConflictAction::Nothing => {
let insert_only = columns
.iter()
.zip(row)
.filter(|(c, _)| non_target(c))
.map(|(c, v)| (c.as_str(), v))
.collect();
(UpsertMode::InsertOnly, Vec::new(), insert_only)
}
};
Ok(UpsertPlan {
row,
mode,
on_conflict,
insert_only,
})
}
pub(crate) fn reject_include(spec: &QuerySpec, target: &str) -> Result<(), QueryError> {
match spec.include.first() {
Some(inc) => Err(QueryError::FeatureUnsupportedByTarget {
feature: format!("include '{}'", inc.relation),
target: target.to_string(),
}),
None => Ok(()),
}
}
pub(crate) fn reject_many_to_many(rel: &RelRef, target: &str) -> Result<(), QueryError> {
if rel.through.is_some() {
return Err(QueryError::FeatureUnsupportedByTarget {
feature: format!("many-to-many relation '{}'", rel.name),
target: target.to_string(),
});
}
Ok(())
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SqlDialect {
Sqlite,
Postgres,
Mysql,
}
impl From<DbBackend> for SqlDialect {
fn from(b: DbBackend) -> Self {
match b {
DbBackend::Sqlite => SqlDialect::Sqlite,
DbBackend::Postgres => SqlDialect::Postgres,
DbBackend::Mysql => SqlDialect::Mysql,
}
}
}