【问题标题】:apache calcite distinguish column names from table nameapache calcite 区分列名和表名
【发布时间】:2016-05-18 00:12:29
【问题描述】:

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

为了实现这一点,我使用了 Apache Calcite 的 SQL 解析器。我将 SQL 字符串解析为SqlNode,接受创建重命名SqlNodeSqlVisitor,然后将所有内容写回String(使用SqlNode.toSqlString())。

问题是在接受SqlVisitor 时,我不知道如何区分解析的SqlNode 对象中的列和表之间的区别。两者都表示为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 是列还是表?

【问题讨论】:

    标签: java sql parsing apache-calcite


    【解决方案1】:

    要将标识符解析为表和列,并确定它们的类型,您需要使用 Calcite 的验证器 (SqlValidator)。验证器了解 SQL 名称解析规则(例如,是否可以在子查询中看到 FROM 子句中的别名),而我们故意没有制作解析器,它产生的 SqlNode 数据结构知道这些事情。

    验证器中的两个关键概念是作用域 (SqlValidatorScope) 和命名空间 (SqlValidatorNamespace)。

    作用域 是您站立并尝试解析标识符的位置。例如,您可能在查询的 SELECT 子句中。或者在特定子查询的 WHERE 子句中。您将能够看到不同范围内的不同表和列集合。即使是 GROUP BY 子句和 ORDER BY 子句也有不同的作用域。

    命名空间 看起来像一个表,并且有一个列列表。它可能是一个表,或者说,FROM 子句中的一个子查询。如果你在一个范围内,你可以查找一个表别名,获取一个命名空间,然后查看它有哪些列。

    出于您的目的,如果有一个 SqlShuttle 的变体可以准确地知道您在哪个范围内,以及您可以在哪里要求将标识符扩展为表和列引用,那将会很有用。不幸的是,还没有人建造过这样的东西。

    【讨论】:

    • SqlScopedShuttle 怎么样?
    • SqlScopedShuttle 听起来很有用,但它并没有你想要的那么多。当您递归到树中时,它只保留一堆 AST 节点(SqlNode)。它对 SQL 范围规则一无所知。
    • @PiotrŚmietana 如果你最终得到了一些工作代码,你能分享一下吗?
    • @Arvidaa 我想我使用了一些不稳定的解决方法。我不记得它到底是什么,也无法再访问此代码,所以我无法提供任何详细信息。
    【解决方案2】:

    我碰巧用过calcitesqlParser。下面发布的一些sn-p。

      public void convertSelect(SqlSelect root) {
        convertFrom(root.getFrom());
        convertWhere(root.getWhere());
      }
    
      public void convertFrom(SqlNode from) {
        if (from instanceof SqlJoin) {
          convertFromOfJoinExpression((SqlJoin)from);
        }
      }
    
      public String extractTableFromJoinNode(SqlNode jnn) {
        if (jnn instanceof SqlBasicCall) {
          SqlBasicCall asExp = (SqlBasicCall)jnn;
          if (asExp.getKind().equals(SqlKind.AS)) {
            extractTableFromJoinNodeAsExpression(asExp);
          }
        }
        return "SomeTableAlias";
      }
    

    通常,您会在from 语句中得到table。你会在select 语句中得到columns

    最后但同样重要的是,calcite 专门通过应用大量优化规则来优化查询。取决于您需要什么(转换列/表名),calcite 可能不是最合适的。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多