java apache calcite区分列名和表名

ztmd8pv5  于 2023-06-28  发布在  Java
关注(0)|答案(3)|浏览(194)

我正在实现一个简单的应用程序,它可以在SQL语句中更改列名(而不更改表名)。该语句作为String传递,修改后的语句也作为String返回,不涉及数据库连接。
为了实现这一点,我使用ApacheCalcite的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是列还是表?

33qvvth1

33qvvth11#

要将标识符解析为表和列,并确定它们的类型,您需要使用Calcite的验证器(SqlValidator)。验证器理解SQL名称解析规则(例如FROM子句中的别名是否可以在子查询中看到),而我们故意不使解析器及其产生的SqlNode数据结构知道这些事情。
验证器中的两个关键概念是作用域(SqlValidatorScope)和名称空间(SqlValidatorNamespace)。
一个 scope 是你所站的位置,并试图解析一个标识符。例如,您可能在查询的SELECT子句中。或者在特定子查询的WHERE子句中。您将能够在不同的范围中看到不同的表和列集合。甚至GROUP BY子句和ORDER BY子句也有不同的作用域。
一个 namespace 看起来像一个表,有一个列列表。它可能是一个表,也可能是FROM子句中的一个子查询。如果在作用域中,可以查找表别名,获得名称空间,然后查看它有哪些列。
对于您的目的,如果有一个SqlShuttle的变体,它确切地知道您在哪个范围内,以及您可以在哪里请求将标识符扩展为表和列引用,这将是非常有用的。不幸的是,还没有人建造这样的东西。

0md85ypi

0md85ypi2#

我碰巧使用了calcitesqlParser一点。下面贴出的一些片段。

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可能不是最佳选择。

ht4b089n

ht4b089n3#

我现在在访问者的旅行中使用布尔值isInFrom来告诉我这个节点是否在子句from [xxxx]中。
下面是我的Visitor实现的一个简化示例。

class Visitor extends SqlBasicVisitor<Void> {
    boolean isInFrom = false;
    
    @Override
    Void visit(SqlCall call) {
        switch (call.getKind()) {
            case SELECT:
                return visit((SqlSelect) call);
            case JOIN:
                return visit((SqlJoin) call);
            default:
                return super.visit(call);
        }
    }

    Void visit(SqlSelect select) {
        boolean isInFromBackup = isInFrom;
        for (SqlNode child : select.getOperandList()) {
            if (child == null) continue;
            isInFrom = select.getFrom() == child;
            child.accept(this);
        }
        isInFrom = isInFromBackup;
        
    }
    
    Void visit(SqlJoin join) {
        boolean isInFromBackup = isInFrom;
        for (SqlNode child : join.getOperandList()) {
            if (child == null) continue;
            if (child == join.getCondition()) {
                isInFrom = false;
            }
            child.accept(this);
            if (child == join.getCondition()) {
                isInFrom = isInFromBackup;
            }
        }
    }

    @Override
    public Void visit(SqlIdentifier id) {
        if (isInFrom) {
            // it's table name
        } else {
            // it's field name
        }
    }
}

P.S. isInFrom的名字并不准确,但我没有更好的名字。

相关问题