【问题标题】:How to skip the SQL(part of the SQL) parsing in antlr4?如何跳过antlr4中的SQL(部分SQL)解析?
【发布时间】:2021-12-30 21:04:44
【问题描述】:

很抱歉这个问题已关闭,无法重新打开,而且我的英语很差,它确实是由网站翻译的。 :) https://stackoverflow.com/questions/70035964/how-to-skip-sql-parsing-in-antlr4

@BartKiers 感谢您对这个问题感兴趣,让我举一个详细的例子。

SQL查询有很多,比如select * from userupdate user set field1 = 'value1' where condition = 'value'等,我们称之为原始SQL查询。

有一个java程序,通过ANTLR4将所有原始SQL查询截取并解析成Parse Tree节点,然后由java程序重写查询(取决于解析阶段),因此原始SQL查询可能被解析并改写为

select field1, field1_encrypted, field1_digest, field2 from user

update user 
  set field1 = value1, 
      field1_encrypted = encrypt_algorithm(value1), 
      field1_digest = digest_algorithm(value1) 
  where condition_digest = digest_algorithm(values)

等等

虽然他们完成了重写阶段,但它们应该作为 SQLStatement 执行,SELECT 作为 SelectSQLStatement 执行,而 UPDATE 作为 UpdateSQLStatement 执行。

现在我认为一些原始的 SQL 查询应该跳过解析阶段,应该跳过的重写阶段一样,但原始的 SQL 查询应该按原样执行。

我想把那些有评论的标记为

/* PARSE_PHASE_SKIPPED=TRUE */ originalSQL

或前缀SKIP

SKIP originalSQL

,我希望通过ANTLR4将整个标记但原始的SQL查询部分解析为Parse Tree节点,并将其作为ParsePhaseSkippedSQLStatement执行。

ANTLR4可以支持这种情况吗,语法应该怎么写?提前致谢。

=====================

感谢您@Mike Cargal 的回复,是的,差不多。

我再说一遍,再举一个更详细的例子。

有一个java系统我们称之为X,X有很多开发者编写的SQL查询,并保证这些SQL可以被Ibatis/JPA等正确执行,我们把这些SQL查询命名为原始SQL查询。

以以下原始 SQL 查询为例:

insert into user (username, id_no) values ('xyz', '123456')
select username, id_no from user u where u.id_no = '123456'

我们说表user的id_no列是敏感数据,我们应该保存密文而不是明文,所以原来的SQL会被ANTLR解析并用java代码重写如下,我们将这些SQL命名为重写SQL查询,也重写Ibatis / JPA等应该正确执行SQL查询。

insert 
  into user (username, id_no, id_no_cipher, id_no_digest) 
  values ('xyz', '', 'encrypted_123456', 'digest_123456')
select username, id_no_cipher as id_no 
  from user u 
  where u.id_no_digest = 'digest_123456'

在这种情况下:

1、我们看到rewrite阶段依赖于parse阶段,原来的SQL查询需要正确解析然后用java代码重写。

2、所有原始SQL查询都被解析,但只有少数符合敏感规则的查询被重写为重写SQL查询。

但是有很多原始的SQL查询我们清楚的知道不需要重写,也不需要解析,在解析的时候可能会在各种复杂的情况下报异常,但是应该被Ibatis正确执行/ JPA等

所以我打算使用sql注释/自定义关键字注释来“关闭”它的解析阶段。

【问题讨论】:

  • 这仍然很难理解,但我不想接受我用另一种语言提出的问题,所以我很同情。我认为我看到了您正在尝试做的事情,并提供了一些答案,可能会让您朝着正确的方向前进。简而言之,这不是 ANTLR 的事情,但是您可以通过多种方式使用 ANTLR 来解决您的问题。
  • 开启/关闭 SQL 语句的标准方法是使用 WHERE 1=1WHERE True 启动 WHERE 子句,然后使用 AND col1= 'val1' 添加其他条件 BTW : /* 是 SQL 中的非标准注释。

标签: sql parsing antlr4


【解决方案1】:

如果我正确理解您的问题,您希望使用某种注释/注释来“关闭”以下 SQL 语句的执行。

(注意:您不能真正跳过输入的“解析”部分。这将解决您可以跳过处理部分已解析输入的方式,我相信这就是您的最终想要完成。)

这不是 ANTLR 问题。 ANTLR 的职责是解析您的输入流并生成正确表示您的输入结构的解析树(技术上不是 AST)。

执行 SQL 不是 ANTLR 所做的。但是,它确实会生成实用程序 Listener 和 Visitor 类,这些类可用于干净有效地导航生成的解析树。从解析树中实际执行 SQL 可能涉及很多代码。通常,第一步是从解析树生成一个 AST,以便于处理。

你有几个选择(正如你暗示的那样)。

1 - 使用当前语法将这些注释放入 cmets (/* PARSE_PHASE_SKIPPED=TRUE */)

这个可以做,但是有点“乱”。 COMMENT 令牌很可能是 -> skiped(或者可能发送到 -> channel(HIDDEN))。这使得编写解析器规则变得更加容易,因为您不必在任何可能出现评论的地方都包含可选的COMMENTs。也就是说,如果您将COMMENT 令牌发送到HIDDEN 通道,即使它们被解析器忽略,它们仍然在令牌流中。 COMMENT 标记不会出现在侦听器/访问者处理的规则上下文对象中,但您可以在标记流中向后/向前查看 COMMENT 节点。

2 - 您可以为注释引入一些新语法(类似于您的 SKIP 想法)。为此,您必须扩展语法中的语法以识别这些注释。它们必须与有效 SQL 区分开来,因此简单的 SKIP 可能行不通。

这种方法的好处是,当您扩展语法以识别注释时,您可以非常具体地确定允许注释的位置。您可以将它们包含在解析树中,从而更容易找到它们。

使用这两种方法中的任何一种,您都可以使用访问者或侦听器来遍历您的分析树以查找注释/注释,然后用您不想执行的指示符标记后续语句。 (您可以使用这些信息简单地跳过解析树到“跳过”节点的 AST 转换)。

【讨论】:

  • 是的,差不多,因为 SKIP 是 ANTLR 保留的,所以这里使用 SKIPD。你能告诉我一个使用自定义关键字注释SKIPD在mysql上实现它的例子吗?我试了一下,但无法识别 SKIPD 异常。
【解决方案2】:

让我看看我是否正确理解了您的问题。在您的环境中,您运行 SQL 查询(不是“SQLs”,顺便说一句。),其中可能包含不能按原样到达服务器的数据。这是敏感数据还是其他数据都没有关系。重要的是您要替换查询中的文本。

为此,您需要解析查询并重写它们,然后再将它们发送到服务器。但是,您不想对所有查询都这样做,而只对特定查询这样做。您想出了用特殊注释标记不能转换的查询(或查询部分)的想法。到目前为止,这符合您的意图吗?

现在我想知道您为什么要在查询(解析)级别完成此操作。您要修改的不是解析,而是解析结果的语义处理(这里是解析树,正如 Mike Cargal 已经提到的)。因此,我认为您不需要为查询引入特殊标记,而是定义指示必须替换哪些数据的标准。

当您想到这一点时,您可能会意识到需要替换特定表中特定字段(列)的数据。您实际上可以保留一个模式/表/列元组列表,它会告诉您的重写器是否必须重写一个值。其他一切都保持原样。

ANTLR4 在这个过程中没有任何作用。这一切都在语义阶段完成(使用解析树侦听器处理解析树)。在此阶段,您必须收集查询中使用的所有列引用。然后将该列表与重写器的列表进行比较。如果列引用匹配,重写器必须在查询中为它重写文本。

然而,由于嵌套查询(子查询,内部查询可以从外部查询引用表),这项任务并不简单。这是顺便说一句。与代码完成的工作方式非常相似,您必须在查询中为所有提到的表提供可能的列列表。这就是我在 MySQL Workbench 的 SQL 代码补全中编写 (C++) 代码 to collect such references 的原因。

【讨论】:

  • 感谢您的回复,是的,完美匹配。我不想对所有查询都这样做,而只是针对特定查询,我想解释一下为什么我想在查询(解析)级别上这样做。我已经写了很多ANTLR4语法,访问者和监听器来将DDL和DML等解析为SQLStatements,但是在各种复杂的场景和情况下(例如sql函数等),都会发生解析异常,我无法涵盖所有,我的测试同事也无法涵盖所有​​已解析的测试用例。所以我期待找到一些方法来跳过解析阶段。
  • 我正在考虑将 SKIPD sql 之类的查询解析为 SkipdSQLStatement 但不解析 sql 部分,是的,将 sql 部分解析为整个纯字符串。我不知道这是否可行以及如何?
猜你喜欢
  • 2018-12-20
  • 1970-01-01
  • 1970-01-01
  • 2020-03-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-08-11
相关资源
最近更新 更多