3 Stimmen

HQL: Dynamisches Hinzufügen von JOINS und WHERE-Teilen (ANTLR, regex oder String)

Ich habe zu HQL-basierten Motor schreiben, dass Patch HQL-Abfragen on the fly dynamisch hinzufügen Joins und wo Teile (Fragen zu verhindern - ich muss HQL, nicht Kriterien API verwenden).

Z.B. Injektion nach HQL from Object a etwas wie from Object a JOIN a.path b WHERE b.id='XYZ'

Ich sehe mehrere Möglichkeiten, aber keine davon funktioniert für mich:

  1. Java String.insert() Ansatz. Suchen Sie die Position der WHERE-Anweisung und fügen Sie Joins vor und andere Teile nach der Anweisung hinzu. Es ist keine triviale Aufgabe für HQL wie diese

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

Anhängiger Algorithmus: Ich könnte die Anzahl der Klammern () vor jedem WHERE berechnen und wenn sie Null ist, habe ich die richtige WHERE-Position gefunden. Könnte mir jemand einen einfacheren Algorithmus oder eine einfache Implementierung in Java vorschlagen?

2. ich vermute, dass meine Aufgabe durch reguläre Ausdrücke gelöst werden könnte, aber ich kann keinen korrekten regulären Ausdruck schreiben für String.replace()

3. ich habe die AST/ANTLR-basierte Grammatik gesehen und konnte meine HQL parsen, aber ich sehe keinen Weg, wie sie mir helfen könnte (d.h. ich habe die Anweisung, aber nicht die Position der richtigen Anweisung WHERE ).

Es gibt eigenständige Bibliotheken für SQL-Parsing, aber nicht für HQL.

Wie auch immer, vielen Dank für Ihre Meinung :)

2voto

user1201210 Punkte 3801

Hier sind einige Gedanken zu den von Ihnen vorgeschlagenen Optionen.

  1. String.insert mit manuellem Parsing

    Manuelles Parsen der Zeichenkette kann ein vernünftiger Weg sein, wenn Sie a) die Teilmenge der HQL-Grammatik, mit der Sie beginnen werden, leicht definieren können, b) sicher sind, dass Sie sie Zeichen für Zeichen parsen können, und c) wissen, dass sich die Teilmenge der Grammatik selten oder nie ändern wird. Es hört sich an, als müssten Sie einen Ansatz wie diesen verfolgen:

    • Lesen eines Zeichens aus dem Stream.
    • Wenn es der Anfang einer Zeichenkette ist (z. B. eine ' ), einen Ansatz zum Lesen von Zeichenketten starten, so dass '(' y ')' werden nicht als normale Eingabe verarbeitet. Wenn es sich um das Ende einer Zeichenkette handelt, beginnt die normale Verarbeitung erneut.
    • Wenn es der Anfang eines gruppierten Ausdrucks ist (z. B. ein '(' ), die Verfolgung eines neuen Ausdrucks beginnen, usw.
    • Wenn es sich um eine ' ' Schauen Sie sich das soeben konsumierte Wort an und entscheiden Sie, was damit geschehen soll: Wenn es der Anfang eines Ausdrucks ist (z. B., SELECT ), nach einer ID suchen (z.B., a ), prüft dann die nächste Reihe von Möglichkeiten und behandelt sie, und so weiter.
    • Lesen Sie das nächste Zeichen und wiederholen Sie die Schritte.

    Im Allgemeinen scheint es schwierig zu sein, den Ansatz beim ersten Mal richtig zu machen, und schwierig zu ändern, wenn Regeln hinzugefügt oder entfernt werden. Ich würde diesen Ansatz vermeiden, um Ihr Problem zu lösen, aber es ist eine gute Lösung, auf die man zurückgreifen kann, wenn man die Eingabe so einfach wie möglich halten kann.

  2. Reguläre Ausdrücke

    Dieser Ansatz ist sauberer als der erste, aber Sie wären völlig an die Grenzen der regulären Ausdrücke gebunden: Sie wollen strukturierte Daten analysieren ( SELECT a, (SELECT b ...) FROM Object a ... ) und nicht lineare oder flache Daten, für die reguläre Ausdrücke am besten geeignet sind. Dennoch kann es gut funktionieren, wenn es mit einem manuellen Prozess wie Schritt eins kombiniert wird... aber das klingt auch schwierig.

  3. ANTLR mit HQL-Grammatik

    Der Standard-Hibernate-Download enthält die HQL-Grammatikdatei, die ANTLR verwendet, um einen Parser zu erzeugen, der HQL liest. Sie könnten diese Datei vom HQL/Hibernate-Code entkoppeln (es gibt ein paar unbedeutende Abhängigkeiten in der Grammatik) und sie auf das reduzieren, was Sie wissen, dass Sie damit zu tun haben werden. Dies würde Ihnen zwei große Vorteile gegenüber den beiden vorherigen Optionen bieten: Sie hätten von Anfang an einen vollwertigen, funktionierenden Parser und er wäre so kodiert, dass er so viel oder so wenig offizielle HQL-Eingabe parsen kann - es wäre sehr wenig "Grundprogrammierung" erforderlich.

    Es gibt drei Nachteile, die ich mir bei dieser Option vorstellen kann: Die HQL-Grammatik verwendet die Version ANTLR 2.7, die den Leuten hier auf SO bekannt zu sein scheint, aber sie ist eine (und bald zwei) Versionen hinter der neuesten Version zurück. Ich weiß nicht, ob es trivial ist, sie zu aktualisieren, aber aus meiner Erfahrung mit ANTLR 3 und wenn ich mir die Grammatik ansehe, würde es einiges an Wissen über beide Versionen erfordern. Aber natürlich sind Sie nicht verpflichtet, mit der neuesten Version von ANTLR zu gehen, so dass dies nur ein Nachteil ist, wenn Sie wollen, dass es einer ist.

    Der zweite Nachteil besteht darin, dass die Grammatik vom übrigen HQL-Code getrennt werden muss, damit diese Abhängigkeiten nicht mehr bestehen. Vielleicht möchten Sie auch Regeln herausschneiden, die Sie nicht benötigen, obwohl das ein ziemlich trivialer Prozess ist. Für mich sieht hier nichts schwierig aus, da das Herausschneiden von nutzlosem/unbrauchbarem Zeug ziemlich einfach ist.

    Der dritte Nachteil ist, dass man einige ANTLR-Grundlagen erlernen muss, um den Parser-Code zu generieren und um Änderungen sicher vornehmen zu können. Ich denke, die Grundlagen von ANTLR sind einfach zu erlernen und es gibt eine Menge Unterstützung, wenn Sie es brauchen, so dass ich denke, dass dies, wie die vorherigen Nachteile, ziemlich gering ist.

    Insgesamt halte ich diesen Ansatz für einen sehr guten Anfang: Sie bekommen die Grammatik, die Sie brauchen, umsonst und Sie bekommen den Parser, den Sie brauchen, umsonst. Alles, was man braucht, ist die Trennung der Grammatik vom Hibernate-Projekt und etwas ANTLR-Anfängerwissen.

  4. Sonstiges eigenständig

    Mir fallen keine Projekte ein, die in diese Kategorie fallen und Ihr Problem lösen würden.

  5. ANTLR mit Ihrer eigenen Grammatik

    Ich habe diese Option als Alternative zu Option 3 hinzugefügt. Wenn Sie die Teilmenge von HQL kennen, mit der Sie beginnen, und mit einer generischen Grammatik (nicht unbedingt einer ANTLR-Grammatik) vertraut sind, die diese repräsentiert, können Sie Ihre eigene ANTLR-Grammatik von Grund auf neu schreiben und Ihren Parser daraus generieren. Das ist mehr Arbeit als Option 3, weil man eher aufbaut als abbaut, aber Sie werden sich mit diesem Ansatz vielleicht wohler fühlen, wenn Sie mehr über ANTLR lernen.

Für die ANTLR-Lösungen im Allgemeinen würden Sie den Ort finden, an dem Sie Ihre JOIN y WHERE Klauseln durch Analyse des AST-Baums, der von dem (generierten) ANTLR-Parser erzeugt wird ( diese Antwort von Bart Kiers zeigt ein Beispiel für einen einfachen Baum, der von einem ANTLR-Parser erzeugt wird). Wenn Sie eine reine ANTLR-Lösung diskutieren möchten, empfehle ich Ihnen, eine neue Frage zu stellen, damit sie sich nicht mit den anderen hier aufgeführten Optionen verheddert.

Es klingt, als hätten Sie es mit einem schwierigen Problem zu tun, und ich kenne nicht alle Details, aber ich denke, die Verwendung von ANTLR in Ihrer Lösung würde Ihnen im Vorfeld Zeit ersparen (kein von Hand zu schreibender Parser) und bei zukünftigen Änderungen (einfach die Grammatik ändern und den Parser neu erstellen). Ich empfehle entweder Option 3 oder Option 5, je nachdem, was Ihnen am meisten zusagt.

0voto

FoxyBOA Punkte 5762

Ich bin Weg Nr. 1 gefolgt. Er war nicht so komplex wie erwartet:

Code-Erwartung pFindPart muss im Format " WHERE " oder " FROM " erfolgen.

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

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X