queryer merger test progress

This commit is contained in:
2026-03-11 05:18:01 -04:00
parent 1c08a8f2b8
commit 44be75f5d4
104 changed files with 22563 additions and 18859 deletions

View File

@ -23,86 +23,34 @@ impl SqlCompiler {
.get(schema_id)
.ok_or_else(|| format!("Schema not found: {}", schema_id))?;
let resolved_arc;
let target_schema = if let Some(path) = stem_path.filter(|p| !p.is_empty() && *p != "/") {
self.resolve_stem(schema, path)?
if let Some(stems_map) = self.db.stems.get(schema_id) {
if let Some(stem) = stems_map.get(path) {
resolved_arc = 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
));
}
resolved_arc.as_ref()
} else {
schema
};
// 1. We expect the top level to typically be an Object or Array
let (sql, _) = self.walk_schema(target_schema, "t1", None, filter_keys)?;
// We expect the top level to typically be an Object or Array
let is_stem_query = stem_path.is_some();
let (sql, _) = self.walk_schema(target_schema, "t1", None, filter_keys, is_stem_query, 0)?;
Ok(sql)
}
fn resolve_stem<'a>(
&'a self,
mut schema: &'a crate::database::schema::Schema,
path: &str,
) -> Result<&'a crate::database::schema::Schema, String> {
let parts: Vec<&str> = path.trim_start_matches('/').split('/').collect();
for part in parts {
let mut current = schema;
let mut depth = 0;
while let Some(r) = &current.obj.r#ref {
if let Some(s) = self.db.schemas.get(r) {
current = s;
} else {
break;
}
depth += 1;
if depth > 20 {
break;
}
}
if current.obj.properties.is_none() && current.obj.items.is_some() {
if let Some(items) = &current.obj.items {
current = items;
let mut depth2 = 0;
while let Some(r) = &current.obj.r#ref {
if let Some(s) = self.db.schemas.get(r) {
current = s;
} else {
break;
}
depth2 += 1;
if depth2 > 20 {
break;
}
}
}
}
if let Some(props) = &current.obj.properties {
if let Some(next_schema) = props.get(part) {
schema = next_schema;
} else {
return Err(format!("Stem part '{}' not found in schema", part));
}
} else {
return Err(format!(
"Cannot resolve stem part '{}': not an object",
part
));
}
}
let mut current = schema;
let mut depth = 0;
while let Some(r) = &current.obj.r#ref {
if let Some(s) = self.db.schemas.get(r) {
current = s;
} else {
break;
}
depth += 1;
if depth > 20 {
break;
}
}
Ok(current)
}
/// Recursively walks the schema AST emitting native PostgreSQL jsonb mapping
/// Returns a tuple of (SQL_String, Field_Type)
fn walk_schema(
@ -111,6 +59,8 @@ impl SqlCompiler {
parent_alias: &str,
prop_name_context: Option<&str>,
filter_keys: &[String],
is_stem_query: bool,
depth: usize,
) -> Result<(String, String), String> {
// Determine the base schema type (could be an array, object, or literal)
match &schema.obj.type_ {
@ -119,6 +69,9 @@ impl SqlCompiler {
if let Some(items) = &schema.obj.items {
if let Some(ref_id) = &items.obj.r#ref {
if let Some(type_def) = self.db.types.get(ref_id) {
if is_stem_query && depth > 0 {
return Ok(("".to_string(), "abort".to_string()));
}
return self.compile_entity_node(
items,
type_def,
@ -126,11 +79,19 @@ impl SqlCompiler {
prop_name_context,
true,
filter_keys,
is_stem_query,
depth,
);
}
}
let (item_sql, _) =
self.walk_schema(items, parent_alias, prop_name_context, filter_keys)?;
let (item_sql, _) = self.walk_schema(
items,
parent_alias,
prop_name_context,
filter_keys,
is_stem_query,
depth + 1,
)?;
return Ok((
format!("(SELECT jsonb_agg({}) FROM TODO)", item_sql),
"array".to_string(),
@ -143,29 +104,57 @@ impl SqlCompiler {
))
}
_ => {
// Handle Objects & Direct Refs
if let Some(ref_id) = &schema.obj.r#ref {
// If it's a $ref, check if it points to an Entity Type
if let Some(type_def) = self.db.types.get(ref_id) {
return self.compile_entity_node(
schema,
type_def,
parent_alias,
prop_name_context,
false,
filter_keys,
);
// Determine if this schema represents a Database Entity
let mut resolved_type = None;
// Target is generally a specific schema (e.g. 'base.person'), but it tells us what physical
// database table hierarchy it maps to via the `schema.id` prefix/suffix convention.
if let Some(lookup_key) = schema.obj.id.as_ref().or(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 {
if is_stem_query && depth > 0 {
return Ok(("".to_string(), "abort".to_string()));
}
return self.compile_entity_node(
schema,
type_def,
parent_alias,
prop_name_context,
false,
filter_keys,
is_stem_query,
depth,
);
}
// Handle Direct Refs
if let Some(ref_id) = &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) {
return self.walk_schema(target_schema, parent_alias, prop_name_context, filter_keys);
return self.walk_schema(
target_schema,
parent_alias,
prop_name_context,
filter_keys,
is_stem_query,
depth,
);
}
return Err(format!("Unresolved $ref: {}", ref_id));
}
// Just an inline object definition?
if let Some(props) = &schema.obj.properties {
return self.compile_inline_object(props, parent_alias, filter_keys);
return self.compile_inline_object(
props,
parent_alias,
filter_keys,
is_stem_query,
depth,
);
}
// Literal fallback
@ -181,6 +170,27 @@ impl SqlCompiler {
}
}
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
}
fn compile_entity_node(
&self,
schema: &crate::database::schema::Schema,
@ -189,6 +199,8 @@ impl SqlCompiler {
prop_name: Option<&str>,
is_array: bool,
filter_keys: &[String],
is_stem_query: bool,
depth: usize,
) -> Result<(String, String), String> {
// We are compiling a query block for an Entity.
let mut select_args = Vec::new();
@ -220,35 +232,45 @@ impl SqlCompiler {
// grouped_fields is { "person": ["first_name", ...], "user": ["password"], ... }
let grouped_fields = type_def.grouped_fields.as_ref().and_then(|v| v.as_object());
if let Some(props) = &schema.obj.properties {
for (prop_key, prop_schema) in props {
// Find which table owns this property
// Find which table owns this property
let mut owner_alias = table_aliases
.get("entity")
.cloned()
.unwrap_or_else(|| format!("{}_t_err", parent_alias));
let merged_props = self.get_merged_properties(schema);
for (prop_key, prop_schema) in &merged_props {
// Find which table owns this property
// Find which table owns this property
let mut owner_alias = table_aliases
.get("entity")
.cloned()
.unwrap_or_else(|| format!("{}_t_err", 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(|| parent_alias.to_string());
break;
}
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(|| parent_alias.to_string());
break;
}
}
}
// Now we know `owner_alias`, e.g., `parent_t1` or `parent_t3`.
// Walk the property to get its SQL value
let (val_sql, _) =
self.walk_schema(prop_schema, &owner_alias, Some(prop_key), filter_keys)?;
select_args.push(format!("'{}', {}", prop_key, val_sql));
}
// Now we know `owner_alias`, e.g., `parent_t1` or `parent_t3`.
// Walk the property to get its SQL value
let (val_sql, val_type) = self.walk_schema(
prop_schema,
&owner_alias,
Some(prop_key),
filter_keys,
is_stem_query,
depth + 1,
)?;
if val_type == "abort" {
continue;
}
select_args.push(format!("'{}', {}", prop_key, val_sql));
}
let jsonb_obj_sql = if select_args.is_empty() {
@ -266,7 +288,7 @@ impl SqlCompiler {
where_clauses.push(format!("NOT {}.archived", base_alias));
// Filter Mapping - Only append filters if this is the ROOT table query (i.e. parent_alias is "t1")
// Because cue.filters operates strictly on top-level root properties right now.
if parent_alias == "t1" && prop_name.is_none() {
if parent_alias == "t1" {
for (i, filter_key) in filter_keys.iter().enumerate() {
// Find which table owns this filter key
let mut filter_alias = base_alias.clone(); // default to root table (e.g. t3 entity)
@ -288,23 +310,36 @@ impl SqlCompiler {
let mut is_ilike = false;
let mut cast = "";
// Check schema for filter_key to determine datatype operation
if let Some(props) = &schema.obj.properties {
if let Some(ps) = props.get(filter_key) {
let is_enum = ps.obj.enum_.is_some();
if let Some(crate::database::schema::SchemaTypeOrArray::Single(t)) = &ps.obj.type_ {
if t == "string" {
if ps.obj.format.as_deref() == Some("uuid") {
cast = "::uuid";
} else if ps.obj.format.as_deref() == Some("date-time") {
cast = "::timestamptz";
} else if !is_enum {
// Use PostgreSQL column type metadata for exact argument casting
if let Some(field_types) = type_def.field_types.as_ref().and_then(|v| v.as_object()) {
if let Some(pg_type_val) = field_types.get(filter_key) {
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") {
// Determine if this is an enum in the schema locally to avoid ILIKE on strict enums
let mut is_enum = false;
if let Some(props) = &schema.obj.properties {
if let Some(ps) = props.get(filter_key) {
is_enum = ps.obj.enum_.is_some();
}
}
if !is_enum {
is_ilike = true;
}
} else if t == "boolean" {
cast = "::boolean";
} else if t == "integer" || t == "number" {
cast = "::numeric";
}
}
}
@ -357,10 +392,22 @@ impl SqlCompiler {
props: &std::collections::BTreeMap<String, std::sync::Arc<crate::database::schema::Schema>>,
parent_alias: &str,
filter_keys: &[String],
is_stem_query: bool,
depth: usize,
) -> Result<(String, String), String> {
let mut build_args = Vec::new();
for (k, v) in props {
let (child_sql, _) = self.walk_schema(v, parent_alias, Some(k), filter_keys)?;
let (child_sql, val_type) = self.walk_schema(
v,
parent_alias,
Some(k),
filter_keys,
is_stem_query,
depth + 1,
)?;
if val_type == "abort" {
continue;
}
build_args.push(format!("'{}', {}", k, child_sql));
}
let combined = format!("jsonb_build_object({})", build_args.join(", "));