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
@@ -3,12 +3,19 @@ module ElasticsearchRecord
3
3
  extend ActiveSupport::Concern
4
4
 
5
5
  included do
6
+
7
+ # Rails resolves the primary_key's value by accessing the +#id+ method.
8
+ # Since Elasticsearch also supports an additional, independent +id+ attribute, it would only be able to access
9
+ # this through +read_attribute(:id)+.
10
+ # To also have the ability of accessing this attribute through the default, this flag can be enabled.
11
+ # @attribute! Boolean
6
12
  class_attribute :relay_id_attribute, instance_writer: false, default: false
7
13
  end
8
14
 
9
15
  # overwrite to provide a Elasticsearch version of returning a 'primary_key' attribute.
10
16
  # Elasticsearch uses the static +_id+ column as primary_key, but also supports an additional +id+ column.
11
- # To provide functionality of returning the +id+ attribute, this method must also support it.
17
+ # To provide functionality of returning the +id+ attribute, this method must also support it
18
+ # with enabled +relay_id_attribute+.
12
19
  # @return [Object]
13
20
  def id
14
21
  # check, if the model has a +id+ attribute
@@ -19,7 +26,8 @@ module ElasticsearchRecord
19
26
 
20
27
  # overwrite to provide a Elasticsearch version of setting a 'primary_key' attribute.
21
28
  # Elasticsearch uses the static +_id+ column as primary_key, but also supports an additional +id+ column.
22
- # To provide functionality of setting the +id+ attribute, this method must also support it.
29
+ # To provide functionality of setting the +id+ attribute, this method must also support it
30
+ # with enabled +relay_id_attribute+.
23
31
  # @param [Object] value
24
32
  def id=(value)
25
33
  # check, if the model has a +id+ attribute
@@ -33,23 +41,26 @@ module ElasticsearchRecord
33
41
 
34
42
  # overwrite to provide a Elasticsearch version of returning a 'primary_key' was attribute.
35
43
  # Elasticsearch uses the static +_id+ column as primary_key, but also supports an additional +id+ column.
36
- # To provide functionality of returning the +id_Was+ attribute, this method must also support it.
44
+ # To provide functionality of returning the +id_Was+ attribute, this method must also support it
45
+ # with enabled +relay_id_attribute+.
37
46
  def id_was
38
- relay_id_attribute? && has_attribute?('id') ? attribute_was('id') : attribute_was(@primary_key)
47
+ relay_id_attribute? && has_attribute?('id') ? attribute_was('id') : super
39
48
  end
40
49
 
41
- # overwrite the write_attribute method to write 'id', if present
50
+ # overwrite the write_attribute method to always write to the 'id'-attribute, if present.
51
+ # This methods does not check for +relay_id_attribute+ flag!
42
52
  # see @ ActiveRecord::AttributeMethods::Write#write_attribute
43
53
  def write_attribute(attr_name, value)
44
- return _write_attribute('id', value) if attr_name.to_s == 'id' && relay_id_attribute? && has_attribute?('id')
54
+ return _write_attribute('id', value) if attr_name.to_s == 'id' && has_attribute?('id')
45
55
 
46
56
  super
47
57
  end
48
58
 
49
- # overwrite read_attribute method to read 'id', if present
59
+ # overwrite read_attribute method to read from the 'id'-attribute, if present.
60
+ # This methods does not check for +relay_id_attribute+ flag!
50
61
  # see @ ActiveRecord::AttributeMethods::Read#read_attribute
51
62
  def read_attribute(attr_name, &block)
52
- return _read_attribute('id', &block) if attr_name.to_s == 'id' && relay_id_attribute? && has_attribute?('id')
63
+ return _read_attribute('id', &block) if attr_name.to_s == 'id' && has_attribute?('id')
53
64
 
54
65
  super
55
66
  end
@@ -73,6 +84,11 @@ module ElasticsearchRecord
73
84
  cache.compute_if_absent(key) { ElasticsearchRecord::StatementCache.create(connection, &block) }
74
85
  end
75
86
 
87
+ # not supported atm - maybe enable in future versions to resolve migrations easier
88
+ # def primary_class? # :nodoc:
89
+ # self == ::ElasticsearchRecord::Base
90
+ # end
91
+
76
92
  private
77
93
 
78
94
  # creates a new relation object and extends it with our own Relation.
@@ -8,7 +8,7 @@ module ElasticsearchRecord
8
8
 
9
9
  module VERSION
10
10
  MAJOR = 1
11
- MINOR = 1
11
+ MINOR = 2
12
12
  TINY = 0
13
13
  PRE = nil
14
14
 
@@ -3,10 +3,25 @@ module ElasticsearchRecord
3
3
  extend ActiveSupport::Concern
4
4
 
5
5
  included do
6
+ # Rails resolves a pluralized underscore table_name from the class name - which will not work for some models.
7
+ # To support a general +table_name_prefix+ & +table_name_suffix+ a custom 'index_base_name' can be provided.
8
+ # @attribute! String|Symbol
6
9
  class_attribute :index_base_name, instance_writer: false, default: nil
7
10
  end
8
11
 
9
12
  module ClassMethods
13
+ # overwrite this method to provide an optional +table_name_prefix+ from the connection config.
14
+ # @return [String]
15
+ def table_name_prefix
16
+ super.presence || connection.table_name_prefix
17
+ end
18
+
19
+ # overwrite this method to provide an optional +table_name_suffix+ from the connection config.
20
+ # @return [String]
21
+ def table_name_suffix
22
+ super.presence || connection.table_name_suffix
23
+ end
24
+
10
25
  # Guesses the table name, but does not decorate it with prefix and suffix information.
11
26
  def undecorated_table_name(model_name)
12
27
  # check the 'index_base_name' first, so +table_name_prefix+ & +table_name_suffix+ can still be used
@@ -14,20 +29,29 @@ module ElasticsearchRecord
14
29
  end
15
30
 
16
31
  # returns the configured +max_result_window+ (default: 10000)
32
+ # @return [Integer]
17
33
  def max_result_window
18
34
  @max_result_window ||= connection.max_result_window(table_name)
19
35
  end
20
36
 
37
+ # returns true, if the table should behave as +auto_increment+ by creating new records.
38
+ # resolves the auto_increment status from the tables +_meta+.
39
+ # @return [Boolean]
40
+ def auto_increment?
41
+ @auto_increment ||= !!connection.table_metas(table_name).dig('auto_increment')
42
+ end
43
+
21
44
  # returns an array with columns names, that are not virtual (and not a base structure).
22
45
  # so this is a array of real document (+_source+) attributes of the index.
23
46
  # @return [Array<String>]
24
47
  def source_column_names
25
- @source_column_names ||= columns.reject(&:virtual).map(&:name) - ActiveRecord::ConnectionAdapters::ElasticsearchAdapter.base_structure_keys
48
+ @source_column_names ||= columns.reject(&:virtual?).map(&:name) - ActiveRecord::ConnectionAdapters::ElasticsearchAdapter.base_structure_keys
26
49
  end
27
50
 
28
51
  # returns an array with columns names, that are searchable (also includes nested fields & properties )
52
+ # @return [Array<String>]
29
53
  def searchable_column_names
30
- @searchable_column_names ||= columns.reject(&:virtual).reduce([]) { |m, column|
54
+ @searchable_column_names ||= columns.select(&:enabled?).reduce([]) { |m, column|
31
55
  m << column.name
32
56
  m += column.field_names
33
57
  m += column.property_names
@@ -3,7 +3,6 @@ module ElasticsearchRecord
3
3
  extend ActiveSupport::Concern
4
4
 
5
5
  module ClassMethods
6
-
7
6
  # insert a new record into the Elasticsearch index
8
7
  # NOTICE: We don't want to mess up with the Arel-builder - so we send new data directly to the API
9
8
  # @param [ActiveModel::Attribute] values
@@ -12,41 +11,20 @@ module ElasticsearchRecord
12
11
  # values is not a "key=>values"-Hash, but a +ActiveModel::Attribute+ - so the casted values gets resolved here
13
12
  values = values.transform_values(&:value)
14
13
 
15
- update_auto_increment = false
16
-
17
- # resolve possible provided primary_key value from values
18
- arguments = if (id = values[self.primary_key]).present?
19
- {id: id}
20
- elsif self.columns_hash[self.primary_key]&.meta['auto_increment'] # BETA: should not be used on mass imports to the Elasticsearch-index
21
- update_auto_increment = true
22
- ids = [
23
- connection.table_mappings(self.table_name).dig('properties', self.primary_key, 'meta', 'auto_increment').to_i + 1,
24
- self.unscoped.all.maximum(self.primary_key).to_i + 1
25
- ]
26
- {id: ids.max}
27
- else
28
- {}
29
- end
30
-
31
- # IMPORTANT: Always drop possible provided 'primary_key' column +_id+.
32
- values.delete(self.primary_key)
33
-
34
- # build new query
35
- query = ElasticsearchRecord::Query.new(
36
- index: table_name,
37
- type: ElasticsearchRecord::Query::TYPE_CREATE,
38
- body: values,
39
- arguments: arguments,
40
- refresh: true)
41
-
42
- # execute query and return inserted id
43
- id = connection.insert(query, "#{self} Create")
14
+ # resolve & update a auto_increment value
15
+ _insert_with_auto_increment(values) do |arguments|
16
+ # build new query
17
+ query = ElasticsearchRecord::Query.new(
18
+ index: table_name,
19
+ type: ElasticsearchRecord::Query::TYPE_CREATE,
20
+ # IMPORTANT: always exclude possible provided +_id+ field
21
+ body: values.except('_id'),
22
+ arguments: arguments,
23
+ refresh: true)
44
24
 
45
- if id.present? && update_auto_increment
46
- connection.change_mapping_meta(table_name, self.primary_key, auto_increment: id)
25
+ # execute query and return inserted id
26
+ connection.insert(query, "#{self} Create")
47
27
  end
48
-
49
- id
50
28
  end
51
29
 
52
30
  # updates a persistent entry in the Elasticsearch index
@@ -80,6 +58,39 @@ module ElasticsearchRecord
80
58
  # execute query and return total deletes
81
59
  connection.delete(query, "#{self} Delete")
82
60
  end
61
+
62
+ private
63
+
64
+ # WARNING: BETA!!!
65
+ # Resolves the +auto_increment+ status from the tables +_meta+ attributes.
66
+ def _insert_with_auto_increment(values)
67
+ # check, if the primary_key's values is provided.
68
+ # so, no need to resolve a +auto_increment+ value, but provide
69
+ if values[self.primary_key].present?
70
+ # resolve id from values
71
+ id = values[self.primary_key]
72
+
73
+ yield({id: id})
74
+ elsif auto_increment?
75
+ ids = [
76
+ # we try to resolve the current-auto-increment value from the tables meta
77
+ connection.table_metas(self.table_name).dig('auto_increment').to_i + 1,
78
+ # for secure reasons, we also resolve the current maximum value for the primary key
79
+ self.unscoped.all.maximum(self.primary_key).to_i + 1
80
+ ]
81
+
82
+ id = yield({ id: ids.max })
83
+
84
+ if id.present?
85
+ connection.change_meta(self.table_name, :auto_increment, id)
86
+ end
87
+
88
+ # return inserted id
89
+ id
90
+ else
91
+ yield({})
92
+ end
93
+ end
83
94
  end
84
95
  end
85
96
  end
@@ -22,6 +22,7 @@ module ElasticsearchRecord
22
22
 
23
23
  # -- INDEX TYPES ---------------------------------------------------------------------------------------------------
24
24
  TYPE_INDEX_CREATE = :index_create
25
+ TYPE_INDEX_CLONE = :index_clone
25
26
  # INDEX update is not implemented by Elasticsearch
26
27
  # - this is handled through individual updates of +mappings+, +settings+ & +aliases+.
27
28
  # INDEX delete is handled directly as API-call
@@ -38,7 +39,8 @@ module ElasticsearchRecord
38
39
  TYPE_CREATE, TYPE_UPDATE, TYPE_UPDATE_BY_QUERY, TYPE_DELETE, TYPE_DELETE_BY_QUERY,
39
40
 
40
41
  # -- INDEX TYPES
41
- TYPE_INDEX_CREATE, TYPE_INDEX_UPDATE_MAPPING, TYPE_INDEX_UPDATE_SETTING, TYPE_INDEX_UPDATE_ALIAS,
42
+ TYPE_INDEX_CREATE, TYPE_INDEX_CLONE,
43
+ TYPE_INDEX_UPDATE_MAPPING, TYPE_INDEX_UPDATE_SETTING, TYPE_INDEX_UPDATE_ALIAS,
42
44
  TYPE_INDEX_DELETE_ALIAS
43
45
  ].freeze
44
46
 
@@ -59,6 +61,7 @@ module ElasticsearchRecord
59
61
  GATES = {
60
62
  TYPE_SQL => [:sql, :query],
61
63
  TYPE_INDEX_CREATE => [:indices, :create],
64
+ TYPE_INDEX_CLONE => [:indices, :clone],
62
65
  TYPE_INDEX_UPDATE_MAPPING => [:indices, :put_mapping],
63
66
  TYPE_INDEX_UPDATE_SETTING => [:indices, :put_settings],
64
67
  TYPE_INDEX_UPDATE_ALIAS => [:indices, :put_alias],
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_record/schema_migration"
4
+
5
+ module ElasticsearchRecord
6
+ class SchemaMigration < ElasticsearchRecord::Base # :nodoc:
7
+ class << self
8
+ def primary_key
9
+ "version"
10
+ end
11
+
12
+ def table_name
13
+ "#{table_name_prefix}#{schema_migrations_table_name}#{table_name_suffix}"
14
+ end
15
+
16
+ def create_table
17
+ unless connection.table_exists?(table_name)
18
+ connection.create_table(table_name, id: false) do |t|
19
+ t.string :version, **connection.internal_string_options_for_primary_key
20
+ end
21
+ end
22
+ end
23
+
24
+ def drop_table
25
+ connection.drop_table table_name, if_exists: true
26
+ end
27
+
28
+ def normalize_migration_number(number)
29
+ "%.3d" % number.to_i
30
+ end
31
+
32
+ def normalized_versions
33
+ all_versions.map { |v| normalize_migration_number v }
34
+ end
35
+
36
+ def all_versions
37
+ order(:version).pluck(:version)
38
+ end
39
+
40
+ def table_exists?
41
+ connection.data_source_exists?(table_name)
42
+ end
43
+ end
44
+
45
+ def version
46
+ super.to_i
47
+ end
48
+ end
49
+ end
@@ -3,20 +3,20 @@
3
3
  module ElasticsearchRecord
4
4
  module Tasks
5
5
  class ElasticsearchDatabaseTasks
6
- delegate :connection, :establish_connection, to: ActiveRecord::Base
6
+ delegate :connection, :establish_connection, to: ElasticsearchRecord::Base
7
7
 
8
8
  def initialize(db_config)
9
9
  @db_config = db_config
10
10
  end
11
11
 
12
12
  def create
13
- #establish_connection(db_config)
14
- $stdout.puts "\n>>> 'create' elasticsearch is not supported - the following message is insignificant!"
13
+ # 'create' database / cluster is not supported
14
+ nil
15
15
  end
16
16
 
17
17
  def drop
18
- #establish_connection(db_config)
19
- $stdout.puts "\n>>> 'drop' elasticsearch is not supported - the following message is insignificant!"
18
+ # 'drop' database / cluster is not supported
19
+ nil
20
20
  end
21
21
 
22
22
  def purge
@@ -24,6 +24,16 @@ module ElasticsearchRecord
24
24
  drop
25
25
  end
26
26
 
27
+ def structure_dump(*)
28
+ $stdout.puts "\n>>> 'structure_dump' elasticsearch is not supported and will be ignored!"
29
+ nil
30
+ end
31
+
32
+ def structure_load(*)
33
+ $stdout.puts "\n>>> 'structure_load' elasticsearch is not supported and will be ignored!"
34
+ nil
35
+ end
36
+
27
37
  private
28
38
 
29
39
  attr_reader :db_config
@@ -27,6 +27,7 @@ module ElasticsearchRecord
27
27
  autoload :Querying
28
28
  autoload :Query
29
29
  autoload :Result
30
+ autoload :SchemaMigration
30
31
  autoload :StatementCache
31
32
  end
32
33
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: elasticsearch_record
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tobias Gonsior
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-12-01 00:00:00.000000000 Z
11
+ date: 2022-12-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -106,11 +106,13 @@ files:
106
106
  - lib/active_record/connection_adapters/elasticsearch/schema_creation.rb
107
107
  - lib/active_record/connection_adapters/elasticsearch/schema_definitions.rb
108
108
  - lib/active_record/connection_adapters/elasticsearch/schema_definitions/attribute_methods.rb
109
+ - lib/active_record/connection_adapters/elasticsearch/schema_definitions/clone_table_definition.rb
109
110
  - lib/active_record/connection_adapters/elasticsearch/schema_definitions/column_methods.rb
110
111
  - lib/active_record/connection_adapters/elasticsearch/schema_definitions/create_table_definition.rb
111
112
  - lib/active_record/connection_adapters/elasticsearch/schema_definitions/table_alias_definition.rb
112
113
  - lib/active_record/connection_adapters/elasticsearch/schema_definitions/table_definition.rb
113
114
  - lib/active_record/connection_adapters/elasticsearch/schema_definitions/table_mapping_definition.rb
115
+ - lib/active_record/connection_adapters/elasticsearch/schema_definitions/table_meta_definition.rb
114
116
  - lib/active_record/connection_adapters/elasticsearch/schema_definitions/table_setting_definition.rb
115
117
  - lib/active_record/connection_adapters/elasticsearch/schema_definitions/update_table_definition.rb
116
118
  - lib/active_record/connection_adapters/elasticsearch/schema_dumper.rb
@@ -161,6 +163,7 @@ files:
161
163
  - lib/elasticsearch_record/relation/result_methods.rb
162
164
  - lib/elasticsearch_record/relation/value_methods.rb
163
165
  - lib/elasticsearch_record/result.rb
166
+ - lib/elasticsearch_record/schema_migration.rb
164
167
  - lib/elasticsearch_record/statement_cache.rb
165
168
  - lib/elasticsearch_record/tasks/elasticsearch_database_tasks.rb
166
169
  - lib/elasticsearch_record/version.rb
@@ -189,7 +192,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
189
192
  - !ruby/object:Gem::Version
190
193
  version: '0'
191
194
  requirements: []
192
- rubygems_version: 3.2.22
195
+ rubygems_version: 3.3.7
193
196
  signing_key:
194
197
  specification_version: 4
195
198
  summary: ActiveRecord adapter for Elasticsearch