基于Xtext定制SQL DSL:表达式左右节点获取及生成器问题
解决Xtext自定义SQL DSL的表达式处理与生成器完善问题
我来帮你拆解并解决这个问题——核心是你需要递归遍历Xtext生成的表达式模型,同时修正语法中不符合SQL逻辑的部分,才能正确生成复杂SQL语句。
1. 先解决表达式无法获取left/right的问题
你当前的语法定义已经正确生成了带left/right属性的表达式模型,但生成器里只处理了单个Literal的情况(直接访问ID)。复杂表达式(比如A OR B)的类型是OrExpression/AndExpression等子类,需要通过类型判断来访问对应的属性。
另外,你的NotExpression语法定义有问题:
NotExpression returns Expression: ComparisonExpression({NotExpression.left=current} name='NOT' right=ComparisonExpression)*;
这会把NOT当成二元运算符(比如A NOT B),不符合SQL中NOT是一元运算符的逻辑(比如NOT (A = B))。需要修改为:
NotExpression returns Expression: ('NOT' expr=ComparisonExpression) | ComparisonExpression;
修改后生成的模型中,NotExpression会有一个expr属性,对应被否定的表达式。
2. 编写递归的表达式转SQL方法
在生成器中添加一个递归方法,根据表达式的实际类型将其转换为SQL字符串:
def String toSql(Expression expr) { switch expr { // 二元表达式处理 case OrExpression: return toSql(expr.left) + " " + expr.name + " " + toSql(expr.right) case AndExpression: return toSql(expr.left) + " " + expr.name + " " + toSql(expr.right) case ComparisonExpression: return toSql(expr.left) + " " + expr.name + " " + toSql(expr.right) case BitwiseOR, BitwiseAND, BitwiseXOR: return toSql(expr.left) + " " + expr.name + " " + toSql(expr.right) case Addition, Substraction, Multiplication, Division, Modulo: return toSql(expr.left) + " " + expr.name + " " + toSql(expr.right) // 一元表达式与特殊结构 case NotExpression: return "NOT " + toSql(expr.expr) case Bracket: return "(" + toSql(expr.inner) + ")" case Primary: var sql = "" if (expr.unop !== null) { sql += expr.unop + " " } if (expr.ID !== null) { sql += toSql(expr.ID) if (expr.isNotNull !== null) { sql += " IS " + (expr.isNotNull ? "NOT " : "") + "NULL" } } return sql // 字面量与函数 case Literal: val value = expr.value if (value instanceof StringValue) { return "'" + value.name.replace("'", "''") + "'" // 转义单引号避免语法错误 } else if (value instanceof NumberValue) { return value.name } else if (value instanceof NullValue) { return "NULL" } return value.name case Funccall: return expr.func.name + "(" + expr.func.arguments.map[toSql(it)].join(", ") + ")" case Count: return "COUNT(" + (expr.count.column ? toSql(expr.count.column) : "*") + ")" default: return expr.toString() } }
3. 完善SQL生成器的核心逻辑
替换生成器中处理SELECT、FROM、WHERE的代码,调用上面的toSql方法处理复杂表达式:
class TsqlGenerator extends AbstractGenerator { StringBuilder st = new StringBuilder(); TimeConditionHandler tch = new TimeConditionHandler() int whereIndex = 0; def clear() { st.setLength(0); whereIndex = 0; } override beforeGenerate(Resource input, IFileSystemAccess2 fsa, IGeneratorContext context) { clear() } override void doGenerate(Resource resource, IFileSystemAccess2 fsa, IGeneratorContext context) { for (e : resource.allContents.toIterable.filter(ComplexSelect)) { // 处理SELECT子句 st.append("SELECT "); val selectArgs = e.left.selectArguments.arguments.map[toSql(expr)].join(", ") st.append(selectArgs); // 处理FROM子句 st.append(" FROM ") val fromSources = e.left.from.sources.map[name].join(", ") st.append(fromSources); // 处理WHERE子句 if (e.left.where !== null || e.left.timedef !== null) { st.append(" WHERE"); if (e.left.where !== null) { val wherePredicates = e.left.where.predicates.expr.map[toSql(it)].join(" AND ") st.append(" " + wherePredicates); whereIndex = 1; } // 处理时间条件 if (e.left.timedef !== null) { if (whereIndex > 0) { st.append(" AND") } st.append(" " + tch.toIso8601(e.left.timedef)) } fsa.generateFile("query.txt", st.toString()); } } } // 上面的toSql方法放在这里 def String toSql(Expression expr) { // ... 实现代码同上 ... } }
关键说明
- 递归遍历:表达式是嵌套结构(比如
(A + B) * C),必须用递归才能正确展开为合法的SQL字符串。 - 类型判断:通过
switch expr识别表达式的实际类型,才能访问对应的left/right/expr属性。 - 字面量转义:处理字符串字面量时转义单引号,避免SQL语法错误或注入风险。
- 语法修正:修正
NotExpression的定义,使其符合SQL中一元运算符的逻辑,才能正确生成NOT相关的表达式。
内容的提问来源于stack exchange,提问作者Saymos
相关产品推荐
相关产品推荐

