elastic-rails 0.1.0 → 0.5.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 +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
|