Files
2019-05-13 20:38:06 +03:00

263 lines
6.7 KiB
Go

// Copyright 2013 Matthew Baird
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package elastigo
import (
"encoding/json"
"fmt"
//"log"
"strings"
)
// Query creates a new Query Dsl
func Query() *QueryDsl {
return &QueryDsl{}
}
/*
some ways to serialize
"query": {
"filtered": {
"query": {
"query_string": {
"default_operator": "OR",
"default_field": "_all",
"query": " actor:\"bob\" AND type:\"EventType\""
}
},
"filter": {
"range": {
"@timestamp": {
"from": "2012-12-29T16:52:48+00:00",
"to": "2012-12-29T17:52:48+00:00"
}
}
}
}
},
"query" : {
"term" : { "user" : "kimchy" }
}
"query" : {
"match_all" : {}
},
*/
type QueryDsl struct {
QueryEmbed
FilterVal *FilterOp `json:"filter,omitempty"`
}
// The core Query Syntax can be embedded as a child of a variety of different parents
type QueryEmbed struct {
MatchAll *MatchAll `json:"match_all,omitempty"`
Terms map[string]string `json:"term,omitempty"`
Qs *QueryString `json:"query_string,omitempty"`
MultiMatch *MultiMatch `json:"multi_match,omitempty"`
FunctionScore map[string]interface{} `json:"function_score,omitempty"`
//Exist string `json:"_exists_,omitempty"`
}
// MarshalJSON provides custom marshalling to support the query dsl which is a conditional
// json format, not always the same parent/children
func (qd *QueryDsl) MarshalJSON() ([]byte, error) {
q := qd.QueryEmbed
hasQuery := false
if q.Qs != nil || len(q.Terms) > 0 || q.MatchAll != nil || q.MultiMatch != nil {
hasQuery = true
}
// If a query has a
if qd.FilterVal != nil && hasQuery {
queryB, err := json.Marshal(q)
if err != nil {
return queryB, err
}
filterB, err := json.Marshal(qd.FilterVal)
if err != nil {
return filterB, err
}
return []byte(fmt.Sprintf(`{"filtered":{"query":%s,"filter":%s}}`, queryB, filterB)), nil
}
return json.Marshal(q)
}
// get all
func (q *QueryDsl) All() *QueryDsl {
q.MatchAll = &MatchAll{""}
return q
}
// Range adds a RANGE FilterOp to the search query
// Legacy. Use the Filter() function instead
func (q *QueryDsl) Range(fop *FilterOp) *QueryDsl {
if q.FilterVal == nil {
q.FilterVal = fop
return q
}
return q
}
// Add a term search for a specific field
// Term("user","kimchy")
func (q *QueryDsl) Term(name, value string) *QueryDsl {
if len(q.Terms) == 0 {
q.Terms = make(map[string]string)
}
q.Terms[name] = value
return q
}
// FunctionScore sets functions to use to score the documents.
// http://www.elastic.co/guide/en/elasticsearch/reference/1.x/query-dsl-function-score-query.html
func (q *QueryDsl) FunctionScore(mode string, functions ...map[string]interface{}) *QueryDsl {
q.QueryEmbed.FunctionScore = map[string]interface{}{
"functions": functions,
"score_mode": mode,
}
return q
}
// The raw search strings (lucene valid)
func (q *QueryDsl) Search(searchFor string) *QueryDsl {
//I don't think this is right, it is not a filter.query, it should be q query?
qs := NewQueryString("", "")
q.QueryEmbed.Qs = &qs
q.QueryEmbed.Qs.Query = searchFor
return q
}
// Querystring operations
func (q *QueryDsl) Qs(qs *QueryString) *QueryDsl {
q.QueryEmbed.Qs = qs
return q
}
// SetLenient sets whether the query should ignore format based failures,
// such as passing in text to a number field.
func (q *QueryDsl) SetLenient(lenient bool) *QueryDsl {
q.QueryEmbed.Qs.Lenient = lenient
return q
}
// Fields in query_string search
// Fields("fieldname","search_for","","")
//
// Fields("fieldname,field2,field3","search_for","","")
//
// Fields("fieldname,field2,field3","search_for","field_exists","")
func (q *QueryDsl) Fields(fields, search, exists, missing string) *QueryDsl {
fieldList := strings.Split(fields, ",")
qs := NewQueryString("", "")
q.QueryEmbed.Qs = &qs
q.QueryEmbed.Qs.Query = search
if len(fieldList) == 1 {
q.QueryEmbed.Qs.DefaultField = fields
} else {
q.QueryEmbed.Qs.Fields = fieldList
}
q.QueryEmbed.Qs.Exists = exists
q.QueryEmbed.Qs.Missing = missing
return q
}
// Filter this query
func (q *QueryDsl) Filter(f *FilterOp) *QueryDsl {
q.FilterVal = f
return q
}
// MultiMatch allows searching against multiple fields.
func (q *QueryDsl) MultiMatch(s string, fields []string) *QueryDsl {
q.QueryEmbed.MultiMatch = &MultiMatch{Query: s, Fields: fields}
return q
}
type MultiMatch struct {
Query string `json:"query"`
Fields []string `json:"fields"`
}
type MatchAll struct {
All string `json:"-"`
}
// should we reuse QueryDsl here?
type QueryWrap struct {
Qs QueryString `json:"query_string,omitempty"`
}
// QueryString based search
func NewQueryString(field, query string) QueryString {
return QueryString{"", field, query, "", "", nil, false}
}
type QueryString struct {
DefaultOperator string `json:"default_operator,omitempty"`
DefaultField string `json:"default_field,omitempty"`
Query string `json:"query,omitempty"`
Exists string `json:"_exists_,omitempty"`
Missing string `json:"_missing_,omitempty"`
Fields []string `json:"fields,omitempty"`
Lenient bool `json:"lenient,omitempty"`
//_exists_:field1,
//_missing_:field1,
}
//I don't know how any of the Term stuff below is supposed to work. -mikeyoon
// Generic Term based (used in query, facet, filter)
type Term struct {
Terms Terms `json:"terms,omitempty"`
FilterVal *FilterWrap `json:"facet_filter,omitempty"`
}
type Terms struct {
Fields []string `json:"field,omitempty"`
Size string `json:"size,omitempty"`
Regex string `json:"regex,omitempty"`
}
func NewTerm(fields ...string) *Term {
m := &Term{Terms{Fields: fields}, nil}
return m
}
func (s *Term) Filter(fl ...interface{}) *Term {
if s.FilterVal == nil {
s.FilterVal = NewFilterWrap()
}
s.FilterVal.addFilters(fl)
return s
}
// Custom marshalling
func (t *Terms) MarshalJSON() ([]byte, error) {
m := make(map[string]interface{})
// TODO: this isn't getting called!?
if len(t.Fields) == 1 {
m["field"] = t.Fields[0]
} else if len(t.Fields) > 1 {
m["fields"] = t.Fields
}
if len(t.Regex) > 0 {
m["regex"] = t.Regex
}
if len(t.Size) > 0 {
m["size"] = t.Size
}
return json.Marshal(m)
}