elasticsearch_record 1.1.0 → 1.2.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.
Files changed (30) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +1 -1
  3. data/README.md +22 -8
  4. data/docs/CHANGELOG.md +20 -1
  5. data/lib/active_record/connection_adapters/elasticsearch/column.rb +9 -2
  6. data/lib/active_record/connection_adapters/elasticsearch/schema_definitions/clone_table_definition.rb +88 -0
  7. data/lib/active_record/connection_adapters/elasticsearch/schema_definitions/column_methods.rb +1 -1
  8. data/lib/active_record/connection_adapters/elasticsearch/schema_definitions/create_table_definition.rb +29 -27
  9. data/lib/active_record/connection_adapters/elasticsearch/schema_definitions/table_definition.rb +67 -12
  10. data/lib/active_record/connection_adapters/elasticsearch/schema_definitions/table_mapping_definition.rb +48 -13
  11. data/lib/active_record/connection_adapters/elasticsearch/schema_definitions/table_meta_definition.rb +24 -0
  12. data/lib/active_record/connection_adapters/elasticsearch/schema_definitions/table_setting_definition.rb +9 -4
  13. data/lib/active_record/connection_adapters/elasticsearch/schema_definitions/update_table_definition.rb +38 -13
  14. data/lib/active_record/connection_adapters/elasticsearch/schema_definitions.rb +3 -0
  15. data/lib/active_record/connection_adapters/elasticsearch/schema_dumper.rb +41 -0
  16. data/lib/active_record/connection_adapters/elasticsearch/schema_statements.rb +36 -10
  17. data/lib/active_record/connection_adapters/elasticsearch/table_statements.rb +102 -8
  18. data/lib/active_record/connection_adapters/elasticsearch_adapter.rb +17 -2
  19. data/lib/arel/collectors/elasticsearch_query.rb +3 -0
  20. data/lib/arel/visitors/elasticsearch_query.rb +1 -0
  21. data/lib/arel/visitors/elasticsearch_schema.rb +48 -7
  22. data/lib/elasticsearch_record/core.rb +24 -8
  23. data/lib/elasticsearch_record/gem_version.rb +1 -1
  24. data/lib/elasticsearch_record/model_schema.rb +26 -2
  25. data/lib/elasticsearch_record/persistence.rb +45 -34
  26. data/lib/elasticsearch_record/query.rb +4 -1
  27. data/lib/elasticsearch_record/schema_migration.rb +49 -0
  28. data/lib/elasticsearch_record/tasks/elasticsearch_database_tasks.rb +15 -5
  29. data/lib/elasticsearch_record.rb +1 -0
  30. metadata +6 -3
@@ -10,7 +10,7 @@ module ActiveRecord
10
10
  include ActiveModel::Validations
11
11
 
12
12
  # exclude settings, that are provided through the API but are not part of the index-settings API
13
- IGNORE_NAMES = ['provided_name', 'creation_date', 'uuid', 'version'].freeze
13
+ IGNORE_NAMES = ['provided_name', 'creation_date', 'uuid', 'version','routing.allocation.initial_recovery','resize'].freeze
14
14
 
15
15
  # available setting names
16
16
  # - see @ https://www.elastic.co/guide/en/elasticsearch/reference/current/index-modules.html#index-modules-settings
@@ -33,7 +33,8 @@ module ActiveRecord
33
33
  'max_docvalue_fields_search', 'max_script_fields', 'max_ngram_diff', 'max_shingle_diff',
34
34
  'max_refresh_listeners', 'analyze.max_token_count', 'highlight.max_analyzed_offset',
35
35
  'max_terms_count', 'max_regex_length', 'query.default_field', 'routing.allocation.enable',
36
- 'routing.rebalance.enable', 'gc_deletes', 'default_pipeline', 'final_pipeline', 'hidden'].freeze
36
+ 'routing.rebalance.enable', 'gc_deletes', 'default_pipeline', 'final_pipeline',
37
+ 'hidden', 'blocks'].freeze
37
38
 
38
39
  VALID_NAMES = (FINAL_NAMES + STATIC_NAMES + DYNAMIC_NAMES).freeze
39
40
 
@@ -43,6 +44,9 @@ module ActiveRecord
43
44
 
44
45
  # validations
45
46
  validates_presence_of :name
47
+
48
+ # disable validation for name - maybe future updates of Elasticsearch have other names.
49
+ # To not be hooked on those possible changes we disable the validation
46
50
  validate :_validate_name
47
51
  validate :_validate_final_name
48
52
  validate :_validate_static_name
@@ -78,7 +82,8 @@ module ActiveRecord
78
82
  end
79
83
 
80
84
  def static?
81
- !dynamic?
85
+ @static = flat_names.all? { |flat_name| self.class.match_static_names?(flat_name) } if @static.nil?
86
+ @static
82
87
  end
83
88
 
84
89
  def dynamic?
@@ -100,7 +105,7 @@ module ActiveRecord
100
105
  end
101
106
 
102
107
  def _validate_static_name
103
- return true if dynamic?
108
+ return true unless static?
104
109
  return true if ['missing', 'close'].include?(_table_status)
105
110
 
106
111
  invalid!("is static - this setting can only be changed on a closed index!", :name)
@@ -14,7 +14,7 @@ module ActiveRecord
14
14
  COMPOSITE_DEFINITIONS = [
15
15
  AddMappingDefinition,
16
16
  ChangeMappingDefinition,
17
-
17
+ ChangeMetaDefinition,
18
18
  AddSettingDefinition,
19
19
  DeleteAliasDefinition
20
20
  ].freeze
@@ -29,10 +29,29 @@ module ActiveRecord
29
29
  alias :mapping :add_mapping
30
30
  alias :column :add_mapping
31
31
 
32
+ def change_meta(name, value, **options)
33
+ load_meta_definition!
34
+
35
+ define! ChangeMetaDefinition, new_meta_definition(name, value, **options)
36
+ end
37
+
38
+ def delete_meta(name, **options)
39
+ load_meta_definition!
40
+
41
+ define! ChangeMetaDefinition, new_meta_definition(name, nil, **options)
42
+ end
43
+
32
44
  def change_mapping(name, type, if_exists: false, **options, &block)
33
45
  return if if_exists && !mapping_exists?(self.name, name, type)
34
46
 
35
- define! ChangeMappingDefinition, new_mapping_definition(name, type, **options, &block)
47
+ mapping = new_mapping_definition(name, type, **options, &block)
48
+ define! ChangeMappingDefinition, mapping
49
+
50
+ # check if the mapping is assigned as primary_key
51
+ if mapping.primary_key?
52
+ change_meta :primary_key, mapping.name
53
+ change_meta(:auto_increment, mapping.auto_increment) if mapping.auto_increment?
54
+ end
36
55
  end
37
56
 
38
57
  alias :change_column :change_mapping
@@ -56,6 +75,7 @@ module ActiveRecord
56
75
 
57
76
  define! ChangeMappingDefinition, new_mapping_definition(name, mapping['type'], **options, &block)
58
77
  end
78
+
59
79
  alias :change_mapping_attribute :change_mapping_attributes
60
80
 
61
81
  def add_setting(name, value, if_not_exists: false, **options, &block)
@@ -105,6 +125,17 @@ module ActiveRecord
105
125
 
106
126
  private
107
127
 
128
+ # loads existing meta definitions from table_mappings
129
+ def load_meta_definition!
130
+ return if @load_meta_definition
131
+ @load_meta_definition = true
132
+
133
+
134
+ table_metas(self.name).each do |name, value|
135
+ define! ChangeMetaDefinition, new_meta_definition(name, value)
136
+ end
137
+ end
138
+
108
139
  def define!(klass, item)
109
140
  @definitions ||= {}
110
141
  @definitions[klass] ||= []
@@ -119,11 +150,6 @@ module ActiveRecord
119
150
  clear_state!
120
151
  end
121
152
 
122
- def _after_assign
123
- # run the same content as +_after_exec+, but only if the assignment failed
124
- _after_exec if failed?
125
- end
126
-
127
153
  def _after_exec
128
154
  # reopen the table again
129
155
  open_table(self.name) if _toggle_table_status?
@@ -131,6 +157,8 @@ module ActiveRecord
131
157
  # reset table state
132
158
  clear_state!
133
159
  end
160
+ alias :_rescue_assign :_after_exec
161
+ alias :_rescue_exec :_after_exec
134
162
 
135
163
  def _toggle_table_status?
136
164
  @toggle_table_status = (force? && state[:status] == 'open') if @toggle_table_status.nil?
@@ -141,9 +169,6 @@ module ActiveRecord
141
169
  def _exec
142
170
  return unless definitions.any?
143
171
 
144
- # check, if the table should be closed before executing the queries
145
- close_table(self.name) if opts[:close] == true
146
-
147
172
  definitions.each do |klass, items|
148
173
  # check if the provided definition klass is a composite definition
149
174
  executable_definitions = if COMPOSITE_DEFINITIONS.include?(klass)
@@ -159,10 +184,10 @@ module ActiveRecord
159
184
  end
160
185
  end
161
186
 
162
- open_table(self.name) if opts[:close] == true
163
-
164
- # cleanup definitions
187
+ # cleanup executed definitions
165
188
  @definitions = {}
189
+
190
+ true
166
191
  end
167
192
 
168
193
  def _to_composite_log(klass)
@@ -9,6 +9,7 @@ module ActiveRecord
9
9
  # mapping definitions
10
10
  AddMappingDefinition = Struct.new(:items) # composite
11
11
  ChangeMappingDefinition = Struct.new(:items) # composite
12
+ ChangeMetaDefinition = Struct.new(:items) # composite
12
13
 
13
14
  # setting definitions
14
15
  AddSettingDefinition = Struct.new(:items) # composite
@@ -30,8 +31,10 @@ require 'active_record/connection_adapters/elasticsearch/schema_definitions/colu
30
31
 
31
32
  require 'active_record/connection_adapters/elasticsearch/schema_definitions/table_alias_definition'
32
33
  require 'active_record/connection_adapters/elasticsearch/schema_definitions/table_mapping_definition'
34
+ require 'active_record/connection_adapters/elasticsearch/schema_definitions/table_meta_definition'
33
35
  require 'active_record/connection_adapters/elasticsearch/schema_definitions/table_setting_definition'
34
36
 
35
37
  require 'active_record/connection_adapters/elasticsearch/schema_definitions/table_definition'
36
38
  require 'active_record/connection_adapters/elasticsearch/schema_definitions/create_table_definition'
37
39
  require 'active_record/connection_adapters/elasticsearch/schema_definitions/update_table_definition'
40
+ require 'active_record/connection_adapters/elasticsearch/schema_definitions/clone_table_definition'
@@ -6,8 +6,40 @@ module ActiveRecord
6
6
  module ConnectionAdapters
7
7
  module Elasticsearch
8
8
  class SchemaDumper < ActiveRecord::ConnectionAdapters::SchemaDumper # :nodoc:
9
+
10
+ def initialize(connection, options = {})
11
+ super
12
+
13
+ _expand_options!
14
+ end
15
+
9
16
  private
10
17
 
18
+ def _expand_options!
19
+ @options[:table_name_prefix] = @connection.table_name_prefix if @options[:table_name_prefix].blank?
20
+ @options[:table_name_suffix] = @connection.table_name_suffix if @options[:table_name_suffix].blank?
21
+ end
22
+
23
+ # overwrite the method to 'fix' a possible ActiveRecord bug:
24
+ # If a +table_name_prefix+ or +table_name_suffix+ was provided we also only want to dump those tables,
25
+ # which matches the prefix/suffix. So possible, environment-related tables will be ignored
26
+ # (e.g. if a '-development' as +table_name_suffix+ was provided ... )
27
+ def ignored?(table_name)
28
+ super(table_name) || ignored_table?(table_name)
29
+ end
30
+
31
+ # returns true if the provided table name does not match possible provided
32
+ # options +table_name_prefix+ or +table_name_suffix+.
33
+ def ignored_table?(table_name)
34
+ # if the table starts NOT with +prefix+ it must be ignored
35
+ return true if @options[:table_name_prefix].present? && !table_name.start_with?(@options[:table_name_prefix].to_s)
36
+
37
+ # if the table ends NOT with +suffix+ it must be ignored
38
+ return true if @options[:table_name_suffix].present? && !table_name.end_with?(@options[:table_name_suffix].to_s)
39
+
40
+ false
41
+ end
42
+
11
43
  def table(table, stream, nested_blocks: false, **)
12
44
  begin
13
45
  self.table_name = table
@@ -21,6 +53,15 @@ module ActiveRecord
21
53
  tbl.print " create_table #{remove_prefix_and_suffix(table).inspect}"
22
54
  tbl.print ", force: true do |t|"
23
55
 
56
+ # META
57
+ if (metas = definition.metas).present?
58
+ tbl.puts
59
+
60
+ metas.each do |meta|
61
+ tbl.puts " t.meta #{meta.name.inspect}, #{format_attribute(meta.value)}"
62
+ end
63
+ end
64
+
24
65
  # ALIASES
25
66
  if (aliases = definition.aliases).present?
26
67
  tbl.puts
@@ -92,7 +92,7 @@ module ActiveRecord
92
92
  :dump_schema_information
93
93
 
94
94
  def assume_migrated_upto_version(version)
95
- version = version.to_i
95
+ version = version.to_i
96
96
  migrated = migration_context.get_all_versions
97
97
  versions = migration_context.migrations.map(&:version)
98
98
 
@@ -108,7 +108,7 @@ module ActiveRecord
108
108
  end
109
109
 
110
110
  # use a ActiveRecord syntax to create new versions
111
- inserting.each {|iversion| schema_migration.create(version: iversion) }
111
+ inserting.each { |iversion| schema_migration.create(version: iversion) }
112
112
  end
113
113
 
114
114
  true
@@ -137,6 +137,14 @@ module ActiveRecord
137
137
  api(:indices, :get_mapping, { index: table_name, expand_wildcards: [:open, :closed] }, 'SCHEMA').dig(table_name, 'mappings')
138
138
  end
139
139
 
140
+ # returns a hash of all meta data by provided table_name (index).
141
+ # HINT: +_meta+ is resolved from the table mappings
142
+ # @param [String] table_name
143
+ # @return [Hash]
144
+ def table_metas(table_name)
145
+ table_mappings(table_name).dig('_meta').presence || {}
146
+ end
147
+
140
148
  # returns a hash of all settings by provided table_name
141
149
  # @param [String] table_name
142
150
  # @param [Boolean] flat_settings (default: true)
@@ -223,7 +231,8 @@ module ActiveRecord
223
231
  meta: field['meta'],
224
232
  virtual: field['virtual'],
225
233
  fields: field['fields'],
226
- properties: field['properties']
234
+ properties: field['properties'],
235
+ enabled: field['enabled']
227
236
  )
228
237
  end
229
238
 
@@ -239,15 +248,18 @@ module ActiveRecord
239
248
  # Returns a array of tables primary keys.
240
249
  # PLEASE NOTE: Elasticsearch does not have a concept of primary key.
241
250
  # The only thing that uniquely identifies a document is the index together with the +_id+.
242
- # To not break the "ConnectionAdapters" concept we simulate this through the +meta+ attribute.
251
+ # To support this concept we simulate this through the +_meta+ field (from the index).
252
+ #
253
+ # As a alternative, the primary_key can also be provided through the mappings +meta+ field.
254
+ #
255
+ # see @ https://www.elastic.co/guide/en/elasticsearch/reference/8.5/mapping-meta-field.html
243
256
  # @see ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter#primary_keys
244
257
  # @param [String] table_name
245
258
  def primary_keys(table_name)
246
- column_definitions(table_name)
247
- # ActiveRecord::ConnectionAdapters::ElasticsearchAdapter::BASE_STRUCTURE
248
- .select { |f| f['meta'] && f['meta']['primary_key'] == 'true' }
259
+ table_metas(table_name).dig('primary_key').presence || column_definitions(table_name).
260
+ select { |f| f['meta'] && f['meta']['primary_key'] == 'true' }.
249
261
  # only take the last found primary key (if no custom primary_key was provided this will return +_id+ )
250
- .map { |f| f["name"] }[-1..-1]
262
+ map { |f| f["name"] }[-1..-1]
251
263
  end
252
264
 
253
265
  # Checks to see if the data source +name+ exists on the database.
@@ -307,6 +319,17 @@ module ActiveRecord
307
319
  column_exists?(table_name, mapping_name, type)
308
320
  end
309
321
 
322
+ # Checks to see if a meta +meta_name+ within a table +table_name+ exists on the database.
323
+ #
324
+ # meta_exists?(:developers, 'class')
325
+ #
326
+ # @param [String] table_name
327
+ # @param [String,Symbol] meta_name
328
+ # @return [Boolean]
329
+ def meta_exists?(table_name, meta_name)
330
+ table_metas(table_name).keys.include?(meta_name.to_s)
331
+ end
332
+
310
333
  # overwrite original methods to provide a elasticsearch version
311
334
  def create_schema_dumper(options)
312
335
  ActiveRecord::ConnectionAdapters::Elasticsearch::SchemaDumper.create(self, options)
@@ -318,11 +341,14 @@ module ActiveRecord
318
341
  end
319
342
 
320
343
  # overwrite original methods to provide a elasticsearch version
321
- def update_table_definition(name, base = self, **options)
322
- # :nodoc:
344
+ def update_table_definition(name, base = self, **options) # :nodoc:
323
345
  ::ActiveRecord::ConnectionAdapters::Elasticsearch::UpdateTableDefinition.new(base, name, **options)
324
346
  end
325
347
 
348
+ def clone_table_definition(name, target, **options)
349
+ ::ActiveRecord::ConnectionAdapters::Elasticsearch::CloneTableDefinition.new(self, name, target, **options)
350
+ end
351
+
326
352
  # overwrite original methods to provide a elasticsearch version
327
353
  def schema_creation
328
354
  ::ActiveRecord::ConnectionAdapters::Elasticsearch::SchemaCreation.new(self)
@@ -31,13 +31,17 @@ module ActiveRecord
31
31
  # - refresh
32
32
  # - rename_table
33
33
 
34
- define_unsupported_method :rename_table
34
+ define_unsupported_method :create_join_table, :drop_join_table, :create_alter_table,
35
+ :change_column_default, :change_column_null, :rename_column, :rename_table
35
36
 
36
37
  # Opens a closed index.
37
38
  # @param [String] table_name
38
39
  # @return [Boolean] acknowledged status
39
40
  def open_table(table_name)
40
- schema_cache.clear_data_source_cache!(table_name.to_s)
41
+ # IMPORTANT: compute will add possible configured prefix & suffix
42
+ table_name = _compute_table_name(table_name)
43
+
44
+ schema_cache.clear_data_source_cache!(table_name)
41
45
  api(:indices, :open, { index: table_name }, 'OPEN TABLE').dig('acknowledged')
42
46
  end
43
47
 
@@ -55,7 +59,10 @@ module ActiveRecord
55
59
  # @param [String] table_name
56
60
  # @return [Boolean] acknowledged status
57
61
  def close_table(table_name)
58
- schema_cache.clear_data_source_cache!(table_name.to_s)
62
+ # IMPORTANT: compute will add possible configured prefix & suffix
63
+ table_name = _compute_table_name(table_name)
64
+
65
+ schema_cache.clear_data_source_cache!(table_name)
59
66
  api(:indices, :close, { index: table_name }, 'CLOSE TABLE').dig('acknowledged')
60
67
  end
61
68
 
@@ -77,6 +84,9 @@ module ActiveRecord
77
84
  # @param [String] table_name
78
85
  # @return [Boolean] acknowledged status
79
86
  def truncate_table(table_name)
87
+ # IMPORTANT: compute will add possible configured prefix & suffix
88
+ table_name = _compute_table_name(table_name)
89
+
80
90
  # force: automatically drops an existing index
81
91
  create_table(table_name, force: true, **table_schema(table_name))
82
92
  end
@@ -98,11 +108,71 @@ module ActiveRecord
98
108
  # Set to +true+ to only drop the table if it exists.
99
109
  # Defaults to false.
100
110
  # @param [String] table_name
111
+ # @param [Boolean] if_exists
112
+ # @return [Array] acknowledged status
113
+ def drop_table(table_name, if_exists: false, **)
114
+ # IMPORTANT: compute will add possible configured prefix & suffix
115
+ table_name = _compute_table_name(table_name)
116
+
117
+ schema_cache.clear_data_source_cache!(table_name)
118
+ api(:indices, :delete, { index: table_name, ignore: (if_exists ? 404 : nil) }, 'DROP TABLE').dig('acknowledged')
119
+ end
120
+
121
+ # blocks access to the provided table (index) and +block+ name.
122
+ # @param [String] table_name
123
+ # @param [Symbol] block_name The block to add (one of :read, :write, :read_only or :metadata)
124
+ # @return [Boolean] acknowledged status
125
+ def block_table(table_name, block_name = :write)
126
+ # IMPORTANT: compute will add possible configured prefix & suffix
127
+ table_name = _compute_table_name(table_name)
128
+
129
+ api(:indices, :add_block, { index: table_name, block: block_name }, "BLOCK #{block_name.to_s.upcase} TABLE").dig('acknowledged')
130
+ end
131
+
132
+ # unblocks access to the provided table (index) and +block+ name.
133
+ # provide a nil-value to unblock all blocks, otherwise provide the blocked name.
134
+ # @param [String] table_name
135
+ # @param [Symbol] block_name The block to add (one of :read, :write, :read_only or :metadata)
136
+ # @return [Boolean] acknowledged status
137
+ def unblock_table(table_name, block_name = nil)
138
+ # IMPORTANT: compute will add possible configured prefix & suffix
139
+ table_name = _compute_table_name(table_name)
140
+
141
+ if block_name.nil?
142
+ change_table(table_name) do |t|
143
+ t.change_setting('index.blocks.read', false)
144
+ t.change_setting('index.blocks.write', false)
145
+ t.change_setting('index.blocks.read_only', false)
146
+ t.change_setting('index.blocks.metadata', false)
147
+ end
148
+ else
149
+ change_setting(table_name, "index.blocks.#{block_name}", false)
150
+ end
151
+ end
152
+
153
+ # clones an entire table to the provided +target_name+.
154
+ # During cloning, the table will be automatically 'write'-blocked.
155
+ # @param [String] table_name
156
+ # @param [String] target_name
101
157
  # @param [Hash] options
102
- # @return [Array] acknowledged status for provided table
103
- def drop_table(table_name, **options)
104
- schema_cache.clear_data_source_cache!(table_name.to_s)
105
- api(:indices, :delete, { index: table_name, ignore: (options[:if_exists] ? 404 : nil) }, 'DROP TABLE').dig('acknowledged')
158
+ # @param [Proc] block
159
+ def clone_table(table_name, target_name, **options, &block)
160
+ # IMPORTANT: compute will add possible configured prefix & suffix
161
+ table_name = _compute_table_name(table_name)
162
+ target_name = _compute_table_name(target_name)
163
+
164
+ # create new definition
165
+ definition = clone_table_definition(table_name, target_name, **extract_table_options!(options), &block)
166
+
167
+ # yield optional block
168
+ if block_given?
169
+ definition.assign do |d|
170
+ yield d
171
+ end
172
+ end
173
+
174
+ # execute definition query(ies)
175
+ definition.exec!
106
176
  end
107
177
 
108
178
  # creates a new table (index).
@@ -120,6 +190,9 @@ module ActiveRecord
120
190
  # @param [Hash] options
121
191
  # @return [Boolean] acknowledged status
122
192
  def create_table(table_name, force: false, copy_from: nil, if_not_exists: false, **options)
193
+ # IMPORTANT: compute will add possible configured prefix & suffix
194
+ table_name = _compute_table_name(table_name)
195
+
123
196
  return if if_not_exists && table_exists?(table_name)
124
197
 
125
198
  # copy schema from existing table
@@ -154,6 +227,9 @@ module ActiveRecord
154
227
  # # Other column alterations here
155
228
  # end
156
229
  def change_table(table_name, **options)
230
+ # IMPORTANT: compute will add possible configured prefix & suffix
231
+ table_name = _compute_table_name(table_name)
232
+
157
233
  definition = update_table_definition(table_name, self, **options)
158
234
 
159
235
  # yield optional block
@@ -185,11 +261,20 @@ module ActiveRecord
185
261
  _exec_change_table_with(:change_mapping_meta, table_name, name, **options)
186
262
  end
187
263
 
188
- def change_mapping_attributes(table_name, name, **options,&block)
264
+ def change_mapping_attributes(table_name, name, **options, &block)
189
265
  _exec_change_table_with(:change_mapping_attributes, table_name, name, **options, &block)
190
266
  end
267
+
191
268
  alias :change_mapping_attribute :change_mapping_attributes
192
269
 
270
+ def change_meta(table_name, name, value, **options)
271
+ _exec_change_table_with(:change_meta, table_name, name, value, **options)
272
+ end
273
+
274
+ def delete_meta(table_name, name, **options)
275
+ _exec_change_table_with(:delete_meta, table_name, name, **options)
276
+ end
277
+
193
278
  # -- setting -------------------------------------------------------------------------------------------------
194
279
 
195
280
  def add_setting(table_name, name, value, **options, &block)
@@ -220,6 +305,15 @@ module ActiveRecord
220
305
 
221
306
  private
222
307
 
308
+ def _compute_table_name(table_name)
309
+ # HINT: +"" creates a new +unfrozen+ string!
310
+ str = +""
311
+ str << table_name_prefix unless table_name.to_s.start_with?(table_name_prefix)
312
+ str << table_name
313
+ str << table_name_suffix unless table_name.to_s.end_with?(table_name_suffix)
314
+ str
315
+ end
316
+
223
317
  def _exec_change_table_with(method, table_name, *args, **kwargs, &block)
224
318
  change_table(table_name) do |t|
225
319
  t.send(method, *args, **kwargs, &block)
@@ -45,10 +45,11 @@ module ActiveRecord # :nodoc:
45
45
 
46
46
  # defines the Elasticsearch 'base' structure, which is always included but cannot be resolved through mappings ...
47
47
  BASE_STRUCTURE = [
48
- { 'name' => '_id', 'type' => 'keyword', 'virtual' => true, 'meta' => { 'primary_key' => 'true' } },
48
+ { 'name' => '_id', 'type' => 'keyword', 'virtual' => true, 'enabled' => true, 'meta' => { 'primary_key' => 'true' } },
49
49
  { 'name' => '_index', 'type' => 'keyword', 'virtual' => true },
50
50
  { 'name' => '_score', 'type' => 'float', 'virtual' => true },
51
- { 'name' => '_type', 'type' => 'keyword', 'virtual' => true }
51
+ { 'name' => '_type', 'type' => 'keyword', 'virtual' => true },
52
+ { 'name' => '_ignored', 'type' => 'boolean', 'virtual' => true }
52
53
  ].freeze
53
54
 
54
55
  include Elasticsearch::UnsupportedImplementation
@@ -152,6 +153,20 @@ module ActiveRecord # :nodoc:
152
153
  @prepared_statements = false
153
154
  end
154
155
 
156
+ def schema_migration # :nodoc:
157
+ @schema_migration ||= ElasticsearchRecord::SchemaMigration
158
+ end
159
+
160
+ # provide a table_name_prefix from the configuration to create & restrict schema creation
161
+ def table_name_prefix
162
+ @config.fetch(:table_name_prefix, '')
163
+ end
164
+
165
+ # provide a table_name_suffix from the configuration to create & restrict schema creation
166
+ def table_name_suffix
167
+ @config.fetch(:table_name_suffix, '')
168
+ end
169
+
155
170
  # overwrite method to provide a Elasticsearch path
156
171
  def migrations_paths
157
172
  @config[:migrations_paths] || ['db/migrate_elasticsearch']
@@ -22,6 +22,9 @@ module Arel # :nodoc: all
22
22
  # @param [Array] args - args to claim
23
23
  def claim(action, *args)
24
24
  case action
25
+ when :refresh
26
+ # change the refresh state
27
+ @refresh = args[0]
25
28
  when :index
26
29
  # change the index name
27
30
  @index = args[0]
@@ -91,6 +91,7 @@ module Arel # :nodoc: all
91
91
  if o.relation.is_a?(::Arel::Table)
92
92
  # prepare query
93
93
  claim(:type, ::ElasticsearchRecord::Query::TYPE_CREATE)
94
+ claim(:refresh, true)
94
95
 
95
96
  # sets the index
96
97
  resolve(o.relation)
@@ -23,11 +23,35 @@ module Arel # :nodoc: all
23
23
  # set the name of the index
24
24
  claim(:index, visit(o.name))
25
25
 
26
+ if o.metas.present? || o.mappings.present?
27
+ assign(:mappings, {}) do
28
+ # sets metas
29
+ resolve(o, :visit_TableMetas) if o.metas.present?
30
+
31
+ # sets mappings
32
+ resolve(o, :visit_TableMappings) if o.mappings.present?
33
+ end
34
+ end
35
+
26
36
  # sets settings
27
37
  resolve(o, :visit_TableSettings) if o.settings.present?
28
38
 
29
- # sets mappings
30
- resolve(o, :visit_TableMappings) if o.mappings.present?
39
+ # sets aliases
40
+ resolve(o, :visit_TableAliases) if o.aliases.present?
41
+ end
42
+
43
+ def visit_CloneTableDefinition(o)
44
+ # prepare query
45
+ claim(:type, ::ElasticsearchRecord::Query::TYPE_INDEX_CLONE)
46
+
47
+ # set the name of the index
48
+ claim(:index, visit(o.name))
49
+
50
+ # set the name of the target
51
+ claim(:argument, :target, visit(o.target))
52
+
53
+ # sets settings
54
+ resolve(o, :visit_TableSettings) if o.settings.present?
31
55
 
32
56
  # sets aliases
33
57
  resolve(o, :visit_TableAliases) if o.aliases.present?
@@ -41,6 +65,15 @@ module Arel # :nodoc: all
41
65
  visit(o.definition)
42
66
  end
43
67
 
68
+ def visit_ChangeMetaDefinition(o)
69
+ # prepare query
70
+ claim(:type, ::ElasticsearchRecord::Query::TYPE_INDEX_UPDATE_MAPPING)
71
+
72
+ assign(:_meta, {}) do
73
+ resolve(o.items, :visit_TableMetaDefinition)
74
+ end
75
+ end
76
+
44
77
  def visit_ChangeMappingDefinition(o)
45
78
  # prepare query
46
79
  claim(:type, ::ElasticsearchRecord::Query::TYPE_INDEX_UPDATE_MAPPING)
@@ -95,10 +128,14 @@ module Arel # :nodoc: all
95
128
  end
96
129
 
97
130
  def visit_TableMappings(o)
98
- assign(:mappings, {}) do
99
- assign(:properties, {}) do
100
- resolve(o.mappings, :visit_TableMappingDefinition)
101
- end
131
+ assign(:properties, {}) do
132
+ resolve(o.mappings, :visit_TableMappingDefinition)
133
+ end
134
+ end
135
+
136
+ def visit_TableMetas(o)
137
+ assign(:_meta, {}) do
138
+ resolve(o.metas, :visit_TableMetaDefinition)
102
139
  end
103
140
  end
104
141
 
@@ -112,8 +149,12 @@ module Arel # :nodoc: all
112
149
  assign(o.name, o.value, :__force__)
113
150
  end
114
151
 
152
+ def visit_TableMetaDefinition(o)
153
+ assign(o.name, o.value)
154
+ end
155
+
115
156
  def visit_TableMappingDefinition(o)
116
- assign(o.name, o.attributes.merge({type: type_to_sql(o.type)}))
157
+ assign(o.name, o.attributes.merge({ type: type_to_sql(o.type) }))
117
158
  end
118
159
 
119
160
  def visit_TableAliasDefinition(o)