3

我必须编写基于 HQL 的引擎,动态修补 HQL 查询,动态添加连接和位置(为了防止出现问题——我必须使用 HQL,而不是标准 API)。

例如注入到 HQL 之类的from Object a东西from Object a JOIN a.path b WHERE b.id='XYZ'

我看到了几个选项,但都不适合我:

  1. JavaString.insert()方法。查找 WHERE 语句的位置并在语句之前和之后添加其他部分。对于这样的 HQL 来说,这不是一件容易的事

    SELECT a, (SELECT b FROM Object2 b WHERE b.path=a) FROM Object a WHERE EXISTS(SELECT 1 FROM ... WHERE)

待定算法:我可以计算每个 WHERE 之前的括号 () 数量,如果它为零 - 我找到了正确的 WHERE 位置。有人可以在我的 Java 上向我提出一个更简单的算法或简单的实现吗?

2.我怀疑我的任务可以通过正则表达式来解决,但不能写出正确的正则表达式String.replace()

3.我看到了基于 AST/ANTLR 的语法并且可以解析我的 HQL,但看不到它如何帮助我(即我有声明,但没有正确声明的位置WHERE)。

4. 独立库用于 SQL 解析,但不用于 HQL。

无论如何,感谢您的任何想法:)

4

2 回答 2

2

以下是对您提出的选项的一些想法。

  1. String.insert手动解析

    如果可以的话,手动解析字符串可能是一种合理的方法:a) 轻松定义您将要开始使用的 HQL 语法的子集,b) 确信您可以逐个字符地解析它,并且 c)知道子集语法很少或永远不会改变。听起来你必须遵循这样的方法:

    • 从流中读取一个字符。
    • 如果它是字符串的开头(例如, a '),则启动字符串读取方法,以便'('')'不作为正常输入处理。如果是字符串的结尾,则重新开始正常处理。
    • 如果它是分组表达式的开头(例如 a '('),则开始跟踪新表达式等。
    • 如果是 a ' ',查看刚刚使用的单词并决定如何处理它:如果它是表达式的开头(例如,SELECT),检查 ID(例如,a),然后检查下一组可能性和处理它们,等等。
    • 阅读下一个字符并重复这些步骤。

    通常,如果添加或删除规则,该方法听起来很难在第一次就正确并且难以修改。我会避免使用这种方法来解决您的问题,但是如果您可以将输入保持得像泥土一样简单,那么这是一个不错的解决方案。

  2. 常用表达

    这种方法比第一种方法更简洁,但您将完全受限于正则表达式的限制:您正在寻找分析结构化数据 ( SELECT a, (SELECT b ...) FROM Object a ...) 而不是正则表达式最擅长的线性或平面数据。尽管如此,如果与第一步这样的手动过程相结合,它可能会很好地工作……但听起来也很困难。

  3. ANTLR 使用 HQL 语法

    标准的 Hibernate 下载包括 ANTLR 用来生成读取 HQL 的解析器的 HQL 语法文件。您可以将它与 HQL/Hibernate 代码分离(语法中有一些看起来很小的依赖项)并将其修剪到您知道您将要处理的内容。与前两个选项相比,这将给您带来两大优势:从一开始您就拥有一个成熟的、可工作的解析器,并且它将被编码为解析尽可能多或尽可能少的官方 HQL 输入——很少“基础”涉及到编程。

    对于这个选项,我可以想到三个缺点:HQL 语法使用版本 ANTLR 2.7,这对于 SO 上的人们来说似乎是众所周知的,但它是最新版本之后的一个(并且很快将是两个)版本。我不知道更新它是否微不足道,但根据我对 ANTLR 3 的经验和从语法来看,这两个版本都需要一些知识。但是当然你没有义务使用最新版本的 ANTLR,所以如果你想要它,这只是一个缺点。

    第二个缺点是将语法与其他 HQL 代码分开的行为,这样您就不会让这些依赖项四处飘荡。您可能还想删除不需要的规则,尽管这是一个非常简单的过程。对我来说,这里没有什么困难的,因为删除无用/不可用的东西非常容易。

    第三个缺点是您必须学习一些基本的 ANTLR 才能生成解析器代码并自信地进行任何更改。我认为 ANTLR 的基础知识很容易学习,如果你需要的话,这里有很多支持,所以我认为这和之前的缺点一样,是相当小的。

    总的来说,我认为这种方法是一个非常好的开始:您可以免费获得所需的语法,并免费从该语法中获得所需的解析器。所需要的只是将语法与 Hibernate 项目和一些初学者的 ANTLR 知识分开。

  4. 杂项。独立的

    我想不出任何属于此类别的项目可以解决您的问题。

  5. ANTLR 使用你自己的语法

    我添加了这个选项作为选项 3 的替代方案。如果您知道您开始使用的 HQL 子集并且对代表它的通用语法(不一定是 ANTLR 语法)有信心,您可以从头开始编写自己的 ANTLR 语法并从中生成您的解析器。它比选项 3 工作量更大,因为您将建立而不是缩减,但是当您了解更多有关 ANTLR 的信息时,您可能会更适应这种方法。

对于一般的 ANTLR 解决方案,您可以通过分析(生成的)ANTLR 解析器生成的 AST 树来找到放置JOIN和子句的位置(此 Bart Kiers 回答显示了由 ANTLR 生成的简单树的示例解析器)。如果您想讨论仅 ANTLR 的解决方案,我建议您提出一个新问题,以免与此处列出的其他选项纠缠在一起。WHERE

听起来你正在处理一个棘手的问题,我不知道所有细节,但我认为在你的解决方案中使用 ANTLR 可以节省你的时间(无需手动编写解析器)和任何未来的变化需要(只需更改语法并重新生成解析器)。我推荐选项 3 或选项 5,以适合您的舒适度为准。

于 2012-10-21T07:16:44.853 回答
0

我遵循路径#1。它并没有我想象的那么复杂:

代码期望pFindPart必须采用“WHERE”或“FROM”格式。

private int findInsertPosition(StringBuilder pStringBuilder, String pFindPart){
    String HQL = pStringBuilder.toString().toUpperCase(Locale.US);
    int whereIndex = HQL.length();
    int findPartLength = pFindPart.length();
    while(whereIndex >= 0){
        whereIndex = HQL.lastIndexOf(pFindPart, whereIndex);
        if (whereIndex >=0){
            String rightPart = HQL.substring(whereIndex + findPartLength);
            int count = 0;
            for(char c : rightPart.toCharArray()){
                switch(c){
                    case ')': count--; break;
                    case '(': count++; break;
                }
            }
            if (count == 0) break;
            whereIndex--;
        }
    }
    return whereIndex;
}
于 2012-10-31T14:37:21.417 回答