pub mod backend;
pub mod error;
pub mod ir;
pub mod lower;
pub mod schema;
pub mod spec;
pub mod vocab;
pub mod write;
pub use backend::SqlDialect;
pub use error::QueryError;
pub use lower::Params;
pub use schema::EntityRegistry;
pub use spec::QuerySpec;
pub use write::{ResolvedWrite, WriteError, WriteOp, resolve_write};
use ir::Cond;
use sea_query::SelectStatement;
use serde_json::Value as Json;
pub fn translate_sql(
query: &Json,
params: &Params,
dialect: SqlDialect,
default_limit: u64,
max_limit: u64,
) -> Result<SelectStatement, QueryError> {
translate_sql_with_schema(
query,
params,
&EntityRegistry::default(),
dialect,
default_limit,
max_limit,
)
}
pub fn translate_sql_with_schema(
query: &Json,
params: &Params,
reg: &EntityRegistry,
dialect: SqlDialect,
default_limit: u64,
max_limit: u64,
) -> Result<SelectStatement, QueryError> {
let spec = spec::parse(query)?;
let cond = match &spec.filter {
Some(f) => lower::lower_with(f, params, reg, &spec.source)?,
None => Cond::True,
};
let root_table = reg.physical_table(&spec.source);
backend::sql::render(&spec, &cond, &root_table, dialect, default_limit, max_limit)
}
#[derive(Debug, Clone)]
pub struct SqlPlan {
pub main: SelectStatement,
pub includes: Vec<IncludePlan>,
pub strip: Vec<String>,
}
#[derive(Debug, Clone)]
pub struct IncludePlan {
pub field: String,
pub target_table: String,
pub local: String,
pub foreign: String,
pub fields: Vec<String>,
pub limit: Option<u64>,
}
pub fn plan_sql(
query: &Json,
params: &Params,
reg: &EntityRegistry,
dialect: SqlDialect,
default_limit: u64,
max_limit: u64,
) -> Result<SqlPlan, QueryError> {
let spec = spec::parse(query)?;
let cond = match &spec.filter {
Some(f) => lower::lower_with(f, params, reg, &spec.source)?,
None => Cond::True,
};
let mut main_spec = spec.clone();
let mut includes = Vec::new();
let mut strip: Vec<String> = Vec::new();
for inc in &spec.include {
let (rel, _target) = reg.resolve_relation(&spec.source, &inc.relation, "include")?;
if rel.through.is_some() {
return Err(QueryError::FeatureUnsupportedByTarget {
feature: format!("many-to-many include '{}'", inc.relation),
target: "sql".to_string(),
});
}
if !main_spec.fields.is_empty() && !main_spec.fields.iter().any(|f| f == &rel.local) {
main_spec.fields.push(rel.local.clone());
if !strip.contains(&rel.local) {
strip.push(rel.local.clone());
}
}
includes.push(IncludePlan {
field: inc.relation.clone(),
target_table: rel.target_table,
local: rel.local,
foreign: rel.foreign,
fields: inc.fields.clone(),
limit: inc.limit,
});
}
let root_table = reg.physical_table(&spec.source);
let main = backend::sql::render(
&main_spec,
&cond,
&root_table,
dialect,
default_limit,
max_limit,
)?;
Ok(SqlPlan {
main,
includes,
strip,
})
}
pub fn translate_mongo(
query: &Json,
params: &Params,
reg: &EntityRegistry,
default_limit: u64,
max_limit: u64,
) -> Result<backend::mongo::MongoQuery, QueryError> {
let spec = spec::parse(query)?;
let cond = match &spec.filter {
Some(f) => lower::lower_with(f, params, reg, &spec.source)?,
None => Cond::True,
};
let collection = reg.physical_table(&spec.source);
backend::mongo::render(&spec, &cond, &collection, default_limit, max_limit)
}
pub fn translate_es(
query: &Json,
params: &Params,
reg: &EntityRegistry,
default_limit: u64,
max_limit: u64,
) -> Result<backend::es::EsQuery, QueryError> {
let spec = spec::parse(query)?;
let cond = match &spec.filter {
Some(f) => lower::lower_with(f, params, reg, &spec.source)?,
None => Cond::True,
};
let index = reg.physical_table(&spec.source);
backend::es::render(&spec, &cond, &index, default_limit, max_limit)
}
pub fn validate_sql(
query: &Json,
params: &Params,
dialect: SqlDialect,
default_limit: u64,
max_limit: u64,
) -> Result<(), QueryError> {
translate_sql(query, params, dialect, default_limit, max_limit).map(|_| ())
}