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
1 change: 1 addition & 0 deletions rust/lance-graph/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ pub mod parser;
pub mod query;
pub mod query_processor;
pub mod semantic;
pub mod simple_executor;
pub mod source_catalog;
pub mod sql_converter;

Expand Down
18 changes: 6 additions & 12 deletions rust/lance-graph/src/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,11 @@ use crate::config::GraphConfig;
use crate::error::{GraphError, Result};
use crate::logical_plan::LogicalPlanner;
use crate::parser::parse_cypher_query;
use crate::simple_executor::{
to_df_boolean_expr_simple, to_df_order_by_expr_simple, to_df_value_expr_simple, PathExecutor,
};
use std::collections::HashMap;

mod path_executor;
use self::path_executor::PathExecutor;
mod aliases;
mod clauses;
mod expr;
mod simple_executor;

/// Execution strategy for Cypher queries
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum ExecutionStrategy {
Expand Down Expand Up @@ -723,9 +719,7 @@ impl CypherQuery {

// Apply WHERE if present (limited support: simple comparisons on a single column)
if let Some(where_clause) = &self.ast.where_clause {
if let Some(filter_expr) =
simple_executor::to_df_boolean_expr_simple(&where_clause.expression)
{
if let Some(filter_expr) = to_df_boolean_expr_simple(&where_clause.expression) {
df = df.filter(filter_expr).map_err(|e| GraphError::PlanError {
message: format!("Failed to apply filter: {}", e),
location: snafu::Location::new(file!(), line!(), column!()),
Expand All @@ -739,7 +733,7 @@ impl CypherQuery {
.return_clause
.items
.iter()
.map(|item| simple_executor::to_df_value_expr_simple(&item.expression))
.map(|item| to_df_value_expr_simple(&item.expression))
.collect();
if !proj_exprs.is_empty() {
df = df.select(proj_exprs).map_err(|e| GraphError::PlanError {
Expand All @@ -758,7 +752,7 @@ impl CypherQuery {

// Apply ORDER BY if present
if let Some(order_by) = &self.ast.order_by {
let sort_expr = simple_executor::to_df_order_by_expr_simple(&order_by.items);
let sort_expr = to_df_order_by_expr_simple(&order_by.items);
df = df.sort(sort_expr).map_err(|e| GraphError::PlanError {
message: format!("Failed to apply ORDER BY: {}", e),
location: snafu::Location::new(file!(), line!(), column!()),
Expand Down
74 changes: 0 additions & 74 deletions rust/lance-graph/src/query/expr.rs

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: Copyright The Lance Authors

pub(super) fn qualify_alias_property(alias: &str, property: &str) -> String {
format!("{}__{}", alias, property)
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: Copyright The Lance Authors

use crate::error::Result;

pub(super) fn apply_where_with_qualifier(
mut df: datafusion::dataframe::DataFrame,
ast: &crate::ast::CypherQuery,
qualify: &dyn Fn(&str, &str) -> String,
) -> Result<datafusion::dataframe::DataFrame> {
use super::expr::to_df_boolean_expr_with_vars;
use crate::error::GraphError;
use crate::query::expr::to_df_boolean_expr_with_vars;
if let Some(where_clause) = &ast.where_clause {
if let Some(expr) =
to_df_boolean_expr_with_vars(&where_clause.expression, &|v, p| qualify(v, p))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,88 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: Copyright The Lance Authors

//! Helper functions for simple single-table query execution
//! Expression translation helpers for the simple executor

pub(super) fn to_df_boolean_expr_with_vars<F>(
expr: &crate::ast::BooleanExpression,
qualify: &F,
) -> Option<datafusion::logical_expr::Expr>
where
F: Fn(&str, &str) -> String,
{
use crate::ast::{BooleanExpression as BE, ComparisonOperator as CO, ValueExpression as VE};
use datafusion::logical_expr::{col, Expr, Operator};
match expr {
BE::Comparison {
left,
operator,
right,
} => {
let (var, prop, lit_expr) = match (left, right) {
(VE::Property(p), VE::Literal(val)) => {
(p.variable.as_str(), p.property.as_str(), to_df_literal(val))
}
(VE::Literal(val), VE::Property(p)) => {
(p.variable.as_str(), p.property.as_str(), to_df_literal(val))
}
_ => return None,
};
let qualified = qualify(var, prop);
let op = match operator {
CO::Equal => Operator::Eq,
CO::NotEqual => Operator::NotEq,
CO::LessThan => Operator::Lt,
CO::LessThanOrEqual => Operator::LtEq,
CO::GreaterThan => Operator::Gt,
CO::GreaterThanOrEqual => Operator::GtEq,
};
Some(Expr::BinaryExpr(datafusion::logical_expr::BinaryExpr {
left: Box::new(col(&qualified)),
op,
right: Box::new(lit_expr),
}))
}
BE::And(l, r) => Some(datafusion::logical_expr::Expr::BinaryExpr(
datafusion::logical_expr::BinaryExpr {
left: Box::new(to_df_boolean_expr_with_vars(l, qualify)?),
op: Operator::And,
right: Box::new(to_df_boolean_expr_with_vars(r, qualify)?),
},
)),
BE::Or(l, r) => Some(datafusion::logical_expr::Expr::BinaryExpr(
datafusion::logical_expr::BinaryExpr {
left: Box::new(to_df_boolean_expr_with_vars(l, qualify)?),
op: Operator::Or,
right: Box::new(to_df_boolean_expr_with_vars(r, qualify)?),
},
)),
BE::Not(inner) => Some(datafusion::logical_expr::Expr::Not(Box::new(
to_df_boolean_expr_with_vars(inner, qualify)?,
))),
_ => None,
}
}

pub(super) fn to_df_literal(val: &crate::ast::PropertyValue) -> datafusion::logical_expr::Expr {
use datafusion::logical_expr::lit;
match val {
crate::ast::PropertyValue::String(s) => lit(s.clone()),
crate::ast::PropertyValue::Integer(i) => lit(*i),
crate::ast::PropertyValue::Float(f) => lit(*f),
crate::ast::PropertyValue::Boolean(b) => lit(*b),
crate::ast::PropertyValue::Null => {
datafusion::logical_expr::Expr::Literal(datafusion::scalar::ScalarValue::Null, None)
}
crate::ast::PropertyValue::Parameter(_) => lit(0),
crate::ast::PropertyValue::Property(prop) => datafusion::logical_expr::col(&prop.property),
}
}

/// Minimal translator for simple boolean expressions into DataFusion Expr
pub(super) fn to_df_boolean_expr_simple(
pub(crate) fn to_df_boolean_expr_simple(
expr: &crate::ast::BooleanExpression,
) -> Option<datafusion::logical_expr::Expr> {
use crate::ast::{BooleanExpression as BE, ComparisonOperator as CO, ValueExpression as VE};
use crate::query::expr::to_df_literal;
use datafusion::logical_expr::{col, Expr, Operator};
match expr {
BE::Comparison {
Expand Down Expand Up @@ -66,7 +140,7 @@ pub(super) fn to_df_boolean_expr_simple(
}

/// Build ORDER BY expressions for simple queries
pub(super) fn to_df_order_by_expr_simple(
pub(crate) fn to_df_order_by_expr_simple(
items: &[crate::ast::OrderByItem],
) -> Vec<datafusion::logical_expr::SortExpr> {
use datafusion::logical_expr::SortExpr;
Expand All @@ -85,11 +159,10 @@ pub(super) fn to_df_order_by_expr_simple(
}

/// Build value expressions for simple queries
pub(super) fn to_df_value_expr_simple(
pub(crate) fn to_df_value_expr_simple(
expr: &crate::ast::ValueExpression,
) -> datafusion::logical_expr::Expr {
use crate::ast::ValueExpression as VE;
use crate::query::expr::to_df_literal;
use datafusion::logical_expr::{col, lit};
match expr {
VE::Property(prop) => col(&prop.property),
Expand Down
20 changes: 20 additions & 0 deletions rust/lance-graph/src/simple_executor/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: Copyright The Lance Authors

//! Simple single-table query executor with limited Cypher feature support
//!
//! This module provides a lightweight execution strategy for basic Cypher queries
//! that don't require the full DataFusion planner. It supports:
//! - Single-table scans with property filters
//! - Multi-hop path patterns via join chains
//! - Basic projections, DISTINCT, ORDER BY, SKIP, and LIMIT

mod aliases;
mod clauses;
mod expr;
mod path_executor;

pub(crate) use expr::{
to_df_boolean_expr_simple, to_df_order_by_expr_simple, to_df_value_expr_simple,
};
pub(crate) use path_executor::PathExecutor;
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: Copyright The Lance Authors

use crate::error::{GraphError, Result};
use datafusion::logical_expr::JoinType;

// Internal helper that plans and executes a single path by chaining joins.
pub(super) struct PathExecutor<'a> {
pub(crate) struct PathExecutor<'a> {
pub(super) ctx: &'a datafusion::prelude::SessionContext,
pub(super) path: &'a crate::ast::PathPattern,
pub(super) start_label: &'a str,
Expand All @@ -22,7 +25,7 @@ struct SegMeta<'a> {
}

impl<'a> PathExecutor<'a> {
pub(super) fn new(
pub(crate) fn new(
ctx: &'a datafusion::prelude::SessionContext,
cfg: &'a crate::config::GraphConfig,
path: &'a crate::ast::PathPattern,
Expand Down Expand Up @@ -174,7 +177,7 @@ impl<'a> PathExecutor<'a> {
})
}

pub(super) async fn build_chain(&self) -> Result<datafusion::dataframe::DataFrame> {
pub(crate) async fn build_chain(&self) -> Result<datafusion::dataframe::DataFrame> {
// Start node
let mut df = self
.open_aliased(self.start_label, &self.start_alias)
Expand Down Expand Up @@ -282,7 +285,7 @@ impl<'a> PathExecutor<'a> {
None
}

pub(super) fn apply_where(
pub(crate) fn apply_where(
&self,
df: datafusion::dataframe::DataFrame,
ast: &crate::ast::CypherQuery,
Expand All @@ -293,7 +296,7 @@ impl<'a> PathExecutor<'a> {
})
}

pub(super) fn apply_return(
pub(crate) fn apply_return(
&self,
df: datafusion::dataframe::DataFrame,
ast: &crate::ast::CypherQuery,
Expand Down
Loading