Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion helix-db/src/grammar.pest
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ evaluates_to_number = {
// ---------------------------------------------------------------------
// Return statement
// ---------------------------------------------------------------------
return_stmt = { "RETURN" ~ evaluates_to_anything ~ ("," ~ evaluates_to_anything)* }
return_stmt = { "RETURN" ~ (evaluates_to_anything ~ ("," ~ evaluates_to_anything)* | array_creation | object_creation) }

// ---------------------------------------------------------------------
// Creation steps
Expand Down Expand Up @@ -226,6 +226,10 @@ exclude_field = { "!" ~ "{" ~ identifier ~ ("," ~ identifier)* ~ "}" }
closure_step = { "|" ~ identifier ~ "|" ~ object_step }
spread_object = { ".." ~ ","?}
mapping_field = { (identifier ~ (":" ~ (anonymous_traversal | evaluates_to_anything | object_step))) | identifier }
array_creation = { "[" ~ (identifier | object_creation ) ~ ("," ~ (identifier | object_creation))* ~ ","? ~ "]" }
object_creation = { "{" ~ object_inner ~ ("," ~ object_inner)* ~ ","? ~ "}" }
object_inner = { identifier ~ ":" ~ object_field }
object_field = { (object_creation | array_creation | evaluates_to_anything) }

// ---------------------------------------------------------------------
// Macros
Expand Down
12 changes: 8 additions & 4 deletions helix-db/src/helixc/analyzer/analyzer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ use crate::helixc::{
methods::{
migration_validation::validate_migration,
query_validation::validate_query,
schema_methods::{SchemaVersionMap, build_field_lookups, check_schema},
schema_methods::{build_field_lookups, check_schema, SchemaVersionMap},
},
types::Type,
},
generator::Source as GeneratedSource,
parser::helix_parser::{EdgeSchema, ExpressionType, Field, Query, Source},
parser::helix_parser::{EdgeSchema, ExpressionType, Field, Query, ReturnType, Source},
};
use itertools::Itertools;
use serde::Serialize;
Expand Down Expand Up @@ -250,8 +250,12 @@ impl QueryData {
.return_values
.iter()
.flat_map(|e| {
if let ExpressionType::Identifier(ident) = &e.expr {
Some(ident.clone())
if let ReturnType::Expression(expr) = e {
if let ExpressionType::Identifier(ident) = &expr.expr {
Some(ident.clone())
} else {
None
}
} else {
None
}
Expand Down
335 changes: 231 additions & 104 deletions helix-db/src/helixc/analyzer/methods/query_validation.rs

Large diffs are not rendered by default.

35 changes: 18 additions & 17 deletions helix-db/src/helixc/generator/queries.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,10 @@ impl Query {

fn print_query(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// prints the function signature
self.print_input_struct(f)?;
self.print_parameters(f)?;
if !self.parameters.is_empty() {
self.print_input_struct(f)?;
self.print_parameters(f)?;
}
self.print_handler(f)?;
writeln!(
f,
Expand All @@ -95,17 +97,19 @@ impl Query {

// print the db boilerplate
writeln!(f, "let db = Arc::clone(&input.graph.storage);")?;
match self.hoisted_embedding_calls.is_empty() {
true => writeln!(
f,
"let data = input.request.in_fmt.deserialize::<{}Input>(&input.request.body)?;",
self.name
)?,
false => writeln!(
f,
"let data = input.request.in_fmt.deserialize::<{}Input>(&input.request.body)?.into_owned();",
self.name
)?,
if !self.parameters.is_empty() {
match self.hoisted_embedding_calls.is_empty() {
true => writeln!(
f,
"let data = input.request.in_fmt.deserialize::<{}Input>(&input.request.body)?;",
self.name
)?,
false => writeln!(
f,
"let data = input.request.in_fmt.deserialize::<{}Input>(&input.request.body)?.into_owned();",
self.name
)?,
}
}

// print embedding calls
Expand Down Expand Up @@ -221,10 +225,7 @@ impl Query {
)?;

writeln!(f, "connection.iter = result.into_iter();")?;
writeln!(
f,
"let mut connections = connections.lock().unwrap();"
)?;
writeln!(f, "let mut connections = connections.lock().unwrap();")?;
writeln!(f, "connections.add_connection(connection);")?;
writeln!(f, "drop(connections);")?;
writeln!(
Expand Down
58 changes: 54 additions & 4 deletions helix-db/src/helixc/generator/return_values.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
use core::fmt;
use std::fmt::Display;
use std::{collections::HashMap, fmt::Display};

use crate::helixc::generator::{traversal_steps::Traversal, utils::GeneratedValue};


pub struct ReturnValue {
pub value: ReturnValueExpr,
pub return_type: ReturnType,
Expand Down Expand Up @@ -40,8 +39,25 @@ impl Display for ReturnValue {
)
}
ReturnType::UnnamedExpr => {
write!(f, "// need to implement unnamed return value\n todo!()")?;
panic!("Unnamed return value is not supported");
writeln!(
f,
" return_vals.insert(\"data\".to_string(), ReturnValue::from_traversal_value_array_with_mixin({}.clone(), remapping_vals.borrow_mut()));",
self.value
)
}
ReturnType::HashMap => {
writeln!(
f,
" return_vals.insert(\"data\".to_string(), ReturnValue::from({}));",
self.value
)
}
ReturnType::Array => {
writeln!(
f,
" return_vals.insert(\"data\".to_string(), ReturnValue::from({}));",
self.value
)
}
}
}
Expand All @@ -55,6 +71,8 @@ impl ReturnValue {
ReturnType::NamedExpr(name) => name.inner().inner().to_string(),
ReturnType::SingleExpr(name) => name.inner().inner().to_string(),
ReturnType::UnnamedExpr => todo!(),
ReturnType::HashMap => todo!(),
ReturnType::Array => todo!(),
Comment on lines 73 to +75
Copy link
Preview

Copilot AI Sep 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These todo!() macros will cause runtime panics. Consider implementing these cases or returning a default value with a comment explaining the limitation.

Suggested change
ReturnType::UnnamedExpr => todo!(),
ReturnType::HashMap => todo!(),
ReturnType::Array => todo!(),
// These variants do not have an associated name; return a default value.
ReturnType::UnnamedExpr | ReturnType::HashMap | ReturnType::Array => "data".to_string(),

Copilot uses AI. Check for mistakes.

}
}

Expand Down Expand Up @@ -88,6 +106,18 @@ impl ReturnValue {
return_type: ReturnType::UnnamedExpr,
}
}
pub fn new_array(values: Vec<ReturnValueExpr>) -> Self {
Self {
value: ReturnValueExpr::Array(values),
return_type: ReturnType::Array,
}
}
pub fn new_object(values: HashMap<String, ReturnValueExpr>) -> Self {
Self {
value: ReturnValueExpr::Object(values),
return_type: ReturnType::HashMap,
}
}
}

#[derive(Clone)]
Expand All @@ -97,19 +127,39 @@ pub enum ReturnType {
NamedExpr(GeneratedValue),
SingleExpr(GeneratedValue),
UnnamedExpr,
HashMap,
Array,
}
#[derive(Clone)]
pub enum ReturnValueExpr {
Traversal(Traversal),
Identifier(GeneratedValue),
Value(GeneratedValue),
Array(Vec<ReturnValueExpr>),
Object(HashMap<String, ReturnValueExpr>),
}
impl Display for ReturnValueExpr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ReturnValueExpr::Traversal(traversal) => write!(f, "{traversal}"),
ReturnValueExpr::Identifier(identifier) => write!(f, "{identifier}"),
ReturnValueExpr::Value(value) => write!(f, "{value}"),
ReturnValueExpr::Array(values) => {
write!(f, "vec![")?;
// if traversal then use the other from functions
for value in values {
write!(f, "ReturnValue::from({value}),")?;
}
write!(f, "]")
}
ReturnValueExpr::Object(values) => {
write!(f, "HashMap::from([")?;
// if traversal then use the other from functions
for (key, value) in values {
write!(f, "(String::from(\"{key}\"), ReturnValue::from({value})),")?;
}
write!(f, "])")
}
}
}
}
97 changes: 93 additions & 4 deletions helix-db/src/helixc/parser/helix_parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -411,7 +411,7 @@ pub struct Query {
pub name: String,
pub parameters: Vec<Parameter>,
pub statements: Vec<Statement>,
pub return_values: Vec<Expression>,
pub return_values: Vec<ReturnType>,
pub loc: Loc,
}

Expand Down Expand Up @@ -501,6 +501,15 @@ pub enum ExpressionType {
BM25Search(BM25Search),
Empty,
}

#[derive(Debug, Clone)]
pub enum ReturnType {
Array(Vec<ReturnType>),
Object(HashMap<String, ReturnType>),
Expression(Expression),
Empty,
}

impl Debug for ExpressionType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Expand Down Expand Up @@ -2212,11 +2221,91 @@ impl HelixParser {
})
}

fn parse_return_statement(&self, pair: Pair<Rule>) -> Result<Vec<Expression>, ParserError> {
fn parse_return_statement(&self, pair: Pair<Rule>) -> Result<Vec<ReturnType>, ParserError> {
// println!("pair: {:?}", pair.clone().into_inner());
let inner = pair.into_inner();
let mut return_types = Vec::new();
for pair in inner {
match pair.as_rule() {
Rule::array_creation => {
return_types.push(ReturnType::Array(self.parse_array_creation(pair)?));
}
Rule::object_creation => {
return_types.push(ReturnType::Object(self.parse_object_creation(pair)?));
}
Rule::evaluates_to_anything => {
return_types.push(ReturnType::Expression(self.parse_expression(pair)?));
}
_ => {
return Err(ParserError::from(format!(
"Unexpected rule in return statement: {:?}",
pair.as_rule()
)));
}
}
}
Ok(return_types)
}

fn parse_array_creation(&self, pair: Pair<Rule>) -> Result<Vec<ReturnType>, ParserError> {
let pairs = pair.into_inner();
let mut objects = Vec::new();
for p in pairs {
match p.as_rule() {
Rule::identifier => {
objects.push(ReturnType::Expression(Expression {
loc: p.loc(),
expr: ExpressionType::Identifier(p.as_str().to_string()),
}));
}
_ => {
objects.push(ReturnType::Object(self.parse_object_creation(p)?));
}
Comment on lines +2261 to +2263
Copy link
Preview

Copilot AI Sep 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This assumes all non-identifier rules are object_creation, which may not be correct. The code should explicitly match against Rule::object_creation and handle unexpected rules with an error.

Suggested change
_ => {
objects.push(ReturnType::Object(self.parse_object_creation(p)?));
}
Rule::object_creation => {
objects.push(ReturnType::Object(self.parse_object_creation(p)?));
}
_ => {
return Err(ParserError::from(format!(
"Unexpected rule in parse_array_creation: {:?}",
p.as_rule()
)));
}

Copilot uses AI. Check for mistakes.

}
}
Ok(objects)
}

fn parse_object_creation(
&self,
pair: Pair<Rule>,
) -> Result<HashMap<String, ReturnType>, ParserError> {
pair.into_inner()
.map(|p| self.parse_expression(p))
.collect()
.map(|p| {
let mut object_inner = p.into_inner();
let key = object_inner
.next()
.ok_or_else(|| ParserError::from("Missing object inner"))?;
let value = object_inner
.next()
.ok_or_else(|| ParserError::from("Missing object inner"))?;
let value = self.parse_object_inner(value)?;
Ok((key.as_str().to_string(), value))
})
.collect::<Result<HashMap<String, ReturnType>, _>>()
}

fn parse_object_inner(&self, object_field: Pair<Rule>) -> Result<ReturnType, ParserError> {
let object_field_inner = object_field
.into_inner()
.next()
.ok_or_else(|| ParserError::from("Missing object inner"))?;

match object_field_inner.as_rule() {
Rule::evaluates_to_anything => Ok(ReturnType::Expression(
self.parse_expression(object_field_inner)?,
)),
Rule::object_creation => Ok(ReturnType::Object(
self.parse_object_creation(object_field_inner)?,
)),
Rule::array_creation => Ok(ReturnType::Array(
self.parse_array_creation(object_field_inner)?,
)),
_ => Err(ParserError::from(format!(
"Unexpected rule in parse_object_inner: {:?}",
object_field_inner.as_rule()
))),
}
}

fn parse_expression_vec(&self, pairs: Pairs<Rule>) -> Result<Vec<Expression>, ParserError> {
Expand Down
Loading
Loading