elasticsearch_record 1.8.2 → 2.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/.rspec +0 -0
- data/.yardopts +0 -0
- data/Gemfile +7 -0
- data/README.md +75 -9
- data/Rakefile +0 -0
- data/docs/CHANGELOG.md +46 -0
- data/docs/CODE_OF_CONDUCT.md +0 -0
- data/docs/LICENSE +0 -0
- data/elasticsearch_record.gemspec +1 -1
- data/lib/active_record/connection_adapters/elasticsearch/column.rb +0 -0
- data/lib/active_record/connection_adapters/elasticsearch/database_statements.rb +18 -17
- data/lib/active_record/connection_adapters/elasticsearch/quoting.rb +0 -0
- data/lib/active_record/connection_adapters/elasticsearch/schema_creation.rb +0 -0
- data/lib/active_record/connection_adapters/elasticsearch/schema_definitions/attribute_methods.rb +0 -0
- data/lib/active_record/connection_adapters/elasticsearch/schema_definitions/clone_table_definition.rb +0 -0
- data/lib/active_record/connection_adapters/elasticsearch/schema_definitions/column_methods.rb +0 -0
- data/lib/active_record/connection_adapters/elasticsearch/schema_definitions/create_table_definition.rb +6 -0
- data/lib/active_record/connection_adapters/elasticsearch/schema_definitions/table_alias_definition.rb +0 -0
- data/lib/active_record/connection_adapters/elasticsearch/schema_definitions/table_definition.rb +0 -0
- data/lib/active_record/connection_adapters/elasticsearch/schema_definitions/table_meta_definition.rb +0 -0
- data/lib/active_record/connection_adapters/elasticsearch/schema_definitions/table_setting_definition.rb +7 -5
- data/lib/active_record/connection_adapters/elasticsearch/schema_definitions/update_table_definition.rb +3 -1
- data/lib/active_record/connection_adapters/elasticsearch/schema_definitions.rb +0 -0
- data/lib/active_record/connection_adapters/elasticsearch/schema_dumper.rb +24 -3
- data/lib/active_record/connection_adapters/elasticsearch/schema_statements.rb +39 -34
- data/lib/active_record/connection_adapters/elasticsearch/table_statements.rb +251 -72
- data/lib/active_record/connection_adapters/elasticsearch/transactions.rb +0 -0
- data/lib/active_record/connection_adapters/elasticsearch/type/format_string.rb +0 -0
- data/lib/active_record/connection_adapters/elasticsearch/type/multicast_value.rb +0 -0
- data/lib/active_record/connection_adapters/elasticsearch/type/nested.rb +0 -0
- data/lib/active_record/connection_adapters/elasticsearch/type/object.rb +0 -0
- data/lib/active_record/connection_adapters/elasticsearch/type/range.rb +0 -0
- data/lib/active_record/connection_adapters/elasticsearch/type.rb +0 -0
- data/lib/active_record/connection_adapters/elasticsearch/unsupported_implementation.rb +0 -0
- data/lib/active_record/connection_adapters/elasticsearch_adapter.rb +144 -87
- data/lib/arel/collectors/elasticsearch_query.rb +0 -0
- data/lib/arel/nodes/select_agg.rb +0 -0
- data/lib/arel/nodes/select_configure.rb +0 -0
- data/lib/arel/nodes/select_kind.rb +0 -0
- data/lib/arel/nodes/select_query.rb +0 -0
- data/lib/arel/visitors/elasticsearch.rb +0 -0
- data/lib/arel/visitors/elasticsearch_query.rb +51 -9
- data/lib/elasticsearch_record/base.rb +0 -0
- data/lib/elasticsearch_record/core.rb +0 -0
- data/lib/elasticsearch_record/errors.rb +0 -0
- data/lib/elasticsearch_record/extensions/relation.rb +0 -0
- data/lib/elasticsearch_record/gem_version.rb +3 -3
- data/lib/elasticsearch_record/instrumentation/controller_runtime.rb +0 -0
- data/lib/elasticsearch_record/instrumentation/log_subscriber.rb +11 -9
- data/lib/elasticsearch_record/instrumentation/railtie.rb +0 -0
- data/lib/elasticsearch_record/instrumentation.rb +0 -0
- data/lib/elasticsearch_record/model_api.rb +8 -5
- data/lib/elasticsearch_record/model_schema.rb +1 -1
- data/lib/elasticsearch_record/patches/active_record/relation_merger_patch.rb +0 -0
- data/lib/elasticsearch_record/patches/arel/select_core_patch.rb +0 -0
- data/lib/elasticsearch_record/patches/arel/select_manager_patch.rb +0 -0
- data/lib/elasticsearch_record/patches/arel/select_statement_patch.rb +0 -0
- data/lib/elasticsearch_record/patches/arel/update_manager_patch.rb +0 -0
- data/lib/elasticsearch_record/patches/arel/update_statement_patch.rb +0 -0
- data/lib/elasticsearch_record/persistence.rb +35 -18
- data/lib/elasticsearch_record/query.rb +53 -33
- data/lib/elasticsearch_record/querying.rb +5 -7
- data/lib/elasticsearch_record/relation/calculation_methods.rb +38 -2
- data/lib/elasticsearch_record/relation/core_methods.rb +0 -0
- data/lib/elasticsearch_record/relation/query_clause.rb +0 -0
- data/lib/elasticsearch_record/relation/query_clause_tree.rb +0 -0
- data/lib/elasticsearch_record/relation/query_methods.rb +30 -0
- data/lib/elasticsearch_record/relation/result_methods.rb +47 -43
- data/lib/elasticsearch_record/relation/value_methods.rb +0 -0
- data/lib/elasticsearch_record/result.rb +144 -60
- data/lib/elasticsearch_record/schema_migration.rb +0 -0
- data/lib/elasticsearch_record/statement_cache.rb +0 -0
- data/lib/elasticsearch_record/tasks/elasticsearch_database_tasks.rb +0 -0
- data/lib/elasticsearch_record/version.rb +0 -0
- data/lib/elasticsearch_record.rb +15 -0
- metadata +17 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 27f0d74b39e373493b56511cb08e07ed2fc447da5f33cf5a23a8a080072f3d4e
|
|
4
|
+
data.tar.gz: 52f5843b03ea19f3f236910f2268aadb1eba5b7eae48c59734d162fdd2bc6000
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: '07733791c4f29ed331631d6ede8ba83b23ebad2af68afffda6e998eca7d09f4aad4a57a83c87e10dbd866a98bb8f77a6d4fffc905b1dd214d13f48313d603955'
|
|
7
|
+
data.tar.gz: 4f3e9ffb52a06c4db77fb698b7e715eac3ad77aa259df5f66905890f69646b35e4f6ee809c52feae3180544de8e47943f6cff95faeff12c43bfaecc9d0e01cb4
|
data/.rspec
CHANGED
|
File without changes
|
data/.yardopts
CHANGED
|
File without changes
|
data/Gemfile
CHANGED
|
@@ -4,3 +4,10 @@ source "https://rubygems.org"
|
|
|
4
4
|
|
|
5
5
|
# Specify your gem's dependencies in elasticsearch_record.gemspec
|
|
6
6
|
gemspec
|
|
7
|
+
|
|
8
|
+
# Pin the Elasticsearch client to the 8.x line for local development & specs.
|
|
9
|
+
# The gemspec intentionally allows '>= 7.17', but the 9.x client sends an
|
|
10
|
+
# 'Accept: application/vnd.elasticsearch+json; compatible-with=9' header that
|
|
11
|
+
# 8.x servers reject with a media_type_header_exception (HTTP 400).
|
|
12
|
+
# Keep this in sync with the server you develop against.
|
|
13
|
+
gem 'elasticsearch', '~> 8.0'
|
data/README.md
CHANGED
|
@@ -16,8 +16,9 @@ _ElasticsearchRecord is a ActiveRecord adapter and provides similar functionalit
|
|
|
16
16
|
|
|
17
17
|
- This is the `main`-branch, which currently supports rails **7.1** _(see section 'Rails_Versions' for supported versions)_
|
|
18
18
|
- supports ActiveRecord ~> 7.1 + Elasticsearch >= 7.17
|
|
19
|
-
- added features up to Elasticsearch `8.
|
|
20
|
-
-
|
|
19
|
+
- added features up to Elasticsearch `8.17.1`
|
|
20
|
+
- _ES|QL_ queries _(`TYPE_ESQL` / the `esql.query` gate)_ require **Elasticsearch >= 8.11**, where the feature became
|
|
21
|
+
generally available. All other features remain available from Elasticsearch `7.17`.
|
|
21
22
|
|
|
22
23
|
-----
|
|
23
24
|
|
|
@@ -50,7 +51,7 @@ gem 'elasticsearch_record', '~> 1.8'
|
|
|
50
51
|
|
|
51
52
|
# alternative
|
|
52
53
|
gem 'elasticsearch_record', git: 'https://github.com/ruby-smart/elasticsearch_record', branch: 'rails-7-1-stable'
|
|
53
|
-
gem 'elasticsearch_record', git: 'https://github.com/ruby-smart/elasticsearch_record', branch: 'rails-
|
|
54
|
+
gem 'elasticsearch_record', git: 'https://github.com/ruby-smart/elasticsearch_record', branch: 'rails-7-0-stable'
|
|
54
55
|
|
|
55
56
|
```
|
|
56
57
|
|
|
@@ -616,17 +617,29 @@ end
|
|
|
616
617
|
```
|
|
617
618
|
|
|
618
619
|
## environment-related-table-name:
|
|
619
|
-
|
|
620
|
-
|
|
620
|
+
Table (index) names are resolved within the current environment, even if the environments share the
|
|
621
|
+
same cluster ...
|
|
621
622
|
|
|
622
623
|
This can be provided through the `database.yml` by using the `table_name_prefix/suffix` configuration keys.
|
|
623
|
-
|
|
624
|
+
**Every table statement applies them by default**, so a migration only ever names the table (index)
|
|
625
|
+
_base_ name.
|
|
624
626
|
|
|
625
627
|
**Example:**
|
|
626
628
|
Production uses a index suffix with '-pro', development uses '-dev' - they share the same cluster, but different indexes.
|
|
627
629
|
|
|
628
|
-
|
|
630
|
+
```yml
|
|
631
|
+
production:
|
|
632
|
+
elasticsearch:
|
|
633
|
+
# ...
|
|
634
|
+
table_name_suffix: '-pro'
|
|
635
|
+
|
|
636
|
+
development:
|
|
637
|
+
elasticsearch:
|
|
638
|
+
# ...
|
|
639
|
+
table_name_suffix: '-dev'
|
|
640
|
+
```
|
|
629
641
|
|
|
642
|
+
For the **settings** table / index this results in the following names:
|
|
630
643
|
* settings-pro
|
|
631
644
|
* settings-dev
|
|
632
645
|
|
|
@@ -636,7 +649,8 @@ A single migration can be created to be used within each environment:
|
|
|
636
649
|
# Example migration
|
|
637
650
|
class AddSettings < ActiveRecord::Migration[7.0]
|
|
638
651
|
def up
|
|
639
|
-
|
|
652
|
+
# creates 'settings-pro' on production & 'settings-dev' on development
|
|
653
|
+
create_table "settings", force: true do |t|
|
|
640
654
|
t.mapping :created_at, :date
|
|
641
655
|
t.mapping :key, :integer do |m|
|
|
642
656
|
m.primary_key = true
|
|
@@ -653,11 +667,63 @@ class AddSettings < ActiveRecord::Migration[7.0]
|
|
|
653
667
|
end
|
|
654
668
|
|
|
655
669
|
def down
|
|
656
|
-
drop_table
|
|
670
|
+
drop_table "settings"
|
|
657
671
|
end
|
|
658
672
|
end
|
|
659
673
|
```
|
|
660
674
|
|
|
675
|
+
### opting out with `decorate: false`
|
|
676
|
+
|
|
677
|
+
Provide `decorate: false` to address an index by its **literal** name:
|
|
678
|
+
|
|
679
|
+
```ruby
|
|
680
|
+
# addresses 'settings-pro' - even from a '-dev' suffixed connection
|
|
681
|
+
drop_table "settings-pro", decorate: false
|
|
682
|
+
```
|
|
683
|
+
|
|
684
|
+
This is required in two cases:
|
|
685
|
+
|
|
686
|
+
* the name is **already resolved** _(e.g. `Model.table_name`, or a name read back from `#tables`)_
|
|
687
|
+
* the base name itself **starts with the prefix** or **ends with the suffix** - `_env_table_name`
|
|
688
|
+
keeps itself idempotent through a `start_with?` / `end_with?` check and cannot tell such a name
|
|
689
|
+
apart from an already resolved one
|
|
690
|
+
|
|
691
|
+
The flag only ever applies to table (index) names - `alias`, `mapping`, `setting` & `meta` names are
|
|
692
|
+
never touched. Statements taking **two** names _(`clone_table`, `rename_table`, `reindex_table`,
|
|
693
|
+
`restore_table`, `backup_table`, `create_table copy_from:`)_ resolve both.
|
|
694
|
+
|
|
695
|
+
The **schema statements** _(`table_exists?`, `table_schema`, `table_mappings`, `table_settings`,
|
|
696
|
+
`columns`, ...)_ are deliberately **not** decorated - ActiveRecord and the schema dumper call them
|
|
697
|
+
with an already resolved index name.
|
|
698
|
+
|
|
699
|
+
The `_env_table_name`-method itself is still public, so existing migrations keep working - it is now
|
|
700
|
+
redundant, since it resolves the very same name the statement would resolve on its own.
|
|
701
|
+
|
|
702
|
+
### global kill-switch
|
|
703
|
+
|
|
704
|
+
The default of a **not explicitly provided** `decorate:` argument is resolved from a global flag:
|
|
705
|
+
|
|
706
|
+
```ruby
|
|
707
|
+
# e.g. in an initializer
|
|
708
|
+
ElasticsearchRecord.decorate_table_names = false
|
|
709
|
+
```
|
|
710
|
+
|
|
711
|
+
Setting it to `false` restores the former, opt-in behaviour, where the decoration had to be applied
|
|
712
|
+
by hand through `_env_table_name`. A single statement can still opt in or out on its own, so
|
|
713
|
+
`decorate: true` keeps working while the flag is off:
|
|
714
|
+
|
|
715
|
+
```ruby
|
|
716
|
+
ElasticsearchRecord.decorate_table_names = false
|
|
717
|
+
|
|
718
|
+
drop_table "settings" # => drops 'settings'
|
|
719
|
+
drop_table "settings", decorate: true # => drops 'settings-dev'
|
|
720
|
+
drop_table _env_table_name("settings") # => drops 'settings-dev' (the former syntax)
|
|
721
|
+
```
|
|
722
|
+
|
|
723
|
+
The schema dumper follows the flag: while the decoration is globally disabled it dumps the **full**
|
|
724
|
+
index name with an explicit `decorate: false`, so a dumped schema stays correct even if the flag is
|
|
725
|
+
flipped back on before it is loaded.
|
|
726
|
+
|
|
661
727
|
## Docs
|
|
662
728
|
|
|
663
729
|
[CHANGELOG](docs/CHANGELOG.md)
|
data/Rakefile
CHANGED
|
File without changes
|
data/docs/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,43 @@
|
|
|
1
1
|
# ElasticsearchRecord - CHANGELOG
|
|
2
2
|
|
|
3
|
+
## [2.0.0] - 2026-08-10
|
|
4
|
+
* [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
|
+
* [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)_
|
|
6
|
+
* [add] `QueryMethods#select` raises on metadata fields _(`_id`, `_score`, ...)_ - they are always returned anyway
|
|
7
|
+
* [add] `ElasticsearchRecord::Result` now resolves **tabular** responses _(`sql`: columns + rows, `esql`: columns + values)_, so `find_by_sql` _(String)_ & `find_by_esql` instantiate records - rows are zipped against the **response** columns
|
|
8
|
+
* [add] `CalculationMethods#matrix_stats` raises an `ArgumentError` for less than two columns - the metric quantifies the relationship **between** fields
|
|
9
|
+
* [add] `ResultMethods#meta_only!` to resolve the metadata nodes _(`_id`, `_score`, ...)_ of each hit without transferring the `_source`
|
|
10
|
+
* [add] `Query::COLUMNS_NONE` constant for the `'!'` projection marker - replaces the bare literal in `visit_Selects` & `meta_only!`
|
|
11
|
+
* [add] specs for `ElasticsearchRecord::Persistence`, `ModelApi`, `SchemaMigration`, `Querying::ClassMethods`, the `Relation` methods _(Core, Query, Calculation & Result)_, the `Elasticsearch::SchemaStatements`, `TableStatements` & `SchemaDumper`, every class of the `Elasticsearch::Type` namespace _(incl. its `TYPE_MAP` registration)_, `Arel::Collectors::ElasticsearchQuery` and all three `Arel::Visitors::Elasticsearch*` visitors - incl. the 1.8.1 "only ten migrations" and 1.8.2 nested-reset regressions and a `TestIndexWithAutoIncrement` support that builds a model against a real `auto_increment` schema _(destructive calls still run through `TestIndex` and its name guard)_. Behaviours pinned as-is:
|
|
12
|
+
* `#or` compiles into a **failed** query _(the visitor fails every `Arel::Nodes::Grouping`)_, and AR's blank-arg stripping silently drops `configure(key, nil)` _(only the Hash form removes a key)_
|
|
13
|
+
* an index **alias** is dumped as a mapping of the elasticsearch 'alias' field type, which makes the `t.alias` branch of the dumper unreachable
|
|
14
|
+
* opposed nil-handling of metas _(deleted)_ and settings _(kept through `:__force__`)_
|
|
15
|
+
* a failed `create` has no `FAILED_BODIES` entry and falls back to an empty body; a multi-column `order` keeps only the last sort
|
|
16
|
+
* `SchemaMigration#count` and `#delete_version` / `#delete_all_versions` cannot be served by elasticsearch
|
|
17
|
+
* `_update_record` / `_delete_record` always return `0` _(a document-API response carries no `total`)_, and an auto-incremented id is only written as document `_id`, never into the mapped primary-key field
|
|
18
|
+
* `meta_only!` **spawns** instead of mutating the receiver _(unlike the other bang methods)_ and pins the `COLUMNS_NONE` projection; `pit_results` pins its infinite-loop guard
|
|
19
|
+
* [ref] **BREAKING**: every table statement resolves its table (index) name through `#_env_table_name` **by default** - the `table_name_prefix` / `table_name_suffix` no longer have to be applied by hand, which silently wrote into another environment's index. A new `decorate:`-argument _(default: `true`)_ switches it off per call for already resolved names. Only TABLE names are decorated; schema statements stay undecorated
|
|
20
|
+
* [ref] `SchemaDumper#table` dumps the **base** name instead of a `_env_table_name(...)` call - if that name would not resolve back, the full name is dumped with an explicit `decorate: false`
|
|
21
|
+
* [ref] **BREAKING**: `#open_tables`, `#close_tables`, `#refresh_tables` & `#truncate_tables` no longer subtract the AR-internal indices _(an explicitly named index was silently dropped)_ - plain loops now, forwarding `decorate:` to their singular statement and returning an `Array` for every provided name
|
|
22
|
+
* [ref] `TableStatements` drops the stale `:rename_table` entry from `define_unsupported_method` - the real implementation overwrote it anyway
|
|
23
|
+
* [ref] `#restore_table` replaces its `open`-argument with `unblock:` _(default: true)_ - a restore clones and inherits the 'write'-block, so the table was open but **read-only**. `ModelApi#restore!` follows along
|
|
24
|
+
* [ref] `ResultMethods#pit_results` resolves through `ElasticsearchRecord::Result` and respects the current projection _(`ids_only` removed in favour of `meta_only!`)_
|
|
25
|
+
* [ref] `TestIndex` spec support to optionally create/drop a second index _(still guarded by the `ALLOWED` name check)_
|
|
26
|
+
* [fix] `#rename_table` to resolve both names **itself** - the `clear_data_source_cache!` and `cluster_health` calls in between were left with the undecorated name
|
|
27
|
+
* [fix] `#backup_table` to build the auto-generated target from the **already resolved** name _(the suffix landed behind the `-snapshot-` part)_
|
|
28
|
+
* [fix] `#_env_table_name` to cast prefix & suffix - an empty `table_name_prefix:` yml entry resolves to `nil` and raised a `TypeError`
|
|
29
|
+
* [fix] `Result#cast_values` to resolve values from the `_source` node, metadata fields from the document level
|
|
30
|
+
* [fix] `visit_Selects` to no longer provide metadata fields to the `_source`-filter _(never matched)_ - projects `_source: false` if only metadata is selected
|
|
31
|
+
* [fix] `Querying::ClassMethods#esql` & `#msearch` to dispatch through the public `exec_query` _(rails 7.1 made `internal_exec_query` private)_ - the `async:`-argument was dropped along with it
|
|
32
|
+
* [fix] `Querying::ClassMethods#find_by_sql` to reference the provided `sql` for `String` queries _(undefined `query_or_sql` raised a `NameError`)_
|
|
33
|
+
* [fix] `SchemaStatements#max_result_window` to resolve the **flat** setting key and cast it to an `Integer` _(always fell back to 10000 and broke the batch_size guards)_
|
|
34
|
+
* [fix] `SchemaStatements#primary_keys` to always return an `Array` _(the `_meta` branch returned a raw String)_
|
|
35
|
+
* [fix] `CalculationMethods#count` to apply the SQL `LIMIT n OFFSET m` semantic on the resolved total - `terminate_after` alone acts **per shard** and never fires on a count query. `#size` on an unloaded relation was affected as well
|
|
36
|
+
* [fix] `UpdateTableDefinition#change_mapping_attributes` to resolve the current mapping type with a **String** key _(fell back to `:object` / `:nested`, so elasticsearch rejected every mapping parameter)_
|
|
37
|
+
* [fix] `#restore_table` to no longer touch the backup after a `drop_backup: true` _(it no longer exists at that point)_
|
|
38
|
+
* [fix] `_insert_with_auto_increment` to write the **plain, integer** id into `_meta.auto_increment` - the block returns an Array of the `returning` values, which was stored as-is and raised on the NEXT insert. Now unwraps Array / Hash / plain and casts to `Integer`
|
|
39
|
+
* [fix] `ResultMethods#pit_delete` to no longer `select('_id')` _(rejected by the new metadata guard)_ - resolves the ids through `meta_only!`
|
|
40
|
+
|
|
3
41
|
## [1.8.2] - 2024-11-26
|
|
4
42
|
* [fix] `ElasticsearchRecord::Relation::QueryMethods#build_query_clause` to raise an exception on `nil` assignments
|
|
5
43
|
* [fix] `Arel::Visitors::ElasticsearchBase#compile` to always reset temporary assignments _(causes missing assignments after a query-build-exception)_
|
|
@@ -18,6 +56,14 @@
|
|
|
18
56
|
* [add] new repository branch `rails-7-1-stable` to support different rails version
|
|
19
57
|
* [ref] gemspec to lock on rails 7.1
|
|
20
58
|
|
|
59
|
+
## [1.7.5] - 2024-11-26 _(no gem release)_
|
|
60
|
+
* [ref] `ElasticsearchRecord::Relation::QueryMethods#build_query_clause` to raise an exception instead of building an empty `QueryClause`
|
|
61
|
+
|
|
62
|
+
## [1.7.4] - 2024-11-25 _(no gem release)_
|
|
63
|
+
* [fix] `Arel::Visitors::ElasticsearchBase#compile` to always reset temporary assignments _(causes missing assignments after a query-build-exception)_
|
|
64
|
+
* [fix] `Arel::Nodes::SelectAgg` to not merge nil-values
|
|
65
|
+
* [fix] `ElasticsearchRecord::Relation::QueryMethods#build_query_clause` to prevent nil-Array assignment _(e.g. `[nil]` causes q query exception)_
|
|
66
|
+
|
|
21
67
|
## [1.7.3] - 2024-05-07 _(no gem release)_
|
|
22
68
|
* [add] new elasticsearch mapping types _(percolator, geo, vector, texts, ...)_
|
|
23
69
|
* [ref] `ElasticsearchRecord::Relation#limit` to detect `Float::INFINITY` to also set the **max_result_window**
|
data/docs/CODE_OF_CONDUCT.md
CHANGED
|
File without changes
|
data/docs/LICENSE
CHANGED
|
File without changes
|
|
@@ -35,7 +35,7 @@ DESC
|
|
|
35
35
|
spec.add_dependency 'activerecord', '~> 7.1.0'
|
|
36
36
|
spec.add_dependency 'elasticsearch', '>= 7.17'
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
spec.add_development_dependency 'coveralls_reborn', '~> 0.25'
|
|
39
39
|
spec.add_development_dependency 'rspec', '~> 3.0'
|
|
40
40
|
spec.add_development_dependency 'rake', "~> 13.0"
|
|
41
41
|
spec.add_development_dependency 'yard', '~> 0.9'
|
|
File without changes
|
|
@@ -131,14 +131,29 @@ module ActiveRecord
|
|
|
131
131
|
|
|
132
132
|
private
|
|
133
133
|
|
|
134
|
+
# gets called for all queries - a +ElasticsearchRecord::Query+ must be provided.
|
|
135
|
+
# @param [ElasticsearchRecord::Query] query
|
|
136
|
+
# @param [String (frozen),nil] name
|
|
137
|
+
# @param [Array] binds - not supported on the top-level and therefore ignored!
|
|
138
|
+
# @param [Boolean] prepare - used by the default AbstractAdapter - but not supported and therefore never ignored!
|
|
139
|
+
# @param [Boolean] async
|
|
140
|
+
# @return [ElasticsearchRecord::Result]
|
|
141
|
+
def internal_exec_query(query, name = "QUERY", binds = [], prepare: false, async: false)
|
|
142
|
+
build_result(
|
|
143
|
+
internal_execute(query, name, async: async),
|
|
144
|
+
columns: query.columns
|
|
145
|
+
)
|
|
146
|
+
end
|
|
147
|
+
|
|
134
148
|
# Executes the query object in the context of this connection and returns the raw result
|
|
135
149
|
# from the connection adapter.
|
|
136
150
|
# @param [ElasticsearchRecord::Query] query
|
|
137
151
|
# @param [String (frozen),nil] name
|
|
138
|
-
# @param [Boolean] async (default: false)
|
|
152
|
+
# @param [Boolean] async (default: false) - NOT supported!
|
|
139
153
|
# @param [Boolean] allow_retry (default: false)
|
|
154
|
+
# @param [Boolean] materialize_transactions (default: false) - NOT supported!
|
|
140
155
|
# @return [ElasticsearchRecord::Result]
|
|
141
|
-
def internal_execute(query, name = nil, async: false, allow_retry: false, materialize_transactions:
|
|
156
|
+
def internal_execute(query, name = nil, async: false, allow_retry: false, materialize_transactions: false)
|
|
142
157
|
# validate the query
|
|
143
158
|
raise ActiveRecord::StatementInvalid, 'Unable to execute! Provided query is not a "ElasticsearchRecord::Query".' unless query.is_a?(ElasticsearchRecord::Query)
|
|
144
159
|
raise ActiveRecord::StatementInvalid, 'Unable to execute! Provided query is invalid.' unless query.valid?
|
|
@@ -146,21 +161,7 @@ module ActiveRecord
|
|
|
146
161
|
# checks for write query - raises an exception if connection is locked to readonly ...
|
|
147
162
|
check_if_write_query(query)
|
|
148
163
|
|
|
149
|
-
api(
|
|
150
|
-
end
|
|
151
|
-
|
|
152
|
-
# gets called for all queries - a +ElasticsearchRecord::Query+ must be provided.
|
|
153
|
-
# @param [ElasticsearchRecord::Query] query
|
|
154
|
-
# @param [String (frozen),nil] name
|
|
155
|
-
# @param [Array] binds - not supported on the top-level and therefore ignored!
|
|
156
|
-
# @param [Boolean] prepare - used by the default AbstractAdapter - but not supported and therefore never ignored!
|
|
157
|
-
# @param [Boolean] async
|
|
158
|
-
# @return [ElasticsearchRecord::Result]
|
|
159
|
-
def internal_exec_query(query, name = "QUERY", binds = [], prepare: false, async: false)
|
|
160
|
-
build_result(
|
|
161
|
-
internal_execute(query, name, async: async),
|
|
162
|
-
columns: query.columns
|
|
163
|
-
)
|
|
164
|
+
api(query.gate, query.query_arguments, name, async: async, allow_retry: allow_retry, materialize_transactions: materialize_transactions)
|
|
164
165
|
end
|
|
165
166
|
end
|
|
166
167
|
end
|
|
File without changes
|
|
File without changes
|
data/lib/active_record/connection_adapters/elasticsearch/schema_definitions/attribute_methods.rb
CHANGED
|
File without changes
|
|
File without changes
|
data/lib/active_record/connection_adapters/elasticsearch/schema_definitions/column_methods.rb
CHANGED
|
File without changes
|
|
@@ -14,6 +14,12 @@ module ActiveRecord
|
|
|
14
14
|
@aliases = HashWithIndifferentAccess.new
|
|
15
15
|
@metas = HashWithIndifferentAccess.new
|
|
16
16
|
|
|
17
|
+
# HINT: Currently there is only one attribute, that cannot be assigned to any of the top hash kind, and also not opts:
|
|
18
|
+
# * dynamic
|
|
19
|
+
# This attribute must be assigned below the *mappings* node but as sibling to *properties*.
|
|
20
|
+
# This can only be done to create a special kind of options (@flags ???) and assign & fetch them within the required arel.
|
|
21
|
+
# Since this only setting can also be assigned to each individual mapping, there is currently no need to build this ...
|
|
22
|
+
|
|
17
23
|
transform_settings!(settings) if settings.present?
|
|
18
24
|
transform_mappings!(mappings) if mappings.present?
|
|
19
25
|
transform_aliases!(aliases) if aliases.present?
|
|
File without changes
|
data/lib/active_record/connection_adapters/elasticsearch/schema_definitions/table_definition.rb
CHANGED
|
File without changes
|
data/lib/active_record/connection_adapters/elasticsearch/schema_definitions/table_meta_definition.rb
CHANGED
|
File without changes
|
|
@@ -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','routing.allocation.initial_recovery','resize'].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
|
|
@@ -19,13 +19,12 @@ module ActiveRecord
|
|
|
19
19
|
FINAL_NAMES = ['number_of_shards', 'routing_partition_size', 'soft_deletes.enabled'].freeze
|
|
20
20
|
|
|
21
21
|
# static names can only be set during index creation or closed
|
|
22
|
-
STATIC_NAMES = ['number_of_routing_shards', 'codec',
|
|
22
|
+
STATIC_NAMES = ['number_of_routing_shards', 'codec', 'mode',
|
|
23
23
|
'soft_deletes.retention_lease.period',
|
|
24
24
|
'load_fixed_bitset_filters_eagerly', 'shard.check_on_startup',
|
|
25
25
|
|
|
26
26
|
# modules
|
|
27
|
-
'analysis', 'routing', 'unassigned', 'merge', 'similarity', 'search', 'store', '
|
|
28
|
-
'indexing_pressure'].freeze
|
|
27
|
+
'analysis', 'routing', 'unassigned', 'merge', 'similarity', 'search', 'store', 'indexing_pressure'].freeze
|
|
29
28
|
|
|
30
29
|
# dynamic names can always be changed
|
|
31
30
|
DYNAMIC_NAMES = ['number_of_replicas', 'auto_expand_replicas', "search.idle.after", 'refresh_interval',
|
|
@@ -34,7 +33,10 @@ module ActiveRecord
|
|
|
34
33
|
'max_refresh_listeners', 'analyze.max_token_count', 'highlight.max_analyzed_offset',
|
|
35
34
|
'max_terms_count', 'max_regex_length', 'query.default_field', 'routing.allocation.enable',
|
|
36
35
|
'routing.rebalance.enable', 'gc_deletes', 'default_pipeline', 'final_pipeline',
|
|
37
|
-
'hidden', 'blocks'
|
|
36
|
+
'hidden', 'blocks',
|
|
37
|
+
|
|
38
|
+
# modules
|
|
39
|
+
'translog'].freeze
|
|
38
40
|
|
|
39
41
|
VALID_NAMES = (FINAL_NAMES + STATIC_NAMES + DYNAMIC_NAMES).freeze
|
|
40
42
|
|
|
@@ -66,7 +66,9 @@ module ActiveRecord
|
|
|
66
66
|
raise ArgumentError, "you cannot change an unknown mapping '#{name}'" if current_mapping.blank?
|
|
67
67
|
|
|
68
68
|
# build new mapping
|
|
69
|
-
mapping
|
|
69
|
+
# IMPORTANT: the received mapping is a String-keyed Hash - a symbol access would resolve
|
|
70
|
+
# nil here, which +TableMappingDefinition+ then falls back to :object / :nested
|
|
71
|
+
mapping = new_mapping_definition(name, current_mapping['type'], **options, &block)
|
|
70
72
|
define! ChangeMappingDefinition, mapping
|
|
71
73
|
|
|
72
74
|
# check if the mapping is assigned as new primary_key
|
|
File without changes
|
|
@@ -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
|
-
|
|
61
|
-
|
|
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 " #{
|
|
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
|
|
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
|
|
101
|
-
schema_migration.
|
|
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
|
|
111
|
-
inserting.each { |iversion| schema_migration.
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
181
|
+
response = api('indices.get', { index: table_name, expand_wildcards: [:open, :closed], features: features, flat_settings: true }, 'SCHEMA')
|
|
182
182
|
else
|
|
183
|
-
response = api(
|
|
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:
|
|
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
|
|
208
|
-
ActiveRecord::ConnectionAdapters::ElasticsearchAdapter::
|
|
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:
|
|
232
|
-
virtual:
|
|
233
|
-
fields:
|
|
231
|
+
meta: field['meta'],
|
|
232
|
+
virtual: field['virtual'],
|
|
233
|
+
fields: field['fields'],
|
|
234
234
|
properties: field['properties'],
|
|
235
|
-
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
|
|
254
|
-
#
|
|
255
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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(:
|
|
396
|
+
response = api(:info, {}, 'CLUSTER INFO')
|
|
392
397
|
|
|
393
398
|
{
|
|
394
|
-
name:
|
|
395
|
-
cluster_name:
|
|
396
|
-
cluster_uuid:
|
|
397
|
-
version:
|
|
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(
|
|
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(
|
|
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
|
|
473
|
-
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'] }
|