elasticsearch_record 2.0.0 → 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.
- checksums.yaml +4 -4
- data/README.md +103 -11
- data/docs/CHANGELOG.md +20 -0
- data/elasticsearch_record.gemspec +2 -2
- data/lib/active_record/connection_adapters/elasticsearch/database_statements.rb +3 -2
- data/lib/active_record/connection_adapters/elasticsearch/quoting.rb +16 -0
- data/lib/active_record/connection_adapters/elasticsearch/schema_definitions/column_methods.rb +0 -0
- data/lib/active_record/connection_adapters/elasticsearch_adapter.rb +20 -12
- data/lib/arel/collectors/elasticsearch_query.rb +3 -0
- data/lib/elasticsearch_record/core.rb +1 -1
- data/lib/elasticsearch_record/gem_version.rb +1 -1
- data/lib/elasticsearch_record/internal_metadata.rb +19 -0
- data/lib/elasticsearch_record/patches/active_record/connection_pool_patch.rb +57 -0
- data/lib/elasticsearch_record/persistence.rb +4 -3
- data/lib/elasticsearch_record/querying.rb +38 -14
- data/lib/elasticsearch_record/relation/core_methods.rb +1 -1
- data/lib/elasticsearch_record/relation/query_clause_tree.rb +2 -1
- data/lib/elasticsearch_record/relation/query_methods.rb +1 -1
- data/lib/elasticsearch_record/relation/value_methods.rb +4 -4
- data/lib/elasticsearch_record/schema_migration.rb +5 -2
- data/lib/elasticsearch_record.rb +2 -0
- metadata +6 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: df21aa38516f4e56830e16ffcff4164b65541a869e3964771771eacdf4e8ad16
|
|
4
|
+
data.tar.gz: 7ba579e28c1a85e7f890654c99c675eb60dd2fddf1b1eb877b0967e17f1d7097
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: cb521dc265ec3a517ebda7b8b6e60b5471b1411bd8f04d6be72098e95177e32af06267bc9a2e2881699d0f7f317c3bf1ef77b6613d9657df6ac7347affa80cf9
|
|
7
|
+
data.tar.gz: 981b0955f21d2260d794cc1946ed560fa70d0e429b5c0bc97d2c9c5b29b396af471cd8d6460929fa451913ad44f7d9992c9b8c0d8a6cd6a7035c338147161138
|
data/README.md
CHANGED
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
[](https://badge.fury.io/rb/elasticsearch_record)
|
|
7
7
|
[](docs/LICENSE)
|
|
8
8
|
|
|
9
|
+
[](https://coveralls.io/github/ruby-smart/elasticsearch_record?branch=main)
|
|
10
|
+
|
|
9
11
|
ActiveRecord adapter for Elasticsearch
|
|
10
12
|
|
|
11
13
|
_ElasticsearchRecord is a ActiveRecord adapter and provides similar functionality for Elasticsearch._
|
|
@@ -14,9 +16,9 @@ _ElasticsearchRecord is a ActiveRecord adapter and provides similar functionalit
|
|
|
14
16
|
|
|
15
17
|
**PLEASE NOTE:**
|
|
16
18
|
|
|
17
|
-
- This is the `main`-branch, which
|
|
18
|
-
- supports ActiveRecord ~> 7.
|
|
19
|
-
- added features up to Elasticsearch `8.17.1`
|
|
19
|
+
- This is the `main`-branch, which supports rails **7.2** _(see section 'Rails_Versions' for supported versions)_
|
|
20
|
+
- supports ActiveRecord ~> 7.2 + Elasticsearch >= 7.17
|
|
21
|
+
- added features up to Elasticsearch `8.17.1` _(tested against `8.19.14`)_
|
|
20
22
|
- _ES|QL_ queries _(`TYPE_ESQL` / the `esql.query` gate)_ require **Elasticsearch >= 8.11**, where the feature became
|
|
21
23
|
generally available. All other features remain available from Elasticsearch `7.17`.
|
|
22
24
|
|
|
@@ -26,6 +28,13 @@ _ElasticsearchRecord is a ActiveRecord adapter and provides similar functionalit
|
|
|
26
28
|
|
|
27
29
|
Supported rails versions:
|
|
28
30
|
|
|
31
|
+
### Rails 7.2:
|
|
32
|
+
_(since gem version 2.1)_
|
|
33
|
+
|
|
34
|
+
https://github.com/ruby-smart/elasticsearch_record/tree/rails-7-2-stable
|
|
35
|
+
|
|
36
|
+
[](https://github.com/ruby-smart/elasticsearch_record/tree/rails-7-2-stable)
|
|
37
|
+
|
|
29
38
|
### Rails 7.1:
|
|
30
39
|
_(since gem version 1.8)_
|
|
31
40
|
|
|
@@ -47,7 +56,7 @@ https://github.com/ruby-smart/elasticsearch_record/tree/rails-7-0-stable
|
|
|
47
56
|
Add this line to your application's Gemfile:
|
|
48
57
|
|
|
49
58
|
```ruby
|
|
50
|
-
gem 'elasticsearch_record'
|
|
59
|
+
gem 'elasticsearch_record'
|
|
51
60
|
|
|
52
61
|
# alternative
|
|
53
62
|
gem 'elasticsearch_record', git: 'https://github.com/ruby-smart/elasticsearch_record', branch: 'rails-7-1-stable'
|
|
@@ -63,6 +72,78 @@ Or install it yourself as:
|
|
|
63
72
|
|
|
64
73
|
$ gem install elasticsearch_record
|
|
65
74
|
|
|
75
|
+
-----
|
|
76
|
+
|
|
77
|
+
## Upgrading to 2.0
|
|
78
|
+
|
|
79
|
+
Version **2.0** contains breaking changes. Coming from **1.8.x**, check the following:
|
|
80
|
+
|
|
81
|
+
### 1. Table (index) names are resolved by default
|
|
82
|
+
|
|
83
|
+
Every table statement now resolves its name through `#_env_table_name`, so the
|
|
84
|
+
`table_name_prefix` / `table_name_suffix` no longer have to be applied by hand in migrations or database statements
|
|
85
|
+
_(which silently wrote into another environment's index)_.
|
|
86
|
+
|
|
87
|
+
```ruby
|
|
88
|
+
# 1.8.x - the prefix / suffix had to be applied manually
|
|
89
|
+
drop_table _env_table_name("settings")
|
|
90
|
+
|
|
91
|
+
# 2.0 - resolved on its own
|
|
92
|
+
drop_table "settings"
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Existing migrations keep working - `_env_table_name` is idempotent and still public, so a
|
|
96
|
+
hand-resolved name resolves to the very same index. A new `decorate:`-argument opts out per
|
|
97
|
+
call, and `ElasticsearchRecord.decorate_table_names` acts as a global kill-switch.
|
|
98
|
+
|
|
99
|
+
_see @ [environment-related-table-name](#environment-related-table-name), [opting out with `decorate: false`](#opting-out-with-decorate-false) & [global kill-switch](#global-kill-switch)_
|
|
100
|
+
|
|
101
|
+
### 2. `select` raises on metadata fields
|
|
102
|
+
|
|
103
|
+
Metadata fields _(`_id`, `_score`, `_index`, ...)_ are not part of the `_source` node, so they
|
|
104
|
+
could never be resolved through the `_source`-filter this method builds - providing them
|
|
105
|
+
silently created a filter that never matched.
|
|
106
|
+
|
|
107
|
+
```ruby
|
|
108
|
+
Search.select(:_id)
|
|
109
|
+
# => ActiveRecord::UnknownAttributeReference
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
They are returned anyway and accessible on each record _(`Search.first._id`)_. To resolve the
|
|
113
|
+
metadata **without** transferring the `_source`, use the new `#meta_only!` method.
|
|
114
|
+
|
|
115
|
+
### 3. `restore_table` replaces its `open:`-argument with `unblock:`
|
|
116
|
+
|
|
117
|
+
A restore runs through a `clone`, so the restored table **inherits** the 'write'-block of its
|
|
118
|
+
source - the table was open, but read-only. `unblock:` _(default: `true`)_ releases it again.
|
|
119
|
+
`ModelApi#restore!` follows along.
|
|
120
|
+
|
|
121
|
+
```ruby
|
|
122
|
+
# 1.8.x
|
|
123
|
+
restore_table 'settings', from: 'settings-snapshot-2024', open: true
|
|
124
|
+
|
|
125
|
+
# 2.0
|
|
126
|
+
restore_table 'settings', from: 'settings-snapshot-2024', unblock: true
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### 4. The plural table statements no longer skip AR-internal indices
|
|
130
|
+
|
|
131
|
+
`#open_tables`, `#close_tables`, `#refresh_tables` & `#truncate_tables` no longer subtract the
|
|
132
|
+
ActiveRecord-internal indices _(`schema_migrations` & `ar_internal_metadata`)_ - an explicitly
|
|
133
|
+
named index was silently dropped from the list. They now return an `Array` with an entry for
|
|
134
|
+
**every** provided name.
|
|
135
|
+
|
|
136
|
+
### 5. `truncate_table` raises for AR-internal indices
|
|
137
|
+
|
|
138
|
+
The statement runs a `drop` & `create` and would wipe the migration state.
|
|
139
|
+
`#drop_table` stays unguarded by design _(ActiveRecord resets both tables through it)_.
|
|
140
|
+
|
|
141
|
+
### 6. `esql` & `msearch` dropped their `async:`-argument
|
|
142
|
+
|
|
143
|
+
Both dispatch through the public `exec_query` now, since rails 7.1 made `internal_exec_query`
|
|
144
|
+
private - the `async:`-argument was dropped along with it.
|
|
145
|
+
|
|
146
|
+
-----
|
|
66
147
|
|
|
67
148
|
## Features
|
|
68
149
|
* ActiveRecord's `create, read, update & delete` behaviours
|
|
@@ -72,6 +153,8 @@ Or install it yourself as:
|
|
|
72
153
|
* additional relation methods to find records with `filter, must, must_not, should`
|
|
73
154
|
* aggregated queries with Elasticsearch `aggregation` methods
|
|
74
155
|
* resolve search response `hits`, `aggregations`, `buckets`, ... instead of ActiveRecord objects
|
|
156
|
+
* `SQL` & `ES|QL` queries resolve their **tabular** response into records _(`find_by_sql` with a String query, `find_by_esql`)_
|
|
157
|
+
* table (index) names are resolved within the current environment _(`table_name_prefix` / `table_name_suffix`)_
|
|
75
158
|
* Third-party gem support
|
|
76
159
|
* access `elasticsearch-dsl` query builder through `model.search{ ... }`
|
|
77
160
|
* Schema
|
|
@@ -255,6 +338,11 @@ buckets = Search.where(name: 'A nice object').aggregate(:total, {sum: {field: :a
|
|
|
255
338
|
results = Search.where(name: 'A nice object').pit_results
|
|
256
339
|
# > [{ "_id": "abc123", "name": "A nice object", ...
|
|
257
340
|
|
|
341
|
+
# resolves ONLY the metadata nodes of each hit - the '_source' is not transferred at all.
|
|
342
|
+
# (this is the replacement for a - no longer supported - 'select(:_id)')
|
|
343
|
+
metas = Search.where(name: 'A nice object').meta_only!.results
|
|
344
|
+
# > [{ "_id": "abc123", "_index": "search", "_score": 1.0 }, ...
|
|
345
|
+
|
|
258
346
|
# returns the total value of the query without querying again (it uses the total value from the response)
|
|
259
347
|
scope = Search.where(name: 'A nice object').limit(5)
|
|
260
348
|
results_count = scope.count
|
|
@@ -288,10 +376,7 @@ _(also see @ [github](https://github.com/ruby-smart/elasticsearch_record/blob/ma
|
|
|
288
376
|
- must
|
|
289
377
|
- should
|
|
290
378
|
- aggregate
|
|
291
|
-
-
|
|
292
|
-
- hits_only!
|
|
293
|
-
- aggs_only!
|
|
294
|
-
- total_only!
|
|
379
|
+
- select _(raises on metadata fields - see @ [Upgrading to 2.0](#2-select-raises-on-metadata-fields))_
|
|
295
380
|
|
|
296
381
|
_see simple documentation about these methods @ {ElasticsearchRecord::Relation::QueryMethods rubydoc}_
|
|
297
382
|
|
|
@@ -308,7 +393,7 @@ _(also see @ [github](https://github.com/ruby-smart/elasticsearch_record/blob/ma
|
|
|
308
393
|
- boxplot
|
|
309
394
|
- stats
|
|
310
395
|
- string_stats
|
|
311
|
-
- matrix_stats
|
|
396
|
+
- matrix_stats _(requires at least two columns)_
|
|
312
397
|
- median_absolute_deviation
|
|
313
398
|
- calculate
|
|
314
399
|
|
|
@@ -316,6 +401,13 @@ _see simple documentation about these methods @ {ElasticsearchRecord::Relation::
|
|
|
316
401
|
|
|
317
402
|
_(also see @ [github](https://github.com/ruby-smart/elasticsearch_record/blob/main/lib/elasticsearch_record/relation/calculation_methods.rb) )_
|
|
318
403
|
|
|
404
|
+
### Available query configuration methods
|
|
405
|
+
|
|
406
|
+
- hits_only! _(prevents to resolve aggs)_
|
|
407
|
+
- aggs_only! _(prevents to resolve hits / source data)_
|
|
408
|
+
- total_only! _(prevents to resolve aggs, hits / source data)_
|
|
409
|
+
- meta_only! _(resolves the metadata nodes (`_id`, `_score`, ...) of each hit without transferring the `_source`)_
|
|
410
|
+
|
|
319
411
|
### Available result methods
|
|
320
412
|
- aggregations
|
|
321
413
|
- buckets
|
|
@@ -544,7 +636,7 @@ Access these methods through the model's connection or within any `Migration`.
|
|
|
544
636
|
**Example migration:**
|
|
545
637
|
|
|
546
638
|
```ruby
|
|
547
|
-
class AddTests < ActiveRecord::Migration[7.
|
|
639
|
+
class AddTests < ActiveRecord::Migration[7.1]
|
|
548
640
|
def up
|
|
549
641
|
create_table "assignments", if_not_exists: true do |t|
|
|
550
642
|
t.string :key, primary_key: true
|
|
@@ -647,7 +739,7 @@ A single migration can be created to be used within each environment:
|
|
|
647
739
|
|
|
648
740
|
```ruby
|
|
649
741
|
# Example migration
|
|
650
|
-
class AddSettings < ActiveRecord::Migration[7.
|
|
742
|
+
class AddSettings < ActiveRecord::Migration[7.1]
|
|
651
743
|
def up
|
|
652
744
|
# creates 'settings-pro' on production & 'settings-dev' on development
|
|
653
745
|
create_table "settings", force: true do |t|
|
data/docs/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# ElasticsearchRecord - CHANGELOG
|
|
2
2
|
|
|
3
|
+
## [3.0.0] - 2026-09-08
|
|
4
|
+
* [add] **BREAKING**: requires `activerecord ~> 7.2.0`
|
|
5
|
+
* [add] `Quoting::ClassMethods#quote_column_name` & `#quote_table_name` - identifier quoting is class-level & mandatory now
|
|
6
|
+
* [add] `ElasticsearchRecord::InternalMetadata` - always disabled _(the flag moved from the adapter to the database config)_
|
|
7
|
+
* [add] `patches/active_record/connection_pool_patch` - routes `#migrations_paths`, `#schema_migration` & `#internal_metadata` back to the adapter
|
|
8
|
+
* [add] specs for `ElasticsearchAdapter#translate_exception`
|
|
9
|
+
* [add] specs for `Relation::ValueMethods#limit_value=`
|
|
10
|
+
* [add] specs for `Core#id=`, `#id_was`, `#write_attribute` & `#read_attribute`
|
|
11
|
+
* [add] specs for `TableSettingDefinition`
|
|
12
|
+
* [add] specs for `InternalMetadata#enabled?`
|
|
13
|
+
* [ref] the adapter registers itself through `ActiveRecord::ConnectionAdapters.register` - `ConnectionHandling#elasticsearch_connection` removed
|
|
14
|
+
* [ref] the adapter provides `#internal_metadata` & `#migration_context` again; `#schema_migration` builds from the **pool**
|
|
15
|
+
* [ref] `SchemaMigration#integer_versions` resolves through `pool.with_connection`
|
|
16
|
+
* [ref] `#find_by_sql`, `#find_by_query` & `#find_by_esql` resolve through `with_connection`; `allow_retry:` forwarded down to `#internal_exec_query`
|
|
17
|
+
* [ref] `#_insert_record`, `#_insert_with_auto_increment` & `#cached_find_by_statement` take the connection as first argument
|
|
18
|
+
* [ref] `Arel::Collectors::ElasticsearchQuery#retryable` accessor added _(rails marks idempotent queries as retryable)_
|
|
19
|
+
* [ref] `assert_mutability!` -> `assert_modifiable!` & `ImmutableRelation` -> `UnmodifiableRelation`
|
|
20
|
+
* [ref] `Arel::Nodes::Or` is a nary node now - takes its children as a single `Array`
|
|
21
|
+
* [fix] `Querying::ClassMethods#search` to nest the `Elasticsearch::DSL` block result into the `body` and to only enter the DSL branch **if a block was provided**
|
|
22
|
+
|
|
3
23
|
## [2.0.0] - 2026-08-10
|
|
4
24
|
* [add] `ElasticsearchRecord.decorate_table_names` _(default: `true`)_ as global kill-switch for the table name decoration - only provides the default for an omitted `decorate:`-argument, `#_env_table_name` stays unaffected
|
|
5
25
|
* [add] `TableStatements#truncate_table` raises an `ArgumentError` for AR-internal indices - the statement runs a `drop` & `create` and would wipe the migration state. `#drop_table` stays unguarded by design _(AR resets both tables through it)_
|
|
@@ -14,7 +14,7 @@ DESC
|
|
|
14
14
|
|
|
15
15
|
spec.homepage = "https://github.com/ruby-smart/elasticsearch_record"
|
|
16
16
|
spec.license = "MIT"
|
|
17
|
-
spec.required_ruby_version = ">=
|
|
17
|
+
spec.required_ruby_version = ">= 3.1.0"
|
|
18
18
|
|
|
19
19
|
spec.metadata["allowed_push_host"] = "https://rubygems.org"
|
|
20
20
|
spec.metadata["homepage_uri"] = spec.homepage
|
|
@@ -32,7 +32,7 @@ DESC
|
|
|
32
32
|
|
|
33
33
|
spec.require_paths = ["lib"]
|
|
34
34
|
|
|
35
|
-
spec.add_dependency 'activerecord', '~> 7.
|
|
35
|
+
spec.add_dependency 'activerecord', '~> 7.2.0'
|
|
36
36
|
spec.add_dependency 'elasticsearch', '>= 7.17'
|
|
37
37
|
|
|
38
38
|
spec.add_development_dependency 'coveralls_reborn', '~> 0.25'
|
|
@@ -137,10 +137,11 @@ module ActiveRecord
|
|
|
137
137
|
# @param [Array] binds - not supported on the top-level and therefore ignored!
|
|
138
138
|
# @param [Boolean] prepare - used by the default AbstractAdapter - but not supported and therefore never ignored!
|
|
139
139
|
# @param [Boolean] async
|
|
140
|
+
# @param [Boolean] allow_retry
|
|
140
141
|
# @return [ElasticsearchRecord::Result]
|
|
141
|
-
def internal_exec_query(query, name = "QUERY", binds = [], prepare: false, async: false)
|
|
142
|
+
def internal_exec_query(query, name = "QUERY", binds = [], prepare: false, async: false, allow_retry: false)
|
|
142
143
|
build_result(
|
|
143
|
-
internal_execute(query, name, async: async),
|
|
144
|
+
internal_execute(query, name, async: async, allow_retry: allow_retry),
|
|
144
145
|
columns: query.columns
|
|
145
146
|
)
|
|
146
147
|
end
|
|
@@ -4,6 +4,22 @@ module ActiveRecord
|
|
|
4
4
|
module ConnectionAdapters
|
|
5
5
|
module Elasticsearch
|
|
6
6
|
module Quoting # :nodoc:
|
|
7
|
+
extend ActiveSupport::Concern
|
|
8
|
+
|
|
9
|
+
module ClassMethods # :nodoc:
|
|
10
|
+
# since rails 7.2 identifier quoting is class-level & mandatory (the abstract
|
|
11
|
+
# implementation raises a +NotImplementedError+). Elasticsearch fields & indices are
|
|
12
|
+
# plain JSON keys - so they pass through unquoted.
|
|
13
|
+
def quote_column_name(column_name)
|
|
14
|
+
column_name.to_s
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Quotes the table (index) name. Defaults to column name quoting.
|
|
18
|
+
def quote_table_name(table_name)
|
|
19
|
+
quote_column_name(table_name)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
7
23
|
def quoted_true
|
|
8
24
|
'true'
|
|
9
25
|
end
|
data/lib/active_record/connection_adapters/elasticsearch/schema_definitions/column_methods.rb
CHANGED
|
File without changes
|
|
@@ -22,16 +22,6 @@ gem 'elasticsearch'
|
|
|
22
22
|
require 'elasticsearch'
|
|
23
23
|
|
|
24
24
|
module ActiveRecord # :nodoc:
|
|
25
|
-
module ConnectionHandling # :nodoc:
|
|
26
|
-
def elasticsearch_adapter_class
|
|
27
|
-
ConnectionAdapters::ElasticsearchAdapter
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
def elasticsearch_connection(config)
|
|
31
|
-
elasticsearch_adapter_class.new(config)
|
|
32
|
-
end
|
|
33
|
-
end
|
|
34
|
-
|
|
35
25
|
module ConnectionAdapters # :nodoc:
|
|
36
26
|
class ElasticsearchAdapter < AbstractAdapter
|
|
37
27
|
include Elasticsearch::UnsupportedImplementation
|
|
@@ -168,7 +158,17 @@ module ActiveRecord # :nodoc:
|
|
|
168
158
|
end
|
|
169
159
|
|
|
170
160
|
def schema_migration # :nodoc:
|
|
171
|
-
ElasticsearchRecord::SchemaMigration.new(
|
|
161
|
+
ElasticsearchRecord::SchemaMigration.new(pool)
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def internal_metadata # :nodoc:
|
|
165
|
+
ElasticsearchRecord::InternalMetadata.new(pool)
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# since rails 7.2 the migration context is resolved through the connection pool -
|
|
169
|
+
# provided here as connection method again (used by +#assume_migrated_upto_version+)
|
|
170
|
+
def migration_context # :nodoc:
|
|
171
|
+
pool.migration_context
|
|
172
172
|
end
|
|
173
173
|
|
|
174
174
|
# provide a table_name_prefix from the configuration to create & restrict schema creation
|
|
@@ -429,4 +429,12 @@ module ActiveRecord # :nodoc:
|
|
|
429
429
|
::ActiveRecord::Type.register(:range, ::ActiveRecord::ConnectionAdapters::Elasticsearch::Type::Range, adapter: :elasticsearch)
|
|
430
430
|
end
|
|
431
431
|
end
|
|
432
|
-
end
|
|
432
|
+
end
|
|
433
|
+
|
|
434
|
+
# since rails 7.2 an adapter has to register itself - the former load-by-convention
|
|
435
|
+
# through +ConnectionHandling#elasticsearch_connection+ is gone
|
|
436
|
+
ActiveRecord::ConnectionAdapters.register(
|
|
437
|
+
'elasticsearch',
|
|
438
|
+
'ActiveRecord::ConnectionAdapters::ElasticsearchAdapter',
|
|
439
|
+
'active_record/connection_adapters/elasticsearch_adapter'
|
|
440
|
+
)
|
|
@@ -9,6 +9,9 @@ module Arel # :nodoc: all
|
|
|
9
9
|
# required for ActiveRecord
|
|
10
10
|
attr_accessor :preparable
|
|
11
11
|
|
|
12
|
+
# required for ActiveRecord (rails 7.2 marks idempotent queries as retryable)
|
|
13
|
+
attr_accessor :retryable
|
|
14
|
+
|
|
12
15
|
# returns the current bind index (default: 1)
|
|
13
16
|
attr_reader :bind_index
|
|
14
17
|
|
|
@@ -96,7 +96,7 @@ module ElasticsearchRecord
|
|
|
96
96
|
# used to create a cacheable statement.
|
|
97
97
|
# This is a 1:1 copy, except that we use our own class +ElasticsearchRecord::StatementCache+
|
|
98
98
|
# see @ ActiveRecord::Core::ClassMethods#cached_find_by_statement
|
|
99
|
-
def cached_find_by_statement(key, &block)
|
|
99
|
+
def cached_find_by_statement(connection, key, &block)
|
|
100
100
|
cache = @find_by_statement_cache[connection.prepared_statements]
|
|
101
101
|
cache.compute_if_absent(key) { ElasticsearchRecord::StatementCache.create(connection, &block) }
|
|
102
102
|
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_record/internal_metadata"
|
|
4
|
+
|
|
5
|
+
module ElasticsearchRecord
|
|
6
|
+
class InternalMetadata < ::ActiveRecord::InternalMetadata
|
|
7
|
+
|
|
8
|
+
# overwrite method to always disable the internal metadata index.
|
|
9
|
+
#
|
|
10
|
+
# Elasticsearch cannot serve the internal metadata table
|
|
11
|
+
# (see +ElasticsearchAdapter#use_metadata_table?+) - but since rails 7.2 the flag is
|
|
12
|
+
# resolved through the database config (default: enabled) instead of the adapter,
|
|
13
|
+
# so a default configuration would suddenly create & write a 'ar_internal_metadata'
|
|
14
|
+
# index on the cluster.
|
|
15
|
+
def enabled?
|
|
16
|
+
false
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'active_record/connection_adapters/abstract/connection_pool'
|
|
4
|
+
|
|
5
|
+
module ElasticsearchRecord
|
|
6
|
+
module Patches
|
|
7
|
+
module ActiveRecord
|
|
8
|
+
# Since rails 7.2 the migration plumbing is resolved through the connection POOL
|
|
9
|
+
# (+ConnectionPool#migration_context+ builds from +#migrations_paths+, +#schema_migration+ &
|
|
10
|
+
# +#internal_metadata+) instead of the connection - so the adapters own implementations are
|
|
11
|
+
# no longer consulted. Without this patch a Elasticsearch pool would migrate with the generic
|
|
12
|
+
# +ActiveRecord::SchemaMigration+ (which resolves the index name through +ActiveRecord::Base+
|
|
13
|
+
# and only ever sees the first ten migrations - Elasticsearch default search size), enable the
|
|
14
|
+
# internal metadata index (the adapter cannot serve it) and fall back to the default
|
|
15
|
+
# 'db/migrate' path.
|
|
16
|
+
#
|
|
17
|
+
# The patch simply routes those factories back to the adapter class, which keeps the
|
|
18
|
+
# Elasticsearch-specific implementations in one place.
|
|
19
|
+
module ConnectionPoolPatch
|
|
20
|
+
def self.included(base)
|
|
21
|
+
base.send(:prepend, PrependMethods)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
module PrependMethods
|
|
25
|
+
def migrations_paths
|
|
26
|
+
return super unless elasticsearch_pool?
|
|
27
|
+
|
|
28
|
+
db_config.migrations_paths || ['db/migrate_elasticsearch']
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def schema_migration
|
|
32
|
+
return super unless elasticsearch_pool?
|
|
33
|
+
|
|
34
|
+
ElasticsearchRecord::SchemaMigration.new(self)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def internal_metadata
|
|
38
|
+
return super unless elasticsearch_pool?
|
|
39
|
+
|
|
40
|
+
ElasticsearchRecord::InternalMetadata.new(self)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
# returns true, if the pools config resolves to the Elasticsearch adapter
|
|
46
|
+
# @return [Boolean]
|
|
47
|
+
def elasticsearch_pool?
|
|
48
|
+
db_config.adapter_class <= ::ActiveRecord::ConnectionAdapters::ElasticsearchAdapter
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# include once only!
|
|
57
|
+
::ActiveRecord::ConnectionAdapters::ConnectionPool.include(ElasticsearchRecord::Patches::ActiveRecord::ConnectionPoolPatch) unless ::ActiveRecord::ConnectionAdapters::ConnectionPool.included_modules.include?(ElasticsearchRecord::Patches::ActiveRecord::ConnectionPoolPatch)
|
|
@@ -5,14 +5,15 @@ module ElasticsearchRecord
|
|
|
5
5
|
module ClassMethods
|
|
6
6
|
# insert a new record into the Elasticsearch index
|
|
7
7
|
# NOTICE: We don't want to mess up with the Arel-builder - so we send new data directly to the API
|
|
8
|
+
# @param [ActiveRecord::ConnectionAdapters::ElasticsearchAdapter] connection
|
|
8
9
|
# @param [ActiveModel::Attribute] values
|
|
9
10
|
# @return [Object] id
|
|
10
|
-
def _insert_record(values, returning)
|
|
11
|
+
def _insert_record(connection, values, returning)
|
|
11
12
|
# values is not a "key=>values"-Hash, but a +ActiveModel::Attribute+ - so the casted values gets resolved here
|
|
12
13
|
values = values.transform_values(&:value)
|
|
13
14
|
|
|
14
15
|
# resolve & update a auto_increment value, if configured
|
|
15
|
-
_insert_with_auto_increment(values) do |arguments|
|
|
16
|
+
_insert_with_auto_increment(connection, values) do |arguments|
|
|
16
17
|
# build new query
|
|
17
18
|
query = ElasticsearchRecord::Query.new(
|
|
18
19
|
index: table_name,
|
|
@@ -62,7 +63,7 @@ module ElasticsearchRecord
|
|
|
62
63
|
private
|
|
63
64
|
|
|
64
65
|
# Resolves the +auto_increment+ status from the tables +_meta+ attributes.
|
|
65
|
-
def _insert_with_auto_increment(values)
|
|
66
|
+
def _insert_with_auto_increment(connection, values)
|
|
66
67
|
# check, if the primary_key's value is provided.
|
|
67
68
|
# so, no need to resolve a +auto_increment+ value, but provide the id directly
|
|
68
69
|
if (id = values[self.primary_key]).present?
|
|
@@ -42,8 +42,9 @@ module ElasticsearchRecord
|
|
|
42
42
|
# @param [String, Hash, ElasticsearchRecord::Query] sql
|
|
43
43
|
# @param [Array] binds
|
|
44
44
|
# @param [nil] preparable
|
|
45
|
+
# @param [Boolean] allow_retry
|
|
45
46
|
# @param [Proc] block
|
|
46
|
-
def find_by_sql(sql, binds = [], preparable: nil, &block)
|
|
47
|
+
def find_by_sql(sql, binds = [], preparable: nil, allow_retry: false, &block)
|
|
47
48
|
query = case sql
|
|
48
49
|
when String # really find by SQL
|
|
49
50
|
ElasticsearchRecord::Query.new(
|
|
@@ -61,7 +62,11 @@ module ElasticsearchRecord
|
|
|
61
62
|
sql
|
|
62
63
|
end
|
|
63
64
|
|
|
64
|
-
|
|
65
|
+
result = with_connection do |c|
|
|
66
|
+
_query_by_sql(c, query, binds, preparable: preparable, allow_retry: allow_retry)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
_load_from_sql(result, &block)
|
|
65
70
|
end
|
|
66
71
|
|
|
67
72
|
# finds records by query arguments
|
|
@@ -74,7 +79,11 @@ module ElasticsearchRecord
|
|
|
74
79
|
# IMPORTANT: Always provide all columns to prevent unknown attributes that should be nil ...
|
|
75
80
|
columns: source_column_names)
|
|
76
81
|
|
|
77
|
-
|
|
82
|
+
result = with_connection do |c|
|
|
83
|
+
_query_by_sql(c, query)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
_load_from_sql(result, &block)
|
|
78
87
|
end
|
|
79
88
|
|
|
80
89
|
# ES|QL query API
|
|
@@ -90,7 +99,11 @@ module ElasticsearchRecord
|
|
|
90
99
|
# IMPORTANT: Always provide all columns
|
|
91
100
|
columns: source_column_names)
|
|
92
101
|
|
|
93
|
-
|
|
102
|
+
result = with_connection do |c|
|
|
103
|
+
_query_by_sql(c, query)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
_load_from_sql(result, &block)
|
|
94
107
|
end
|
|
95
108
|
|
|
96
109
|
# executes a +esql+ by provided *ES|SL* query
|
|
@@ -123,17 +136,28 @@ module ElasticsearchRecord
|
|
|
123
136
|
connection.exec_query(query, "#{name} Msearch")
|
|
124
137
|
end
|
|
125
138
|
|
|
126
|
-
# executes a search by provided +RAW+ query - supports +Elasticsearch::DSL+ gem if loaded
|
|
139
|
+
# executes a search by provided +RAW+ query - supports +Elasticsearch::DSL+ gem if loaded.
|
|
140
|
+
#
|
|
141
|
+
# Without a block the trailing options-hash is used as query arguments.
|
|
142
|
+
# With a block the +Elasticsearch::DSL+ gem builds the query - if the gem is not
|
|
143
|
+
# available (or the block fails), the block is SILENTLY ignored and the
|
|
144
|
+
# options-hash is used instead.
|
|
127
145
|
def search(*args, &block)
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
146
|
+
query = if block_given?
|
|
147
|
+
begin
|
|
148
|
+
# require the Elasticsearch::DSL gem, if loaded
|
|
149
|
+
require 'elasticsearch/dsl'
|
|
150
|
+
# PLEASE NOTE: +Search#to_hash+ returns the request BODY - it must be nested
|
|
151
|
+
# into the query arguments, otherwise it is sent as (invalid) URL parameters.
|
|
152
|
+
{ body: ::Elasticsearch::DSL::Search::Search.new(*args, &block).to_hash }
|
|
153
|
+
rescue LoadError
|
|
154
|
+
args.extract_options!
|
|
155
|
+
rescue
|
|
156
|
+
args.extract_options!
|
|
157
|
+
end
|
|
158
|
+
else
|
|
159
|
+
args.extract_options!
|
|
160
|
+
end
|
|
137
161
|
|
|
138
162
|
find_by_query(query)
|
|
139
163
|
end
|
|
@@ -10,7 +10,7 @@ module ElasticsearchRecord
|
|
|
10
10
|
# transforms the current relation into arel, compiles it to query and executes the query.
|
|
11
11
|
# returns the result object.
|
|
12
12
|
#
|
|
13
|
-
# PLEASE NOTE: This makes the query +immutable+ and raises a +ActiveRecord::
|
|
13
|
+
# PLEASE NOTE: This makes the query +immutable+ and raises a +ActiveRecord::UnmodifiableRelation+
|
|
14
14
|
# if you try to change it's values.
|
|
15
15
|
#
|
|
16
16
|
# PLEASE NOTE: resolving records _(instantiate)_ is never possible after calling this method!
|
|
@@ -109,7 +109,8 @@ module ElasticsearchRecord # :nodoc:
|
|
|
109
109
|
left = left[key]
|
|
110
110
|
right = right[key]
|
|
111
111
|
|
|
112
|
-
|
|
112
|
+
# since rails 7.2 +Arel::Nodes::Or+ is a nary node and takes its children as a single Array
|
|
113
|
+
or_clause = Arel::Nodes::Or.new([left, right])
|
|
113
114
|
|
|
114
115
|
common.predicates[key] = ElasticsearchRecord::Relation::QueryClause.new(key, [Arel::Nodes::Grouping.new(or_clause)])
|
|
115
116
|
common
|
|
@@ -260,7 +260,7 @@ module ElasticsearchRecord
|
|
|
260
260
|
unless _valid_unscoping_values.include?(scope)
|
|
261
261
|
raise ArgumentError, "Called unscope() with invalid unscoping argument ':#{scope}'. Valid arguments are :#{_valid_unscoping_values.to_a.join(", :")}."
|
|
262
262
|
end
|
|
263
|
-
|
|
263
|
+
assert_modifiable!
|
|
264
264
|
@values.delete(scope)
|
|
265
265
|
when Hash
|
|
266
266
|
scope.each do |key, target_value|
|
|
@@ -8,7 +8,7 @@ module ElasticsearchRecord
|
|
|
8
8
|
|
|
9
9
|
def kind_value=(value)
|
|
10
10
|
# checks if records are already loaded - in this case we cannot mutate the query anymore
|
|
11
|
-
|
|
11
|
+
assert_modifiable!
|
|
12
12
|
|
|
13
13
|
@values[:kind] = value.to_sym
|
|
14
14
|
end
|
|
@@ -18,7 +18,7 @@ module ElasticsearchRecord
|
|
|
18
18
|
end
|
|
19
19
|
|
|
20
20
|
def configure_value=(value)
|
|
21
|
-
|
|
21
|
+
assert_modifiable!
|
|
22
22
|
|
|
23
23
|
@values[:configure] = value
|
|
24
24
|
end
|
|
@@ -28,7 +28,7 @@ module ElasticsearchRecord
|
|
|
28
28
|
end
|
|
29
29
|
|
|
30
30
|
def query_clause=(value)
|
|
31
|
-
|
|
31
|
+
assert_modifiable!
|
|
32
32
|
|
|
33
33
|
@values[:query] = value
|
|
34
34
|
end
|
|
@@ -38,7 +38,7 @@ module ElasticsearchRecord
|
|
|
38
38
|
end
|
|
39
39
|
|
|
40
40
|
def aggs_clause=(value)
|
|
41
|
-
|
|
41
|
+
assert_modifiable!
|
|
42
42
|
|
|
43
43
|
@values[:aggs] = value
|
|
44
44
|
end
|
|
@@ -14,9 +14,12 @@ module ElasticsearchRecord
|
|
|
14
14
|
sm = Arel::SelectManager.new(arel_table)
|
|
15
15
|
sm.project(arel_table[primary_key])
|
|
16
16
|
sm.order(arel_table[primary_key].asc)
|
|
17
|
-
sm.take(connection.max_result_window(table_name))
|
|
18
17
|
|
|
19
|
-
|
|
18
|
+
@pool.with_connection do |connection|
|
|
19
|
+
sm.take(connection.max_result_window(table_name))
|
|
20
|
+
|
|
21
|
+
connection.select_values(sm, "#{self.class} Load")
|
|
22
|
+
end
|
|
20
23
|
end
|
|
21
24
|
end
|
|
22
25
|
end
|
data/lib/elasticsearch_record.rb
CHANGED
|
@@ -22,6 +22,7 @@ module ElasticsearchRecord
|
|
|
22
22
|
eager_autoload do
|
|
23
23
|
autoload :Base
|
|
24
24
|
autoload :Core
|
|
25
|
+
autoload :InternalMetadata
|
|
25
26
|
autoload :ModelSchema
|
|
26
27
|
autoload :ModelApi
|
|
27
28
|
autoload :Persistence
|
|
@@ -84,6 +85,7 @@ end
|
|
|
84
85
|
|
|
85
86
|
ActiveSupport.on_load(:active_record) do
|
|
86
87
|
# load patches
|
|
88
|
+
require 'elasticsearch_record/patches/active_record/connection_pool_patch'
|
|
87
89
|
require 'elasticsearch_record/patches/active_record/relation_merger_patch'
|
|
88
90
|
require 'elasticsearch_record/patches/arel/select_core_patch'
|
|
89
91
|
require 'elasticsearch_record/patches/arel/select_manager_patch'
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: elasticsearch_record
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 3.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Tobias Gonsior
|
|
@@ -15,14 +15,14 @@ dependencies:
|
|
|
15
15
|
requirements:
|
|
16
16
|
- - "~>"
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: 7.
|
|
18
|
+
version: 7.2.0
|
|
19
19
|
type: :runtime
|
|
20
20
|
prerelease: false
|
|
21
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
22
|
requirements:
|
|
23
23
|
- - "~>"
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
|
-
version: 7.
|
|
25
|
+
version: 7.2.0
|
|
26
26
|
- !ruby/object:Gem::Dependency
|
|
27
27
|
name: elasticsearch
|
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -186,8 +186,10 @@ files:
|
|
|
186
186
|
- lib/elasticsearch_record/instrumentation/controller_runtime.rb
|
|
187
187
|
- lib/elasticsearch_record/instrumentation/log_subscriber.rb
|
|
188
188
|
- lib/elasticsearch_record/instrumentation/railtie.rb
|
|
189
|
+
- lib/elasticsearch_record/internal_metadata.rb
|
|
189
190
|
- lib/elasticsearch_record/model_api.rb
|
|
190
191
|
- lib/elasticsearch_record/model_schema.rb
|
|
192
|
+
- lib/elasticsearch_record/patches/active_record/connection_pool_patch.rb
|
|
191
193
|
- lib/elasticsearch_record/patches/active_record/relation_merger_patch.rb
|
|
192
194
|
- lib/elasticsearch_record/patches/arel/select_core_patch.rb
|
|
193
195
|
- lib/elasticsearch_record/patches/arel/select_manager_patch.rb
|
|
@@ -226,7 +228,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
226
228
|
requirements:
|
|
227
229
|
- - ">="
|
|
228
230
|
- !ruby/object:Gem::Version
|
|
229
|
-
version:
|
|
231
|
+
version: 3.1.0
|
|
230
232
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
231
233
|
requirements:
|
|
232
234
|
- - ">="
|