elasticquery 0.1.2 → 0.1.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|