elasticsearch_record 1.0.2 → 1.1.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/.yardopts +4 -0
- data/Gemfile.lock +10 -14
- data/README.md +180 -27
- data/docs/CHANGELOG.md +36 -18
- data/docs/LICENSE.txt +1 -1
- data/elasticsearch_record.gemspec +42 -0
- data/lib/active_record/connection_adapters/elasticsearch/column.rb +20 -6
- data/lib/active_record/connection_adapters/elasticsearch/database_statements.rb +142 -125
- data/lib/active_record/connection_adapters/elasticsearch/quoting.rb +2 -23
- data/lib/active_record/connection_adapters/elasticsearch/schema_creation.rb +30 -0
- data/lib/active_record/connection_adapters/elasticsearch/schema_definitions/attribute_methods.rb +103 -0
- data/lib/active_record/connection_adapters/elasticsearch/schema_definitions/column_methods.rb +42 -0
- data/lib/active_record/connection_adapters/elasticsearch/schema_definitions/create_table_definition.rb +158 -0
- data/lib/active_record/connection_adapters/elasticsearch/schema_definitions/table_alias_definition.rb +32 -0
- data/lib/active_record/connection_adapters/elasticsearch/schema_definitions/table_definition.rb +132 -0
- data/lib/active_record/connection_adapters/elasticsearch/schema_definitions/table_mapping_definition.rb +110 -0
- data/lib/active_record/connection_adapters/elasticsearch/schema_definitions/table_setting_definition.rb +136 -0
- data/lib/active_record/connection_adapters/elasticsearch/schema_definitions/update_table_definition.rb +174 -0
- data/lib/active_record/connection_adapters/elasticsearch/schema_definitions.rb +37 -0
- data/lib/active_record/connection_adapters/elasticsearch/schema_dumper.rb +110 -0
- data/lib/active_record/connection_adapters/elasticsearch/schema_statements.rb +398 -174
- data/lib/active_record/connection_adapters/elasticsearch/table_statements.rb +232 -0
- data/lib/active_record/connection_adapters/elasticsearch/type/multicast_value.rb +2 -0
- data/lib/active_record/connection_adapters/elasticsearch/unsupported_implementation.rb +32 -0
- data/lib/active_record/connection_adapters/elasticsearch_adapter.rb +112 -19
- data/lib/arel/collectors/elasticsearch_query.rb +0 -1
- data/lib/arel/visitors/elasticsearch.rb +7 -579
- data/lib/arel/visitors/elasticsearch_base.rb +234 -0
- data/lib/arel/visitors/elasticsearch_query.rb +463 -0
- data/lib/arel/visitors/elasticsearch_schema.rb +124 -0
- data/lib/elasticsearch_record/core.rb +44 -10
- data/lib/elasticsearch_record/errors.rb +13 -0
- data/lib/elasticsearch_record/gem_version.rb +6 -2
- data/lib/elasticsearch_record/instrumentation/log_subscriber.rb +27 -9
- data/lib/elasticsearch_record/model_schema.rb +5 -0
- data/lib/elasticsearch_record/persistence.rb +31 -26
- data/lib/elasticsearch_record/query.rb +56 -17
- data/lib/elasticsearch_record/querying.rb +17 -0
- data/lib/elasticsearch_record/relation/calculation_methods.rb +3 -0
- data/lib/elasticsearch_record/relation/core_methods.rb +57 -17
- data/lib/elasticsearch_record/relation/query_clause_tree.rb +38 -1
- data/lib/elasticsearch_record/relation/query_methods.rb +6 -0
- data/lib/elasticsearch_record/relation/result_methods.rb +15 -9
- data/lib/elasticsearch_record/result.rb +32 -5
- data/lib/elasticsearch_record/statement_cache.rb +2 -1
- data/lib/elasticsearch_record.rb +2 -2
- metadata +29 -11
- data/.ruby-version +0 -1
- data/lib/elasticsearch_record/schema_migration.rb +0 -30
@@ -3,144 +3,161 @@
|
|
3
3
|
module ActiveRecord
|
4
4
|
module ConnectionAdapters
|
5
5
|
module Elasticsearch
|
6
|
+
# extend adapter with query-related statements
|
7
|
+
#
|
8
|
+
# *ORIGINAL* methods untouched:
|
9
|
+
# - to_sql
|
10
|
+
# - to_sql_and_binds
|
11
|
+
# - insert
|
12
|
+
# - create
|
13
|
+
# - update
|
14
|
+
# - delete
|
15
|
+
# - arel_from_relation
|
16
|
+
#
|
17
|
+
# *SUPPORTED* but not used:
|
18
|
+
# - select
|
19
|
+
# - select_all
|
20
|
+
# - select_one
|
21
|
+
# - select_value
|
22
|
+
# - select_values
|
23
|
+
#
|
24
|
+
# *UNSUPPORTED* methods that will be +ignored+:
|
25
|
+
# - build_fixture_sql
|
26
|
+
# - build_fixture_statements
|
27
|
+
# - build_truncate_statement
|
28
|
+
# - build_truncate_statements
|
29
|
+
#
|
30
|
+
# *UNSUPPORTED* methods that will +fail+:
|
31
|
+
# - insert_fixture
|
32
|
+
# - insert_fixtures_set
|
33
|
+
# - execute_batch
|
34
|
+
# - select_prepared
|
35
|
+
# - combine_multi_statements
|
36
|
+
#
|
6
37
|
module DatabaseStatements
|
38
|
+
extend ActiveSupport::Concern
|
39
|
+
|
40
|
+
included do
|
41
|
+
define_unsupported_method :insert_fixture, :insert_fixtures_set, :execute_batch, :select_prepared,
|
42
|
+
:combine_multi_statements
|
43
|
+
|
44
|
+
# detects if a query is a write query.
|
45
|
+
# since we don't provide a simple string / hash we can now access the query-object and ask for it :)
|
46
|
+
# @see ActiveRecord::ConnectionAdapters::DatabaseStatements#write_query?
|
47
|
+
# @param [ElasticsearchRecord::Query] query
|
48
|
+
# @return [Boolean]
|
49
|
+
def write_query?(query)
|
50
|
+
query.write?
|
51
|
+
end
|
7
52
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
query
|
15
|
-
|
53
|
+
# Executes the query object in the context of this connection and returns the raw result
|
54
|
+
# from the connection adapter.
|
55
|
+
# @param [ElasticsearchRecord::Query] query
|
56
|
+
# @param [String (frozen)] name
|
57
|
+
# @param [Boolean] async
|
58
|
+
# @return [ElasticsearchRecord::Result]
|
59
|
+
def execute(query, name = nil, async: false)
|
60
|
+
# validate the query
|
61
|
+
raise ActiveRecord::StatementInvalid, 'Unable to execute! Provided query is not a "ElasticsearchRecord::Query".' unless query.is_a?(ElasticsearchRecord::Query)
|
62
|
+
raise ActiveRecord::StatementInvalid, 'Unable to execute! Provided query is invalid.' unless query.valid?
|
63
|
+
|
64
|
+
# checks for write query - raises an exception if connection is locked to readonly ...
|
65
|
+
check_if_write_query(query)
|
66
|
+
|
67
|
+
api(*query.gate, query.query_arguments, name, async: async)
|
68
|
+
end
|
16
69
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
build_result(
|
32
|
-
api(*query.gate, query.query_arguments, name, async: async),
|
33
|
-
columns: query.columns
|
34
|
-
)
|
35
|
-
end
|
70
|
+
# gets called for all queries - a +ElasticsearchRecord::Query+ must be provided.
|
71
|
+
# @param [ElasticsearchRecord::Query] query
|
72
|
+
# @param [String (frozen)] name
|
73
|
+
# @param [Array] binds - not supported on the top-level and therefore ignored!
|
74
|
+
# @param [Boolean] prepare - used by the default AbstractAdapter - but not supported and therefore never ignored!
|
75
|
+
# @param [Boolean] async
|
76
|
+
# @return [ElasticsearchRecord::Result]
|
77
|
+
def exec_query(query, name = "QUERY", binds = [], prepare: false, async: false)
|
78
|
+
build_result(
|
79
|
+
execute(query, name, async: async),
|
80
|
+
columns: query.columns
|
81
|
+
)
|
82
|
+
end
|
36
83
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
84
|
+
# Executes insert +query+ statement in the context of this connection using
|
85
|
+
# +binds+ as the bind substitutes. +name+ is logged along with
|
86
|
+
# the executed +query+ arguments.
|
87
|
+
# @return [ElasticsearchRecord::Result]
|
88
|
+
def exec_insert(query, name = nil, binds = [], _pk = nil, _sequence_name = nil)
|
89
|
+
result = exec_query(query, name, binds)
|
43
90
|
|
44
|
-
|
45
|
-
|
46
|
-
# the executed +query+ arguments.
|
47
|
-
# expects a integer as return.
|
48
|
-
# @return [Integer]
|
49
|
-
def exec_delete(query, name = nil, binds = [])
|
50
|
-
exec_query(query, name, binds).total
|
51
|
-
end
|
91
|
+
# fetch additional Elasticsearch response result
|
92
|
+
# raise ::ElasticsearchRecord::ResponseResultError.new('created', result.result) unless result.result == 'created'
|
52
93
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
# expects a integer as return.
|
57
|
-
# @return [Integer]
|
58
|
-
def exec_update(query, name = nil, binds = [])
|
59
|
-
exec_query(query, name, binds).total
|
60
|
-
end
|
94
|
+
# return the result object
|
95
|
+
result
|
96
|
+
end
|
61
97
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
#
|
66
|
-
|
98
|
+
# Executes update +query+ statement in the context of this connection using
|
99
|
+
# +binds+ as the bind substitutes. +name+ is logged along with
|
100
|
+
# the executed +query+ arguments.
|
101
|
+
# expects a integer as return.
|
102
|
+
# @return [Integer]
|
103
|
+
def exec_update(query, name = nil, binds = [])
|
104
|
+
result = exec_query(query, name, binds)
|
67
105
|
|
68
|
-
|
69
|
-
|
70
|
-
index: queries.first&.index,
|
71
|
-
type: ElasticsearchRecord::Query::TYPE_MSEARCH,
|
72
|
-
body: queries.map { |q| { search: q.body } })
|
106
|
+
# fetch additional Elasticsearch response result
|
107
|
+
# raise ::ElasticsearchRecord::ResponseResultError.new('updated', result.result) unless result.result == 'updated'
|
73
108
|
|
74
|
-
|
75
|
-
|
109
|
+
result.total
|
110
|
+
end
|
76
111
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
112
|
+
# Executes delete +query+ statement in the context of this connection using
|
113
|
+
# +binds+ as the bind substitutes. +name+ is logged along with
|
114
|
+
# the executed +query+ arguments.
|
115
|
+
# expects a integer as return.
|
116
|
+
# @return [Integer]
|
117
|
+
def exec_delete(query, name = nil, binds = [])
|
118
|
+
result = exec_query(query, name, binds)
|
81
119
|
|
82
|
-
|
83
|
-
|
84
|
-
index: query.index,
|
85
|
-
type: ElasticsearchRecord::Query::TYPE_COUNT,
|
86
|
-
body: query.body,
|
87
|
-
arguments: query.arguments)
|
120
|
+
# fetch additional Elasticsearch response result
|
121
|
+
# raise ::ElasticsearchRecord::ResponseResultError.new('deleted', result.result) unless result.result == 'deleted'
|
88
122
|
|
89
|
-
|
90
|
-
|
123
|
+
result.total
|
124
|
+
end
|
125
|
+
|
126
|
+
# executes a msearch for provided arels
|
127
|
+
# @return [ElasticsearchRecord::Result]
|
128
|
+
def select_multiple(arels, name = "Multi", async: false)
|
129
|
+
# transform arels to query objects
|
130
|
+
queries = arels.map { |arel| to_sql(arel_from_relation(arel)) }
|
131
|
+
|
132
|
+
# build new msearch query
|
133
|
+
query = ElasticsearchRecord::Query.new(
|
134
|
+
index: queries.first&.index,
|
135
|
+
type: ElasticsearchRecord::Query::TYPE_MSEARCH,
|
136
|
+
body: queries.map { |q| { search: q.body } })
|
137
|
+
|
138
|
+
exec_query(query, name, async: async)
|
139
|
+
end
|
140
|
+
|
141
|
+
# executes a count query for provided arel
|
142
|
+
# @return [Integer]
|
143
|
+
def select_count(arel, name = "Count", async: false)
|
144
|
+
query = to_sql(arel_from_relation(arel))
|
145
|
+
|
146
|
+
# build new count query from existing query
|
147
|
+
query = ElasticsearchRecord::Query.new(
|
148
|
+
index: query.index,
|
149
|
+
type: ElasticsearchRecord::Query::TYPE_COUNT,
|
150
|
+
body: query.body,
|
151
|
+
status: query.status,
|
152
|
+
arguments: query.arguments)
|
153
|
+
|
154
|
+
exec_query(query, name, async: async).response['count']
|
155
|
+
end
|
91
156
|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
# # the provided klass is a ActiveRecord::StatementCache and only supports SQL _(String)_ queries.
|
97
|
-
# # We force overwrite this class with our own
|
98
|
-
#
|
99
|
-
#
|
100
|
-
# ActiveRecord::StatementCache.partial_query_collector
|
101
|
-
#
|
102
|
-
# raise "STOP HERE!!!!!!"
|
103
|
-
# if prepared_statements
|
104
|
-
# sql, binds = visitor.compile(arel.ast, collector)
|
105
|
-
# query = klass.query(sql)
|
106
|
-
# else
|
107
|
-
# collector = klass.partial_query_collector
|
108
|
-
# parts, binds = visitor.compile(arel.ast, collector)
|
109
|
-
# query = klass.partial_query(parts)
|
110
|
-
# end
|
111
|
-
# [query, binds]
|
112
|
-
# end
|
113
|
-
|
114
|
-
# calls the +elasticsearch-api+ endpoints by provided namespace and action.
|
115
|
-
# if a block was provided it'll yield the response.body and returns the blocks result.
|
116
|
-
# otherwise it will return the response itself...
|
117
|
-
# @param [Symbol] namespace - the API namespace (e.g. indices, nodes, sql, ...)
|
118
|
-
# @param [Symbol] action - the API action to call in tha namespace
|
119
|
-
# @param [Hash] arguments - action arguments
|
120
|
-
# @param [String (frozen)] name - the logging name
|
121
|
-
# @param [Boolean] async - send async (default: false) - currently not supported
|
122
|
-
# @return [Elasticsearch::API::Response, Object]
|
123
|
-
def api(namespace, action, arguments = {}, name = 'API', async: false)
|
124
|
-
raise ::StandardError, 'ASYNC api calls are not supported' if async
|
125
|
-
|
126
|
-
# resolve the API target
|
127
|
-
target = namespace == :core ? @connection : @connection.__send__(namespace)
|
128
|
-
|
129
|
-
log("#{namespace}.#{action}", arguments, name, async: async) do
|
130
|
-
response = ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
|
131
|
-
target.__send__(action, arguments)
|
132
|
-
end
|
133
|
-
|
134
|
-
if response.is_a?(::Elasticsearch::API::Response)
|
135
|
-
# reverse information for the LogSubscriber - shows the 'query-time' in the logs
|
136
|
-
# this works, since we use a referenced hash ...
|
137
|
-
arguments[:_qt] = response['took']
|
138
|
-
|
139
|
-
# raise timeouts
|
140
|
-
raise(ActiveRecord::StatementTimeout, "Elasticsearch api request failed due a timeout") if response['timed_out']
|
141
|
-
end
|
142
|
-
|
143
|
-
response
|
157
|
+
# returns the last inserted id from the result.
|
158
|
+
# called through +#insert+
|
159
|
+
def last_inserted_id(result)
|
160
|
+
result.response['_id']
|
144
161
|
end
|
145
162
|
end
|
146
163
|
end
|
@@ -4,33 +4,12 @@ module ActiveRecord
|
|
4
4
|
module ConnectionAdapters
|
5
5
|
module Elasticsearch
|
6
6
|
module Quoting # :nodoc:
|
7
|
-
# Quotes the column value to help prevent
|
8
|
-
def quote(value)
|
9
|
-
case value
|
10
|
-
# those values do not need to be quoted
|
11
|
-
when BigDecimal, Numeric, String, Symbol, nil, true, false then value
|
12
|
-
when ActiveSupport::Duration then value.to_i
|
13
|
-
when Array then value.map { |val| quote(val) }
|
14
|
-
when Hash then value.transform_values { |val| quote(val) }
|
15
|
-
else
|
16
|
-
super
|
17
|
-
end
|
18
|
-
end
|
19
|
-
|
20
7
|
def quoted_true
|
21
|
-
true
|
22
|
-
end
|
23
|
-
|
24
|
-
def unquoted_true
|
25
|
-
true
|
8
|
+
'true'
|
26
9
|
end
|
27
10
|
|
28
11
|
def quoted_false
|
29
|
-
false
|
30
|
-
end
|
31
|
-
|
32
|
-
def unquoted_false
|
33
|
-
false
|
12
|
+
'false'
|
34
13
|
end
|
35
14
|
end
|
36
15
|
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module ConnectionAdapters
|
5
|
+
module Elasticsearch
|
6
|
+
# this class is totally refactored from the original and acts here as "wrapper"
|
7
|
+
# - to support rails backwards compatibility on the +#schema_creation+ method
|
8
|
+
class SchemaCreation # :nodoc:
|
9
|
+
|
10
|
+
attr_reader :connection
|
11
|
+
|
12
|
+
def initialize(connection)
|
13
|
+
@connection = connection
|
14
|
+
end
|
15
|
+
|
16
|
+
def accept(o)
|
17
|
+
visitor.dispatch_as(:simple) do
|
18
|
+
visitor.compile(o)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def visitor
|
25
|
+
@visitor ||= connection.visitor
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
data/lib/active_record/connection_adapters/elasticsearch/schema_definitions/attribute_methods.rb
ADDED
@@ -0,0 +1,103 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module ConnectionAdapters
|
5
|
+
module Elasticsearch
|
6
|
+
module AttributeMethods
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
|
9
|
+
included do
|
10
|
+
attr_reader :state
|
11
|
+
|
12
|
+
def error_messages
|
13
|
+
errors.full_messages.join(', ')
|
14
|
+
end
|
15
|
+
|
16
|
+
def with_state(state)
|
17
|
+
@state = state
|
18
|
+
|
19
|
+
self
|
20
|
+
end
|
21
|
+
|
22
|
+
def state?
|
23
|
+
state.present?
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def __set_attribute(key, value)
|
29
|
+
if value.nil?
|
30
|
+
__remove_attribute(key)
|
31
|
+
elsif value.is_a?(Array) || value.is_a?(Hash)
|
32
|
+
@attributes[key] = value.compact
|
33
|
+
else
|
34
|
+
@attributes[key] = value
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def __get_attribute(key)
|
39
|
+
@attributes[key]
|
40
|
+
end
|
41
|
+
|
42
|
+
def __remove_attribute(key)
|
43
|
+
@attributes.delete(key)
|
44
|
+
end
|
45
|
+
|
46
|
+
def __set_nested(attr, key, value)
|
47
|
+
values = self.send(attr).presence || {}
|
48
|
+
|
49
|
+
if value.nil?
|
50
|
+
values.delete(key)
|
51
|
+
else
|
52
|
+
values[key.to_s] = value
|
53
|
+
end
|
54
|
+
self.send("#{attr}=", values)
|
55
|
+
end
|
56
|
+
|
57
|
+
def __get_nested(attr, key)
|
58
|
+
values = self.send(attr).presence || {}
|
59
|
+
values[key.to_s]
|
60
|
+
end
|
61
|
+
|
62
|
+
def __attributes_keys
|
63
|
+
@attributes.keys
|
64
|
+
end
|
65
|
+
|
66
|
+
def __attributes_values
|
67
|
+
@attributes.values
|
68
|
+
end
|
69
|
+
|
70
|
+
def __assign(attributes)
|
71
|
+
attributes.each do |key, value|
|
72
|
+
send("#{key}=", value)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def invalid!(message, key = :base)
|
77
|
+
errors.add(key, message)
|
78
|
+
false
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
class_methods do
|
83
|
+
def build_attribute_methods!(*args)
|
84
|
+
opts = args.extract_options!
|
85
|
+
opts[:key] ||= :attributes
|
86
|
+
|
87
|
+
args.each do |name|
|
88
|
+
class_eval <<-CODE, __FILE__, __LINE__ + 1
|
89
|
+
def #{name}
|
90
|
+
__get_attribute(:#{name})
|
91
|
+
end
|
92
|
+
|
93
|
+
def #{name}=(value)
|
94
|
+
__set_attribute(:#{name}, value)
|
95
|
+
end
|
96
|
+
CODE
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module ConnectionAdapters
|
5
|
+
module Elasticsearch
|
6
|
+
module ColumnMethods
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
|
9
|
+
included do
|
10
|
+
# see @ ::ActiveRecord::ConnectionAdapters::ElasticsearchAdapter::NATIVE_DATABASE_TYPES.keys
|
11
|
+
define_column_methods :string, :blob, :datetime, :bigint, :json, :binary, :boolean, :keyword,
|
12
|
+
:constant_keyword, :wildcard, :long, :integer, :short, :byte, :double, :float,
|
13
|
+
:half_float, :scaled_float, :unsigned_long, :date, :object, :flattened, :nested,
|
14
|
+
:integer_range, :float_range, :long_range, :double_range, :date_range, :ip_range,
|
15
|
+
:ip, :version, :text
|
16
|
+
|
17
|
+
# Appends a primary key definition to the table definition.
|
18
|
+
# Can be called multiple times, but this is probably not a good idea.
|
19
|
+
def primary_key(name, type = :primary_key, **options)
|
20
|
+
mapping(name, type, **options.merge(primary_key: true))
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class_methods do
|
25
|
+
def define_column_methods(*column_types)
|
26
|
+
# :nodoc:
|
27
|
+
column_types.each do |column_type|
|
28
|
+
module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
29
|
+
def #{column_type}(*names, **options)
|
30
|
+
raise ArgumentError, "Missing column name(s) for #{column_type}" if names.empty?
|
31
|
+
names.each { |name| mapping(name, :#{column_type}, **options) }
|
32
|
+
end
|
33
|
+
RUBY
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
private :define_column_methods
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,158 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_record/connection_adapters/elasticsearch/schema_definitions/table_definition'
|
4
|
+
|
5
|
+
module ActiveRecord
|
6
|
+
module ConnectionAdapters
|
7
|
+
module Elasticsearch
|
8
|
+
class CreateTableDefinition < TableDefinition
|
9
|
+
def initialize(conn, name, settings: nil, mappings: nil, aliases: nil, **opts)
|
10
|
+
super(conn, name, **opts)
|
11
|
+
|
12
|
+
@settings = HashWithIndifferentAccess.new
|
13
|
+
@mappings = HashWithIndifferentAccess.new
|
14
|
+
@aliases = HashWithIndifferentAccess.new
|
15
|
+
|
16
|
+
transform_settings!(settings) if settings.present?
|
17
|
+
transform_mappings!(mappings) if mappings.present?
|
18
|
+
transform_aliases!(aliases) if aliases.present?
|
19
|
+
end
|
20
|
+
|
21
|
+
# returns an array with all +TableSettingDefinition+.
|
22
|
+
# @return [Array]
|
23
|
+
def settings
|
24
|
+
@settings.values
|
25
|
+
end
|
26
|
+
|
27
|
+
# returns an array with all +TableMappingDefinition+.
|
28
|
+
# @return [Array]
|
29
|
+
def mappings
|
30
|
+
@mappings.values
|
31
|
+
end
|
32
|
+
|
33
|
+
# provide backwards compatibility to columns
|
34
|
+
alias columns mappings
|
35
|
+
|
36
|
+
# returns an array with all +TableAliasDefinition+.
|
37
|
+
# @return [Array]
|
38
|
+
def aliases
|
39
|
+
@aliases.values
|
40
|
+
end
|
41
|
+
|
42
|
+
# Returns a MappingDefinition for the mapping with name +name+.
|
43
|
+
def [](name)
|
44
|
+
@mappings[name]
|
45
|
+
end
|
46
|
+
|
47
|
+
######################
|
48
|
+
# DEFINITION METHODS #
|
49
|
+
######################
|
50
|
+
|
51
|
+
# adds a new mapping
|
52
|
+
def mapping(name, type, force: false, **options, &block)
|
53
|
+
raise ArgumentError, "you cannot define an already defined mapping '#{name}'!" if @mappings.key?(name) && !force?(force)
|
54
|
+
|
55
|
+
@mappings[name] = new_mapping_definition(name, type, **options, &block)
|
56
|
+
|
57
|
+
self
|
58
|
+
end
|
59
|
+
|
60
|
+
# provide backwards compatibility to columns
|
61
|
+
alias column mapping
|
62
|
+
|
63
|
+
def remove_mapping(name)
|
64
|
+
@mappings.delete(name)
|
65
|
+
end
|
66
|
+
|
67
|
+
# provide backwards compatibility to columns
|
68
|
+
alias remove_column remove_mapping
|
69
|
+
|
70
|
+
def setting(name, value, force: false, **options, &block)
|
71
|
+
raise ArgumentError, "you cannot define an already defined setting '#{name}'!" if @settings.key?(name) && !force?(force)
|
72
|
+
|
73
|
+
@settings[name] = new_setting_definition(name, value, **options, &block)
|
74
|
+
|
75
|
+
self
|
76
|
+
end
|
77
|
+
|
78
|
+
def remove_setting(name)
|
79
|
+
@settings.delete name
|
80
|
+
end
|
81
|
+
|
82
|
+
# we can use +alias+ here, since the instance method is not a reserved keyword!
|
83
|
+
|
84
|
+
def alias(name, force: false, **options, &block)
|
85
|
+
raise ArgumentError, "you cannot define an already defined alias '#{name}'." if @aliases.key?(name) && !force?(force)
|
86
|
+
|
87
|
+
@aliases[name] = new_alias_definition(name, **options, &block)
|
88
|
+
|
89
|
+
self
|
90
|
+
end
|
91
|
+
|
92
|
+
def remove_alias(name)
|
93
|
+
@aliases.delete name
|
94
|
+
end
|
95
|
+
|
96
|
+
# Adds a reference.
|
97
|
+
#
|
98
|
+
# t.references(:user)
|
99
|
+
# t.belongs_to(:supplier)
|
100
|
+
#
|
101
|
+
# See {connection.add_reference}[rdoc-ref:SchemaStatements#add_reference] for details of the options you can use.
|
102
|
+
def references(*args, **options)
|
103
|
+
args.each do |ref_name|
|
104
|
+
ActiveRecord::ConnectionAdapters::ReferenceDefinition.new(ref_name, **options).add_to(self)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
alias :belongs_to :references
|
109
|
+
|
110
|
+
# Appends <tt>:datetime</tt> columns <tt>:created_at</tt> and
|
111
|
+
# <tt>:updated_at</tt> to the table. See {connection.add_timestamps}[rdoc-ref:SchemaStatements#add_timestamps]
|
112
|
+
#
|
113
|
+
# t.timestamps
|
114
|
+
def timestamps(**options)
|
115
|
+
column(:created_at, :datetime, **options)
|
116
|
+
column(:updated_at, :datetime, **options)
|
117
|
+
end
|
118
|
+
|
119
|
+
private
|
120
|
+
|
121
|
+
def _exec
|
122
|
+
execute(schema_creation.accept(self), 'CREATE TABLE').dig('acknowledged')
|
123
|
+
end
|
124
|
+
|
125
|
+
# force empty states to prevent "Name is static for an open table" error.
|
126
|
+
def state
|
127
|
+
nil
|
128
|
+
end
|
129
|
+
|
130
|
+
def transform_mappings!(mappings)
|
131
|
+
return unless mappings['properties'].present?
|
132
|
+
|
133
|
+
mappings['properties'].each do |name, attributes|
|
134
|
+
self.mapping(name, attributes.delete('type'), **attributes)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def transform_settings!(settings)
|
139
|
+
# exclude settings, that are provided through the API but are not part of the index-settings
|
140
|
+
settings
|
141
|
+
.with_indifferent_access
|
142
|
+
.each { |name, value|
|
143
|
+
# don't transform ignored names
|
144
|
+
next if ActiveRecord::ConnectionAdapters::Elasticsearch::TableSettingDefinition.match_ignore_names?(name)
|
145
|
+
|
146
|
+
self.setting(name, value)
|
147
|
+
}
|
148
|
+
end
|
149
|
+
|
150
|
+
def transform_aliases!(aliases)
|
151
|
+
aliases.each do |name, attributes|
|
152
|
+
self.alias(name, **attributes)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|