elasticquery 0.1.2 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/History.md +12 -0
- data/README.md +151 -70
- data/Rakefile +1 -9
- data/elasticquery.gemspec +2 -2
- data/lib/elasticquery.rb +1 -0
- data/lib/elasticquery/base.rb +6 -48
- data/lib/elasticquery/builder.rb +74 -17
- data/lib/elasticquery/es.rb +32 -0
- data/lib/elasticquery/filters/base.rb +4 -0
- data/lib/elasticquery/filters/exists.rb +24 -0
- data/lib/elasticquery/filters/not.rb +7 -11
- data/lib/elasticquery/filters/range.rb +3 -7
- data/lib/elasticquery/filters/term.rb +4 -28
- data/lib/elasticquery/filters/terms.rb +23 -0
- data/lib/elasticquery/queries/base.rb +16 -0
- data/lib/elasticquery/queries/multi_match.rb +34 -0
- data/lib/elasticquery/query.rb +25 -36
- data/lib/elasticquery/version.rb +1 -1
- data/test/base_test.rb +4 -4
- data/test/builder_test.rb +24 -8
- data/test/es_test.rb +23 -0
- data/test/filters/exists_test.rb +30 -0
- data/test/filters/not_test.rb +23 -10
- data/test/filters/range_test.rb +10 -11
- data/test/filters/term_test.rb +6 -7
- data/test/filters/terms_test.rb +50 -0
- data/test/integration/chainable_call_test.rb +6 -6
- data/test/integration/exists_case_test.rb +56 -0
- data/test/integration/not_case_test.rb +11 -5
- data/test/integration/queries_inheritence_test.rb +12 -4
- data/test/integration/range_case_test.rb +4 -2
- data/test/integration/search_case_test.rb +20 -11
- data/test/integration/term_case_test.rb +4 -2
- data/test/integration/terms_case_test.rb +49 -0
- data/test/queries/multi_match_test.rb +49 -0
- data/test/query_test.rb +40 -40
- metadata +26 -27
- data/lib/elasticquery/filters/search.rb +0 -54
- data/test/filters/search_test.rb +0 -62
data/lib/elasticquery/builder.rb
CHANGED
@@ -1,7 +1,10 @@
|
|
1
1
|
require "elasticquery/filters/term"
|
2
|
-
require "elasticquery/filters/search"
|
3
2
|
require "elasticquery/filters/not"
|
3
|
+
require "elasticquery/filters/terms"
|
4
4
|
require "elasticquery/filters/range"
|
5
|
+
require "elasticquery/filters/exists"
|
6
|
+
|
7
|
+
require "elasticquery/queries/multi_match"
|
5
8
|
|
6
9
|
require "active_support/concern"
|
7
10
|
require "active_support/core_ext/string/inflections"
|
@@ -10,23 +13,77 @@ module Elasticquery
|
|
10
13
|
module Builder
|
11
14
|
extend ActiveSupport::Concern
|
12
15
|
|
13
|
-
filters
|
14
|
-
Filters
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
16
|
+
def filters
|
17
|
+
@namespace = "Filters"
|
18
|
+
if block_given?
|
19
|
+
yield
|
20
|
+
@namespace = nil
|
21
|
+
end
|
22
|
+
self
|
23
|
+
end
|
24
|
+
|
25
|
+
def queries
|
26
|
+
@namespace = "Queries"
|
27
|
+
if block_given?
|
28
|
+
yield
|
29
|
+
@namespace = nil
|
30
|
+
end
|
31
|
+
self
|
32
|
+
end
|
33
|
+
|
34
|
+
def execute(method, *args)
|
35
|
+
filter_class = "Elasticquery::#{@namespace}::#{method.camelize}".constantize
|
36
|
+
instance = filter_class.new *args
|
37
|
+
case @namespace
|
38
|
+
when "Filters"
|
39
|
+
query.push_filter instance
|
40
|
+
when "Queries"
|
41
|
+
query.push_query instance
|
42
|
+
else
|
43
|
+
raise "Please specify block. Queries and Filters are supported"
|
29
44
|
end
|
45
|
+
self
|
46
|
+
end
|
47
|
+
private :execute
|
48
|
+
|
49
|
+
def term(*args)
|
50
|
+
execute "term", *args
|
51
|
+
end
|
52
|
+
|
53
|
+
def terms(*args)
|
54
|
+
execute "terms", *args
|
55
|
+
end
|
56
|
+
def where(*args)
|
57
|
+
terms *args
|
58
|
+
end
|
59
|
+
|
60
|
+
def not(*args)
|
61
|
+
execute "not", *args
|
62
|
+
end
|
63
|
+
|
64
|
+
def range(*args)
|
65
|
+
execute "range", *args
|
66
|
+
end
|
67
|
+
|
68
|
+
def multi_match(*args)
|
69
|
+
execute "multi_match", *args
|
70
|
+
end
|
71
|
+
def search(*args)
|
72
|
+
multi_match *args
|
73
|
+
end
|
74
|
+
|
75
|
+
def exists(*args)
|
76
|
+
execute "exists", *args
|
77
|
+
end
|
78
|
+
def with(*args)
|
79
|
+
exists *args
|
80
|
+
end
|
81
|
+
|
82
|
+
def missing(*args)
|
83
|
+
exists.not *args
|
84
|
+
end
|
85
|
+
def without(*args)
|
86
|
+
missing *args
|
30
87
|
end
|
31
88
|
end
|
32
89
|
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Elasticquery
|
2
|
+
EsNotSupported = Class.new(StandardError)
|
3
|
+
|
4
|
+
class BaseChain < Elasticquery::Base
|
5
|
+
def initialize(klass)
|
6
|
+
@klass = klass
|
7
|
+
super
|
8
|
+
end
|
9
|
+
|
10
|
+
delegate :each, :empty?, :size, :slice, :[], :to_ary, to: :results
|
11
|
+
|
12
|
+
def records
|
13
|
+
results.records
|
14
|
+
end
|
15
|
+
|
16
|
+
def results
|
17
|
+
@klass.__elasticsearch__.search(build)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
module Es
|
22
|
+
def self.extended(base)
|
23
|
+
unless base.ancestors.include? Elasticsearch::Model
|
24
|
+
raise EsNotSupported.new("Can't extend. It's not Elasticsearch::Model")
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def es
|
29
|
+
BaseChain.new(self)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require_relative "base"
|
2
|
+
|
3
|
+
module Elasticquery
|
4
|
+
module Filters
|
5
|
+
class Exists < Base
|
6
|
+
|
7
|
+
def initialize(field = nil)
|
8
|
+
@field = field
|
9
|
+
end
|
10
|
+
|
11
|
+
def valid?
|
12
|
+
@field.present?
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_hash
|
16
|
+
{exists: {field: @field}}
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_not_hash
|
20
|
+
{missing: {field: @field}}
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -5,25 +5,21 @@ module Elasticquery
|
|
5
5
|
class Not < Base
|
6
6
|
|
7
7
|
def initialize(*args)
|
8
|
-
@args = args
|
8
|
+
@args = Marshal.load(Marshal.dump(args))
|
9
9
|
end
|
10
10
|
|
11
11
|
def dup_with(*args)
|
12
12
|
raise StandardError, "Cannot use Filters::Not twice"
|
13
13
|
end
|
14
14
|
|
15
|
+
def valid?
|
16
|
+
args = @args
|
17
|
+
-> { filters[-2].dup_with(*args).valid? }
|
18
|
+
end
|
19
|
+
|
15
20
|
def to_hash
|
16
21
|
args = @args
|
17
|
-
->
|
18
|
-
filter = filters.last.dup_with *args
|
19
|
-
if filter.valid?
|
20
|
-
subquery = filter.to_hash
|
21
|
-
q = subquery[:query][:filtered][:filter][:and][0]
|
22
|
-
{query: {filtered: {filter: {and: [{not: {filter: q}}]}}}}
|
23
|
-
else
|
24
|
-
{}
|
25
|
-
end
|
26
|
-
end
|
22
|
+
-> { filters[-2].dup_with(*args).to_not_hash }
|
27
23
|
end
|
28
24
|
end
|
29
25
|
end
|
@@ -5,8 +5,8 @@ module Elasticquery
|
|
5
5
|
class Range < Base
|
6
6
|
OPTIONS = %i(_cache execution)
|
7
7
|
|
8
|
-
def initialize(field=nil, gte: nil, lte: nil, **options)
|
9
|
-
@options = options.
|
8
|
+
def initialize(field = nil, gte: nil, lte: nil, **options)
|
9
|
+
@options = options.slice *OPTIONS
|
10
10
|
@field, @gte, @lte = field, gte, lte
|
11
11
|
end
|
12
12
|
|
@@ -15,11 +15,7 @@ module Elasticquery
|
|
15
15
|
end
|
16
16
|
|
17
17
|
def to_hash
|
18
|
-
|
19
|
-
{query: {filtered: {filter: {and: [{range: range_with_options}]}}}}
|
20
|
-
else
|
21
|
-
{}
|
22
|
-
end
|
18
|
+
{range: range_with_options}
|
23
19
|
end
|
24
20
|
|
25
21
|
private
|
@@ -5,41 +5,17 @@ module Elasticquery
|
|
5
5
|
class Term < Base
|
6
6
|
OPTIONS = %i(_cache)
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
#
|
12
|
-
# @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-filtered-query.html#_filtering_without_a_query
|
13
|
-
def initialize(data={})
|
14
|
-
@options = data.extract! *OPTIONS
|
15
|
-
@condition = data
|
8
|
+
def initialize(data = {})
|
9
|
+
@options = data.slice *OPTIONS
|
10
|
+
@condition = data.except *OPTIONS
|
16
11
|
end
|
17
12
|
|
18
|
-
# Is passed condition valid. Passed condition must have
|
19
|
-
# one key and present value
|
20
|
-
#
|
21
|
-
# @return [Boolean] is condition have at least one key
|
22
|
-
#
|
23
|
-
# @example
|
24
|
-
# rule = Elasticquery::Rules::Term.new
|
25
|
-
# rule.valid? #=> false
|
26
13
|
def valid?
|
27
14
|
@condition.keys.size == 1 && @condition.values[0].present?
|
28
15
|
end
|
29
16
|
|
30
|
-
# Hash presentation of query.
|
31
|
-
#
|
32
|
-
# @return [Hash] presentation of filter.
|
33
|
-
#
|
34
|
-
# @example
|
35
|
-
# valid = Elasticquery::Filters::Term.new {name: "John"}
|
36
|
-
# valid.to_hash #=> {query: {filtered: {filter: {and: [{term: {name: 'John'}}]}}}
|
37
17
|
def to_hash
|
38
|
-
|
39
|
-
{query: {filtered: {filter: {and: [{term: @condition.merge(@options)}]}}}}
|
40
|
-
else
|
41
|
-
{}
|
42
|
-
end
|
18
|
+
{term: @condition.merge(@options)}
|
43
19
|
end
|
44
20
|
end
|
45
21
|
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require "active_support/core_ext/array/wrap"
|
2
|
+
require_relative "base"
|
3
|
+
|
4
|
+
module Elasticquery
|
5
|
+
module Filters
|
6
|
+
class Terms < Base
|
7
|
+
OPTIONS = %i(_cache execution)
|
8
|
+
|
9
|
+
def initialize(data = {})
|
10
|
+
@options = data.slice *OPTIONS
|
11
|
+
@conditions = data.except(*OPTIONS).delete_if{ |_, value| value.blank? }
|
12
|
+
end
|
13
|
+
|
14
|
+
def valid?
|
15
|
+
@conditions.keys.any?
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_hash
|
19
|
+
{terms: @conditions.merge(@options)}
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require_relative "base"
|
2
|
+
|
3
|
+
module Elasticquery
|
4
|
+
module Queries
|
5
|
+
class MultiMatch < Base
|
6
|
+
OPERATORS = %w(and or)
|
7
|
+
TYPES = %w(best_fields most_fields cross_fields phrase pharse_prefix)
|
8
|
+
|
9
|
+
def initialize(query, fields: "_all", operator: "and", type: "best_fields")
|
10
|
+
@fields = fields
|
11
|
+
@operator = operator
|
12
|
+
@type = type
|
13
|
+
@query = query
|
14
|
+
end
|
15
|
+
|
16
|
+
def valid?
|
17
|
+
OPERATORS.include?(@operator) &&
|
18
|
+
TYPES.include?(@type) &&
|
19
|
+
@query.present? &&
|
20
|
+
( Array === @fields || @fields == "_all" )
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_hash
|
24
|
+
{multi_match: subquery}
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def subquery
|
30
|
+
{fields: @fields, operator: @operator, type: @type, query: @query}
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/lib/elasticquery/query.rb
CHANGED
@@ -1,64 +1,53 @@
|
|
1
|
-
require "deep_merge/rails_compat"
|
2
|
-
|
3
1
|
module Elasticquery
|
4
2
|
class Query
|
5
3
|
|
6
|
-
attr_reader :filters
|
4
|
+
attr_reader :filters, :queries
|
7
5
|
|
8
6
|
DEFAULT = {query: {filtered: {query: {match_all:{}}}}}
|
9
7
|
|
10
|
-
# Create new query from hash.
|
11
|
-
#
|
12
|
-
# @param [Hash] query passed as hash. When it not passed use default es 'match_all' query
|
13
|
-
#
|
14
|
-
# @example
|
15
|
-
# Elasticsearch::Query.new.to_hash #=> {query: {filtered: {query: {match_all:{}}}}}
|
16
8
|
def initialize(query = DEFAULT)
|
17
9
|
@query = query
|
18
|
-
@filters = []
|
10
|
+
@filters, @queries = [], []
|
19
11
|
end
|
20
12
|
|
21
|
-
# Convery query object ot hash
|
22
|
-
#
|
23
|
-
# @return [Hash] hash presentation of query
|
24
13
|
def to_hash
|
25
|
-
return DEFAULT if @query == {}
|
26
14
|
@query
|
27
15
|
end
|
28
16
|
|
29
|
-
|
30
|
-
# then clear it and use new value. Populate #filters array
|
31
|
-
# with given classes
|
32
|
-
#
|
33
|
-
# @param [Elasticquery::filter::Base] filter passed
|
34
|
-
#
|
35
|
-
# @see #match_all?
|
36
|
-
def <<(filter)
|
37
|
-
@query = {} if match_all?
|
38
|
-
merge filter
|
17
|
+
def push_filter(filter)
|
39
18
|
@filters << filter
|
19
|
+
merge_filter filter
|
40
20
|
end
|
41
21
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
def match_all?
|
46
|
-
@query == DEFAULT
|
22
|
+
def push_query(query)
|
23
|
+
@queries << query
|
24
|
+
merge_query query
|
47
25
|
end
|
48
26
|
|
49
27
|
private
|
50
28
|
|
51
|
-
def
|
29
|
+
def merge_filter(filter)
|
30
|
+
return unless filter_valid?(filter)
|
31
|
+
|
52
32
|
hash = filter.to_hash
|
53
33
|
subquery = hash.respond_to?(:call) ? instance_exec(&hash) : hash
|
54
|
-
@query
|
34
|
+
@query = {query: {filtered: {}}} if @query == DEFAULT
|
35
|
+
|
36
|
+
@query[:query][:filtered][:filter] ||= {and: []}
|
37
|
+
@query[:query][:filtered][:filter][:and] << subquery
|
55
38
|
end
|
56
39
|
|
57
|
-
def
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
40
|
+
def filter_valid?(filter)
|
41
|
+
validation = filter.valid?
|
42
|
+
validation.respond_to?(:call) ? instance_exec(&validation) : validation
|
43
|
+
end
|
44
|
+
|
45
|
+
def merge_query(query)
|
46
|
+
return unless query.valid?
|
47
|
+
@query = {query: {filtered: {}}} if @query == DEFAULT
|
48
|
+
|
49
|
+
@query[:query][:filtered][:query] ||= {bool: {must: []}}
|
50
|
+
@query[:query][:filtered][:query][:bool][:must] << query.to_hash
|
62
51
|
end
|
63
52
|
end
|
64
53
|
end
|