elasticsearch_record 1.8.2 → 3.0.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 (78) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +0 -0
  3. data/.yardopts +0 -0
  4. data/Gemfile +7 -0
  5. data/README.md +177 -19
  6. data/Rakefile +0 -0
  7. data/docs/CHANGELOG.md +66 -0
  8. data/docs/CODE_OF_CONDUCT.md +0 -0
  9. data/docs/LICENSE +0 -0
  10. data/elasticsearch_record.gemspec +3 -3
  11. data/lib/active_record/connection_adapters/elasticsearch/column.rb +0 -0
  12. data/lib/active_record/connection_adapters/elasticsearch/database_statements.rb +19 -17
  13. data/lib/active_record/connection_adapters/elasticsearch/quoting.rb +16 -0
  14. data/lib/active_record/connection_adapters/elasticsearch/schema_creation.rb +0 -0
  15. data/lib/active_record/connection_adapters/elasticsearch/schema_definitions/attribute_methods.rb +0 -0
  16. data/lib/active_record/connection_adapters/elasticsearch/schema_definitions/clone_table_definition.rb +0 -0
  17. data/lib/active_record/connection_adapters/elasticsearch/schema_definitions/create_table_definition.rb +6 -0
  18. data/lib/active_record/connection_adapters/elasticsearch/schema_definitions/table_alias_definition.rb +0 -0
  19. data/lib/active_record/connection_adapters/elasticsearch/schema_definitions/table_definition.rb +0 -0
  20. data/lib/active_record/connection_adapters/elasticsearch/schema_definitions/table_meta_definition.rb +0 -0
  21. data/lib/active_record/connection_adapters/elasticsearch/schema_definitions/table_setting_definition.rb +7 -5
  22. data/lib/active_record/connection_adapters/elasticsearch/schema_definitions/update_table_definition.rb +3 -1
  23. data/lib/active_record/connection_adapters/elasticsearch/schema_definitions.rb +0 -0
  24. data/lib/active_record/connection_adapters/elasticsearch/schema_dumper.rb +24 -3
  25. data/lib/active_record/connection_adapters/elasticsearch/schema_statements.rb +39 -34
  26. data/lib/active_record/connection_adapters/elasticsearch/table_statements.rb +251 -72
  27. data/lib/active_record/connection_adapters/elasticsearch/transactions.rb +0 -0
  28. data/lib/active_record/connection_adapters/elasticsearch/type/format_string.rb +0 -0
  29. data/lib/active_record/connection_adapters/elasticsearch/type/multicast_value.rb +0 -0
  30. data/lib/active_record/connection_adapters/elasticsearch/type/nested.rb +0 -0
  31. data/lib/active_record/connection_adapters/elasticsearch/type/object.rb +0 -0
  32. data/lib/active_record/connection_adapters/elasticsearch/type/range.rb +0 -0
  33. data/lib/active_record/connection_adapters/elasticsearch/type.rb +0 -0
  34. data/lib/active_record/connection_adapters/elasticsearch/unsupported_implementation.rb +0 -0
  35. data/lib/active_record/connection_adapters/elasticsearch_adapter.rb +164 -99
  36. data/lib/arel/collectors/elasticsearch_query.rb +3 -0
  37. data/lib/arel/nodes/select_agg.rb +0 -0
  38. data/lib/arel/nodes/select_configure.rb +0 -0
  39. data/lib/arel/nodes/select_kind.rb +0 -0
  40. data/lib/arel/nodes/select_query.rb +0 -0
  41. data/lib/arel/visitors/elasticsearch.rb +0 -0
  42. data/lib/arel/visitors/elasticsearch_query.rb +51 -9
  43. data/lib/elasticsearch_record/base.rb +0 -0
  44. data/lib/elasticsearch_record/core.rb +1 -1
  45. data/lib/elasticsearch_record/errors.rb +0 -0
  46. data/lib/elasticsearch_record/extensions/relation.rb +0 -0
  47. data/lib/elasticsearch_record/gem_version.rb +3 -3
  48. data/lib/elasticsearch_record/instrumentation/controller_runtime.rb +0 -0
  49. data/lib/elasticsearch_record/instrumentation/log_subscriber.rb +11 -9
  50. data/lib/elasticsearch_record/instrumentation/railtie.rb +0 -0
  51. data/lib/elasticsearch_record/instrumentation.rb +0 -0
  52. data/lib/elasticsearch_record/internal_metadata.rb +19 -0
  53. data/lib/elasticsearch_record/model_api.rb +8 -5
  54. data/lib/elasticsearch_record/model_schema.rb +1 -1
  55. data/lib/elasticsearch_record/patches/active_record/connection_pool_patch.rb +57 -0
  56. data/lib/elasticsearch_record/patches/active_record/relation_merger_patch.rb +0 -0
  57. data/lib/elasticsearch_record/patches/arel/select_core_patch.rb +0 -0
  58. data/lib/elasticsearch_record/patches/arel/select_manager_patch.rb +0 -0
  59. data/lib/elasticsearch_record/patches/arel/select_statement_patch.rb +0 -0
  60. data/lib/elasticsearch_record/patches/arel/update_manager_patch.rb +0 -0
  61. data/lib/elasticsearch_record/patches/arel/update_statement_patch.rb +0 -0
  62. data/lib/elasticsearch_record/persistence.rb +39 -21
  63. data/lib/elasticsearch_record/query.rb +53 -33
  64. data/lib/elasticsearch_record/querying.rb +43 -21
  65. data/lib/elasticsearch_record/relation/calculation_methods.rb +38 -2
  66. data/lib/elasticsearch_record/relation/core_methods.rb +1 -1
  67. data/lib/elasticsearch_record/relation/query_clause.rb +0 -0
  68. data/lib/elasticsearch_record/relation/query_clause_tree.rb +2 -1
  69. data/lib/elasticsearch_record/relation/query_methods.rb +31 -1
  70. data/lib/elasticsearch_record/relation/result_methods.rb +47 -43
  71. data/lib/elasticsearch_record/relation/value_methods.rb +4 -4
  72. data/lib/elasticsearch_record/result.rb +144 -60
  73. data/lib/elasticsearch_record/schema_migration.rb +5 -2
  74. data/lib/elasticsearch_record/statement_cache.rb +0 -0
  75. data/lib/elasticsearch_record/tasks/elasticsearch_database_tasks.rb +0 -0
  76. data/lib/elasticsearch_record/version.rb +0 -0
  77. data/lib/elasticsearch_record.rb +17 -0
  78. metadata +22 -9
@@ -45,6 +45,25 @@ module ActiveRecord
45
45
  false
46
46
  end
47
47
 
48
+ # returns true if the BASE name may be dumped, which keeps the dump loadable in another
49
+ # environment (with its own prefix / suffix): every table statement decorates its name on
50
+ # its own, so the base name may only be dumped if that decoration lands on exactly this
51
+ # index again. Otherwise the FULL name is dumped with a +decorate: false+.
52
+ #
53
+ # see @ ActiveRecord::ConnectionAdapters::Elasticsearch::TableStatements#_env_table_name
54
+ def _dumps_base_name?(table, base_name)
55
+ # without a prefix & suffix nothing is ever decorated - the plain name IS the index
56
+ return true unless _has_env_table_names?
57
+
58
+ # a globally disabled decoration would leave the base name untouched while loading
59
+ return false unless ElasticsearchRecord.decorate_table_names
60
+
61
+ # the base name must resolve BACK to the real index. It does not if the base name itself
62
+ # starts with the prefix (or ends with the suffix), and not if the dumper was built with a
63
+ # prefix / suffix that differs from the one of the connection.
64
+ @connection._env_table_name(base_name) == table
65
+ end
66
+
48
67
  def table(table, stream, nested_blocks: false, **)
49
68
  begin
50
69
  self.table_name = table
@@ -57,10 +76,12 @@ module ActiveRecord
57
76
 
58
77
  tbl.print " create_table"
59
78
 
60
- if _has_env_table_names?
61
- tbl.print " _env_table_name(#{remove_prefix_and_suffix(table).inspect})"
79
+ base_name = remove_prefix_and_suffix(table)
80
+
81
+ if _dumps_base_name?(table, base_name)
82
+ tbl.print " #{base_name.inspect}"
62
83
  else
63
- tbl.print " #{remove_prefix_and_suffix(table).inspect}"
84
+ tbl.print " #{table.inspect}, decorate: false"
64
85
  end
65
86
 
66
87
  tbl.print ", force: true do |t|"
@@ -92,13 +92,13 @@ 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
 
99
99
  unless migrated.include?(version)
100
- # use a ActiveRecord syntax to create a new version
101
- schema_migration.create(version: version)
100
+ # use Arel syntax to create a new version
101
+ schema_migration.create_version(version)
102
102
  end
103
103
 
104
104
  inserting = (versions - migrated).select { |v| v < version }
@@ -107,8 +107,8 @@ module ActiveRecord
107
107
  raise "Duplicate migration #{duplicate}. Please renumber your migrations to resolve the conflict."
108
108
  end
109
109
 
110
- # use a ActiveRecord syntax to create new versions
111
- inserting.each { |iversion| schema_migration.create(version: iversion) }
110
+ # use Arel syntax to create new versions
111
+ inserting.each { |iversion| schema_migration.create_version(iversion) }
112
112
  end
113
113
 
114
114
  true
@@ -119,7 +119,7 @@ module ActiveRecord
119
119
  # @see ActiveRecord::ConnectionAdapters::SchemaStatements#data_sources
120
120
  # @return [Array<String>]
121
121
  def data_sources
122
- api(:indices, :get, { index: :_all, expand_wildcards: [:open, :closed] }, 'SCHEMA').keys
122
+ api('indices.get', { index: :_all, expand_wildcards: [:open, :closed] }, 'SCHEMA').keys
123
123
  end
124
124
 
125
125
  # Returns an array of table names defined in the database.
@@ -134,7 +134,7 @@ module ActiveRecord
134
134
  # @param [String] table_name
135
135
  # @return [Hash]
136
136
  def table_mappings(table_name)
137
- api(:indices, :get_mapping, { index: table_name, expand_wildcards: [:open, :closed] }, 'SCHEMA').dig(table_name, 'mappings')
137
+ api('indices.get_mapping', { index: table_name, expand_wildcards: [:open, :closed] }, 'SCHEMA').dig(table_name, 'mappings')
138
138
  end
139
139
 
140
140
  # returns a hash of all meta data by provided table_name (index).
@@ -150,21 +150,21 @@ module ActiveRecord
150
150
  # @param [Boolean] flat_settings (default: true)
151
151
  # @return [Hash]
152
152
  def table_settings(table_name, flat_settings = true)
153
- api(:indices, :get_settings, { index: table_name, expand_wildcards: [:open, :closed], flat_settings: flat_settings }, 'SCHEMA').dig(table_name, 'settings')
153
+ api('indices.get_settings', { index: table_name, expand_wildcards: [:open, :closed], flat_settings: flat_settings }, 'SCHEMA').dig(table_name, 'settings')
154
154
  end
155
155
 
156
156
  # returns a hash of all aliases by provided table_name (index).
157
157
  # @param [String] table_name
158
158
  # @return [Hash]
159
159
  def table_aliases(table_name)
160
- api(:indices, :get_alias, { index: table_name, expand_wildcards: [:open, :closed] }, 'SCHEMA').dig(table_name, 'aliases')
160
+ api('indices.get_alias', { index: table_name, expand_wildcards: [:open, :closed] }, 'SCHEMA').dig(table_name, 'aliases')
161
161
  end
162
162
 
163
163
  # returns information about number of primaries and replicas, document counts, disk size, ... by provided table_name (index).
164
164
  # @param [String] table_name
165
165
  # @return [Hash]
166
166
  def table_state(table_name)
167
- response = api(:cat, :indices, { index: table_name, expand_wildcards: [:open, :closed] }, 'SCHEMA')
167
+ response = api('cat.indices', { index: table_name, expand_wildcards: [:open, :closed] }, 'SCHEMA')
168
168
 
169
169
  [:health, :status, :name, :uuid, :pri, :rep, :docs_count, :docs_deleted, :store_size, :pri_store_size].zip(
170
170
  response.body.split(' ')
@@ -178,15 +178,15 @@ module ActiveRecord
178
178
  # @return [Hash]
179
179
  def table_schema(table_name, features = [:aliases, :mappings, :settings])
180
180
  if cluster_info[:version] >= '8.5.0'
181
- response = api(:indices, :get, { index: table_name, expand_wildcards: [:open, :closed], features: features, flat_settings: true }, 'SCHEMA')
181
+ response = api('indices.get', { index: table_name, expand_wildcards: [:open, :closed], features: features, flat_settings: true }, 'SCHEMA')
182
182
  else
183
- response = api(:indices, :get, { index: table_name, expand_wildcards: [:open, :closed], flat_settings: true }, 'SCHEMA')
183
+ response = api('indices.get', { index: table_name, expand_wildcards: [:open, :closed], flat_settings: true }, 'SCHEMA')
184
184
  end
185
185
 
186
186
  {
187
187
  settings: response.dig(table_name, 'settings'),
188
188
  mappings: response.dig(table_name, 'mappings'),
189
- aliases: response.dig(table_name, 'aliases')
189
+ aliases: response.dig(table_name, 'aliases')
190
190
  }
191
191
  end
192
192
 
@@ -204,8 +204,8 @@ module ActiveRecord
204
204
  # raise(ActiveRecord::StatementInvalid, "Could not find valid mappings for '#{table_name}'") if mappings.blank? || mappings['properties'].blank?
205
205
 
206
206
  # since the received mappings do not have the "primary" +_id+-column we manually need to add this here
207
- # The BASE_STRUCTURE will also include some meta keys like '_score', '_type', ...
208
- ActiveRecord::ConnectionAdapters::ElasticsearchAdapter::BASE_STRUCTURE + mappings['properties'].map { |key, prop|
207
+ # The METADATA_FIELDS will also include some meta keys like '_score', '_type', ...
208
+ ActiveRecord::ConnectionAdapters::ElasticsearchAdapter::METADATA_FIELDS + mappings['properties'].map { |key, prop|
209
209
  # resolve (nested) fields and properties
210
210
  fields, properties = resolve_fields_and_properties(key, prop, true)
211
211
 
@@ -228,11 +228,11 @@ module ActiveRecord
228
228
  field["name"],
229
229
  field["null_value"],
230
230
  fetch_type_metadata(field["type"]),
231
- meta: field['meta'],
232
- virtual: field['virtual'],
233
- fields: field['fields'],
231
+ meta: field['meta'],
232
+ virtual: field['virtual'],
233
+ fields: field['fields'],
234
234
  properties: field['properties'],
235
- enabled: field['enabled']
235
+ enabled: field['enabled']
236
236
  )
237
237
  end
238
238
 
@@ -250,13 +250,16 @@ module ActiveRecord
250
250
  # The only thing that uniquely identifies a document is the index together with the +_id+.
251
251
  # To support this concept we simulate this through the +_meta+ field (from the index).
252
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
253
+ # As an alternative, the primary_key can also be provided through the mappings +meta+ field.
254
+ # - see @ https://www.elastic.co/guide/en/elasticsearch/reference/8.5/mapping-meta-field.html
255
+
256
256
  # @see ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter#primary_keys
257
257
  # @param [String] table_name
258
+ # @return [Array<String>]
258
259
  def primary_keys(table_name)
259
- table_metas(table_name).dig('primary_key').presence || column_definitions(table_name).
260
+ # IMPORTANT: the +_meta+ node stores the primary_key as a plain String - it must be
261
+ # wrapped, so every branch of this method returns an Array (as the API demands).
262
+ Array.wrap(table_metas(table_name).dig('primary_key')).presence || column_definitions(table_name).
260
263
  select { |f| f['meta'] && f['meta']['primary_key'] == 'true' }.
261
264
  # only take the last found primary key (if no custom primary_key was provided this will return +_id+ )
262
265
  map { |f| f["name"] }[-1..-1]
@@ -270,7 +273,7 @@ module ActiveRecord
270
273
  # @return [Boolean]
271
274
  def data_source_exists?(name)
272
275
  # response returns boolean
273
- api(:indices, :exists?, { index: name, expand_wildcards: [:open, :closed] }, 'SCHEMA')
276
+ api('indices.exists?', { index: name, expand_wildcards: [:open, :closed] }, 'SCHEMA')
274
277
  end
275
278
 
276
279
  # Checks to see if the table +table_name+ exists on the database.
@@ -358,7 +361,9 @@ module ActiveRecord
358
361
  # The query will raise an ActiveRecord::StatementInvalid if the requested limit is above this value.
359
362
  # @return [Integer]
360
363
  def max_result_window(table_name)
361
- table_settings(table_name).dig('index', 'max_result_window').presence || 10000
364
+ # IMPORTANT: the settings API returns every value as a String - without the cast the
365
+ # callers would compare a String against their (Integer) batch sizes.
366
+ table_settings(table_name).dig('index.max_result_window').presence&.to_i || 10_000
362
367
  end
363
368
 
364
369
  # returns true if the cluster option 'id_field_data' is enabled or not configured.
@@ -388,13 +393,13 @@ module ActiveRecord
388
393
  # @return [Hash{Symbol->Unknown}]
389
394
  def cluster_info
390
395
  @cluster_info ||= begin
391
- response = api(:core, :info, {}, 'CLUSTER INFO')
396
+ response = api(:info, {}, 'CLUSTER INFO')
392
397
 
393
398
  {
394
- name: response.dig('name'),
395
- cluster_name: response.dig('cluster_name'),
396
- cluster_uuid: response.dig('cluster_uuid'),
397
- version: Gem::Version.new(response.dig('version', 'number')),
399
+ name: response.dig('name'),
400
+ cluster_name: response.dig('cluster_name'),
401
+ cluster_uuid: response.dig('cluster_uuid'),
402
+ version: Gem::Version.new(response.dig('version', 'number')),
398
403
  lucene_version: response.dig('version', 'lucene_version')
399
404
  }
400
405
  end
@@ -403,14 +408,14 @@ module ActiveRecord
403
408
  # returns a hash of current set, none-default settings in flat
404
409
  # @return [Hash]
405
410
  def cluster_settings
406
- settings = api(:cluster, :get_settings, { flat_settings: true }, 'CLUSTER SETTINGS')
411
+ settings = api('cluster.get_settings', { flat_settings: true }, 'CLUSTER SETTINGS')
407
412
  settings['persistent'].merge(settings['transient'])
408
413
  end
409
414
 
410
415
  # returns the cluster health
411
416
  # @return [Hash]
412
417
  def cluster_health(**options)
413
- api(:cluster, :health, options, 'CLUSTER HEALTH').to_h
418
+ api('cluster.health', options, 'CLUSTER HEALTH').to_h
414
419
  end
415
420
 
416
421
  # transforms provided schema-type to a sql-type
@@ -469,8 +474,8 @@ module ActiveRecord
469
474
  if prop['properties'].present?
470
475
  prop['properties'].each do |nested_key, nested_prop|
471
476
  nested_fields, nested_properties = resolve_fields_and_properties("#{key}.#{nested_key}", nested_prop)
472
- fields |= nested_fields
473
- properties |= nested_properties
477
+ fields |= nested_fields
478
+ properties |= nested_properties
474
479
  end
475
480
  elsif !root # don't add the root property as sub-property
476
481
  properties << { 'name' => key, 'type' => prop['type'] }