elasticated 1.0.0
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 +7 -0
- data/.gitignore +35 -0
- data/Gemfile +4 -0
- data/README.md +3 -0
- data/Rakefile +6 -0
- data/elasticated.gemspec +29 -0
- data/lib/elasticated.rb +102 -0
- data/lib/elasticated/aggregation.rb +36 -0
- data/lib/elasticated/aggregations/cardinality_aggregation.rb +15 -0
- data/lib/elasticated/aggregations/count_aggregation.rb +15 -0
- data/lib/elasticated/aggregations/count_distinct_aggregation.rb +15 -0
- data/lib/elasticated/aggregations/count_filtered_aggregation.rb +29 -0
- data/lib/elasticated/aggregations/custom_aggregation.rb +25 -0
- data/lib/elasticated/aggregations/date_histogram_aggregation.rb +35 -0
- data/lib/elasticated/aggregations/filter_aggregation.rb +33 -0
- data/lib/elasticated/aggregations/filter_aggregation_evaluator.rb +22 -0
- data/lib/elasticated/aggregations/group_aggregation.rb +29 -0
- data/lib/elasticated/aggregations/histogram_aggregation.rb +34 -0
- data/lib/elasticated/aggregations/nested_aggregation.rb +30 -0
- data/lib/elasticated/aggregations/range_aggregation.rb +35 -0
- data/lib/elasticated/aggregations/range_aggregation_evaluator.rb +22 -0
- data/lib/elasticated/aggregations/ranges_builder.rb +35 -0
- data/lib/elasticated/aggregations/single_value_aggregation.rb +47 -0
- data/lib/elasticated/aggregations/subaggregated.rb +27 -0
- data/lib/elasticated/aggregations/sum_distinct_aggregation.rb +20 -0
- data/lib/elasticated/aggregations/terms_aggregation.rb +63 -0
- data/lib/elasticated/aggregations/top_hits_aggregation.rb +25 -0
- data/lib/elasticated/block_evaluation.rb +15 -0
- data/lib/elasticated/boolean_clause.rb +43 -0
- data/lib/elasticated/client.rb +84 -0
- data/lib/elasticated/clonable.rb +58 -0
- data/lib/elasticated/conditions/custom_condition.rb +19 -0
- data/lib/elasticated/conditions/exists_condition.rb +11 -0
- data/lib/elasticated/conditions/missing_condition.rb +11 -0
- data/lib/elasticated/conditions/nested_condition.rb +19 -0
- data/lib/elasticated/conditions/range_condition.rb +27 -0
- data/lib/elasticated/conditions/script_condition.rb +22 -0
- data/lib/elasticated/conditions/standard_condition.rb +26 -0
- data/lib/elasticated/conditions/terms_condition.rb +22 -0
- data/lib/elasticated/conditions/wildcard_condition.rb +18 -0
- data/lib/elasticated/conditions_builder.rb +75 -0
- data/lib/elasticated/configurable.rb +9 -0
- data/lib/elasticated/configuration.rb +9 -0
- data/lib/elasticated/default_logger.rb +27 -0
- data/lib/elasticated/delimiters/date_field_delimiter.rb +33 -0
- data/lib/elasticated/delimiters/standard_field_delimiter.rb +33 -0
- data/lib/elasticated/delimiters/term_field_delimiter.rb +24 -0
- data/lib/elasticated/document.rb +46 -0
- data/lib/elasticated/helpers.rb +28 -0
- data/lib/elasticated/index_selector.rb +44 -0
- data/lib/elasticated/inspectionable.rb +9 -0
- data/lib/elasticated/mapping.rb +19 -0
- data/lib/elasticated/mapping/builder.rb +36 -0
- data/lib/elasticated/mapping/fields_builder.rb +148 -0
- data/lib/elasticated/mapping/nested_builder.rb +15 -0
- data/lib/elasticated/mapping/object_builder.rb +15 -0
- data/lib/elasticated/mapping/partial.rb +11 -0
- data/lib/elasticated/mapping/type_builder.rb +14 -0
- data/lib/elasticated/partitioned_repository.rb +27 -0
- data/lib/elasticated/query.rb +159 -0
- data/lib/elasticated/query_aggregations.rb +71 -0
- data/lib/elasticated/query_conditions.rb +89 -0
- data/lib/elasticated/repositories/monthly_partitioned_repository.rb +96 -0
- data/lib/elasticated/repository.rb +139 -0
- data/lib/elasticated/results.rb +43 -0
- data/lib/version.rb +92 -0
- data/spec/aggregation_spec.rb +587 -0
- data/spec/date_field_delimiter_spec.rb +67 -0
- data/spec/document_spec.rb +44 -0
- data/spec/elasticsearch_hit_1.json +14 -0
- data/spec/elasticsearch_response_1.json +29 -0
- data/spec/elasticsearch_response_2.json +44 -0
- data/spec/elasticsearch_top_hits_response.json +20 -0
- data/spec/integration_spec.rb +184 -0
- data/spec/mapping_spec.rb +219 -0
- data/spec/monthly_partitioned_repository_spec.rb +99 -0
- data/spec/query_aggregations_spec.rb +44 -0
- data/spec/query_conditions_spec.rb +314 -0
- data/spec/query_spec.rb +265 -0
- data/spec/results_spec.rb +69 -0
- data/spec/spec_helper.rb +2 -0
- data/spec/term_field_delimiter_spec.rb +39 -0
- metadata +225 -0
@@ -0,0 +1,46 @@
|
|
1
|
+
module Elasticated
|
2
|
+
class Document
|
3
|
+
|
4
|
+
class << self
|
5
|
+
|
6
|
+
def create(opts={}, &block)
|
7
|
+
raise if block && !opts.empty?
|
8
|
+
ret = new
|
9
|
+
if opts.empty?
|
10
|
+
ret.evaluate block
|
11
|
+
else
|
12
|
+
opts.each{ |k, v| ret.send "#{k}=", v if v }
|
13
|
+
end
|
14
|
+
ret
|
15
|
+
end
|
16
|
+
|
17
|
+
def from_elasticsearch_hit(hit)
|
18
|
+
document = new hit['_source']
|
19
|
+
document.id = hit['_id']
|
20
|
+
document.type = hit['_type']
|
21
|
+
document.index = hit['_index']
|
22
|
+
document.score = hit['_score']
|
23
|
+
document.version = hit['_version']
|
24
|
+
document
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
include BlockEvaluation
|
30
|
+
|
31
|
+
attr_accessor :id, :type, :index, :score, :version, :document_source
|
32
|
+
|
33
|
+
def initialize(source=nil)
|
34
|
+
self.source = source if source
|
35
|
+
end
|
36
|
+
|
37
|
+
def source=(hash)
|
38
|
+
self.document_source = Hash::Accessible.new hash
|
39
|
+
end
|
40
|
+
|
41
|
+
def source
|
42
|
+
self.document_source ||= Hash::Accessible.new
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Elasticated
|
2
|
+
module Helpers
|
3
|
+
|
4
|
+
def self.string_to_agg_name(element)
|
5
|
+
element.to_s.gsub /(?![a-zA-Z0-9])./, '_'
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.string_to_camel_case(string)
|
9
|
+
return string if string !~ /_/ && string =~ /[A-Z]+.*/
|
10
|
+
string.split('_').map(&:capitalize).join
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.hash_deep_dup(hash)
|
14
|
+
duplicate = hash.dup
|
15
|
+
duplicate.each_pair do |k, v|
|
16
|
+
if v.is_a? Hash
|
17
|
+
duplicate[k] = hash_deep_dup v
|
18
|
+
elsif v.is_a? Array
|
19
|
+
duplicate[k] = v.clone
|
20
|
+
else
|
21
|
+
duplicate[k] = v
|
22
|
+
end
|
23
|
+
end
|
24
|
+
duplicate
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Elasticated
|
2
|
+
class IndexSelector
|
3
|
+
|
4
|
+
# abstract class
|
5
|
+
# child must implement 'delimiters()'
|
6
|
+
# child must implement 'strategy()'
|
7
|
+
# child can override 'strategy_params_for(document)'
|
8
|
+
|
9
|
+
include BlockEvaluation
|
10
|
+
|
11
|
+
def indices_for_query(query)
|
12
|
+
strategy_params = strategy_params_for_query query
|
13
|
+
indices = strategy.call strategy_params
|
14
|
+
raise "At least one index should be affected for a search" if indices.count < 1
|
15
|
+
indices
|
16
|
+
end
|
17
|
+
|
18
|
+
def index_for_document(document)
|
19
|
+
params = strategy_params_for_document document
|
20
|
+
indices = strategy.call params
|
21
|
+
raise "Only one index can be affected for a document indexation" if indices.count > 1
|
22
|
+
raise "At least one index should be affected for a document indexation" if indices.count < 1
|
23
|
+
indices.first
|
24
|
+
end
|
25
|
+
|
26
|
+
protected
|
27
|
+
|
28
|
+
def strategy_params_for_document(document)
|
29
|
+
delimiters.inject Hash.new do |params, delimiter|
|
30
|
+
key = delimiter.filter_name
|
31
|
+
value = document.source[delimiter.field_name]
|
32
|
+
value ? params.merge(key => value) : params
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def strategy_params_for_query(query)
|
37
|
+
delimiters.inject Hash.new do |params, delimiter|
|
38
|
+
query.fill_delimiter delimiter
|
39
|
+
params.merge delimiter.build_strategy_params
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Elasticated
|
2
|
+
module Mapping
|
3
|
+
class Builder
|
4
|
+
|
5
|
+
class << self
|
6
|
+
def build(&block)
|
7
|
+
instance = new
|
8
|
+
instance.evaluate block
|
9
|
+
instance.build
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
include BlockEvaluation
|
14
|
+
|
15
|
+
attr_accessor :mapping_types
|
16
|
+
|
17
|
+
|
18
|
+
def initialize
|
19
|
+
self.mapping_types = Array.new
|
20
|
+
end
|
21
|
+
|
22
|
+
def type(name, &block)
|
23
|
+
mapping_type = TypeBuilder.new name
|
24
|
+
mapping_type.evaluate block
|
25
|
+
mapping_types << mapping_type
|
26
|
+
end
|
27
|
+
|
28
|
+
def build
|
29
|
+
mapping_types.inject({}) do |hash, mapping_type|
|
30
|
+
hash.merge mapping_type.build
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,148 @@
|
|
1
|
+
module Elasticated
|
2
|
+
module Mapping
|
3
|
+
class FieldsBuilder
|
4
|
+
|
5
|
+
include BlockEvaluation
|
6
|
+
|
7
|
+
attr_accessor :hash, :name, :sub_objects, :nesteds
|
8
|
+
|
9
|
+
def initialize(name)
|
10
|
+
self.hash = Hash.new
|
11
|
+
self.sub_objects = Array.new
|
12
|
+
self.nesteds = Array.new
|
13
|
+
self.name = name
|
14
|
+
end
|
15
|
+
|
16
|
+
def date(field_name)
|
17
|
+
hash[field_name] = build_date_field
|
18
|
+
end
|
19
|
+
|
20
|
+
def string(field_name)
|
21
|
+
hash[field_name] = build_string_field
|
22
|
+
end
|
23
|
+
|
24
|
+
def float(field_name)
|
25
|
+
hash[field_name] = build_float_field
|
26
|
+
end
|
27
|
+
|
28
|
+
def double(field_name)
|
29
|
+
hash[field_name] = build_double_field
|
30
|
+
end
|
31
|
+
|
32
|
+
def integer(field_name)
|
33
|
+
hash[field_name] = build_integer_field
|
34
|
+
end
|
35
|
+
|
36
|
+
def long(field_name)
|
37
|
+
hash[field_name] = build_long_field
|
38
|
+
end
|
39
|
+
|
40
|
+
def analyzed_string(field_name)
|
41
|
+
hash[field_name] = build_analyzed_string_field field_name
|
42
|
+
end
|
43
|
+
|
44
|
+
def bool(field_name)
|
45
|
+
hash[field_name] = build_bool_field
|
46
|
+
end
|
47
|
+
|
48
|
+
def object(object_name, &block)
|
49
|
+
sub_object = ObjectBuilder.new object_name
|
50
|
+
sub_object.evaluate block
|
51
|
+
sub_objects << sub_object
|
52
|
+
end
|
53
|
+
|
54
|
+
def nested(nested_name, &block)
|
55
|
+
nested = NestedBuilder.new nested_name
|
56
|
+
nested.evaluate block
|
57
|
+
nesteds << nested
|
58
|
+
end
|
59
|
+
|
60
|
+
def partial(partial_mapping)
|
61
|
+
partial_mapping.apply_over self
|
62
|
+
end
|
63
|
+
|
64
|
+
def build_body
|
65
|
+
ret = hash
|
66
|
+
sub_objects.each do |sub_object|
|
67
|
+
ret.merge! sub_object.build
|
68
|
+
end
|
69
|
+
nesteds.each do |nested|
|
70
|
+
ret.merge! nested.build
|
71
|
+
end
|
72
|
+
ret
|
73
|
+
end
|
74
|
+
|
75
|
+
def build
|
76
|
+
{ name => build_body }
|
77
|
+
end
|
78
|
+
|
79
|
+
private
|
80
|
+
|
81
|
+
def build_date_field
|
82
|
+
{
|
83
|
+
type: :date,
|
84
|
+
fielddata: { format: :doc_values }
|
85
|
+
}
|
86
|
+
end
|
87
|
+
|
88
|
+
def build_string_field
|
89
|
+
{
|
90
|
+
type: :string,
|
91
|
+
index: :not_analyzed,
|
92
|
+
fielddata: { format: :doc_values }
|
93
|
+
}
|
94
|
+
end
|
95
|
+
|
96
|
+
def build_integer_field
|
97
|
+
{
|
98
|
+
type: :integer,
|
99
|
+
fielddata: { format: :doc_values }
|
100
|
+
}
|
101
|
+
end
|
102
|
+
|
103
|
+
def build_float_field
|
104
|
+
{
|
105
|
+
type: :float,
|
106
|
+
fielddata: { format: :doc_values }
|
107
|
+
}
|
108
|
+
end
|
109
|
+
|
110
|
+
def build_double_field
|
111
|
+
{
|
112
|
+
type: :double,
|
113
|
+
fielddata: { format: :doc_values }
|
114
|
+
}
|
115
|
+
end
|
116
|
+
|
117
|
+
def build_long_field
|
118
|
+
{
|
119
|
+
type: :long,
|
120
|
+
fielddata: { format: :doc_values }
|
121
|
+
}
|
122
|
+
end
|
123
|
+
|
124
|
+
def build_analyzed_string_field(field_name)
|
125
|
+
{
|
126
|
+
type: :multi_field,
|
127
|
+
fields: {
|
128
|
+
field_name => {
|
129
|
+
type: :string,
|
130
|
+
index: :not_analyzed,
|
131
|
+
fielddata: { format: :doc_values }
|
132
|
+
},
|
133
|
+
analyzed: {
|
134
|
+
type: :string
|
135
|
+
}
|
136
|
+
}
|
137
|
+
}
|
138
|
+
end
|
139
|
+
|
140
|
+
def build_bool_field
|
141
|
+
{
|
142
|
+
type: :boolean
|
143
|
+
}
|
144
|
+
end
|
145
|
+
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Elasticated
|
2
|
+
class PartitionedRepository < Repository
|
3
|
+
|
4
|
+
attr_accessor :index_selector
|
5
|
+
|
6
|
+
def initialize(index_selector, opts={})
|
7
|
+
self.index_selector = index_selector
|
8
|
+
super opts
|
9
|
+
end
|
10
|
+
|
11
|
+
protected
|
12
|
+
|
13
|
+
# override
|
14
|
+
def execute(action, query, opts={})
|
15
|
+
affected_indices = index_selector.indices_for_query(query)
|
16
|
+
affected_indices = affected_indices*','
|
17
|
+
super action, query, opts.merge(index: affected_indices)
|
18
|
+
end
|
19
|
+
|
20
|
+
# override
|
21
|
+
def prepare(action, document, opts={})
|
22
|
+
affected_index = index_selector.index_for_document(document)
|
23
|
+
super action, document, opts.merge(index: affected_index)
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,159 @@
|
|
1
|
+
module Elasticated
|
2
|
+
class Query
|
3
|
+
|
4
|
+
class << self
|
5
|
+
def build(&block)
|
6
|
+
q = new
|
7
|
+
q.evaluate block
|
8
|
+
q
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
include BlockEvaluation
|
13
|
+
include Clonable
|
14
|
+
|
15
|
+
attr_accessor :_conditions, :_filter_conditions, :_post_conditions
|
16
|
+
attr_accessor :_source, :_sort, :_size, :_from
|
17
|
+
attr_accessor :_aggregations
|
18
|
+
|
19
|
+
def initialize
|
20
|
+
self._conditions = QueryConditions.new
|
21
|
+
self._filter_conditions = QueryConditions.new
|
22
|
+
self._post_conditions = QueryConditions.new
|
23
|
+
self._aggregations = QueryAggregations.new
|
24
|
+
end
|
25
|
+
|
26
|
+
# builders & attrs
|
27
|
+
|
28
|
+
def conditions(&block)
|
29
|
+
_conditions.evaluate block
|
30
|
+
self
|
31
|
+
end
|
32
|
+
|
33
|
+
def filter(&block)
|
34
|
+
_filter_conditions.evaluate block
|
35
|
+
self
|
36
|
+
end
|
37
|
+
|
38
|
+
def post(&block)
|
39
|
+
_post_conditions.evaluate block
|
40
|
+
self
|
41
|
+
end
|
42
|
+
alias_method :post_filter, :post
|
43
|
+
|
44
|
+
def size(value)
|
45
|
+
self._size = value
|
46
|
+
self
|
47
|
+
end
|
48
|
+
alias_method :limit, :size
|
49
|
+
|
50
|
+
def from(value)
|
51
|
+
self._from = value
|
52
|
+
self
|
53
|
+
end
|
54
|
+
alias_method :offset, :from
|
55
|
+
|
56
|
+
def sort(field, method=nil)
|
57
|
+
self._sort ||= Array.new
|
58
|
+
_sort << { field => { order: method || :asc } }
|
59
|
+
self
|
60
|
+
end
|
61
|
+
|
62
|
+
def source(*fields_array)
|
63
|
+
self._source = fields_array.flatten
|
64
|
+
self
|
65
|
+
end
|
66
|
+
alias_method :fields, :source
|
67
|
+
|
68
|
+
def aggregations(&block)
|
69
|
+
_aggregations.evaluate block
|
70
|
+
self
|
71
|
+
end
|
72
|
+
|
73
|
+
# misc getters
|
74
|
+
|
75
|
+
def limited?
|
76
|
+
!!_size
|
77
|
+
end
|
78
|
+
|
79
|
+
def sorted?
|
80
|
+
!!_sort
|
81
|
+
end
|
82
|
+
|
83
|
+
def aggregated?
|
84
|
+
!_aggregations.empty?
|
85
|
+
end
|
86
|
+
|
87
|
+
# to_hash methods
|
88
|
+
|
89
|
+
def build_for_count
|
90
|
+
return { query: _conditions.build } if _filter_conditions.empty?
|
91
|
+
filtered = { filter: _filter_conditions.build }
|
92
|
+
filtered.merge!(query: _conditions.build) unless _conditions.empty?
|
93
|
+
{ query: { filtered: filtered } }
|
94
|
+
end
|
95
|
+
|
96
|
+
def build_for_aggregations
|
97
|
+
raise "No aggregations present in query" unless aggregated?
|
98
|
+
ret = build_for_count
|
99
|
+
ret.merge! size: 0
|
100
|
+
ret.merge! aggs: _aggregations.build
|
101
|
+
ret
|
102
|
+
end
|
103
|
+
|
104
|
+
def build_for_top_hits
|
105
|
+
ret = Hash.new
|
106
|
+
ret.merge! sort: _sort if _sort
|
107
|
+
ret.merge! size: _size if _size
|
108
|
+
ret.merge! from: _from if _from
|
109
|
+
ret.merge! _source: _source if _source
|
110
|
+
ret
|
111
|
+
end
|
112
|
+
|
113
|
+
def build_for_search
|
114
|
+
ret = build_for_count.merge build_for_top_hits
|
115
|
+
ret.merge! post_filter: _post_conditions.build unless _post_conditions.empty?
|
116
|
+
ret
|
117
|
+
end
|
118
|
+
|
119
|
+
def build_for_aggregated_search
|
120
|
+
ret = build_for_search
|
121
|
+
ret.merge! aggs: _aggregations.build unless _aggregations.empty?
|
122
|
+
ret
|
123
|
+
end
|
124
|
+
alias_method :build, :build_for_aggregated_search
|
125
|
+
|
126
|
+
# parse methods
|
127
|
+
|
128
|
+
def parse_aggregations(response)
|
129
|
+
_aggregations.parse response
|
130
|
+
end
|
131
|
+
|
132
|
+
# delimiters
|
133
|
+
|
134
|
+
def fill_delimiter(field_delimiter)
|
135
|
+
_conditions.fill_delimiter field_delimiter
|
136
|
+
_filter_conditions.fill_delimiter field_delimiter
|
137
|
+
end
|
138
|
+
|
139
|
+
# conditions & filter shorthands
|
140
|
+
|
141
|
+
private
|
142
|
+
|
143
|
+
def method_missing(name, *args, &block)
|
144
|
+
# delegates any "filter_*" method missing to the _filter_conditions object
|
145
|
+
# delegates any other method missing to the _conditions object
|
146
|
+
conditions_obj = name.to_s.start_with?('filter_') ? _filter_conditions : _conditions
|
147
|
+
real_method_name = name.to_s.gsub(/^filter\_/, '').to_sym
|
148
|
+
super unless conditions_obj.respond_to? real_method_name
|
149
|
+
conditions_obj.send real_method_name, *args, &block
|
150
|
+
self
|
151
|
+
end
|
152
|
+
|
153
|
+
def respond_to_missing?(name, include_private=false)
|
154
|
+
real_method_name = name.to_s.gsub(/^filter\_/, '').to_sym
|
155
|
+
_conditions.respond_to?(real_method_name) || super
|
156
|
+
end
|
157
|
+
|
158
|
+
end
|
159
|
+
end
|