This phase addresses the inconsistent expression handling throughout the ORM layer. Currently, the codebase uses raw strings for expressions in many places instead of Invercargill.Expressions.Expression objects.
This violates the original design intent where all expressions should be Invercargill.Expressions.Expression objects.
where() and where_expr() methods create confusion about which to use_where_clauses stores raw strings instead of Expression objectsProjectionQuery.where_expr() throws an error, making Expression-based filtering impossible for projectionsInvercargill.Expressions| Area | Files | String Expression Usage |
|---|---|---|
| Query API | query.vala, entity-query.vala, projection-query.vala |
where(), or_where(), order_by(), order_by_desc() |
| Projection Builder | projection-builder.vala |
join(), select(), select_nested(), select_many(), group_by() |
| Selection Types | selection-types.vala |
ScalarSelection.expression, NestedProjectionSelection.entry_point_expression, CollectionProjectionSelection.entry_point_expression |
| Projection Definition | projection-definition.vala |
JoinDefinition.join_condition, group_by_expressions |
| SQL Builder | projection-sql-builder.vala |
build(), build_with_split(), translate_expression() |
| Variable Translator | variable-translator.vala |
translate_expression() |
| Aggregate Analyzer | aggregate-analyzer.vala |
analyze(), contains_aggregate(), split_expression() |
| Friendly Name Resolver | friendly-name-resolver.vala |
resolve_to_expression() returns string |
Query<T>
├── where(string expression) → stores string in _where_clauses
├── or_where(string expression) → stores string in _where_clauses
├── where_expr(Expression expression) → abstract, implemented differently
│
├── EntityQuery<T>
│ ├── where(string) → parses to Expression, stores in _filter
│ └── where_expr(Expression) → stores in _filter
│
└── ProjectionQuery<T>
├── where(string) → stores string, passes to ProjectionSqlBuilder
└── where_expr(Expression) → THROWS ERROR!
All string expression parameters will be converted to Expression expression parameters. The expr() convenience function from Invercargill.Expressions will be used to create expressions ergonomically.
Query<T>
├── where(Expression expression) → stores Expression in _where_expressions
├── or_where(Expression expression) → stores Expression in _where_expressions
├── order_by(Expression expression) → stores Expression for ordering
├── order_by_desc(Expression expression) → stores Expression for ordering DESC
│
├── EntityQuery<T>
│ └── where(Expression) → combines with existing _filter using AND/OR
│
└── ProjectionQuery<T>
└── where(Expression) → translates Expression through VariableTranslator
expr() Convenience FunctionFrom Invercargill.Expressions:
public Expression expr(string expression, Element first, ...) throws ExpressionError {
return ExpressionParser.parse_with_params(expression, Wrap.va_list<Element>(first, va_list()));
}
Usage examples:
// Simple expression
var results = session.query<User>()
.where(expr("age > 25", new NativeElement<int?>(25)))
.materialise();
// With parameters using $0, $1 syntax
var results = session.query<User>()
.where(expr("age > $0 && name == $1",
new NativeElement<int?>(25),
new NativeElement<string>("Alice")))
.materialise();
// Reusable expressions
var active_filter = expr("is_active == true", new NativeElement<bool>(true));
var results = session.query<User>()
.where(active_filter)
.materialise();
File: src/orm/query.vala
Changes:
_where_clauses: Vector<string> to _where_expressions: Vector<Expression>where(string expression) to where(Expression expression)or_where(string expression) to or_where(Expression expression)order_by(string expression) to order_by(Expression expression)order_by_desc(string expression) to order_by_desc(Expression expression)where_expr(Expression expression) methodUpdate get_combined_where() to work with Expressions
// Before
protected Vector<string> _where_clauses;
public virtual Query<T> where(string expression) { ... }
public abstract Query<T> where_expr(Expression expression);
// After
protected Vector<Expression> _where_expressions;
public virtual Query<T> where(Expression expression) { ... }
// where_expr() removed
File: src/orm/query.vala
Changes:
Change expression: string to expression: Expression
// Before
public class OrderByClause : Object {
public string expression { get; construct; }
public OrderByClause(string expression, bool descending) { ... }
}
// After
public class OrderByClause : Object {
public Expression expression { get; construct; }
public OrderByClause(Expression expression, bool descending) { ... }
}
File: src/orm/entity-query.vala
Changes:
where(string expression) override - base implementation now handles Expressionswhere_expr(Expression expression) overridebuild_select_sql() to handle Expression-based WHERE clausesFile: src/orm/projections/projection-query.vala
Changes:
where_expr() that throws errormaterialise() and materialise_async() to translate ExpressionsVariableTranslator to convert Expressions for projection contextFile: src/orm/projections/projection-builder.vala
Changes:
join<TEntity>(string variable_name, string join_condition) → join<TEntity>(string variable_name, Expression join_condition)select<TValue>(string friendly_name, string expression, ...) → select<TValue>(string friendly_name, Expression expression, ...)select_nested<TNested>(string friendly_name, string entry_point_expression, ...) → select_nested<TNested>(string friendly_name, Expression entry_point_expression, ...)select_many<TItem>(string friendly_name, string entry_point_expression, ...) → select_many<TItem>(string friendly_name, Expression entry_point_expression, ...)group_by(params string[] expressions) → group_by(params Expression[] expressions)File: src/orm/projections/selection-types.vala
Changes:
ScalarSelection.expression: string → ExpressionNestedProjectionSelection.entry_point_expression: string → ExpressionCollectionProjectionSelection.entry_point_expression: string → ExpressionFile: src/orm/projections/projection-definition.vala
Changes:
JoinDefinition.join_condition: string → ExpressionProjectionDefinition.group_by_expressions: Vector<string> → Vector<Expression>add_group_by(string expression) → add_group_by(Expression expression)File: src/orm/projections/projection-sql-builder.vala
Changes:
build(string? where_expression, ...) → build(Expression? where_expression, ...)build_with_split(string? where_expression, ...) → build_with_split(Expression? where_expression, ...)translate_expression(string expression) → translate_expression(Expression expression)File: src/orm/projections/variable-translator.vala
Changes:
translate_expression(string expression) → translate_expression(Expression expression)ExpressionToSqlVisitor pattern for translationExpression and output SQL directlyFile: src/orm/projections/aggregate-analyzer.vala
Changes:
analyze(string expression) → analyze(Expression expression)contains_aggregate(string expression) → contains_aggregate(Expression expression)split_expression(string expression) → split_expression(Expression expression)File: src/orm/projections/friendly-name-resolver.vala
Changes:
resolve_to_expression(string friendly_name) → Returns Expression? instead of string?Files:
Changes:
Invercargill.Expressions namespacewhere("string") calls to where(expr("string"))order_by("column") calls to use Expression-based approachexpr()$0, $1 syntaxExample test conversions:
// Before
var results = session.query<TestUser>()
.where("age > 28")
.materialise();
// After
var results = session.query<TestUser>()
.where(expr("age > $0", new NativeElement<int?>(28)))
.materialise();
// Before - projection builder
ctx.registry.register_projection<UserOrderStats>(new ProjectionBuilder<UserOrderStats>(ctx.registry)
.source<ProjTestUser>("u")
.join<ProjTestOrder>("o", "u.id == o.user_id")
.group_by("u.id")
.select<int64?>("user_id", "u.id", (x, v) => x.user_id = v)
.build()
);
// After - projection builder
ctx.registry.register_projection<UserOrderStats>(new ProjectionBuilder<UserOrderStats>(ctx.registry)
.source<ProjTestUser>("u")
.join<ProjTestOrder>("o", expr("u.id == o.user_id"))
.group_by(expr("u.id"))
.select<int64?>("user_id", expr("u.id"), (x, v) => x.user_id = v)
.build()
);
File: examples/demo.vala
Changes:
using Invercargill.Expressions;Convert all query expressions to use expr()
// Before
var users_over_25 = session.query<User>()
.where("age > 25")
.materialise();
// After
var users_over_25 = session.query<User>()
.where(expr("age > $0", new NativeElement<int?>(25)))
.materialise();
| File | Changes |
|---|---|
src/orm/query.vala |
Core API - where(), or_where(), order_by(), OrderByClause |
src/orm/entity-query.vala |
Remove overrides, update SQL building |
src/orm/projections/projection-query.vala |
Enable Expression support |
src/orm/projections/projection-builder.vala |
join(), select(), group_by() signatures |
src/orm/projections/selection-types.vala |
expression property types |
src/orm/projections/projection-definition.vala |
JoinDefinition, group_by_expressions |
src/orm/projections/projection-sql-builder.vala |
build() signatures, translate_expression() |
src/orm/projections/variable-translator.vala |
translate_expression() signature |
src/orm/projections/aggregate-analyzer.vala |
analyze(), contains_aggregate(), split_expression() |
src/orm/projections/friendly-name-resolver.vala |
resolve_to_expression() return type |
src/tests/orm-test.vala |
Convert all tests |
src/tests/projection-test.vala |
Convert all tests |
examples/demo.vala |
Update demo |
| File | Reason |
|---|---|
src/expressions/expression-to-sql-visitor.vala |
Ensure it works with projection contexts |
// String-based expressions
var users = session.query<User>()
.where("age > 18")
.order_by("name")
.materialise();
// Projection queries
var stats = session.query<UserOrderStats>()
.where("user_id > 100")
.materialise();
// Projection definitions
registry.register_projection<UserStats>(new ProjectionBuilder<UserStats>(registry)
.source<User>("u")
.join<Order>("o", "u.id == o.user_id")
.group_by("u.id")
.select<int64?>("user_id", "u.id", (x, v) => x.user_id = v)
.build()
);
using Invercargill.Expressions;
// Expression-based queries
var users = session.query<User>()
.where(expr("age > $0", new NativeElement<int?>(18)))
.order_by(expr("name"))
.materialise();
// Projection queries work the same way
var stats = session.query<UserOrderStats>()
.where(expr("user_id > $0", new NativeElement<int?>(100)))
.materialise();
// Projection definitions with Expressions
registry.register_projection<UserStats>(new ProjectionBuilder<UserStats>(registry)
.source<User>("u")
.join<Order>("o", expr("u.id == o.user_id"))
.group_by(expr("u.id"))
.select<int64?>("user_id", expr("u.id"), (x, v) => x.user_id = v)
.build()
);
// Simple expressions without parameters
var active = session.query<User>()
.where(expr("is_active == true"))
.materialise();
$0, $1 syntaxInvercargill.Expressions throughoutexpr("age > $0", new NativeElement<int?>(18)) is more verbose than "age > 18"VariableTranslator infrastructureexpr()expr() handles all expression types$0, $1, etc. parameter substitutionwhere_expr() method removed from all Query classeswhere() accepts Expression parameter in all Query classesorder_by() and order_by_desc() accept Expression parametersProjectionBuilder methods accept Expression parametersExpression objectsProjectionSqlBuilder accepts Expression for WHEREExpression objects| Category | Count |
|---|---|
| Core files to modify | 10 |
| Test files to update | 2 |
| Example files to update | 1 |
| Breaking changes | Yes (major API change) |
Invercargill.Expressions.expr() functionInvercargill.Expressions.ExpressionParserInvercargill.Expressions.Expression type hierarchyInvercargill.NativeElement<T> for parameter values