Chuyển đổi Sql như các truy vấn dsl tùy chỉnh sang ElasticSearch?


8

Chúng tôi đang xây dựng ngôn ngữ truy vấn của riêng mình tương tự như Mysql bằng antlr4. Ngoại trừ chúng tôi chỉ sử dụng where clause, nói cách khác, người dùng không nhập các select/fromcâu lệnh.

Tôi đã có thể tạo ngữ pháp cho nó và tạo ra các từ vựng / trình phân tích cú pháp / trình nghe trong golang.

Bên dưới tệp ngữ pháp của chúng tôi 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
   ;

...

Ví dụ truy vấn: color="red" and price=20000 or model="hyundai" and (seats=4 or year=2001)

ElasticSearch hỗ trợ truy vấn sql với plugin tại đây: https://github.com/elastic/elSTERearch/tree/master/x-pack/plugin/sql .

Có thời gian khó để hiểu mã java.

Vì chúng ta có Toán tử logic, tôi không chắc chắn làm thế nào để lấy cây phân tích cú pháp và chuyển đổi nó thành truy vấn ES. Ai đó có thể giúp / đề xuất ý tưởng?

Cập nhật 1: Đã thêm nhiều ví dụ với truy vấn ES tương ứng

Ví dụ truy vấn 1: color="red" AND price=2000

Truy vấn ES 1:

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

Truy vấn Ví dụ 2: color="red" AND price=2000 AND (model="hyundai" OR model="bmw")

Truy vấn ES 2:

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

Ví dụ truy vấn 3: color="red" OR color="blue"

Truy vấn ES 3:

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

Xem xét thêm một số ví dụ đầu ra. Làm thế nào để color="red" and price=20000 or model="hyundai" and (seats=4 or year=2001trông giống như trong cú pháp ES? Bạn muốn cú pháp JSON, hoặc cú pháp chuỗi truy vấn ngắn hoặc một cái gì đó khác nhau? Nó cũng giúp nếu bạn thêm nhiều hơn 1 ví dụ. Ngoài ra, bạn đã thử một cái gì đó cho mình?
Bart Kiers

Tôi đã thêm nhiều ví dụ. Có, tôi muốn xây dựng cú pháp json từ cây phân tích cú pháp. Tôi đang tiến hành thực hiện với golang, vẫn chưa hoàn thành nó
Omurbek Kadyrbekov

Câu trả lời:


6

Url demo hoạt động: https://github.com/omurbekjk/convert-dsl-to-es-query-with-antlr , thời gian ước tính đã sử dụng: ~ 3 tuần

Sau khi điều tra antlr4 và một vài ví dụ tôi tìm thấy giải pháp đơn giản với trình nghe và ngăn xếp. Tương tự như cách biểu thức được tính bằng stack.

Chúng ta cần ghi đè lên trình nghe cơ sở mặc định bằng trình nghe của chúng ta để có các trình kích hoạt cho mỗi quy tắc ngữ pháp nhập / thoát. Các quy tắc quan trọng là:

  1. Biểu thức so sánh (giá = 200, giá> 190)
  2. Toán tử logic (OR, AND)
  3. Chân đế (để xây dựng chính xác truy vấn es, chúng ta cần phải viết chính xác quyền ưu tiên ghi nhớ tệp ngữ pháp, đó là lý do tại sao dấu ngoặc nằm ở vị trí đầu tiên trong tệp ngữ pháp)

Bên dưới mã trình nghe tùy chỉnh của tôi được viết bằng golang:

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

Và tập tin chính:

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"
            }
          }
        }
      }
    ]
  }
}

*/

2

Bạn đã nghĩ về việc chuyển đổi các câu lệnh giống như sql của bạn thành các truy vấn chuỗi truy vấn chưa?

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"
        }
    }
}
'

Nếu trường hợp sử dụng của bạn đơn giản như thế color="red" and price=20000 or model="hyundai" and (seats=4 or year=2001), tôi sẽ làm theo cách trên. Cú pháp khá mạnh nhưng các truy vấn được đảm bảo chạy chậm hơn các truy vấn DSL gốc, được đánh vần do trình phân tích cú pháp ES sẽ cần chuyển đổi chúng sang DSL cho bạn.


Điều ở đây là tôi sẽ cần xác nhận các thuộc tính được thông qua. Nói nếu người dùng nhập sai "pricee" hoặc thay vì chuyển số thì nó chuyển giá trị không hợp lệ. (ví dụ: "price = adfasdf")
Omurbek Kadyrbekov

Vâng, đó là một câu chuyện khác. Bạn có thể muốn có bản đồ của mình trước ( GET index_name/_mapping), xác định trường nào bạn muốn hiển thị cho người dùng để tìm kiếm (để bạn có thể xây dựng trình xác thực của mình hoặc chức năng "không có ý nghĩa"). Nếu bạn muốn thực thi các loại dữ liệu giá trị trường, bạn cũng có thể trích xuất thông tin đó từ ánh xạ ...
jzzfs

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.