Skip to content

Commit 548cda1

Browse files
authored
impr (hql+core): nested ids and math ops (#845)
2 parents ca40ed2 + 3c89855 commit 548cda1

File tree

24 files changed

+3394
-237
lines changed

24 files changed

+3394
-237
lines changed

Cargo.lock

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

helix-cli/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "helix-cli"
3-
version = "2.2.6"
3+
version = "2.2.7"
44
edition = "2024"
55

66
[dependencies]

helix-db/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "helix-db"
3-
version = "1.2.6"
3+
version = "1.2.7"
44
edition = "2024"
55
description = "HelixDB is a powerful, open-source, graph-vector database built in Rust for intelligent data storage for RAG and AI."
66
license = "AGPL-3.0"

helix-db/src/grammar.pest

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,7 @@ embed_method = { "Embed" ~ "(" ~ (identifier | string_literal) ~ ")" }
234234
// ---------------------------------------------------------------------
235235
math_function_call = { math_function_name ~ "(" ~ function_args? ~ ")" }
236236
function_args = { math_expression ~ ("," ~ math_expression)* }
237-
math_expression = { math_function_call | evaluates_to_number | anonymous_traversal }
237+
math_expression = { math_function_call | id_traversal | evaluates_to_number | anonymous_traversal }
238238

239239
math_function_name = {
240240
// Arithmetic (binary)

helix-db/src/helixc/analyzer/methods/object_validation.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -405,10 +405,20 @@ fn validate_property_access<'a>(
405405
FieldValueType::Expression(expr) => {
406406
// Check if this expression contains a traversal
407407
use crate::helixc::analyzer::methods::traversal_validation::validate_traversal;
408-
use crate::helixc::generator::traversal_steps::NestedTraversalInfo;
408+
use crate::helixc::generator::traversal_steps::{ComputedExpressionInfo, NestedTraversalInfo};
409409
use crate::helixc::parser::types::ExpressionType;
410410

411-
if let ExpressionType::Traversal(tr) = &expr.expr {
411+
if let ExpressionType::MathFunctionCall(_) = &expr.expr {
412+
// Math function call - store as computed expression
413+
gen_traversal.computed_expressions.insert(
414+
field_addition.key.clone(),
415+
ComputedExpressionInfo {
416+
field_name: field_addition.key.clone(),
417+
expression: Box::new(expr.clone()),
418+
},
419+
);
420+
gen_traversal.object_fields.push(field_addition.key.clone());
421+
} else if let ExpressionType::Traversal(tr) = &expr.expr {
412422
// Nested traversal within expression - validate it
413423
let mut nested_gen_traversal =
414424
crate::helixc::generator::traversal_steps::Traversal::default();

helix-db/src/helixc/analyzer/methods/query_validation.rs

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -144,16 +144,16 @@ fn build_return_fields(
144144
None
145145
};
146146

147-
// If has_object_step, only add implicit fields if they're explicitly selected
147+
// If has_object_step, only add implicit fields if they're explicitly selected OR has_spread
148148
// Otherwise, add all implicit fields (default behavior)
149149
let should_add_field = |field_name: &str| {
150150
// Exclude if field is in excluded_fields
151151
if traversal.excluded_fields.contains(&field_name.to_string()) {
152152
return false;
153153
}
154-
// If has object step, only include if explicitly selected (possibly with remapping)
154+
// If has object step, only include if explicitly selected (possibly with remapping) OR has_spread
155155
if traversal.has_object_step {
156-
find_output_for_property(field_name).is_some()
156+
find_output_for_property(field_name).is_some() || traversal.has_spread
157157
} else {
158158
true
159159
}
@@ -176,8 +176,8 @@ fn build_return_fields(
176176
RustFieldType::Primitive(GenRef::RefLT("a", RustType::Str)),
177177
));
178178
}
179-
} else if !traversal.has_object_step {
180-
// No object step means return all fields
179+
} else if !traversal.has_object_step || traversal.has_spread {
180+
// No object step or has spread means return all fields
181181
fields.push(ReturnFieldInfo::new_implicit(
182182
"id".to_string(),
183183
RustFieldType::Primitive(GenRef::RefLT("a", RustType::Str)),
@@ -198,7 +198,7 @@ fn build_return_fields(
198198
RustFieldType::Primitive(GenRef::RefLT("a", RustType::Str)),
199199
));
200200
}
201-
} else if !traversal.has_object_step {
201+
} else if !traversal.has_object_step || traversal.has_spread {
202202
fields.push(ReturnFieldInfo::new_implicit(
203203
"label".to_string(),
204204
RustFieldType::Primitive(GenRef::RefLT("a", RustType::Str)),
@@ -222,7 +222,7 @@ fn build_return_fields(
222222
RustFieldType::Primitive(GenRef::RefLT("a", RustType::Str)),
223223
));
224224
}
225-
} else if !traversal.has_object_step {
225+
} else if !traversal.has_object_step || traversal.has_spread {
226226
fields.push(ReturnFieldInfo::new_implicit(
227227
"from_node".to_string(),
228228
RustFieldType::Primitive(GenRef::RefLT("a", RustType::Str)),
@@ -243,7 +243,7 @@ fn build_return_fields(
243243
RustFieldType::Primitive(GenRef::RefLT("a", RustType::Str)),
244244
));
245245
}
246-
} else if !traversal.has_object_step {
246+
} else if !traversal.has_object_step || traversal.has_spread {
247247
fields.push(ReturnFieldInfo::new_implicit(
248248
"to_node".to_string(),
249249
RustFieldType::Primitive(GenRef::RefLT("a", RustType::Str)),
@@ -265,7 +265,7 @@ fn build_return_fields(
265265
RustFieldType::RefArray(RustType::F64),
266266
));
267267
}
268-
} else if !traversal.has_object_step {
268+
} else if !traversal.has_object_step || traversal.has_spread {
269269
fields.push(ReturnFieldInfo::new_implicit(
270270
"data".to_string(),
271271
RustFieldType::RefArray(RustType::F64),
@@ -286,7 +286,7 @@ fn build_return_fields(
286286
RustFieldType::Primitive(GenRef::Std(RustType::F64)),
287287
));
288288
}
289-
} else if !traversal.has_object_step {
289+
} else if !traversal.has_object_step || traversal.has_spread {
290290
fields.push(ReturnFieldInfo::new_implicit(
291291
"score".to_string(),
292292
RustFieldType::Primitive(GenRef::Std(RustType::F64)),
@@ -321,6 +321,11 @@ fn build_return_fields(
321321
continue;
322322
}
323323

324+
// Skip if it's a computed expression (handled separately)
325+
if traversal.computed_expressions.contains_key(field_name) {
326+
continue;
327+
}
328+
324329
// Look up the actual property name from the mapping
325330
let property_name = traversal
326331
.field_name_mappings
@@ -734,6 +739,17 @@ fn build_return_fields(
734739
}
735740
}
736741

742+
// Step 4: Add computed expression fields
743+
for (field_name, computed_info) in &traversal.computed_expressions {
744+
fields.push(ReturnFieldInfo {
745+
name: field_name.clone(),
746+
field_type: ReturnFieldType::Simple(RustFieldType::Value),
747+
source: ReturnFieldSource::ComputedExpression {
748+
expression: computed_info.expression.clone(),
749+
},
750+
});
751+
}
752+
737753
fields
738754
}
739755

@@ -1004,6 +1020,7 @@ fn process_object_literal<'a>(
10041020
aggregate_properties: Vec::new(),
10051021
is_count_aggregate: false,
10061022
closure_param_name: None,
1023+
primitive_literal_value: None,
10071024
}
10081025
}
10091026

@@ -1336,7 +1353,7 @@ fn analyze_return_expr<'a>(
13361353
ReturnValue {
13371354
name: rust_type,
13381355
fields,
1339-
literal_value,
1356+
literal_value: literal_value.clone(),
13401357
},
13411358
));
13421359

@@ -1349,6 +1366,7 @@ fn analyze_return_expr<'a>(
13491366
let mut prim_struct = ReturnValueStruct::new(field_name.clone());
13501367
prim_struct.source_variable = field_name.clone();
13511368
prim_struct.is_primitive = true;
1369+
prim_struct.primitive_literal_value = literal_value;
13521370
query.return_structs.push(prim_struct);
13531371
} else {
13541372
let struct_name_prefix = format!(

0 commit comments

Comments
 (0)