Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 29d8dfb608 | |||
| 5b36ecf06c | |||
| 76467a6fed |
@ -24,10 +24,10 @@ To support high-throughput operations while allowing for runtime updates (e.g.,
|
|||||||
4. **Lock-Free Reads**: Incoming operations acquire a read lock just long enough to clone the `Arc` inside an `RwLock<Option<Arc<Validator>>>`, ensuring zero blocking during schema updates.
|
4. **Lock-Free Reads**: Incoming operations acquire a read lock just long enough to clone the `Arc` inside an `RwLock<Option<Arc<Validator>>>`, ensuring zero blocking during schema updates.
|
||||||
|
|
||||||
### Relational Edge Resolution
|
### Relational Edge Resolution
|
||||||
When compiling nested object graphs or arrays, the JSPG engine must dynamically infer which Postgres Foreign Key constraint correctly bridges the parent to the nested schema. It utilizes a strict 3-step hierarchical resolution:
|
When compiling nested object graphs or arrays, the JSPG engine must dynamically infer which Postgres Foreign Key constraint correctly bridges the parent to the nested schema. It utilizes a strict 3-step hierarchical resolution applied during the `OnceLock` Compilation phase:
|
||||||
1. **Direct Prefix Match**: If an explicitly prefixed Foreign Key (e.g. `fk_invoice_counterparty_entity` -> `prefix: "counterparty"`) matches the exact name of the requested schema property (e.g. `{"counterparty": {...}}`), it is instantly selected.
|
1. **Exact Prefix Match**: If an explicitly prefixed Foreign Key (e.g. `fk_invoice_counterparty_entity` -> `prefix: "counterparty"`) directly matches the name of the requested schema property (e.g. `{"counterparty": {...}}`), it is instantly selected.
|
||||||
2. **Base Edge Fallback (1:M)**: If no explicit prefix directly matches the property name, the compiler filters for explicitly one remaining relation with a `null` prefix (e.g. `fk_invoice_line_invoice` -> `prefix: null`). A `null` prefix mathematically denotes the standard structural parent-child ownership edge (bypassing any M:M ambiguity) and is safely picked over explicit (but unmatched) property edges.
|
2. **Ambiguity Elimination (M:M Twin Deduction)**: If multiple explicitly prefixed relations remain (which happens by design in Many-to-Many junction tables like `contact` or `role`), the compiler uses a process of elimination. It inspects the compiled child JSON schema AST to see which of the relational prefixes the child *natively consumes* as an explicit outbound property (e.g. `contact` natively defines `{ "target": ... }`). It considers that prefix arrow "used up" by the child, and mathematically deduces that its exact twin providing reverse ownership (`"source"`) MUST be the inbound link mapping from the parent. This logic relies on `OnceLock` recursive compilation to accurately peek at child structures.
|
||||||
3. **Ambiguity Elimination (M:M)**: If multiple explicitly prefixed relations remain (which happens by design in Many-to-Many junction tables like `contact` utilizing `fk_relationship_source` and `fk_relationship_target`), the compiler uses a process of elimination. It checks which of the prefix names the child schema *natively consumes* as an outbound property (e.g. `contact` defines `{ "target": ... }`). It considers that prefix "used up" and mathematically deduces the *remaining* explicitly prefixed relation (`"source"`) must be the inbound link from the parent.
|
3. **Implicit Base Fallback (1:M)**: If no explicit prefix matches, and M:M deduction fails, the compiler filters for exactly one remaining relation with a `null` prefix (e.g. `fk_invoice_line_invoice` -> `prefix: null`). A `null` prefix mathematically denotes the core structural parent-child ownership edge and is safely used as a fallback.
|
||||||
|
|
||||||
### Global API Reference
|
### Global API Reference
|
||||||
These functions operate on the global `GLOBAL_JSPG` engine instance and provide administrative boundaries:
|
These functions operate on the global `GLOBAL_JSPG` engine instance and provide administrative boundaries:
|
||||||
|
|||||||
0
agreego.sql
Normal file
0
agreego.sql
Normal file
2
flows
2
flows
Submodule flows updated: a7b0f5dc4d...4d61e13e00
@ -45,7 +45,7 @@ impl MockExecutor {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
impl DatabaseExecutor for MockExecutor {
|
impl DatabaseExecutor for MockExecutor {
|
||||||
fn query(&self, sql: &str, _args: Option<&[Value]>) -> Result<Value, String> {
|
fn query(&self, sql: &str, _args: Option<&[Value]>) -> Result<Value, String> {
|
||||||
println!("DEBUG SQL QUERY: {}", sql);
|
println!("JSPG_SQL: {}", sql);
|
||||||
MOCK_STATE.with(|state| {
|
MOCK_STATE.with(|state| {
|
||||||
let mut s = state.borrow_mut();
|
let mut s = state.borrow_mut();
|
||||||
s.captured_queries.push(sql.to_string());
|
s.captured_queries.push(sql.to_string());
|
||||||
@ -66,7 +66,7 @@ impl DatabaseExecutor for MockExecutor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn execute(&self, sql: &str, _args: Option<&[Value]>) -> Result<(), String> {
|
fn execute(&self, sql: &str, _args: Option<&[Value]>) -> Result<(), String> {
|
||||||
println!("DEBUG SQL EXECUTE: {}", sql);
|
println!("JSPG_SQL: {}", sql);
|
||||||
MOCK_STATE.with(|state| {
|
MOCK_STATE.with(|state| {
|
||||||
let mut s = state.borrow_mut();
|
let mut s = state.borrow_mut();
|
||||||
s.captured_queries.push(sql.to_string());
|
s.captured_queries.push(sql.to_string());
|
||||||
@ -170,7 +170,7 @@ fn parse_and_match_mocks(sql: &str, mocks: &[Value]) -> Option<Vec<Value>> {
|
|||||||
.unwrap_or("")
|
.unwrap_or("")
|
||||||
.trim_matches('"');
|
.trim_matches('"');
|
||||||
let right = part[eq_idx + 1..].trim().trim_matches('\'');
|
let right = part[eq_idx + 1..].trim().trim_matches('\'');
|
||||||
|
|
||||||
let mock_val_str = match mock_obj.get(left) {
|
let mock_val_str = match mock_obj.get(left) {
|
||||||
Some(Value::String(s)) => s.clone(),
|
Some(Value::String(s)) => s.clone(),
|
||||||
Some(Value::Number(n)) => n.to_string(),
|
Some(Value::Number(n)) => n.to_string(),
|
||||||
@ -189,12 +189,12 @@ fn parse_and_match_mocks(sql: &str, mocks: &[Value]) -> Option<Vec<Value>> {
|
|||||||
.last()
|
.last()
|
||||||
.unwrap_or("")
|
.unwrap_or("")
|
||||||
.trim_matches('"');
|
.trim_matches('"');
|
||||||
|
|
||||||
let mock_val_str = match mock_obj.get(left) {
|
let mock_val_str = match mock_obj.get(left) {
|
||||||
Some(Value::Null) => "null".to_string(),
|
Some(Value::Null) => "null".to_string(),
|
||||||
_ => "".to_string(),
|
_ => "".to_string(),
|
||||||
};
|
};
|
||||||
|
|
||||||
if mock_val_str != "null" {
|
if mock_val_str != "null" {
|
||||||
branch_matches = false;
|
branch_matches = false;
|
||||||
break;
|
break;
|
||||||
|
|||||||
@ -497,6 +497,10 @@ impl Schema {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Dynamically infers and compiles all structural database relationships between this Schema
|
||||||
|
/// and its nested children. This functions recursively traverses the JSON Schema abstract syntax
|
||||||
|
/// tree, identifies physical PostgreSQL table boundaries, and locks the resulting relation
|
||||||
|
/// constraint paths directly onto the `compiled_edges` map in O(1) memory.
|
||||||
pub fn compile_edges(
|
pub fn compile_edges(
|
||||||
&self,
|
&self,
|
||||||
db: &crate::database::Database,
|
db: &crate::database::Database,
|
||||||
@ -504,6 +508,9 @@ impl Schema {
|
|||||||
props: &std::collections::BTreeMap<String, std::sync::Arc<Schema>>,
|
props: &std::collections::BTreeMap<String, std::sync::Arc<Schema>>,
|
||||||
) -> std::collections::BTreeMap<String, crate::database::edge::Edge> {
|
) -> std::collections::BTreeMap<String, crate::database::edge::Edge> {
|
||||||
let mut schema_edges = std::collections::BTreeMap::new();
|
let mut schema_edges = std::collections::BTreeMap::new();
|
||||||
|
|
||||||
|
// Determine the physical Database Table Name this schema structurally represents
|
||||||
|
// Plucks the polymorphic discriminator via dot-notation (e.g. extracting "person" from "full.person")
|
||||||
let mut parent_type_name = None;
|
let mut parent_type_name = None;
|
||||||
if let Some(family) = &self.obj.family {
|
if let Some(family) = &self.obj.family {
|
||||||
parent_type_name = Some(family.split('.').next_back().unwrap_or(family).to_string());
|
parent_type_name = Some(family.split('.').next_back().unwrap_or(family).to_string());
|
||||||
@ -512,11 +519,14 @@ impl Schema {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some(p_type) = parent_type_name {
|
if let Some(p_type) = parent_type_name {
|
||||||
|
// Proceed only if the resolved table physically exists within the Postgres Type hierarchy
|
||||||
if db.types.contains_key(&p_type) {
|
if db.types.contains_key(&p_type) {
|
||||||
|
// Iterate over all discovered schema boundaries mapped inside the object
|
||||||
for (prop_name, prop_schema) in props {
|
for (prop_name, prop_schema) in props {
|
||||||
let mut child_type_name = None;
|
let mut child_type_name = None;
|
||||||
let mut target_schema = prop_schema.clone();
|
let mut target_schema = prop_schema.clone();
|
||||||
|
|
||||||
|
// Structurally unpack the inner target entity if the object maps to an array list
|
||||||
if let Some(crate::database::schema::SchemaTypeOrArray::Single(t)) =
|
if let Some(crate::database::schema::SchemaTypeOrArray::Single(t)) =
|
||||||
&prop_schema.obj.type_
|
&prop_schema.obj.type_
|
||||||
{
|
{
|
||||||
@ -527,6 +537,7 @@ impl Schema {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Determine the physical Postgres table backing the nested child schema recursively
|
||||||
if let Some(family) = &target_schema.obj.family {
|
if let Some(family) = &target_schema.obj.family {
|
||||||
child_type_name = Some(family.split('.').next_back().unwrap_or(family).to_string());
|
child_type_name = Some(family.split('.').next_back().unwrap_or(family).to_string());
|
||||||
} else if let Some(ref_id) = target_schema.obj.identifier() {
|
} else if let Some(ref_id) = target_schema.obj.identifier() {
|
||||||
@ -541,10 +552,14 @@ impl Schema {
|
|||||||
|
|
||||||
if let Some(c_type) = child_type_name {
|
if let Some(c_type) = child_type_name {
|
||||||
if db.types.contains_key(&c_type) {
|
if db.types.contains_key(&c_type) {
|
||||||
|
// Ensure the child Schema's AST has accurately compiled its own physical property keys so we can
|
||||||
|
// inject them securely for Many-to-Many Twin Deduction disambiguation matching.
|
||||||
target_schema.compile(db, visited);
|
target_schema.compile(db, visited);
|
||||||
if let Some(compiled_target_props) = target_schema.obj.compiled_properties.get() {
|
if let Some(compiled_target_props) = target_schema.obj.compiled_properties.get() {
|
||||||
let keys_for_ambiguity: Vec<String> =
|
let keys_for_ambiguity: Vec<String> =
|
||||||
compiled_target_props.keys().cloned().collect();
|
compiled_target_props.keys().cloned().collect();
|
||||||
|
|
||||||
|
// Interrogate the Database catalog graph to discover the exact Foreign Key Constraint connecting the components
|
||||||
if let Some((relation, is_forward)) =
|
if let Some((relation, is_forward)) =
|
||||||
resolve_relation(db, &p_type, &c_type, prop_name, Some(&keys_for_ambiguity))
|
resolve_relation(db, &p_type, &c_type, prop_name, Some(&keys_for_ambiguity))
|
||||||
{
|
{
|
||||||
@ -566,6 +581,8 @@ impl Schema {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Inspects the Postgres pg_constraint relations catalog to securely identify
|
||||||
|
/// the precise Foreign Key connecting a parent and child hierarchy path.
|
||||||
pub(crate) fn resolve_relation<'a>(
|
pub(crate) fn resolve_relation<'a>(
|
||||||
db: &'a crate::database::Database,
|
db: &'a crate::database::Database,
|
||||||
parent_type: &str,
|
parent_type: &str,
|
||||||
@ -573,6 +590,8 @@ pub(crate) fn resolve_relation<'a>(
|
|||||||
prop_name: &str,
|
prop_name: &str,
|
||||||
relative_keys: Option<&Vec<String>>,
|
relative_keys: Option<&Vec<String>>,
|
||||||
) -> Option<(&'a crate::database::relation::Relation, bool)> {
|
) -> Option<(&'a crate::database::relation::Relation, bool)> {
|
||||||
|
|
||||||
|
// Enforce graph locality by ensuring we don't accidentally crawl to pure structural entity boundaries
|
||||||
if parent_type == "entity" && child_type == "entity" {
|
if parent_type == "entity" && child_type == "entity" {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
@ -583,6 +602,9 @@ pub(crate) fn resolve_relation<'a>(
|
|||||||
let mut matching_rels = Vec::new();
|
let mut matching_rels = Vec::new();
|
||||||
let mut directions = Vec::new();
|
let mut directions = Vec::new();
|
||||||
|
|
||||||
|
// Scour the complete catalog for any Edge matching the inheritance scope of the two objects
|
||||||
|
// This automatically binds polymorphic structures (e.g. recognizing a relationship targeting User
|
||||||
|
// also natively binds instances specifically typed as Person).
|
||||||
for rel in db.relations.values() {
|
for rel in db.relations.values() {
|
||||||
let is_forward = p_def.hierarchy.contains(&rel.source_type)
|
let is_forward = p_def.hierarchy.contains(&rel.source_type)
|
||||||
&& c_def.hierarchy.contains(&rel.destination_type);
|
&& c_def.hierarchy.contains(&rel.destination_type);
|
||||||
@ -598,10 +620,12 @@ pub(crate) fn resolve_relation<'a>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Abort relation discovery early if no hierarchical inheritance match was found
|
||||||
if matching_rels.is_empty() {
|
if matching_rels.is_empty() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ideal State: The objects only share a solitary structural relation, resolving ambiguity instantly.
|
||||||
if matching_rels.len() == 1 {
|
if matching_rels.len() == 1 {
|
||||||
return Some((matching_rels[0], directions[0]));
|
return Some((matching_rels[0], directions[0]));
|
||||||
}
|
}
|
||||||
@ -609,6 +633,8 @@ pub(crate) fn resolve_relation<'a>(
|
|||||||
let mut chosen_idx = 0;
|
let mut chosen_idx = 0;
|
||||||
let mut resolved = false;
|
let mut resolved = false;
|
||||||
|
|
||||||
|
// Exact Prefix Disambiguation: Determine if the database specifically names this constraint
|
||||||
|
// directly mapping to the JSON Schema property name (e.g., `fk_{child}_{property_name}`)
|
||||||
for (i, rel) in matching_rels.iter().enumerate() {
|
for (i, rel) in matching_rels.iter().enumerate() {
|
||||||
if let Some(prefix) = &rel.prefix {
|
if let Some(prefix) = &rel.prefix {
|
||||||
if prop_name.starts_with(prefix)
|
if prop_name.starts_with(prefix)
|
||||||
@ -622,9 +648,11 @@ pub(crate) fn resolve_relation<'a>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Complex Subgraph Resolution: The database contains multiple equally explicit foreign key constraints
|
||||||
|
// linking these objects (such as pointing to `source` and `target` in Many-to-Many junction models).
|
||||||
if !resolved && relative_keys.is_some() {
|
if !resolved && relative_keys.is_some() {
|
||||||
// 1. M:M Disambiguation: The child schema explicitly defines an outbound property
|
// Twin Deduction Pass 1: We inspect the exact properties structurally defined inside the compiled payload
|
||||||
// matching one of the relational prefixes (e.g. "target"). We first identify that consumed relation.
|
// to observe which explicit relation arrow the child payload natively consumes.
|
||||||
let keys = relative_keys.unwrap();
|
let keys = relative_keys.unwrap();
|
||||||
let mut consumed_rel_idx = None;
|
let mut consumed_rel_idx = None;
|
||||||
for (i, rel) in matching_rels.iter().enumerate() {
|
for (i, rel) in matching_rels.iter().enumerate() {
|
||||||
@ -636,7 +664,8 @@ pub(crate) fn resolve_relation<'a>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Then, we find its exact Twin on the same junction boundary that provides the reverse ownership.
|
// Twin Deduction Pass 2: Knowing which arrow points outbound, we can mathematically deduce its twin
|
||||||
|
// providing the reverse ownership on the same junction boundary must be the incoming Edge to the parent.
|
||||||
if let Some(used_idx) = consumed_rel_idx {
|
if let Some(used_idx) = consumed_rel_idx {
|
||||||
let used_rel = matching_rels[used_idx];
|
let used_rel = matching_rels[used_idx];
|
||||||
let mut twin_ids = Vec::new();
|
let mut twin_ids = Vec::new();
|
||||||
@ -657,8 +686,9 @@ pub(crate) fn resolve_relation<'a>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Implicit Base Fallback: If no complex explicit paths resolve, but exactly one relation
|
||||||
|
// sits entirely naked (without a constraint prefix), it must be the core structural parent ownership.
|
||||||
if !resolved {
|
if !resolved {
|
||||||
// 2. Base 1:M Fallback. If there's EXACTLY ONE relation with a null prefix, it's the base structural edge.
|
|
||||||
let mut null_prefix_ids = Vec::new();
|
let mut null_prefix_ids = Vec::new();
|
||||||
for (i, rel) in matching_rels.iter().enumerate() {
|
for (i, rel) in matching_rels.iter().enumerate() {
|
||||||
if rel.prefix.is_none() {
|
if rel.prefix.is_none() {
|
||||||
@ -667,7 +697,6 @@ pub(crate) fn resolve_relation<'a>(
|
|||||||
}
|
}
|
||||||
if null_prefix_ids.len() == 1 {
|
if null_prefix_ids.len() == 1 {
|
||||||
chosen_idx = null_prefix_ids[0];
|
chosen_idx = null_prefix_ids[0];
|
||||||
// resolved = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -259,13 +259,7 @@ impl Merger {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if let Some(compiled_edges) = schema.obj.compiled_edges.get() {
|
if let Some(compiled_edges) = schema.obj.compiled_edges.get() {
|
||||||
println!(
|
|
||||||
"Compiled Edges keys for relation {}: {:?}",
|
|
||||||
relation_name,
|
|
||||||
compiled_edges.keys().collect::<Vec<_>>()
|
|
||||||
);
|
|
||||||
if let Some(edge) = compiled_edges.get(&relation_name) {
|
if let Some(edge) = compiled_edges.get(&relation_name) {
|
||||||
println!("FOUND EDGE {} -> {:?}", relation_name, edge.constraint);
|
|
||||||
if let Some(relation) = self.db.relations.get(&edge.constraint) {
|
if let Some(relation) = self.db.relations.get(&edge.constraint) {
|
||||||
let parent_is_source = edge.forward;
|
let parent_is_source = edge.forward;
|
||||||
|
|
||||||
|
|||||||
@ -67,7 +67,10 @@ impl<'a> Compiler<'a> {
|
|||||||
if let Some(items) = &node.schema.obj.items {
|
if let Some(items) = &node.schema.obj.items {
|
||||||
let mut resolved_type = None;
|
let mut resolved_type = None;
|
||||||
if let Some(family_target) = items.obj.family.as_ref() {
|
if let Some(family_target) = items.obj.family.as_ref() {
|
||||||
let base_type_name = family_target.split('.').next_back().unwrap_or(family_target);
|
let base_type_name = family_target
|
||||||
|
.split('.')
|
||||||
|
.next_back()
|
||||||
|
.unwrap_or(family_target);
|
||||||
resolved_type = self.db.types.get(base_type_name);
|
resolved_type = self.db.types.get(base_type_name);
|
||||||
} else if let Some(base_type_name) = items.obj.identifier() {
|
} else if let Some(base_type_name) = items.obj.identifier() {
|
||||||
resolved_type = self.db.types.get(&base_type_name);
|
resolved_type = self.db.types.get(&base_type_name);
|
||||||
@ -89,7 +92,10 @@ impl<'a> Compiler<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 3. Fallback for root execution of standalone non-entity arrays
|
// 3. Fallback for root execution of standalone non-entity arrays
|
||||||
Err("Cannot compile a root array without a valid entity reference or table mapped via `items`.".to_string())
|
Err(
|
||||||
|
"Cannot compile a root array without a valid entity reference or table mapped via `items`."
|
||||||
|
.to_string(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compile_reference(&mut self, node: Node<'a>) -> Result<(String, String), String> {
|
fn compile_reference(&mut self, node: Node<'a>) -> Result<(String, String), String> {
|
||||||
@ -452,7 +458,6 @@ impl<'a> Compiler<'a> {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
let (val_sql, val_type) = self.compile_node(child_node)?;
|
let (val_sql, val_type) = self.compile_node(child_node)?;
|
||||||
|
|
||||||
if val_type != "abort" {
|
if val_type != "abort" {
|
||||||
@ -515,7 +520,13 @@ impl<'a> Compiler<'a> {
|
|||||||
// Determine if the property schema resolves to a physical Database Entity
|
// Determine if the property schema resolves to a physical Database Entity
|
||||||
let mut bound_type_name = None;
|
let mut bound_type_name = None;
|
||||||
if let Some(family_target) = prop_schema.obj.family.as_ref() {
|
if let Some(family_target) = prop_schema.obj.family.as_ref() {
|
||||||
bound_type_name = Some(family_target.split('.').next_back().unwrap_or(family_target).to_string());
|
bound_type_name = Some(
|
||||||
|
family_target
|
||||||
|
.split('.')
|
||||||
|
.next_back()
|
||||||
|
.unwrap_or(family_target)
|
||||||
|
.to_string(),
|
||||||
|
);
|
||||||
} else if let Some(lookup_key) = prop_schema.obj.identifier() {
|
} else if let Some(lookup_key) = prop_schema.obj.identifier() {
|
||||||
bound_type_name = Some(lookup_key);
|
bound_type_name = Some(lookup_key);
|
||||||
}
|
}
|
||||||
@ -536,7 +547,10 @@ impl<'a> Compiler<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some(col) = poly_col {
|
if let Some(col) = poly_col {
|
||||||
if let Some(alias) = type_aliases.get(table_to_alias).or_else(|| type_aliases.get(&node.parent_alias)) {
|
if let Some(alias) = type_aliases
|
||||||
|
.get(table_to_alias)
|
||||||
|
.or_else(|| type_aliases.get(&node.parent_alias))
|
||||||
|
{
|
||||||
where_clauses.push(format!("{}.{} = '{}'", alias, col, type_name));
|
where_clauses.push(format!("{}.{} = '{}'", alias, col, type_name));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -710,8 +724,6 @@ impl<'a> Compiler<'a> {
|
|||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
if let Some(prop_ref) = &node.property_name {
|
if let Some(prop_ref) = &node.property_name {
|
||||||
let prop = prop_ref.as_str();
|
let prop = prop_ref.as_str();
|
||||||
println!("DEBUG: Eval prop: {}", prop);
|
|
||||||
|
|
||||||
let mut parent_relation_alias = node.parent_alias.clone();
|
let mut parent_relation_alias = node.parent_alias.clone();
|
||||||
let mut child_relation_alias = base_alias.to_string();
|
let mut child_relation_alias = base_alias.to_string();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user