Skip to content

Conversation

xav-db
Copy link
Member

@xav-db xav-db commented Aug 5, 2025

Description

implementing pattern matching, enums, and optionals into hql

for optionals

N::User {
    name?: String,
    age: U32,
}

for enums

Enum::UserType { 
    Admin,
    User,
}

N::User {
    name?: String,
    age: U32,
    user_type: UserType
}

for pattern matching

N::User {
    user_field: String,
    user_type: UserType,
}

Enum::UserType {
    User,
    Admin,
}

QUERY GetAdmins() => 
    users <- N<User>::MATCH|_::{user_type}|{
        UserType::User => _::{user_field},
        UserType::Admin => _::{user_field},
    }
    RETURN users

QUERY Search() => 
    nodes <- SearchV(queryText)::MATCH|_|{
        N::User(user) => user::{user_field},
        ? => SKIP,
    }
    RETURN nodes

Related Issues

Closes #

Checklist when merging to main

  • No compiler warnings (if applicable)
  • Code is formatted with rustfmt
  • No useless or dead code (if applicable)
  • Code is easy to understand
  • Doc comments are used for all functions, enums, structs, and fields (where appropriate)
  • All tests pass
  • Performance has not regressed (assuming change was not to fix a bug)
  • Version number has been updated in helix-cli/Cargo.toml and helixdb/Cargo.toml
  • Lines are kept under 100 characters where possible
  • Code is good

Additional Notes

@Copilot Copilot AI review requested due to automatic review settings August 5, 2025 14:50
@xav-db xav-db marked this pull request as draft August 5, 2025 14:50
Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This pull request implements pattern matching, enums, and optionals into HQL. The changes include adding optional syntax (?) for query parameters and schema fields, support for enum definitions and pattern matching operations, schema versioning with migration support, and type casting improvements.

  • Adds optional field/parameter syntax using ? notation
  • Implements enum support and pattern matching with MATCH expressions
  • Introduces schema versioning system with automatic migrations
  • Enhances type casting and value conversion capabilities

Reviewed Changes

Copilot reviewed 63 out of 63 changed files in this pull request and generated 16 comments.

Show a summary per file
File Description
hql-tests/file56/file56.hx Updates query parameter to be optional using ? syntax
hql-tests/file59/schema.hx Implements schema versioning with migration example
hql-tests/file57/schema.hx Adds vector embedding schema definition
hql-tests/file57/file57.hx Creates embedding queries with model annotations
hql-tests/file51/schema.hx Defines versioned schemas with field changes
hql-tests/file51/migrations.hx Implements schema migration logic
helix-macros/src/lib.rs Adds migration macro for schema transitions
helix-db/src/utils/items.rs Adds version field to Node and Edge structs
helix-db/src/utils/id.rs Enhances ID type with string conversion and ordering
helix-db/src/protocol/value.rs Implements comprehensive type casting system
helix-db/src/protocol/date.rs Adds DateError type for better error handling

let fn_name = &input_fn.sig.ident;
let fn_name_str = fn_name.to_string();

println!("fn_name_str: {fn_name_str}");
Copy link
Preview

Copilot AI Aug 5, 2025

Choose a reason for hiding this comment

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

Debug print statement should be removed from production code. This println! will output to console during compilation and should be replaced with proper logging or removed entirely.

Suggested change
println!("fn_name_str: {fn_name_str}");

Copilot uses AI. Check for mistakes.

Value::F32(i) => i as i8,
Value::F64(i) => i as i8,
Value::Boolean(i) => i as i8,
Value::String(s) => s.parse::<i8>().unwrap(),
Copy link
Preview

Copilot AI Aug 5, 2025

Choose a reason for hiding this comment

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

Using unwrap() on string parsing can cause panics with invalid input. Consider returning a Result or providing better error handling with a descriptive error message.

Suggested change
Value::String(s) => s.parse::<i8>().unwrap(),
Value::String(s) => s.parse::<i8>().expect(&format!("Failed to parse i8 from string: {}", s)),

Copilot uses AI. Check for mistakes.

Value::F32(i) => i as i16,
Value::F64(i) => i as i16,
Value::Boolean(i) => i as i16,
Value::String(s) => s.parse::<i16>().unwrap(),
Copy link
Preview

Copilot AI Aug 5, 2025

Choose a reason for hiding this comment

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

Using unwrap() on string parsing can cause panics with invalid input. Consider returning a Result or providing better error handling with a descriptive error message.

Suggested change
Value::String(s) => s.parse::<i16>().unwrap(),
Value::String(s) => s.parse::<i16>().expect(&format!("Failed to parse '{}' as i16", s)),

Copilot uses AI. Check for mistakes.

Value::F32(i) => i as i32,
Value::F64(i) => i as i32,
Value::Boolean(i) => i as i32,
Value::String(s) => s.parse::<i32>().unwrap(),
Copy link
Preview

Copilot AI Aug 5, 2025

Choose a reason for hiding this comment

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

Using unwrap() on string parsing can cause panics with invalid input. Consider returning a Result or providing better error handling with a descriptive error message.

Suggested change
Value::String(s) => s.parse::<i32>().unwrap(),
Value::String(s) => s.parse::<i32>().expect("Value::String cannot be parsed as i32"),

Copilot uses AI. Check for mistakes.

Comment on lines 1117 to 1219
fn into_i32(self) -> i32 {
match self {
Value::I32(i) => i,
Value::I8(i) => i as i32,
Value::I16(i) => i as i32,
Value::I64(i) => i as i32,
Value::U8(i) => i as i32,
Value::U16(i) => i as i32,
Value::U32(i) => i as i32,
Value::U64(i) => i as i32,
Value::U128(i) => i as i32,
Value::F32(i) => i as i32,
Value::F64(i) => i as i32,
Value::Boolean(i) => i as i32,
Value::String(s) => s.parse::<i32>().unwrap(),
_ => panic!("Value cannot be cast to i32"),
}
}
fn into_i64(self) -> i64 {
match self {
Value::I64(i) => i,
Value::I8(i) => i as i64,
Value::I16(i) => i as i64,
Value::I32(i) => i as i64,
Value::U8(i) => i as i64,
Value::U16(i) => i as i64,
Value::U32(i) => i as i64,
Value::U64(i) => i as i64,
Value::U128(i) => i as i64,
Value::F32(i) => i as i64,
Value::F64(i) => i as i64,
Value::Boolean(i) => i as i64,
Value::String(s) => s.parse::<i64>().unwrap(),
_ => panic!("Value cannot be cast to i64"),
}
}

fn into_u8(self) -> u8 {
match self {
Value::U8(i) => i,
Value::I8(i) => i as u8,
Value::I16(i) => i as u8,
Value::I32(i) => i as u8,
Value::I64(i) => i as u8,
Value::U16(i) => i as u8,
Value::U32(i) => i as u8,
Value::U64(i) => i as u8,
Value::U128(i) => i as u8,
Value::F32(i) => i as u8,
Value::F64(i) => i as u8,
Value::Boolean(i) => i as u8,
Value::String(s) => s.parse::<u8>().unwrap(),
_ => panic!("Value cannot be cast to u8"),
Copy link
Preview

Copilot AI Aug 5, 2025

Choose a reason for hiding this comment

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

Using unwrap() on string parsing can cause panics with invalid input. Consider returning a Result or providing better error handling with a descriptive error message.

Suggested change
fn into_i32(self) -> i32 {
match self {
Value::I32(i) => i,
Value::I8(i) => i as i32,
Value::I16(i) => i as i32,
Value::I64(i) => i as i32,
Value::U8(i) => i as i32,
Value::U16(i) => i as i32,
Value::U32(i) => i as i32,
Value::U64(i) => i as i32,
Value::U128(i) => i as i32,
Value::F32(i) => i as i32,
Value::F64(i) => i as i32,
Value::Boolean(i) => i as i32,
Value::String(s) => s.parse::<i32>().unwrap(),
_ => panic!("Value cannot be cast to i32"),
}
}
fn into_i64(self) -> i64 {
match self {
Value::I64(i) => i,
Value::I8(i) => i as i64,
Value::I16(i) => i as i64,
Value::I32(i) => i as i64,
Value::U8(i) => i as i64,
Value::U16(i) => i as i64,
Value::U32(i) => i as i64,
Value::U64(i) => i as i64,
Value::U128(i) => i as i64,
Value::F32(i) => i as i64,
Value::F64(i) => i as i64,
Value::Boolean(i) => i as i64,
Value::String(s) => s.parse::<i64>().unwrap(),
_ => panic!("Value cannot be cast to i64"),
}
}
fn into_u8(self) -> u8 {
match self {
Value::U8(i) => i,
Value::I8(i) => i as u8,
Value::I16(i) => i as u8,
Value::I32(i) => i as u8,
Value::I64(i) => i as u8,
Value::U16(i) => i as u8,
Value::U32(i) => i as u8,
Value::U64(i) => i as u8,
Value::U128(i) => i as u8,
Value::F32(i) => i as u8,
Value::F64(i) => i as u8,
Value::Boolean(i) => i as u8,
Value::String(s) => s.parse::<u8>().unwrap(),
_ => panic!("Value cannot be cast to u8"),
fn into_i32(self) -> Result<i32, GraphError> {
match self {
Value::I32(i) => Ok(i),
Value::I8(i) => Ok(i as i32),
Value::I16(i) => Ok(i as i32),
Value::I64(i) => Ok(i as i32),
Value::U8(i) => Ok(i as i32),
Value::U16(i) => Ok(i as i32),
Value::U32(i) => Ok(i as i32),
Value::U64(i) => Ok(i as i32),
Value::U128(i) => Ok(i as i32),
Value::F32(i) => Ok(i as i32),
Value::F64(i) => Ok(i as i32),
Value::Boolean(i) => Ok(i as i32),
Value::String(s) => s.parse::<i32>().map_err(|e| GraphError::ConversionError(format!("Failed to parse string '{}' as i32: {}", s, e))),
_ => Err(GraphError::ConversionError("Value cannot be cast to i32".to_string())),
}
}
fn into_i64(self) -> Result<i64, GraphError> {
match self {
Value::I64(i) => Ok(i),
Value::I8(i) => Ok(i as i64),
Value::I16(i) => Ok(i as i64),
Value::I32(i) => Ok(i as i64),
Value::U8(i) => Ok(i as i64),
Value::U16(i) => Ok(i as i64),
Value::U32(i) => Ok(i as i64),
Value::U64(i) => Ok(i as i64),
Value::U128(i) => Ok(i as i64),
Value::F32(i) => Ok(i as i64),
Value::F64(i) => Ok(i as i64),
Value::Boolean(i) => Ok(i as i64),
Value::String(s) => s.parse::<i64>().map_err(|e| GraphError::ConversionError(format!("Failed to parse string '{}' as i64: {}", s, e))),
_ => Err(GraphError::ConversionError("Value cannot be cast to i64".to_string())),
}
}
fn into_u8(self) -> Result<u8, GraphError> {
match self {
Value::U8(i) => Ok(i),
Value::I8(i) => Ok(i as u8),
Value::I16(i) => Ok(i as u8),
Value::I32(i) => Ok(i as u8),
Value::I64(i) => Ok(i as u8),
Value::U16(i) => Ok(i as u8),
Value::U32(i) => Ok(i as u8),
Value::U64(i) => Ok(i as u8),
Value::U128(i) => Ok(i as u8),
Value::F32(i) => Ok(i as u8),
Value::F64(i) => Ok(i as u8),
Value::Boolean(i) => Ok(i as u8),
Value::String(s) => s.parse::<u8>().map_err(|e| GraphError::ConversionError(format!("Failed to parse string '{}' as u8: {}", s, e))),
_ => Err(GraphError::ConversionError("Value cannot be cast to u8".to_string())),

Copilot uses AI. Check for mistakes.

Value::U32(i) => i as f64,
Value::U64(i) => i as f64,
Value::U128(i) => i as f64,
Value::String(s) => s.parse::<f64>().unwrap(),
Copy link
Preview

Copilot AI Aug 5, 2025

Choose a reason for hiding this comment

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

Using unwrap() on string parsing can cause panics with invalid input. Consider returning a Result or providing better error handling with a descriptive error message.

Suggested change
Value::String(s) => s.parse::<f64>().unwrap(),
Value::String(s) => s.parse::<f64>().expect("Value::String could not be parsed as f64"),

Copilot uses AI. Check for mistakes.

Comment on lines 88 to 101
impl From<String> for ID {
fn from(id: String) -> Self {
ID(uuid::Uuid::parse_str(&id).unwrap().as_u128())
}
}
impl From<&String> for ID {
fn from(id: &String) -> Self {
ID(uuid::Uuid::parse_str(id).unwrap().as_u128())
}
}

impl From<&str> for ID {
fn from(id: &str) -> Self {
ID(uuid::Uuid::parse_str(id).unwrap().as_u128())
Copy link
Preview

Copilot AI Aug 5, 2025

Choose a reason for hiding this comment

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

Using unwrap() on UUID parsing can cause panics with malformed UUIDs. Consider returning a Result or providing better error handling with a descriptive error message.

Suggested change
impl From<String> for ID {
fn from(id: String) -> Self {
ID(uuid::Uuid::parse_str(&id).unwrap().as_u128())
}
}
impl From<&String> for ID {
fn from(id: &String) -> Self {
ID(uuid::Uuid::parse_str(id).unwrap().as_u128())
}
}
impl From<&str> for ID {
fn from(id: &str) -> Self {
ID(uuid::Uuid::parse_str(id).unwrap().as_u128())
impl TryFrom<String> for ID {
type Error = uuid::Error;
fn try_from(id: String) -> Result<Self, Self::Error> {
uuid::Uuid::parse_str(&id).map(|uuid| ID(uuid.as_u128()))
}
}
impl TryFrom<&String> for ID {
type Error = uuid::Error;
fn try_from(id: &String) -> Result<Self, Self::Error> {
uuid::Uuid::parse_str(id).map(|uuid| ID(uuid.as_u128()))
}
}
impl TryFrom<&str> for ID {
type Error = uuid::Error;
fn try_from(id: &str) -> Result<Self, Self::Error> {
uuid::Uuid::parse_str(id).map(|uuid| ID(uuid.as_u128()))

Copilot uses AI. Check for mistakes.

Comment on lines 90 to 104
ID(uuid::Uuid::parse_str(&id).unwrap().as_u128())
}
}
impl From<&String> for ID {
fn from(id: &String) -> Self {
ID(uuid::Uuid::parse_str(id).unwrap().as_u128())
}
}

impl From<&str> for ID {
fn from(id: &str) -> Self {
ID(uuid::Uuid::parse_str(id).unwrap().as_u128())
}
}

Copy link
Preview

Copilot AI Aug 5, 2025

Choose a reason for hiding this comment

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

Using unwrap() on UUID parsing can cause panics with malformed UUIDs. Consider returning a Result or providing better error handling with a descriptive error message.

Suggested change
ID(uuid::Uuid::parse_str(&id).unwrap().as_u128())
}
}
impl From<&String> for ID {
fn from(id: &String) -> Self {
ID(uuid::Uuid::parse_str(id).unwrap().as_u128())
}
}
impl From<&str> for ID {
fn from(id: &str) -> Self {
ID(uuid::Uuid::parse_str(id).unwrap().as_u128())
}
}
ID(uuid::Uuid::parse_str(&id).expect("Invalid UUID string in ID conversion").as_u128())
}
}
impl From<&String> for ID {
fn from(id: &String) -> Self {
ID(uuid::Uuid::parse_str(id).expect("Invalid UUID string in ID conversion").as_u128())
}
}
impl From<&str> for ID {
fn from(id: &str) -> Self {
ID(uuid::Uuid::parse_str(id).expect("Invalid UUID string in ID conversion").as_u128())
}
}
impl ID {
/// Attempts to create an ID from a UUID string, returning an error if the string is invalid.
pub fn try_from_str(id: &str) -> Result<Self, uuid::Error> {
uuid::Uuid::parse_str(id).map(|uuid| ID(uuid.as_u128()))
}
}

Copilot uses AI. Check for mistakes.

Comment on lines 88 to 101
impl From<String> for ID {
fn from(id: String) -> Self {
ID(uuid::Uuid::parse_str(&id).unwrap().as_u128())
}
}
impl From<&String> for ID {
fn from(id: &String) -> Self {
ID(uuid::Uuid::parse_str(id).unwrap().as_u128())
}
}

impl From<&str> for ID {
fn from(id: &str) -> Self {
ID(uuid::Uuid::parse_str(id).unwrap().as_u128())
Copy link
Preview

Copilot AI Aug 5, 2025

Choose a reason for hiding this comment

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

Using unwrap() on UUID parsing can cause panics with malformed UUIDs. Consider returning a Result or providing better error handling with a descriptive error message.

Suggested change
impl From<String> for ID {
fn from(id: String) -> Self {
ID(uuid::Uuid::parse_str(&id).unwrap().as_u128())
}
}
impl From<&String> for ID {
fn from(id: &String) -> Self {
ID(uuid::Uuid::parse_str(id).unwrap().as_u128())
}
}
impl From<&str> for ID {
fn from(id: &str) -> Self {
ID(uuid::Uuid::parse_str(id).unwrap().as_u128())
impl TryFrom<String> for ID {
type Error = uuid::Error;
fn try_from(id: String) -> Result<Self, Self::Error> {
uuid::Uuid::parse_str(&id).map(|uuid| ID(uuid.as_u128()))
}
}
impl TryFrom<&String> for ID {
type Error = uuid::Error;
fn try_from(id: &String) -> Result<Self, Self::Error> {
uuid::Uuid::parse_str(id).map(|uuid| ID(uuid.as_u128()))
}
}
impl TryFrom<&str> for ID {
type Error = uuid::Error;
fn try_from(id: &str) -> Result<Self, Self::Error> {
uuid::Uuid::parse_str(id).map(|uuid| ID(uuid.as_u128()))

Copilot uses AI. Check for mistakes.

Comment on lines 1080 to 1164
impl CastValue for Value {
fn into_i8(self) -> i8 {
match self {
Value::I8(i) => i,
Value::I16(i) => i as i8,
Value::I32(i) => i as i8,
Value::I64(i) => i as i8,
Value::U8(i) => i as i8,
Value::U16(i) => i as i8,
Value::U32(i) => i as i8,
Value::U64(i) => i as i8,
Value::U128(i) => i as i8,
Value::F32(i) => i as i8,
Value::F64(i) => i as i8,
Value::Boolean(i) => i as i8,
Value::String(s) => s.parse::<i8>().unwrap(),
_ => panic!("Value cannot be cast to i8"),
}
}
fn into_i16(self) -> i16 {
match self {
Value::I16(i) => i,
Value::I8(i) => i as i16,
Value::I32(i) => i as i16,
Value::I64(i) => i as i16,
Value::U8(i) => i as i16,
Value::U16(i) => i as i16,
Value::U32(i) => i as i16,
Value::U64(i) => i as i16,
Value::U128(i) => i as i16,
Value::F32(i) => i as i16,
Value::F64(i) => i as i16,
Value::Boolean(i) => i as i16,
Value::String(s) => s.parse::<i16>().unwrap(),
_ => panic!("Value cannot be cast to i16"),
Copy link
Preview

Copilot AI Aug 5, 2025

Choose a reason for hiding this comment

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

Using panic! for error handling is not ideal for production code. Consider returning a Result with a descriptive error message instead.

Suggested change
impl CastValue for Value {
fn into_i8(self) -> i8 {
match self {
Value::I8(i) => i,
Value::I16(i) => i as i8,
Value::I32(i) => i as i8,
Value::I64(i) => i as i8,
Value::U8(i) => i as i8,
Value::U16(i) => i as i8,
Value::U32(i) => i as i8,
Value::U64(i) => i as i8,
Value::U128(i) => i as i8,
Value::F32(i) => i as i8,
Value::F64(i) => i as i8,
Value::Boolean(i) => i as i8,
Value::String(s) => s.parse::<i8>().unwrap(),
_ => panic!("Value cannot be cast to i8"),
}
}
fn into_i16(self) -> i16 {
match self {
Value::I16(i) => i,
Value::I8(i) => i as i16,
Value::I32(i) => i as i16,
Value::I64(i) => i as i16,
Value::U8(i) => i as i16,
Value::U16(i) => i as i16,
Value::U32(i) => i as i16,
Value::U64(i) => i as i16,
Value::U128(i) => i as i16,
Value::F32(i) => i as i16,
Value::F64(i) => i as i16,
Value::Boolean(i) => i as i16,
Value::String(s) => s.parse::<i16>().unwrap(),
_ => panic!("Value cannot be cast to i16"),
// Error type for cast failures
#[derive(Debug)]
pub enum CastError {
InvalidType(String),
ParseError(String),
}
impl std::fmt::Display for CastError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
CastError::InvalidType(msg) => write!(f, "Invalid type: {}", msg),
CastError::ParseError(msg) => write!(f, "Parse error: {}", msg),
}
}
}
impl std::error::Error for CastError {}
impl CastValue for Value {
fn into_i8(self) -> Result<i8, CastError> {
match self {
Value::I8(i) => Ok(i),
Value::I16(i) => Ok(i as i8),
Value::I32(i) => Ok(i as i8),
Value::I64(i) => Ok(i as i8),
Value::U8(i) => Ok(i as i8),
Value::U16(i) => Ok(i as i8),
Value::U32(i) => Ok(i as i8),
Value::U64(i) => Ok(i as i8),
Value::U128(i) => Ok(i as i8),
Value::F32(i) => Ok(i as i8),
Value::F64(i) => Ok(i as i8),
Value::Boolean(i) => Ok(i as i8),
Value::String(s) => s.parse::<i8>()
.map_err(|e| CastError::ParseError(format!("Failed to parse '{}' as i8: {}", s, e))),
_ => Err(CastError::InvalidType("Value cannot be cast to i8".to_string())),
}
}
fn into_i16(self) -> Result<i16, CastError> {
match self {
Value::I16(i) => Ok(i),
Value::I8(i) => Ok(i as i16),
Value::I32(i) => Ok(i as i16),
Value::I64(i) => Ok(i as i16),
Value::U8(i) => Ok(i as i16),
Value::U16(i) => Ok(i as i16),
Value::U32(i) => Ok(i as i16),
Value::U64(i) => Ok(i as i16),
Value::U128(i) => Ok(i as i16),
Value::F32(i) => Ok(i as i16),
Value::F64(i) => Ok(i as i16),
Value::Boolean(i) => Ok(i as i16),
Value::String(s) => s.parse::<i16>()
.map_err(|e| CastError::ParseError(format!("Failed to parse '{}' as i16: {}", s, e))),
_ => Err(CastError::InvalidType("Value cannot be cast to i16".to_string())),

Copilot uses AI. Check for mistakes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant