我正在实现一个简单的应用程序,该应用程序可以更改SQL语句中的列名(并保留表名)。该语句作为String传递,而修改后的语句也作为String返回,不涉及数据库连接。

为此,我使用了Apache Calcite的SQL解析器。我将SQL字符串解析为SqlNode,接受创建重命名的SqlVisitorSqlNode,然后将所有内容写回到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的变体,它确切地知道您位于哪个作用域,以及您可以要求将标识符扩展为表和列引用的位置,这将非常有用。不幸的是,还没有人制造出这样的东西。

08-04 23:44