SQL-ähnliche benutzerdefinierte DSL-Abfragen in ElasticSearch konvertieren?

8

Wir erstellen unsere eigene Abfragesprache ähnlich wie MySQL mit antlr4. Außer wir verwenden nur where clause, mit anderen Worten, der Benutzer gibt keine select/fromAnweisungen ein.

Ich konnte Grammatik dafür erstellen und Lexer / Parser / Listener in Golang generieren.

Unter unserer Grammatikdatei EsDslQuery.g4:

grammar EsDslQuery;

options {
language = Go;
}

query
   : leftBracket = '(' query rightBracket = ')'                             #bracketExp
   | leftQuery=query op=OR rightQuery=query                                 #orLogicalExp
   | leftQuery=query op=AND rightQuery=query                                #andLogicalExp
   | propertyName=attrPath op=COMPARISON_OPERATOR propertyValue=attrValue   #compareExp
   ;

attrPath
   : ATTRNAME ('.' attrPath)?
   ;

fragment ATTR_NAME_CHAR
   : '-' | '_' | ':' | DIGIT | ALPHA
   ;

fragment DIGIT
   : ('0'..'9')
   ;

fragment ALPHA
   : ( 'A'..'Z' | 'a'..'z' )
   ;

attrValue
   : BOOLEAN           #boolean
   | NULL              #null
   | STRING            #string
   | DOUBLE            #double
   | '-'? INT EXP?     #long
   ;

...

Abfragebeispiel: color="red" and price=20000 or model="hyundai" and (seats=4 or year=2001)

ElasticSearch unterstützt SQL-Abfragen mit Plugin hier: https://github.com/elastic/elasticsearch/tree/master/x-pack/plugin/sql .

Es fällt mir schwer, Java-Code zu verstehen.

Da wir logische Operatoren haben, bin ich mir nicht sicher, wie ich den Analysebaum abrufen und in eine ES-Abfrage konvertieren soll. Kann jemand helfen / Ideen vorschlagen?

Update 1: Weitere Beispiele mit entsprechender ES-Abfrage hinzugefügt

Abfragebeispiel 1: color="red" AND price=2000

ES-Abfrage 1:

{
    "query": {
      "bool": {
        "must": [
          {
            "terms": {
              "color": [
                "red"
              ]
            }
          },
          {
            "terms": {
              "price": [
                2000
              ]
            }
          }
        ]
      }
    },
    "size": 100
  }

Abfragebeispiel 2: color="red" AND price=2000 AND (model="hyundai" OR model="bmw")

ES-Abfrage 2:

{
  "query": {
    "bool": {
      "must": [
        {
          "bool": {
            "must": {
              "terms": {
                "color": ["red"]
              }
            }
          }
        },
        {
          "bool": {
            "must": {
              "terms": {
                "price": [2000]
              }
            }
          }
        },
        {
          "bool": {
            "should": [
              {
                "term": {
                  "model": "hyundai"
                }
              },
              {
                "term": {
                  "region": "bmw"
                }
              }
            ]
          }
        }
      ]
    }
  },
  "size": 100
}


Abfragebeispiel 3: color="red" OR color="blue"

ES-Abfrage 3:

{
    "query": {
      "bool": {
        "should": [
          {
            "bool": {
              "must": {
                "terms": {
                  "color": ["red"]
                }
              }
            }
          },
          {
            "bool": {
              "must": {
                "terms": {
                    "color": ["blue"]
                }
              }
            }
          }
        ]
      }
    },
    "size": 100
  }

Omurbek Kadyrbekov
quelle
Erwägen Sie das Hinzufügen einer Beispielausgabe. Wie sieht es color="red" and price=20000 or model="hyundai" and (seats=4 or year=2001in der ES-Syntax aus? Sie möchten die JSON-Syntax oder die Syntax für kurze Abfragezeichenfolgen oder etwas ganz anderes? Es ist auch hilfreich, wenn Sie mehr als ein Beispiel hinzufügen. Haben Sie auch schon selbst etwas ausprobiert?
Bart Kiers
Ich habe weitere Beispiele hinzugefügt. Ja, ich möchte die JSON-Syntax aus dem Analysebaum erstellen. Ich mache es gerade mit Golang, habe es noch nicht fertiggestellt
Omurbek Kadyrbekov

Antworten:

6

Arbeitsdemo-URL: https://github.com/omurbekjk/convert-dsl-to-es-query-with-antlr , geschätzte aufgewendete Zeit: ~ 3 Wochen

Nachdem ich antlr4 und einige Beispiele untersucht hatte, fand ich eine einfache Lösung mit Listener und Stack. Ähnlich wie Ausdrücke mit Stack berechnet werden.

Wir müssen den Standard-Basis-Listener mit unserem überschreiben, um Trigger für jede Grammatikregel zum Eingeben / Beenden zu erhalten. Wichtige Regeln sind:

  1. Vergleichsausdruck (Preis = 200, Preis> 190)
  2. Logische Operatoren (ODER, UND)
  3. Klammern (um die Abfrage korrekt zu erstellen, müssen wir eine korrekte Grammatikdatei schreiben, wobei der Vorrang des Operators berücksichtigt wird. Deshalb stehen Klammern in der Grammatikdatei an erster Stelle.)

Unten mein in Golang geschriebener benutzerdefinierter Listener-Code:

package parser

import (
    "github.com/olivere/elastic"
    "strings"
)

type MyDslQueryListener struct {
    *BaseDslQueryListener
    Stack []*elastic.BoolQuery
}

func (ql *MyDslQueryListener) ExitCompareExp(c *CompareExpContext) {
    boolQuery := elastic.NewBoolQuery()

    attrName := c.GetPropertyName().GetText()
    attrValue := strings.Trim(c.GetPropertyValue().GetText(), `\"`)
    // Based on operator type we build different queries, default is terms query(=)
    termsQuery := elastic.NewTermQuery(attrName, attrValue)
    boolQuery.Must(termsQuery)
    ql.Stack = append(ql.Stack, boolQuery)
}

func (ql *MyDslQueryListener) ExitAndLogicalExp(c *AndLogicalExpContext) {
    size := len(ql.Stack)
    right := ql.Stack[size-1]
    left := ql.Stack[size-2]
    ql.Stack = ql.Stack[:size-2] // Pop last two elements
    boolQuery := elastic.NewBoolQuery()
    boolQuery.Must(right)
    boolQuery.Must(left)
    ql.Stack = append(ql.Stack, boolQuery)
}

func (ql *MyDslQueryListener) ExitOrLogicalExp(c *OrLogicalExpContext) {
    size := len(ql.Stack)
    right := ql.Stack[size-1]
    left := ql.Stack[size-2]
    ql.Stack = ql.Stack[:size-2] // Pop last two elements
    boolQuery := elastic.NewBoolQuery()
    boolQuery.Should(right)
    boolQuery.Should(left)
    ql.Stack = append(ql.Stack, boolQuery)
}

Und Hauptdatei:

package main

import (
    "encoding/json"
    "fmt"
    "github.com/antlr/antlr4/runtime/Go/antlr"
    "github.com/omurbekjk/convert-dsl-to-es-query-with-antlr/parser"
)

func main() {
    fmt.Println("Starting here")
    query := "price=2000 OR model=\"hyundai\" AND (color=\"red\" OR color=\"blue\")"
    stream := antlr.NewInputStream(query)
    lexer := parser.NewDslQueryLexer(stream)
    tokenStream := antlr.NewCommonTokenStream(lexer, antlr.TokenDefaultChannel)
    dslParser := parser.NewDslQueryParser(tokenStream)
    tree := dslParser.Start()

    listener := &parser.MyDslQueryListener{}
    antlr.ParseTreeWalkerDefault.Walk(listener, tree)

    esQuery := listener.Stack[0]

    src, err := esQuery.Source()
    if err != nil {
        panic(err)
    }
    data, err := json.MarshalIndent(src, "", "  ")
    if err != nil {
        panic(err)
    }

    stringEsQuery := string(data)
    fmt.Println(stringEsQuery)
}

/**     Generated es query
{
  "bool": {
    "should": [
      {
        "bool": {
          "must": [
            {
              "bool": {
                "should": [
                  {
                    "bool": {
                      "must": {
                        "term": {
                          "color": "blue"
                        }
                      }
                    }
                  },
                  {
                    "bool": {
                      "must": {
                        "term": {
                          "color": "red"
                        }
                      }
                    }
                  }
                ]
              }
            },
            {
              "bool": {
                "must": {
                  "term": {
                    "model": "hyundai"
                  }
                }
              }
            }
          ]
        }
      },
      {
        "bool": {
          "must": {
            "term": {
              "price": "2000"
            }
          }
        }
      }
    ]
  }
}

*/

Omurbek Kadyrbekov
quelle
2

Haben Sie sich Gedanken über Ihre SQL-ähnliche Aussagen zu konvertieren Query - String - Abfragen ?

curl -X GET "localhost:9200/_search?pretty" -H 'Content-Type: application/json' -d'
{
    "query": {
        "query_string" : {
            "query" : "(new york city) OR (big apple)",
            "default_field" : "content"
        }
    }
}
'

Wenn Ihre Anwendungsfälle so einfach bleiben color="red" and price=20000 or model="hyundai" and (seats=4 or year=2001), würde ich mich an die oben genannten Punkte halten. Die Syntax ist recht leistungsfähig, aber die Abfragen werden garantiert langsamer ausgeführt als die nativen, buchstabierten DSL-Abfragen, da der ES-Parser sie für Sie in DSL konvertieren muss.

jzzfs
quelle
Die Sache hier ist, dass ich die übergebenen Eigenschaften validieren muss. Sagen Sie, wenn der Benutzer "pricee" falsch eingegeben hat oder anstatt eine Nummer zu übergeben, einen ungültigen Wert übergibt. (Beispiel: "price = adfasdf")
Omurbek Kadyrbekov
Nun, das ist eine andere Geschichte. Möglicherweise GET index_name/_mappingmöchten Sie zuerst Ihre Zuordnung abrufen ( ) und ermitteln, welche Felder den Benutzern zur Suche zur Verfügung gestellt werden sollen (damit Sie Ihren Validator oder eine "Did-You-Mean" -Funktionalität erstellen können). Wenn Sie die Feldwertdatentypen erzwingen
möchten