elasticated 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|