2

我正在实现一个简单的应用程序,它可以在 SQL 语句中更改列名(并单独保留表名)。语句作为 a 传递String,修改后的语句也作为 a 返回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的是列还是表?

4

2 回答 2

1

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

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

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

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

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

于 2016-05-31T19:59:15.427 回答
0

我碰巧用过calcite sqlParser一点。下面发布的一些片段。

  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";
  }

一般来说,你会得到tablefrom声明中。你会得到columns陈述select

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

于 2016-05-23T11:09:53.183 回答