elastic-rails 0.1.0 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Guardfile +46 -0
- data/elastic.gemspec +13 -4
- data/lib/elastic/commands/build_agg_from_params.rb +63 -0
- data/lib/elastic/commands/build_query_from_params.rb +132 -0
- data/lib/elastic/commands/import_index_documents.rb +63 -0
- data/lib/elastic/configuration.rb +61 -0
- data/lib/elastic/core/adaptor.rb +102 -0
- data/lib/elastic/core/base_middleware.rb +43 -0
- data/lib/elastic/core/default_middleware.rb +64 -0
- data/lib/elastic/core/definition.rb +118 -0
- data/lib/elastic/core/mapping_manager.rb +120 -0
- data/lib/elastic/core/middleware.rb +26 -0
- data/lib/elastic/core/query_assembler.rb +84 -0
- data/lib/elastic/core/query_config.rb +25 -0
- data/lib/elastic/core/serializer.rb +45 -0
- data/lib/elastic/core/source_formatter.rb +40 -0
- data/lib/elastic/dsl/bool_query_builder.rb +52 -0
- data/lib/elastic/dsl/bool_query_context.rb +42 -0
- data/lib/elastic/dsl/metric_builder.rb +34 -0
- data/lib/elastic/fields/nested.rb +42 -0
- data/lib/elastic/fields/value.rb +60 -0
- data/lib/elastic/nested_type.rb +5 -0
- data/lib/elastic/nodes/agg/average.rb +7 -0
- data/lib/elastic/nodes/agg/base_metric.rb +42 -0
- data/lib/elastic/nodes/agg/date_histogram.rb +48 -0
- data/lib/elastic/nodes/agg/maximum.rb +7 -0
- data/lib/elastic/nodes/agg/minimum.rb +7 -0
- data/lib/elastic/nodes/agg/stats.rb +7 -0
- data/lib/elastic/nodes/agg/sum.rb +7 -0
- data/lib/elastic/nodes/agg/terms.rb +38 -0
- data/lib/elastic/nodes/agg/top_hits.rb +17 -0
- data/lib/elastic/nodes/and.rb +45 -0
- data/lib/elastic/nodes/base.rb +34 -0
- data/lib/elastic/nodes/base_agg.rb +32 -0
- data/lib/elastic/nodes/boolean.rb +87 -0
- data/lib/elastic/nodes/concerns/aggregable.rb +52 -0
- data/lib/elastic/nodes/concerns/boostable.rb +25 -0
- data/lib/elastic/nodes/concerns/bucketed.rb +17 -0
- data/lib/elastic/nodes/concerns/hit_provider.rb +39 -0
- data/lib/elastic/nodes/function_score.rb +116 -0
- data/lib/elastic/nodes/match.rb +45 -0
- data/lib/elastic/nodes/nested.rb +42 -0
- data/lib/elastic/nodes/or.rb +9 -0
- data/lib/elastic/nodes/range.rb +36 -0
- data/lib/elastic/nodes/search.rb +48 -0
- data/lib/elastic/nodes/term.rb +58 -0
- data/lib/elastic/query.rb +84 -22
- data/lib/elastic/railtie.rb +41 -0
- data/lib/elastic/railties/ar_helpers.rb +51 -0
- data/lib/elastic/railties/ar_middleware.rb +45 -0
- data/lib/elastic/{indexable_record.rb → railties/indexable_record.rb} +21 -18
- data/lib/elastic/railties/query_extensions.rb +9 -0
- data/lib/elastic/railties/rspec.rb +6 -0
- data/lib/elastic/railties/tasks/es.rake +19 -0
- data/lib/elastic/railties/type_extensions.rb +14 -0
- data/lib/elastic/railties/utils.rb +62 -0
- data/lib/elastic/results/aggregations.rb +29 -0
- data/lib/elastic/results/base.rb +13 -0
- data/lib/elastic/results/bucket.rb +15 -0
- data/lib/elastic/results/bucket_collection.rb +17 -0
- data/lib/elastic/results/grouped_result.rb +37 -0
- data/lib/elastic/results/hit.rb +25 -0
- data/lib/elastic/results/hit_collection.rb +38 -0
- data/lib/elastic/results/metric.rb +13 -0
- data/lib/elastic/results/result_group.rb +19 -0
- data/lib/elastic/results/root.rb +15 -0
- data/lib/elastic/shims/base.rb +23 -0
- data/lib/elastic/shims/grouping.rb +23 -0
- data/lib/elastic/shims/populating.rb +69 -0
- data/lib/elastic/shims/reducing.rb +20 -0
- data/lib/elastic/support/command.rb +34 -0
- data/lib/elastic/support/transform.rb +37 -0
- data/lib/elastic/support/traversable.rb +22 -0
- data/lib/elastic/type.rb +56 -84
- data/lib/elastic/types/base_type.rb +38 -0
- data/lib/elastic/types/faceted_type.rb +16 -0
- data/lib/elastic/types/nestable_type.rb +14 -0
- data/lib/elastic/version.rb +1 -1
- data/lib/elastic.rb +72 -30
- data/lib/generators/elastic/index_generator.rb +10 -0
- data/lib/generators/elastic/templates/index.rb +17 -0
- metadata +222 -16
- data/lib/elastic/capabilities/aggregation_builder.rb +0 -64
- data/lib/elastic/capabilities/bool_query_builder.rb +0 -74
- data/lib/elastic/capabilities/context_handler.rb +0 -31
- data/lib/elastic/histogram.rb +0 -49
- data/lib/elastic/index.rb +0 -113
- data/lib/elastic/indexable.rb +0 -25
- data/lib/elastic/value_transform.rb +0 -15
@@ -0,0 +1,118 @@
|
|
1
|
+
module Elastic::Core
|
2
|
+
class Definition
|
3
|
+
attr_reader :middleware_options
|
4
|
+
|
5
|
+
def main_target
|
6
|
+
targets.first
|
7
|
+
end
|
8
|
+
|
9
|
+
def targets
|
10
|
+
@target_cache ||= load_targets.freeze
|
11
|
+
end
|
12
|
+
|
13
|
+
def targets=(_values)
|
14
|
+
@target_cache = nil
|
15
|
+
@targets = _values
|
16
|
+
end
|
17
|
+
|
18
|
+
def types
|
19
|
+
targets.map(&:type_name)
|
20
|
+
end
|
21
|
+
|
22
|
+
def mode
|
23
|
+
main_target.mode
|
24
|
+
end
|
25
|
+
|
26
|
+
def initialize
|
27
|
+
@targets = []
|
28
|
+
@field_map = {}
|
29
|
+
@frozen = false
|
30
|
+
@middleware_options = HashWithIndifferentAccess.new
|
31
|
+
end
|
32
|
+
|
33
|
+
def register_field(_field)
|
34
|
+
raise 'definition has been frozen' if @frozen
|
35
|
+
@field_map[_field.name] = _field
|
36
|
+
end
|
37
|
+
|
38
|
+
def fields
|
39
|
+
@field_map.each_value
|
40
|
+
end
|
41
|
+
|
42
|
+
def expanded_field_names
|
43
|
+
@expanded_field_names ||= @field_map.map { |_, field| field.expanded_names }.flatten
|
44
|
+
end
|
45
|
+
|
46
|
+
def get_field(_name)
|
47
|
+
_name = _name.to_s
|
48
|
+
separator = _name.index '.'
|
49
|
+
if separator.nil?
|
50
|
+
@field_map[_name]
|
51
|
+
else
|
52
|
+
parent = @field_map[_name[0...separator]]
|
53
|
+
parent.try(:get_field, _name[separator + 1..-1])
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def has_field?(_name)
|
58
|
+
!get_field(_name).nil?
|
59
|
+
end
|
60
|
+
|
61
|
+
def as_es_mapping
|
62
|
+
# TODO: Make this a command
|
63
|
+
properties = {}
|
64
|
+
@field_map.each_value do |field|
|
65
|
+
field_def = field.mapping_options
|
66
|
+
|
67
|
+
if !field_def.key?(:type) && field.mapping_inference_enabled?
|
68
|
+
inferred = infer_mapping_options(field.name)
|
69
|
+
field_def.merge! inferred.symbolize_keys unless inferred.nil?
|
70
|
+
end
|
71
|
+
|
72
|
+
if Elastic::Configuration.strict_mode && !field_def.key?(:type)
|
73
|
+
raise "explicit field type for #{field.name} required"
|
74
|
+
end
|
75
|
+
|
76
|
+
properties[field.name] = field_def if field_def.key? :type
|
77
|
+
end
|
78
|
+
|
79
|
+
{ 'properties' => properties.as_json }
|
80
|
+
end
|
81
|
+
|
82
|
+
def freeze
|
83
|
+
unless @frozen
|
84
|
+
@field_map.each_value(&:freeze)
|
85
|
+
@frozen = true
|
86
|
+
@middleware_options.freeze
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def frozen?
|
91
|
+
!!@frozen
|
92
|
+
end
|
93
|
+
|
94
|
+
private
|
95
|
+
|
96
|
+
def load_targets
|
97
|
+
mode = nil
|
98
|
+
@targets.map do |target|
|
99
|
+
target = target.to_s.camelize.constantize if target.is_a?(Symbol) || target.is_a?(String)
|
100
|
+
|
101
|
+
target = load_target_middleware(target) unless target.class < BaseMiddleware
|
102
|
+
raise 'index target is not indexable' if target.nil?
|
103
|
+
raise 'mistmatching indexable mode' if mode && mode != target.mode
|
104
|
+
mode = target.mode
|
105
|
+
|
106
|
+
target
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def load_target_middleware(_target)
|
111
|
+
Middleware.wrap(_target)
|
112
|
+
end
|
113
|
+
|
114
|
+
def infer_mapping_options(_name)
|
115
|
+
main_target.field_options_for(_name, middleware_options)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
module Elastic::Core
|
2
|
+
class MappingManager
|
3
|
+
attr_reader :adaptor, :definition
|
4
|
+
|
5
|
+
def initialize(_adaptor, _definition)
|
6
|
+
@adaptor = _adaptor
|
7
|
+
@definition = _definition
|
8
|
+
@status = :pending
|
9
|
+
end
|
10
|
+
|
11
|
+
def out_of_sync?
|
12
|
+
@status == :out_of_sync
|
13
|
+
end
|
14
|
+
|
15
|
+
def incomplete?
|
16
|
+
@status == :incomplete
|
17
|
+
end
|
18
|
+
|
19
|
+
def fetch
|
20
|
+
begin
|
21
|
+
mappings = @adaptor.get_mappings
|
22
|
+
mappings = @definition.types.map { |t| mappings[t] }.reject(&:nil?)
|
23
|
+
@index = merge_mappings_into_index mappings
|
24
|
+
rescue Elasticsearch::Transport::Transport::Errors::NotFound
|
25
|
+
# ignore not-found errors when fetching mappings
|
26
|
+
@index = nil
|
27
|
+
end
|
28
|
+
|
29
|
+
@status = compute_status
|
30
|
+
self
|
31
|
+
end
|
32
|
+
|
33
|
+
def unmapped_fields
|
34
|
+
@definition.expanded_field_names.reject { |f| has_field? f }
|
35
|
+
end
|
36
|
+
|
37
|
+
def has_field?(_name)
|
38
|
+
@index.key? _name
|
39
|
+
end
|
40
|
+
|
41
|
+
def get_field_options(_name)
|
42
|
+
@index[_name]
|
43
|
+
end
|
44
|
+
|
45
|
+
def migrate
|
46
|
+
# TODO: make this a command
|
47
|
+
@adaptor.create unless @adaptor.exists?
|
48
|
+
begin
|
49
|
+
@definition.types.each { |t| @adaptor.set_mapping(t, user_mapping) }
|
50
|
+
rescue Elasticsearch::Transport::Transport::Errors::BadRequest
|
51
|
+
# TODO: https://www.elastic.co/guide/en/elasticsearch/guide/current/reindex.html
|
52
|
+
end
|
53
|
+
fetch
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def compute_status
|
59
|
+
if !synchronized?
|
60
|
+
:out_of_sync
|
61
|
+
elsif unmapped_fields.count > 0
|
62
|
+
:incomplete
|
63
|
+
else
|
64
|
+
:ready
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def synchronized?
|
69
|
+
return false if @index.nil?
|
70
|
+
flatten(user_mapping).all? do |field, properties|
|
71
|
+
compare_field_properties(@index[field], properties)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def compare_field_properties(_current, _user)
|
76
|
+
return false if _current.nil?
|
77
|
+
|
78
|
+
case _current['type']
|
79
|
+
when 'date'
|
80
|
+
_current = { 'format' => 'dateOptionalTime' }.merge(_user)
|
81
|
+
else
|
82
|
+
_current == _user
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def user_mapping
|
87
|
+
@user_mapping ||= definition.as_es_mapping
|
88
|
+
end
|
89
|
+
|
90
|
+
def flatten(_raw, _prefix = '')
|
91
|
+
_raw['properties'].flat_map do |name, raw_field|
|
92
|
+
if raw_field['type'] == 'nested'
|
93
|
+
childs = flatten(raw_field, name + '.')
|
94
|
+
childs << [
|
95
|
+
_prefix + name,
|
96
|
+
raw_field.slice(*(raw_field.keys - ['properties']))
|
97
|
+
]
|
98
|
+
else
|
99
|
+
[[_prefix + name, raw_field.dup]]
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def merge_mappings_into_index(_mappings)
|
105
|
+
{}.tap do |result|
|
106
|
+
_mappings.each do |mapping|
|
107
|
+
index = flatten(mapping)
|
108
|
+
index.each do |field, properties|
|
109
|
+
if result.key? field
|
110
|
+
result[field].merge! properties
|
111
|
+
else
|
112
|
+
result[field] = properties
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
result.each_value(&:freeze)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Elastic::Core
|
2
|
+
module Middleware
|
3
|
+
extend self
|
4
|
+
|
5
|
+
def register(_middleware_class)
|
6
|
+
middlewares << _middleware_class
|
7
|
+
end
|
8
|
+
|
9
|
+
def wrap(_target)
|
10
|
+
middleware_for(_target).new _target
|
11
|
+
end
|
12
|
+
|
13
|
+
def middleware_for(_target)
|
14
|
+
# TODO: improve matching logic
|
15
|
+
middleware = middlewares.reverse_each.find { |m| m.accepts?(_target) }
|
16
|
+
middleware = DefaultMiddleware if middleware.nil?
|
17
|
+
middleware
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def middlewares
|
23
|
+
@middlewares ||= []
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
module Elastic::Core
|
2
|
+
class QueryAssembler
|
3
|
+
def initialize(_index, _config)
|
4
|
+
@index = _index
|
5
|
+
@config = _config
|
6
|
+
end
|
7
|
+
|
8
|
+
def assemble
|
9
|
+
query = build_base_query
|
10
|
+
|
11
|
+
if !grouped?
|
12
|
+
query.size = (@config.limit || Elastic::Configuration.page_size)
|
13
|
+
query.offset = @config.offset
|
14
|
+
else
|
15
|
+
query.size = 0
|
16
|
+
last = attach_groups query
|
17
|
+
last.aggregate(Elastic::Nodes::TopHits.build('default'))
|
18
|
+
|
19
|
+
query = grouped_query query
|
20
|
+
query = reduced_query query
|
21
|
+
end
|
22
|
+
|
23
|
+
populated_query query
|
24
|
+
end
|
25
|
+
|
26
|
+
def assemble_ids
|
27
|
+
raise NotImplementedError, 'ids retrieval not yet implemented'
|
28
|
+
end
|
29
|
+
|
30
|
+
def assemble_total
|
31
|
+
raise NotImplementedError, 'total not yet implemented'
|
32
|
+
end
|
33
|
+
|
34
|
+
def assemble_pluck(_field)
|
35
|
+
raise NotImplementedError, 'pluck not yet implemented'
|
36
|
+
end
|
37
|
+
|
38
|
+
def assemble_metric(_node)
|
39
|
+
query = assemble_metrics([_node])
|
40
|
+
reduced_query query
|
41
|
+
end
|
42
|
+
|
43
|
+
def assemble_metrics(_aggs)
|
44
|
+
query = build_base_query
|
45
|
+
query.size = 0
|
46
|
+
|
47
|
+
last = attach_groups(query)
|
48
|
+
last.aggs = _aggs
|
49
|
+
|
50
|
+
query = grouped_query(query) if grouped?
|
51
|
+
query
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def build_base_query
|
57
|
+
@config.root.simplify
|
58
|
+
end
|
59
|
+
|
60
|
+
def grouped?
|
61
|
+
!@config.groups.empty?
|
62
|
+
end
|
63
|
+
|
64
|
+
def attach_groups(_query)
|
65
|
+
@config.groups.inject(_query) do |last, node|
|
66
|
+
node = node.simplify
|
67
|
+
last.aggregate node
|
68
|
+
node
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def grouped_query(_query)
|
73
|
+
Elastic::Shims::Grouping.new(_query)
|
74
|
+
end
|
75
|
+
|
76
|
+
def reduced_query(_query)
|
77
|
+
Elastic::Shims::Reducing.new(_query)
|
78
|
+
end
|
79
|
+
|
80
|
+
def populated_query(_query)
|
81
|
+
Elastic::Shims::Populating.new(@index, @config, _query)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Elastic::Core
|
2
|
+
class QueryConfig
|
3
|
+
attr_accessor :root, :groups, :limit, :offset, :middleware_options
|
4
|
+
|
5
|
+
def self.initial_config
|
6
|
+
new.tap do |config|
|
7
|
+
config.root = Elastic::Nodes::Search.new
|
8
|
+
config.root.query = Elastic::Nodes::Boolean.new
|
9
|
+
config.root.query.disable_coord = true unless Elastic::Configuration.coord_similarity
|
10
|
+
config.groups = []
|
11
|
+
config.middleware_options = HashWithIndifferentAccess.new
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def clone
|
16
|
+
self.class.new.tap do |clone|
|
17
|
+
clone.root = @root.clone
|
18
|
+
clone.groups = @groups.dup
|
19
|
+
clone.limit = @limit
|
20
|
+
clone.offset = @offset
|
21
|
+
clone.middleware_options = @middleware_options.dup
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Elastic::Core
|
2
|
+
class Serializer
|
3
|
+
attr_reader :object
|
4
|
+
|
5
|
+
def self.original_value_occluded?(_field)
|
6
|
+
public_method_defined? _field
|
7
|
+
end
|
8
|
+
|
9
|
+
def initialize(_definition, _object)
|
10
|
+
# TODO: validate that object is of type <target>?
|
11
|
+
@definition = _definition
|
12
|
+
@object = _object
|
13
|
+
end
|
14
|
+
|
15
|
+
def fields
|
16
|
+
@definition.fields
|
17
|
+
end
|
18
|
+
|
19
|
+
def as_es_document(only_data: false)
|
20
|
+
data = {}.tap do |hash|
|
21
|
+
fields.each do |field|
|
22
|
+
value = read_attribute_for_indexing(field.name)
|
23
|
+
value = field.prepare_value_for_index(value)
|
24
|
+
hash[field.name] = value
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
return data if only_data
|
29
|
+
|
30
|
+
result = { '_type' => object.class.to_s, 'data' => data }
|
31
|
+
result['_id'] = read_attribute_for_indexing(:id) if has_attribute_for_indexing?(:id)
|
32
|
+
result
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def has_attribute_for_indexing?(_name)
|
38
|
+
respond_to?(_name) || @object.respond_to?(_name)
|
39
|
+
end
|
40
|
+
|
41
|
+
def read_attribute_for_indexing(_name)
|
42
|
+
respond_to?(_name) ? public_send(_name) : @object.public_send(_name)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Elastic::Core
|
2
|
+
class SourceFormatter
|
3
|
+
def initialize(_mapping)
|
4
|
+
@mapping = _mapping
|
5
|
+
@treatment_cache = {}
|
6
|
+
end
|
7
|
+
|
8
|
+
def format(_source, _prefix = nil)
|
9
|
+
_source.each do |field, value|
|
10
|
+
field_name = _prefix ? "#{_prefix}.#{field}" : field
|
11
|
+
|
12
|
+
treatment = treatment_for field_name
|
13
|
+
if treatment == :nested
|
14
|
+
value.each { |v| format(v, field_name) }
|
15
|
+
else
|
16
|
+
_source[field] = send(treatment, value) unless treatment == :none
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def treatment_for(_field)
|
24
|
+
treatment = @treatment_cache[_field]
|
25
|
+
treatment = @treatment_cache[_field] = get_treatment(_field) if treatment.nil?
|
26
|
+
treatment
|
27
|
+
end
|
28
|
+
|
29
|
+
def get_treatment(_field)
|
30
|
+
field_options = @mapping.get_field_options(_field) || {}
|
31
|
+
return :nested if field_options['type'] == 'nested'
|
32
|
+
return :parse_date if field_options['type'] == 'date'
|
33
|
+
:none
|
34
|
+
end
|
35
|
+
|
36
|
+
def parse_date(_value)
|
37
|
+
Time.parse _value
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module Elastic::Dsl
|
2
|
+
module BoolQueryBuilder
|
3
|
+
def must(*_queries)
|
4
|
+
with_bool_query do |query|
|
5
|
+
query.must build_query_from_params(_queries)
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
def should(*_queries)
|
10
|
+
with_bool_query do |query|
|
11
|
+
query.should build_query_from_params(_queries)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def with(_modifier, &_block)
|
16
|
+
raise ArgumentError, 'block missing' if _block.nil?
|
17
|
+
raise ArgumentError, 'node is not a modifier' unless _modifier.respond_to? :clone_with_query
|
18
|
+
|
19
|
+
with_bool_query do |query|
|
20
|
+
ctx = BoolQueryContext.new index, query, _modifier
|
21
|
+
ctx.instance_exec(&_block)
|
22
|
+
ctx
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def boost(_amount = nil, field: nil, fixed: false, factor: 1, modifier: :none, missing: 1,
|
27
|
+
&_block)
|
28
|
+
raise ArgumentError, 'must provide at least a boost amount' if _amount.nil? && field.nil?
|
29
|
+
|
30
|
+
node = Elastic::Nodes::FunctionScore.new
|
31
|
+
node.boost_mode = :replace if fixed
|
32
|
+
|
33
|
+
if !field.nil?
|
34
|
+
node.add_field_function(field, factor: factor, modifier: modifier, missing: missing)
|
35
|
+
elsif fixed
|
36
|
+
node.add_weight_function(_amount)
|
37
|
+
else
|
38
|
+
node.boost = _amount
|
39
|
+
end
|
40
|
+
|
41
|
+
# TODO: add decay function support.
|
42
|
+
|
43
|
+
with(node, &_block)
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def build_query_from_params(_params)
|
49
|
+
Elastic::Commands::BuildQueryFromParams.for(index: index, params: _params)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Elastic::Dsl
|
2
|
+
class BoolQueryContext
|
3
|
+
include BoolQueryBuilder
|
4
|
+
|
5
|
+
attr_reader :index
|
6
|
+
|
7
|
+
def initialize(_index, _query, _modifier)
|
8
|
+
@index = _index
|
9
|
+
@wrapper = BoolQueryWrapper.new(_query, _modifier)
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def with_bool_query
|
15
|
+
yield @wrapper
|
16
|
+
self
|
17
|
+
end
|
18
|
+
|
19
|
+
class BoolQueryWrapper
|
20
|
+
def initialize(_query, _modifier)
|
21
|
+
@query = _query
|
22
|
+
@modifier = _modifier
|
23
|
+
end
|
24
|
+
|
25
|
+
def must(_node)
|
26
|
+
@query.must wrap(_node)
|
27
|
+
self
|
28
|
+
end
|
29
|
+
|
30
|
+
def should(_node)
|
31
|
+
@query.should wrap(_node)
|
32
|
+
self
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def wrap(_query)
|
38
|
+
@modifier.clone_with_query _query
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Elastic::Dsl
|
2
|
+
module MetricBuilder
|
3
|
+
def average(_field, _options = {})
|
4
|
+
aggregate_metric(Elastic::Nodes::Agg::Average, _field, _options, 'avg_%s')
|
5
|
+
end
|
6
|
+
|
7
|
+
def sum(_field, _options = {})
|
8
|
+
aggregate_metric(Elastic::Nodes::Agg::Sum, _field, _options, 'sum_%s')
|
9
|
+
end
|
10
|
+
|
11
|
+
def minimum(_field, _options = {})
|
12
|
+
aggregate_metric(Elastic::Nodes::Agg::Minimum, _field, _options, 'min_%s')
|
13
|
+
end
|
14
|
+
|
15
|
+
def maximum(_field, _options = {})
|
16
|
+
aggregate_metric(Elastic::Nodes::Agg::Maximum, _field, _options, 'max_%s')
|
17
|
+
end
|
18
|
+
|
19
|
+
def stats(_field, _options = {})
|
20
|
+
aggregate_metric(Elastic::Nodes::Agg::Stats, _field, _options, '%s_stats')
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def aggregate_metric(_klass, _field, _options, _default_name)
|
26
|
+
with_aggregable_for_metric do |agg|
|
27
|
+
# TODO: detect nested name and wrap node
|
28
|
+
name = _options[:as] || sprintf(_default_name, _field)
|
29
|
+
node = _klass.build(name, _field, missing: _options[:missing])
|
30
|
+
agg.aggregate node
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Elastic::Fields
|
2
|
+
class Nested
|
3
|
+
attr_reader :name, :index
|
4
|
+
|
5
|
+
def initialize(_name, _index)
|
6
|
+
@name = _name.to_s
|
7
|
+
@index = _index
|
8
|
+
end
|
9
|
+
|
10
|
+
def expanded_names
|
11
|
+
[@name] + @index.definition.expanded_field_names.map { |n| @name + '.' + n }
|
12
|
+
end
|
13
|
+
|
14
|
+
def mapping_inference_enabled?
|
15
|
+
false
|
16
|
+
end
|
17
|
+
|
18
|
+
def disable_mapping_inference
|
19
|
+
end
|
20
|
+
|
21
|
+
def freeze
|
22
|
+
@index.freeze_index_definition
|
23
|
+
super
|
24
|
+
end
|
25
|
+
|
26
|
+
def mapping_options
|
27
|
+
@index.definition.as_es_mapping.merge!(type: :nested)
|
28
|
+
end
|
29
|
+
|
30
|
+
def get_field(_name)
|
31
|
+
@index.definition.get_field _name
|
32
|
+
end
|
33
|
+
|
34
|
+
def prepare_value_for_query(_value)
|
35
|
+
_value
|
36
|
+
end
|
37
|
+
|
38
|
+
def prepare_value_for_index(_values)
|
39
|
+
_values.map { |v| @index.new(v).as_es_document(only_data: true) }
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module Elastic::Fields
|
2
|
+
class Value
|
3
|
+
MAPPING_OPTIONS = [
|
4
|
+
:type, :analyzer, :boost, :coerce, :copy_to, :doc_values, :dynamic,
|
5
|
+
:enabled, :fielddata, :geohash, :geohash_precision, :geohash_prefix, :format, :ignore_above,
|
6
|
+
:ignore_malformed, :include_in_all, :index_options, :lat_lon, :index, :fields, :norms,
|
7
|
+
:null_value, :position_increment_gap, :properties, :search_analyzer, :similarity, :store,
|
8
|
+
:term_vector
|
9
|
+
]
|
10
|
+
|
11
|
+
attr_reader :name
|
12
|
+
|
13
|
+
def initialize(_name, _options)
|
14
|
+
@name = _name.to_s
|
15
|
+
@options = _options
|
16
|
+
@mapping_inference = true
|
17
|
+
@transform = Elastic::Support::Transform.new @options[:transform]
|
18
|
+
end
|
19
|
+
|
20
|
+
def expanded_names
|
21
|
+
[@name]
|
22
|
+
end
|
23
|
+
|
24
|
+
def mapping_inference_enabled?
|
25
|
+
@mapping_inference && !@options.key?(:transform)
|
26
|
+
end
|
27
|
+
|
28
|
+
def disable_mapping_inference
|
29
|
+
@mapping_inference = false
|
30
|
+
end
|
31
|
+
|
32
|
+
def mapping_options
|
33
|
+
process_special_types @options.symbolize_keys.slice(*MAPPING_OPTIONS)
|
34
|
+
end
|
35
|
+
|
36
|
+
def has_field?(_name)
|
37
|
+
false
|
38
|
+
end
|
39
|
+
|
40
|
+
def prepare_value_for_query(_value)
|
41
|
+
prepare_value_for_index(_value)
|
42
|
+
end
|
43
|
+
|
44
|
+
def prepare_value_for_index(_value)
|
45
|
+
@transform.apply _value
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def process_special_types(_definition)
|
51
|
+
case _definition[:type].try(:to_sym)
|
52
|
+
when :term
|
53
|
+
_definition[:type] = 'string'
|
54
|
+
_definition[:index] = 'not_analyzed'
|
55
|
+
end
|
56
|
+
|
57
|
+
_definition
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|