743 lines
24 KiB
Rust
743 lines
24 KiB
Rust
use crate::database::Database;
|
|
use std::sync::Arc;
|
|
pub struct Compiler<'a> {
|
|
pub db: &'a Database,
|
|
pub filter_keys: &'a [String],
|
|
pub is_stem_query: bool,
|
|
pub alias_counter: usize,
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub struct Node<'a> {
|
|
pub schema: std::sync::Arc<crate::database::schema::Schema>,
|
|
pub parent_alias: String,
|
|
pub parent_type_aliases: Option<std::sync::Arc<std::collections::HashMap<String, String>>>,
|
|
pub parent_type: Option<&'a crate::database::r#type::Type>,
|
|
pub property_name: Option<String>,
|
|
pub depth: usize,
|
|
pub stem_path: String,
|
|
}
|
|
|
|
impl<'a> Compiler<'a> {
|
|
/// Compiles a JSON schema into a nested PostgreSQL query returning JSONB
|
|
pub fn compile(
|
|
&self,
|
|
schema_id: &str,
|
|
stem_path: Option<&str>,
|
|
filter_keys: &[String],
|
|
) -> Result<String, String> {
|
|
let schema = self
|
|
.db
|
|
.schemas
|
|
.get(schema_id)
|
|
.ok_or_else(|| format!("Schema not found: {}", schema_id))?;
|
|
|
|
let target_schema = if let Some(path) = stem_path.filter(|p| !p.is_empty() && *p != "/") {
|
|
if let Some(stems_map) = self.db.stems.get(schema_id) {
|
|
if let Some(stem) = stems_map.get(path) {
|
|
stem.schema.clone()
|
|
} else {
|
|
return Err(format!(
|
|
"Stem entity type '{}' not found in schema '{}'",
|
|
path, schema_id
|
|
));
|
|
}
|
|
} else {
|
|
return Err(format!(
|
|
"Stem entity type '{}' not found in schema '{}'",
|
|
path, schema_id
|
|
));
|
|
}
|
|
} else {
|
|
std::sync::Arc::new(schema.clone())
|
|
};
|
|
|
|
let is_stem_query = stem_path.is_some();
|
|
|
|
let mut compiler = Compiler {
|
|
db: &self.db,
|
|
filter_keys,
|
|
is_stem_query,
|
|
alias_counter: 0,
|
|
};
|
|
|
|
let node = Node {
|
|
schema: target_schema,
|
|
parent_alias: "t1".to_string(),
|
|
parent_type_aliases: None,
|
|
parent_type: None,
|
|
property_name: None,
|
|
depth: 0,
|
|
stem_path: String::new(),
|
|
};
|
|
|
|
let (sql, _) = compiler.compile_node(node)?;
|
|
Ok(sql)
|
|
}
|
|
|
|
/// Recursively walks the schema AST emitting native PostgreSQL jsonb mapping
|
|
/// Returns a tuple of (SQL_String, Field_Type)
|
|
fn compile_node(&mut self, node: Node<'a>) -> Result<(String, String), String> {
|
|
// Determine the base schema type (could be an array, object, or literal)
|
|
match &node.schema.obj.type_ {
|
|
Some(crate::database::schema::SchemaTypeOrArray::Single(t)) if t == "array" => {
|
|
self.compile_array(node)
|
|
}
|
|
_ => self.compile_reference(node),
|
|
}
|
|
}
|
|
|
|
fn compile_array(&mut self, node: Node<'a>) -> Result<(String, String), String> {
|
|
if let Some(items) = &node.schema.obj.items {
|
|
let next_path = if node.stem_path.is_empty() {
|
|
String::from("#")
|
|
} else {
|
|
format!("{}.#", node.stem_path)
|
|
};
|
|
|
|
if let Some(ref_id) = &items.obj.r#ref {
|
|
if let Some(type_def) = self.db.types.get(ref_id) {
|
|
let mut entity_noke = node.clone();
|
|
entity_noke.stem_path = next_path;
|
|
entity_noke.schema = std::sync::Arc::clone(items);
|
|
return self.compile_entity(type_def, entity_noke, true);
|
|
}
|
|
}
|
|
|
|
let mut next_node = node.clone();
|
|
next_node.depth += 1;
|
|
next_node.stem_path = next_path;
|
|
next_node.schema = std::sync::Arc::clone(items);
|
|
let (item_sql, _) = self.compile_node(next_node)?;
|
|
return Ok((
|
|
format!("(SELECT jsonb_agg({}) FROM TODO)", item_sql),
|
|
"array".to_string(),
|
|
));
|
|
}
|
|
|
|
Ok((
|
|
"SELECT jsonb_agg(TODO) FROM TODO".to_string(),
|
|
"array".to_string(),
|
|
))
|
|
}
|
|
|
|
fn compile_reference(&mut self, node: Node<'a>) -> Result<(String, String), String> {
|
|
// Determine if this schema represents a Database Entity
|
|
let mut resolved_type = None;
|
|
|
|
if let Some(family_target) = node.schema.obj.family.as_ref() {
|
|
resolved_type = self.db.types.get(family_target);
|
|
} else if let Some(lookup_key) = node
|
|
.schema
|
|
.obj
|
|
.id
|
|
.as_ref()
|
|
.or(node.schema.obj.r#ref.as_ref())
|
|
{
|
|
let base_type_name = lookup_key.split('.').next_back().unwrap_or("").to_string();
|
|
resolved_type = self.db.types.get(&base_type_name);
|
|
}
|
|
|
|
if let Some(type_def) = resolved_type {
|
|
return self.compile_entity(type_def, node.clone(), false);
|
|
}
|
|
|
|
// Handle Direct Refs
|
|
if let Some(ref_id) = &node.schema.obj.r#ref {
|
|
// If it's just an ad-hoc struct ref, we should resolve it
|
|
if let Some(target_schema) = self.db.schemas.get(ref_id) {
|
|
let mut ref_node = node.clone();
|
|
ref_node.schema = std::sync::Arc::new(target_schema.clone());
|
|
return self.compile_node(ref_node);
|
|
}
|
|
return Err(format!("Unresolved $ref: {}", ref_id));
|
|
}
|
|
// Handle $family Polymorphism fallbacks for relations
|
|
if let Some(family_target) = &node.schema.obj.family {
|
|
let base_type_name = family_target
|
|
.split('.')
|
|
.next_back()
|
|
.unwrap_or(family_target)
|
|
.to_string();
|
|
|
|
if let Some(type_def) = self.db.types.get(&base_type_name) {
|
|
if type_def.variations.len() == 1 {
|
|
let mut bypass_schema = crate::database::schema::Schema::default();
|
|
bypass_schema.obj.r#ref = Some(family_target.clone());
|
|
let mut bypass_node = node.clone();
|
|
bypass_node.schema = std::sync::Arc::new(bypass_schema);
|
|
return self.compile_node(bypass_node);
|
|
}
|
|
|
|
let mut sorted_variations: Vec<String> = type_def.variations.iter().cloned().collect();
|
|
sorted_variations.sort();
|
|
|
|
let mut family_schemas = Vec::new();
|
|
for variation in &sorted_variations {
|
|
let mut ref_schema = crate::database::schema::Schema::default();
|
|
ref_schema.obj.r#ref = Some(variation.clone());
|
|
family_schemas.push(std::sync::Arc::new(ref_schema));
|
|
}
|
|
|
|
return self.compile_one_of(&family_schemas, node);
|
|
}
|
|
}
|
|
|
|
// Handle oneOf Polymorphism fallbacks for relations
|
|
if let Some(one_of) = &node.schema.obj.one_of {
|
|
return self.compile_one_of(one_of, node.clone());
|
|
}
|
|
|
|
// Just an inline object definition?
|
|
if let Some(props) = &node.schema.obj.properties {
|
|
return self.compile_object(props, node.clone());
|
|
}
|
|
|
|
// Literal fallback
|
|
Ok((
|
|
format!(
|
|
"{}.{}",
|
|
node.parent_alias,
|
|
node.property_name.as_deref().unwrap_or("unknown_prop")
|
|
),
|
|
"string".to_string(),
|
|
))
|
|
}
|
|
|
|
fn compile_entity(
|
|
&mut self,
|
|
r#type: &'a crate::database::r#type::Type,
|
|
node: Node<'a>,
|
|
is_array: bool,
|
|
) -> Result<(String, String), String> {
|
|
let (table_aliases, from_clauses) = self.compile_from_clause(r#type);
|
|
|
|
// 2. Map properties and build jsonb_build_object args
|
|
let mut select_args = self.compile_select_clause(r#type, &table_aliases, node.clone())?;
|
|
|
|
// 2.5 Inject polymorphism directly into the query object
|
|
let mut poly_args = self.compile_polymorphism_select(r#type, &table_aliases, node.clone())?;
|
|
select_args.append(&mut poly_args);
|
|
|
|
let jsonb_obj_sql = if select_args.is_empty() {
|
|
"jsonb_build_object()".to_string()
|
|
} else {
|
|
format!("jsonb_build_object({})", select_args.join(", "))
|
|
};
|
|
|
|
// 3. Build WHERE clauses
|
|
let where_clauses = self.compile_where_clause(r#type, &table_aliases, node)?;
|
|
|
|
let selection = if is_array {
|
|
format!("COALESCE(jsonb_agg({}), '[]'::jsonb)", jsonb_obj_sql)
|
|
} else {
|
|
jsonb_obj_sql
|
|
};
|
|
|
|
let full_sql = format!(
|
|
"(SELECT {} FROM {} WHERE {})",
|
|
selection,
|
|
from_clauses.join(" "),
|
|
where_clauses.join(" AND ")
|
|
);
|
|
|
|
Ok((
|
|
full_sql,
|
|
if is_array {
|
|
"array".to_string()
|
|
} else {
|
|
"object".to_string()
|
|
},
|
|
))
|
|
}
|
|
|
|
fn compile_polymorphism_select(
|
|
&mut self,
|
|
r#type: &'a crate::database::r#type::Type,
|
|
table_aliases: &std::collections::HashMap<String, String>,
|
|
node: Node<'a>,
|
|
) -> Result<Vec<String>, String> {
|
|
let mut select_args = Vec::new();
|
|
|
|
if let Some(family_target) = node.schema.obj.family.as_ref() {
|
|
let base_type_name = family_target
|
|
.split('.')
|
|
.next_back()
|
|
.unwrap_or(family_target)
|
|
.to_string();
|
|
|
|
if let Some(fam_type_def) = self.db.types.get(&base_type_name) {
|
|
if fam_type_def.variations.len() == 1 {
|
|
let mut bypass_schema = crate::database::schema::Schema::default();
|
|
bypass_schema.obj.r#ref = Some(family_target.clone());
|
|
|
|
let mut bypass_node = node.clone();
|
|
bypass_node.schema = std::sync::Arc::new(bypass_schema);
|
|
|
|
let mut bypassed_args =
|
|
self.compile_select_clause(r#type, table_aliases, bypass_node)?;
|
|
select_args.append(&mut bypassed_args);
|
|
} else {
|
|
let mut family_schemas = Vec::new();
|
|
let mut sorted_fam_variations: Vec<String> =
|
|
fam_type_def.variations.iter().cloned().collect();
|
|
sorted_fam_variations.sort();
|
|
|
|
for variation in &sorted_fam_variations {
|
|
let mut ref_schema = crate::database::schema::Schema::default();
|
|
ref_schema.obj.r#ref = Some(variation.clone());
|
|
family_schemas.push(std::sync::Arc::new(ref_schema));
|
|
}
|
|
|
|
let base_alias = table_aliases
|
|
.get(&r#type.name)
|
|
.cloned()
|
|
.unwrap_or_else(|| node.parent_alias.to_string());
|
|
select_args.push(format!("'id', {}.id", base_alias));
|
|
let mut case_node = node.clone();
|
|
case_node.parent_alias = base_alias.clone();
|
|
let arc_aliases = std::sync::Arc::new(table_aliases.clone());
|
|
case_node.parent_type_aliases = Some(arc_aliases);
|
|
|
|
let (case_sql, _) = self.compile_one_of(&family_schemas, case_node)?;
|
|
select_args.push(format!("'type', {}", case_sql));
|
|
}
|
|
}
|
|
} else if let Some(one_of) = &node.schema.obj.one_of {
|
|
let base_alias = table_aliases
|
|
.get(&r#type.name)
|
|
.cloned()
|
|
.unwrap_or_else(|| node.parent_alias.to_string());
|
|
select_args.push(format!("'id', {}.id", base_alias));
|
|
let mut case_node = node.clone();
|
|
case_node.parent_alias = base_alias.clone();
|
|
let arc_aliases = std::sync::Arc::new(table_aliases.clone());
|
|
case_node.parent_type_aliases = Some(arc_aliases);
|
|
|
|
let (case_sql, _) = self.compile_one_of(one_of, case_node)?;
|
|
select_args.push(format!("'type', {}", case_sql));
|
|
}
|
|
|
|
Ok(select_args)
|
|
}
|
|
|
|
fn compile_object(
|
|
&mut self,
|
|
props: &std::collections::BTreeMap<String, std::sync::Arc<crate::database::schema::Schema>>,
|
|
node: Node<'a>,
|
|
) -> Result<(String, String), String> {
|
|
let mut build_args = Vec::new();
|
|
for (k, v) in props {
|
|
let next_path = if node.stem_path.is_empty() {
|
|
k.clone()
|
|
} else {
|
|
format!("{}.{}", node.stem_path, k)
|
|
};
|
|
|
|
let mut child_node = node.clone();
|
|
child_node.property_name = Some(k.clone());
|
|
child_node.depth += 1;
|
|
child_node.stem_path = next_path;
|
|
child_node.schema = std::sync::Arc::clone(v);
|
|
|
|
let (child_sql, val_type) = self.compile_node(child_node)?;
|
|
if val_type == "abort" {
|
|
continue;
|
|
}
|
|
build_args.push(format!("'{}', {}", k, child_sql));
|
|
}
|
|
let combined = format!("jsonb_build_object({})", build_args.join(", "));
|
|
Ok((combined, "object".to_string()))
|
|
}
|
|
|
|
fn compile_one_of(
|
|
&mut self,
|
|
schemas: &[Arc<crate::database::schema::Schema>],
|
|
node: Node<'a>,
|
|
) -> Result<(String, String), String> {
|
|
let mut case_statements = Vec::new();
|
|
let type_col = if let Some(prop) = &node.property_name {
|
|
format!("{}_type", prop)
|
|
} else {
|
|
"type".to_string()
|
|
};
|
|
|
|
for option_schema in schemas {
|
|
if let Some(ref_id) = &option_schema.obj.r#ref {
|
|
// Find the physical type this ref maps to
|
|
let base_type_name = ref_id.split('.').next_back().unwrap_or("").to_string();
|
|
|
|
// Generate the nested SQL for this specific target type
|
|
let mut child_node = node.clone();
|
|
child_node.schema = std::sync::Arc::clone(option_schema);
|
|
let (val_sql, _) = self.compile_node(child_node)?;
|
|
|
|
case_statements.push(format!(
|
|
"WHEN {}.{} = '{}' THEN ({})",
|
|
node.parent_alias, type_col, base_type_name, val_sql
|
|
));
|
|
}
|
|
}
|
|
|
|
if case_statements.is_empty() {
|
|
return Ok(("NULL".to_string(), "string".to_string()));
|
|
}
|
|
|
|
case_statements.sort();
|
|
|
|
let sql = format!("CASE {} ELSE NULL END", case_statements.join(" "));
|
|
|
|
Ok((sql, "object".to_string()))
|
|
}
|
|
|
|
fn compile_from_clause(
|
|
&mut self,
|
|
r#type: &crate::database::r#type::Type,
|
|
) -> (std::collections::HashMap<String, String>, Vec<String>) {
|
|
let mut table_aliases = std::collections::HashMap::new();
|
|
let mut from_clauses = Vec::new();
|
|
|
|
for (i, table_name) in r#type.hierarchy.iter().enumerate() {
|
|
self.alias_counter += 1;
|
|
let alias = format!("{}_{}", table_name, self.alias_counter);
|
|
table_aliases.insert(table_name.clone(), alias.clone());
|
|
|
|
if i == 0 {
|
|
from_clauses.push(format!("agreego.{} {}", table_name, alias));
|
|
} else {
|
|
let prev_alias = format!("{}_{}", r#type.hierarchy[i - 1], self.alias_counter - 1);
|
|
from_clauses.push(format!(
|
|
"JOIN agreego.{} {} ON {}.id = {}.id",
|
|
table_name, alias, alias, prev_alias
|
|
));
|
|
}
|
|
}
|
|
(table_aliases, from_clauses)
|
|
}
|
|
|
|
fn compile_select_clause(
|
|
&mut self,
|
|
r#type: &'a crate::database::r#type::Type,
|
|
table_aliases: &std::collections::HashMap<String, String>,
|
|
node: Node<'a>,
|
|
) -> Result<Vec<String>, String> {
|
|
let mut select_args = Vec::new();
|
|
let grouped_fields = r#type.grouped_fields.as_ref().and_then(|v| v.as_object());
|
|
let merged_props = self.get_merged_properties(node.schema.as_ref());
|
|
let mut sorted_keys: Vec<&String> = merged_props.keys().collect();
|
|
sorted_keys.sort();
|
|
|
|
for prop_key in sorted_keys {
|
|
let prop_schema = &merged_props[prop_key];
|
|
|
|
let is_object_or_array = match &prop_schema.obj.type_ {
|
|
Some(crate::database::schema::SchemaTypeOrArray::Single(s)) => {
|
|
s == "object" || s == "array"
|
|
}
|
|
Some(crate::database::schema::SchemaTypeOrArray::Multiple(v)) => {
|
|
v.contains(&"object".to_string()) || v.contains(&"array".to_string())
|
|
}
|
|
_ => false,
|
|
};
|
|
|
|
let is_primitive = prop_schema.obj.r#ref.is_none()
|
|
&& !is_object_or_array
|
|
&& prop_schema.obj.family.is_none()
|
|
&& prop_schema.obj.one_of.is_none();
|
|
|
|
if is_primitive {
|
|
if let Some(ft) = r#type.field_types.as_ref().and_then(|v| v.as_object()) {
|
|
if !ft.contains_key(prop_key) {
|
|
continue; // Skip frontend virtual properties missing from physical table fields
|
|
}
|
|
}
|
|
}
|
|
|
|
let mut owner_alias = table_aliases
|
|
.get("entity")
|
|
.cloned()
|
|
.unwrap_or_else(|| format!("{}_t_err", node.parent_alias));
|
|
|
|
if let Some(gf) = grouped_fields {
|
|
for (t_name, fields_val) in gf {
|
|
if let Some(fields_arr) = fields_val.as_array() {
|
|
if fields_arr.iter().any(|v| v.as_str() == Some(prop_key)) {
|
|
owner_alias = table_aliases
|
|
.get(t_name)
|
|
.cloned()
|
|
.unwrap_or_else(|| node.parent_alias.to_string());
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
let mut child_node = node.clone();
|
|
child_node.parent_alias = owner_alias.clone();
|
|
let arc_aliases = std::sync::Arc::new(table_aliases.clone());
|
|
child_node.parent_type_aliases = Some(arc_aliases);
|
|
child_node.parent_type = Some(r#type);
|
|
child_node.property_name = Some(prop_key.clone());
|
|
child_node.depth += 1;
|
|
let next_path = if node.stem_path.is_empty() {
|
|
prop_key.clone()
|
|
} else {
|
|
format!("{}.{}", node.stem_path, prop_key)
|
|
};
|
|
|
|
child_node.stem_path = next_path;
|
|
child_node.schema = std::sync::Arc::clone(prop_schema);
|
|
|
|
let (val_sql, val_type) = self.compile_node(child_node)?;
|
|
|
|
if val_type != "abort" {
|
|
select_args.push(format!("'{}', {}", prop_key, val_sql));
|
|
}
|
|
}
|
|
Ok(select_args)
|
|
}
|
|
|
|
fn compile_where_clause(
|
|
&self,
|
|
r#type: &'a crate::database::r#type::Type,
|
|
type_aliases: &std::collections::HashMap<String, String>,
|
|
node: Node<'a>,
|
|
) -> Result<Vec<String>, String> {
|
|
let base_alias = type_aliases
|
|
.get(&r#type.name)
|
|
.cloned()
|
|
.unwrap_or_else(|| "err".to_string());
|
|
|
|
let entity_alias = type_aliases
|
|
.get("entity")
|
|
.cloned()
|
|
.unwrap_or_else(|| base_alias.clone());
|
|
|
|
let mut where_clauses = Vec::new();
|
|
where_clauses.push(format!("NOT {}.archived", entity_alias));
|
|
|
|
self.compile_filter_conditions(r#type, type_aliases, &node, &base_alias, &mut where_clauses);
|
|
self.compile_relation_conditions(r#type, type_aliases, &node, &base_alias, &mut where_clauses)?;
|
|
|
|
Ok(where_clauses)
|
|
}
|
|
|
|
fn resolve_filter_alias(
|
|
r#type: &crate::database::r#type::Type,
|
|
type_aliases: &std::collections::HashMap<String, String>,
|
|
base_alias: &str,
|
|
field_name: &str,
|
|
) -> String {
|
|
if let Some(gf) = r#type.grouped_fields.as_ref().and_then(|v| v.as_object()) {
|
|
for (t_name, fields_val) in gf {
|
|
if let Some(fields_arr) = fields_val.as_array() {
|
|
if fields_arr.iter().any(|v| v.as_str() == Some(field_name)) {
|
|
return type_aliases.get(t_name).cloned().unwrap_or_else(|| base_alias.to_string());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
base_alias.to_string()
|
|
}
|
|
|
|
fn determine_sql_cast_and_op(
|
|
r#type: &crate::database::r#type::Type,
|
|
node: &Node,
|
|
field_name: &str,
|
|
) -> (&'static str, bool) {
|
|
let mut is_ilike = false;
|
|
let mut cast = "";
|
|
|
|
if let Some(field_types) = r#type.field_types.as_ref().and_then(|v| v.as_object()) {
|
|
if let Some(pg_type_val) = field_types.get(field_name) {
|
|
if let Some(pg_type) = pg_type_val.as_str() {
|
|
if pg_type == "uuid" {
|
|
cast = "::uuid";
|
|
} else if pg_type == "boolean" || pg_type == "bool" {
|
|
cast = "::boolean";
|
|
} else if pg_type.contains("timestamp") || pg_type == "timestamptz" || pg_type == "date" {
|
|
cast = "::timestamptz";
|
|
} else if pg_type == "numeric"
|
|
|| pg_type.contains("int")
|
|
|| pg_type == "real"
|
|
|| pg_type == "double precision"
|
|
{
|
|
cast = "::numeric";
|
|
} else if pg_type == "text" || pg_type.contains("char") {
|
|
let mut is_enum = false;
|
|
if let Some(props) = &node.schema.obj.properties {
|
|
if let Some(ps) = props.get(field_name) {
|
|
is_enum = ps.obj.enum_.is_some();
|
|
}
|
|
}
|
|
if !is_enum {
|
|
is_ilike = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
(cast, is_ilike)
|
|
}
|
|
|
|
fn compile_filter_conditions(
|
|
&self,
|
|
r#type: &crate::database::r#type::Type,
|
|
type_aliases: &std::collections::HashMap<String, String>,
|
|
node: &Node,
|
|
base_alias: &str,
|
|
where_clauses: &mut Vec<String>,
|
|
) {
|
|
for (i, filter_key) in self.filter_keys.iter().enumerate() {
|
|
let mut parts = filter_key.split(':');
|
|
let full_field_path = parts.next().unwrap_or(filter_key);
|
|
let op = parts.next().unwrap_or("$eq");
|
|
|
|
let field_name = if node.stem_path.is_empty() {
|
|
if full_field_path.contains('.') || full_field_path.contains('#') {
|
|
continue;
|
|
}
|
|
full_field_path
|
|
} else {
|
|
let prefix = format!("{}.", node.stem_path);
|
|
if full_field_path.starts_with(&prefix) {
|
|
let remainder = &full_field_path[prefix.len()..];
|
|
if remainder.contains('.') || remainder.contains('#') {
|
|
continue;
|
|
}
|
|
remainder
|
|
} else {
|
|
continue;
|
|
}
|
|
};
|
|
|
|
let filter_alias = Self::resolve_filter_alias(r#type, type_aliases, base_alias, field_name);
|
|
let (cast, is_ilike) = Self::determine_sql_cast_and_op(r#type, node, field_name);
|
|
|
|
let param_index = i + 1;
|
|
let p_val = format!("${}#>>'{{}}'", param_index);
|
|
|
|
if op == "$in" || op == "$nin" {
|
|
let sql_op = if op == "$in" { "IN" } else { "NOT IN" };
|
|
let subquery = format!(
|
|
"(SELECT value{} FROM jsonb_array_elements_text(({})::jsonb))",
|
|
cast, p_val
|
|
);
|
|
where_clauses.push(format!(
|
|
"{}.{} {} {}",
|
|
filter_alias, field_name, sql_op, subquery
|
|
));
|
|
} else {
|
|
let sql_op = match op {
|
|
"$eq" => if is_ilike { "ILIKE" } else { "=" },
|
|
"$ne" => if is_ilike { "NOT ILIKE" } else { "!=" },
|
|
"$gt" => ">",
|
|
"$gte" => ">=",
|
|
"$lt" => "<",
|
|
"$lte" => "<=",
|
|
_ => if is_ilike { "ILIKE" } else { "=" },
|
|
};
|
|
|
|
let param_sql = if is_ilike && (op == "$eq" || op == "$ne") {
|
|
p_val
|
|
} else {
|
|
format!("({}){}", p_val, cast)
|
|
};
|
|
|
|
where_clauses.push(format!(
|
|
"{}.{} {} {}",
|
|
filter_alias, field_name, sql_op, param_sql
|
|
));
|
|
}
|
|
}
|
|
}
|
|
|
|
fn compile_relation_conditions(
|
|
&self,
|
|
r#type: &crate::database::r#type::Type,
|
|
type_aliases: &std::collections::HashMap<String, String>,
|
|
node: &Node,
|
|
base_alias: &str,
|
|
where_clauses: &mut Vec<String>,
|
|
) -> Result<(), String> {
|
|
if let Some(prop_ref) = &node.property_name {
|
|
let prop = prop_ref.as_str();
|
|
let mut parent_relation_alias = node.parent_alias.clone();
|
|
let mut child_relation_alias = base_alias.to_string();
|
|
|
|
if let Some(parent_type) = node.parent_type {
|
|
let merged_props = self.get_merged_properties(node.schema.as_ref());
|
|
let relative_keys: Vec<String> = merged_props.keys().cloned().collect();
|
|
|
|
let (relation, is_parent_source) = self
|
|
.db
|
|
.get_relation(&parent_type.name, &r#type.name, prop, Some(&relative_keys))
|
|
.ok_or_else(|| {
|
|
format!(
|
|
"Could not dynamically resolve database relation mapping for {} -> {} on property {}",
|
|
parent_type.name, r#type.name, prop
|
|
)
|
|
})?;
|
|
|
|
let source_col = &relation.source_columns[0];
|
|
let dest_col = &relation.destination_columns[0];
|
|
|
|
if let Some(pta) = &node.parent_type_aliases {
|
|
let p_search_type = if is_parent_source {
|
|
&relation.source_type
|
|
} else {
|
|
&relation.destination_type
|
|
};
|
|
if let Some(a) = pta.get(p_search_type) {
|
|
parent_relation_alias = a.clone();
|
|
}
|
|
}
|
|
|
|
let c_search_type = if is_parent_source {
|
|
&relation.destination_type
|
|
} else {
|
|
&relation.source_type
|
|
};
|
|
if let Some(a) = type_aliases.get(c_search_type) {
|
|
child_relation_alias = a.clone();
|
|
}
|
|
|
|
let sql_string = if is_parent_source {
|
|
format!(
|
|
"{}.{} = {}.{}",
|
|
parent_relation_alias, source_col, child_relation_alias, dest_col
|
|
)
|
|
} else {
|
|
format!(
|
|
"{}.{} = {}.{}",
|
|
child_relation_alias, source_col, parent_relation_alias, dest_col
|
|
)
|
|
};
|
|
where_clauses.push(sql_string);
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn get_merged_properties(
|
|
&self,
|
|
schema: &crate::database::schema::Schema,
|
|
) -> std::collections::BTreeMap<String, Arc<crate::database::schema::Schema>> {
|
|
let mut props = std::collections::BTreeMap::new();
|
|
|
|
if let Some(ref_id) = &schema.obj.r#ref {
|
|
if let Some(parent_schema) = self.db.schemas.get(ref_id) {
|
|
props.extend(self.get_merged_properties(parent_schema));
|
|
}
|
|
}
|
|
|
|
if let Some(local_props) = &schema.obj.properties {
|
|
for (k, v) in local_props {
|
|
props.insert(k.clone(), v.clone());
|
|
}
|
|
}
|
|
|
|
props
|
|
}
|
|
}
|