Loading config/schemas/graph_query.schema.json +10 −0 Original line number Diff line number Diff line Loading @@ -141,6 +141,16 @@ "nodes": { "minItems": 1, "description": "Aggregation requires at least 1 node" }, "relationships": { "items": { "properties": { "direction": { "enum": ["outgoing", "incoming"], "description": "Aggregation only supports outgoing or incoming direction." } } } } } } Loading crates/query-engine/compiler/src/passes/validate.rs +29 −0 Original line number Diff line number Diff line Loading @@ -245,6 +245,19 @@ impl<'a> Validator<'a> { ))); } for rel in &input.relationships { // direction: "both" generates OR join conditions that defeat // ClickHouse index and projection usage. The JSON schema also // rejects this for aggregation, but this guard covers code paths // that bypass schema validation (CLI, internal tests, gRPC). if rel.direction == crate::input::Direction::Both && input.query_type == QueryType::Aggregation { return Err(QueryError::Validation( "aggregation does not support direction: \"both\" on relationships; \ use separate queries for outgoing and incoming directions" .into(), )); } if rel.max_hops > MAX_HOPS_CAP { return Err(QueryError::DepthExceeded(format!( "max_hops ({}) must not exceed {MAX_HOPS_CAP}", Loading Loading @@ -1255,6 +1268,22 @@ mod tests { ); } #[test] fn rejects_aggregation_direction_both() { assert_rejects( r#"{ "query_type": "aggregation", "nodes": [ {"id": "u", "entity": "User", "node_ids": [1]}, {"id": "mr", "entity": "MergeRequest"} ], "relationships": [{"type": "AUTHORED", "from": "u", "to": "mr", "direction": "both"}], "aggregations": [{"function": "count", "target": "mr", "group_by": "u"}] }"#, "does not support direction", ); } #[test] fn accepts_int_filter_on_int_column() { assert_ok( Loading Loading
config/schemas/graph_query.schema.json +10 −0 Original line number Diff line number Diff line Loading @@ -141,6 +141,16 @@ "nodes": { "minItems": 1, "description": "Aggregation requires at least 1 node" }, "relationships": { "items": { "properties": { "direction": { "enum": ["outgoing", "incoming"], "description": "Aggregation only supports outgoing or incoming direction." } } } } } } Loading
crates/query-engine/compiler/src/passes/validate.rs +29 −0 Original line number Diff line number Diff line Loading @@ -245,6 +245,19 @@ impl<'a> Validator<'a> { ))); } for rel in &input.relationships { // direction: "both" generates OR join conditions that defeat // ClickHouse index and projection usage. The JSON schema also // rejects this for aggregation, but this guard covers code paths // that bypass schema validation (CLI, internal tests, gRPC). if rel.direction == crate::input::Direction::Both && input.query_type == QueryType::Aggregation { return Err(QueryError::Validation( "aggregation does not support direction: \"both\" on relationships; \ use separate queries for outgoing and incoming directions" .into(), )); } if rel.max_hops > MAX_HOPS_CAP { return Err(QueryError::DepthExceeded(format!( "max_hops ({}) must not exceed {MAX_HOPS_CAP}", Loading Loading @@ -1255,6 +1268,22 @@ mod tests { ); } #[test] fn rejects_aggregation_direction_both() { assert_rejects( r#"{ "query_type": "aggregation", "nodes": [ {"id": "u", "entity": "User", "node_ids": [1]}, {"id": "mr", "entity": "MergeRequest"} ], "relationships": [{"type": "AUTHORED", "from": "u", "to": "mr", "direction": "both"}], "aggregations": [{"function": "count", "target": "mr", "group_by": "u"}] }"#, "does not support direction", ); } #[test] fn accepts_int_filter_on_int_column() { assert_ok( Loading