我正在实现一个简单的应用程序,该应用程序可以更改SQL语句中的列名(并保留表名)。该语句作为String
传递,而修改后的语句也作为String
返回,不涉及数据库连接。
为此,我使用了Apache Calcite的SQL解析器。我将SQL字符串解析为SqlNode
,接受创建重命名的SqlVisitor
的SqlNode
,然后将所有内容写回到String
(使用SqlNode.toSqlString()
)。
问题是我不知道在接受SqlNode
时如何分辨已解析的SqlVisitor
对象中的列和表之间的区别。两者都表示为SqlIdentifier
,具有相同的SqlKind
。因此,当SqlVisitor
访问SqlIdentifier
时,它将重命名它是列还是表。
private String changeNames(String str) throws SqlParseException {
SqlShuttle visitor = new SqlShuttle() {
private String rename(String str) {
return str + "-test";
}
@Override
public SqlNode visit(SqlIdentifier identifier) {
SqlIdentifier output = new SqlIdentifier(rename(identifier.getSimple()), identifier.getCollation(), identifier.getParserPosition());
return output;
}
};
SqlParser.ConfigBuilder configBuilder = SqlParser.configBuilder();
configBuilder.setLex(Lex.MYSQL);
SqlParser.Config config = configBuilder.build();
SqlParser parser = SqlParser.create(str, config);
SqlNode parsedStatement = parser.parseQuery(str);
SqlNode outputNode = parsedStatement.accept(visitor);
return outputNode.toSqlString(SqlDialect.DUMMY).getSql();
}
例如
SELECT name, address, age FROM mytablename WHERE age = 23 AND name = 'John'
将被修改为
SELECT `name-test`, `address-test`, `age-test` FROM `mytablename-test` WHERE `age-test` = 23 AND `name-test` = 'John'
我怎么知道给定的
SqlIdentifier
是列还是表? 最佳答案
要解析表和列的标识符并找出它们的类型,您将需要使用Calcite的验证器(SqlValidator
)。验证器了解SQL名称解析规则(例如,是否可以在子查询中看到FROM子句中的别名),而我们故意没有使解析器及其生成的SqlNode
数据结构意识到这一点。
验证器中的两个关键概念是范围(SqlValidatorScope
)和名称空间(SqlValidatorNamespace
)。
作用域是您站立并尝试解析标识符的位置。例如,您可能在查询的SELECT子句中。或在特定子查询的WHERE子句中。您将能够在不同的范围内看到表和列的不同集合。甚至GROUP BY子句和ORDER BY子句都有不同的范围。
命名空间是看起来像表的东西,并且具有列列表。它可能是一个表,或者是FROM子句中的子查询。如果您在范围内,则可以查找表别名,获取名称空间,然后查看其具有的列。
出于您的目的,如果存在SqlShuttle
的变体,它确切地知道您位于哪个作用域,以及您可以要求将标识符扩展为表和列引用的位置,这将非常有用。不幸的是,还没有人制造出这样的东西。