|
|
@@ -319,10 +319,14 @@ namespace InvercargillSql.Orm.Projections {
|
|
|
* Translates an Expression object to SQL.
|
|
|
*
|
|
|
* This method handles:
|
|
|
+ * - Friendly name resolution (e.g., "user_id" -> the underlying expression like "u.id")
|
|
|
* - Variable substitution (e.g., "u.id" -> "val_1_User.id")
|
|
|
- * - Friendly name resolution (e.g., "user_id" -> "val_1_User.id")
|
|
|
* - SQL operator conversion (e.g., "==" -> "=")
|
|
|
*
|
|
|
+ * Friendly names are resolved recursively throughout the expression tree,
|
|
|
+ * so expressions like "id == $0" where "id" is a friendly name mapping to
|
|
|
+ * "u.id" will be properly qualified.
|
|
|
+ *
|
|
|
* @param expression The Expression object to translate
|
|
|
* @return The translated SQL expression
|
|
|
*/
|
|
|
@@ -330,22 +334,195 @@ namespace InvercargillSql.Orm.Projections {
|
|
|
// Ensure aliases are assigned
|
|
|
assign_aliases();
|
|
|
|
|
|
- // Check for simple friendly name (a single VariableExpression)
|
|
|
- Expression? expr_to_translate = expression;
|
|
|
+ // First, resolve all friendly names in the expression tree
|
|
|
+ Expression resolved = resolve_friendly_names(expression);
|
|
|
+
|
|
|
+ // Use the variable translator to translate the expression
|
|
|
+ return _translator.translate_expression(resolved);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Recursively resolves friendly names in an expression tree.
|
|
|
+ *
|
|
|
+ * This method walks the expression tree and replaces any VariableExpression
|
|
|
+ * that matches a registered friendly name with its underlying expression.
|
|
|
+ *
|
|
|
+ * For example, if "id" is a friendly name mapping to "u.id", the expression
|
|
|
+ * "id == $0" becomes "u.id == $0".
|
|
|
+ *
|
|
|
+ * @param expression The expression to process
|
|
|
+ * @return A new expression with friendly names resolved
|
|
|
+ */
|
|
|
+ private Expression resolve_friendly_names(Expression expression) {
|
|
|
+ // Handle VariableExpression - check if it's a friendly name
|
|
|
if (expression is VariableExpression) {
|
|
|
var var_expr = (VariableExpression) expression;
|
|
|
string var_name = var_expr.variable_name;
|
|
|
+
|
|
|
// Check if it's a friendly name (no dots and registered)
|
|
|
if (!var_name.contains(".") && _resolver.is_friendly_name(var_name)) {
|
|
|
Expression? resolved = _resolver.resolve_to_expression(var_name);
|
|
|
if (resolved != null) {
|
|
|
- expr_to_translate = resolved;
|
|
|
+ return resolved;
|
|
|
}
|
|
|
}
|
|
|
+ return expression;
|
|
|
}
|
|
|
|
|
|
- // Use the variable translator to translate the expression
|
|
|
- return _translator.translate_expression(expr_to_translate);
|
|
|
+ // Handle PropertyExpression - recurse on target
|
|
|
+ if (expression is PropertyExpression) {
|
|
|
+ var prop_expr = (PropertyExpression) expression;
|
|
|
+ Expression resolved_target = resolve_friendly_names(prop_expr.target);
|
|
|
+
|
|
|
+ // If target changed, create new PropertyExpression
|
|
|
+ if (resolved_target != prop_expr.target) {
|
|
|
+ return new PropertyExpression(resolved_target, prop_expr.property_name);
|
|
|
+ }
|
|
|
+ return expression;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Handle BinaryExpression - recurse on both sides
|
|
|
+ if (expression is BinaryExpression) {
|
|
|
+ var binary = (BinaryExpression) expression;
|
|
|
+ Expression resolved_left = resolve_friendly_names(binary.left);
|
|
|
+ Expression resolved_right = resolve_friendly_names(binary.right);
|
|
|
+
|
|
|
+ // If either side changed, create new BinaryExpression
|
|
|
+ if (resolved_left != binary.left || resolved_right != binary.right) {
|
|
|
+ return new BinaryExpression(resolved_left, resolved_right, binary.op);
|
|
|
+ }
|
|
|
+ return expression;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Handle UnaryExpression - recurse on operand
|
|
|
+ if (expression is UnaryExpression) {
|
|
|
+ var unary = (UnaryExpression) expression;
|
|
|
+ Expression resolved_operand = resolve_friendly_names(unary.operand);
|
|
|
+
|
|
|
+ if (resolved_operand != unary.operand) {
|
|
|
+ return new UnaryExpression(unary.operator, resolved_operand);
|
|
|
+ }
|
|
|
+ return expression;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Handle TernaryExpression - recurse on all three parts
|
|
|
+ if (expression is TernaryExpression) {
|
|
|
+ var ternary = (TernaryExpression) expression;
|
|
|
+ Expression resolved_condition = resolve_friendly_names(ternary.condition);
|
|
|
+ Expression resolved_true = resolve_friendly_names(ternary.true_expression);
|
|
|
+ Expression resolved_false = resolve_friendly_names(ternary.false_expression);
|
|
|
+
|
|
|
+ if (resolved_condition != ternary.condition ||
|
|
|
+ resolved_true != ternary.true_expression ||
|
|
|
+ resolved_false != ternary.false_expression) {
|
|
|
+ return new TernaryExpression(
|
|
|
+ resolved_condition, resolved_true, resolved_false
|
|
|
+ );
|
|
|
+ }
|
|
|
+ return expression;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Handle BracketedExpression - recurse on inner
|
|
|
+ if (expression is BracketedExpression) {
|
|
|
+ var bracketed = (BracketedExpression) expression;
|
|
|
+ Expression resolved_inner = resolve_friendly_names(bracketed.inner);
|
|
|
+
|
|
|
+ if (resolved_inner != bracketed.inner) {
|
|
|
+ return new BracketedExpression(resolved_inner);
|
|
|
+ }
|
|
|
+ return expression;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Handle LambdaExpression - recurse on body
|
|
|
+ if (expression is LambdaExpression) {
|
|
|
+ var lambda = (LambdaExpression) expression;
|
|
|
+ Expression resolved_body = resolve_friendly_names(lambda.body);
|
|
|
+
|
|
|
+ if (resolved_body != lambda.body) {
|
|
|
+ return new LambdaExpression(lambda.parameter_name, resolved_body);
|
|
|
+ }
|
|
|
+ return expression;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Handle FunctionCallExpression - recurse on target and arguments
|
|
|
+ if (expression is FunctionCallExpression) {
|
|
|
+ var func = (FunctionCallExpression) expression;
|
|
|
+ Expression? resolved_target = null;
|
|
|
+ if (func.target != null) {
|
|
|
+ resolved_target = resolve_friendly_names(func.target);
|
|
|
+ }
|
|
|
+
|
|
|
+ Series<Expression>? resolved_args = null;
|
|
|
+ bool args_changed = false;
|
|
|
+ if (func.arguments != null) {
|
|
|
+ resolved_args = new Series<Expression>();
|
|
|
+ foreach (var arg in func.arguments) {
|
|
|
+ Expression resolved_arg = resolve_friendly_names(arg);
|
|
|
+ resolved_args.add_start(resolved_arg);
|
|
|
+ if (resolved_arg != arg) {
|
|
|
+ args_changed = true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if ((resolved_target != null && resolved_target != func.target) || args_changed) {
|
|
|
+ return new FunctionCallExpression(
|
|
|
+ resolved_target ?? func.target,
|
|
|
+ func.function_name,
|
|
|
+ resolved_args ?? func.arguments
|
|
|
+ );
|
|
|
+ }
|
|
|
+ return expression;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Handle GlobalFunctionCallExpression - recurse on arguments
|
|
|
+ if (expression is GlobalFunctionCallExpression) {
|
|
|
+ var global_func = (GlobalFunctionCallExpression) expression;
|
|
|
+
|
|
|
+ Series<Expression>? resolved_args = null;
|
|
|
+ bool args_changed = false;
|
|
|
+ if (global_func.arguments != null) {
|
|
|
+ resolved_args = new Series<Expression>();
|
|
|
+ foreach (var arg in global_func.arguments) {
|
|
|
+ Expression resolved_arg = resolve_friendly_names(arg);
|
|
|
+ resolved_args.add_start(resolved_arg);
|
|
|
+ if (resolved_arg != arg) {
|
|
|
+ args_changed = true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (args_changed) {
|
|
|
+ return new GlobalFunctionCallExpression(
|
|
|
+ global_func.function_name,
|
|
|
+ resolved_args
|
|
|
+ );
|
|
|
+ }
|
|
|
+ return expression;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Handle LotLiteralExpression - recurse on elements
|
|
|
+ if (expression is LotLiteralExpression) {
|
|
|
+ var lot = (LotLiteralExpression) expression;
|
|
|
+
|
|
|
+ Vector<Expression> resolved_elements = new Vector<Expression>();
|
|
|
+ bool elements_changed = false;
|
|
|
+ foreach (var elem in lot.elements) {
|
|
|
+ Expression resolved_elem = resolve_friendly_names(elem);
|
|
|
+ resolved_elements.add(resolved_elem);
|
|
|
+ if (resolved_elem != elem) {
|
|
|
+ elements_changed = true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (elements_changed) {
|
|
|
+ return new LotLiteralExpression(resolved_elements.to_array());
|
|
|
+ }
|
|
|
+ return expression;
|
|
|
+ }
|
|
|
+
|
|
|
+ // LiteralExpression, ParameterExpression - no children, return as-is
|
|
|
+ return expression;
|
|
|
}
|
|
|
|
|
|
/**
|