typed_eav 0.6.0 → 0.7.1

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 (36) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +187 -0
  3. data/README.md +258 -62
  4. data/app/models/typed_eav/field/base.rb +77 -27
  5. data/app/models/typed_eav/field/currency.rb +43 -0
  6. data/app/models/typed_eav/field/file.rb +1 -1
  7. data/app/models/typed_eav/field/image.rb +1 -1
  8. data/app/models/typed_eav/field/reference.rb +11 -0
  9. data/app/models/typed_eav/section.rb +11 -5
  10. data/app/models/typed_eav/value.rb +95 -32
  11. data/db/migrate/20260430000000_add_parent_scope_to_typed_eav_partitions.rb +1 -1
  12. data/db/migrate/20260712000000_enforce_parent_scope_invariant.rb +4 -0
  13. data/db/migrate/20260816000000_use_partial_covering_scalar_indexes.rb +198 -0
  14. data/lib/typed_eav/bulk_read.rb +13 -14
  15. data/lib/typed_eav/bulk_upsert.rb +137 -0
  16. data/lib/typed_eav/bulk_write.rb +65 -39
  17. data/lib/typed_eav/config.rb +19 -21
  18. data/lib/typed_eav/csv_mapper.rb +1 -1
  19. data/lib/typed_eav/engine.rb +16 -27
  20. data/lib/typed_eav/entity_query.rb +34 -9
  21. data/lib/typed_eav/event_dispatcher.rb +21 -30
  22. data/lib/typed_eav/field/typed_storage.rb +48 -0
  23. data/lib/typed_eav/field_deletion.rb +75 -0
  24. data/lib/typed_eav/filter_query.rb +2 -2
  25. data/lib/typed_eav/has_typed_eav/instance_methods.rb +2 -2
  26. data/lib/typed_eav/has_typed_eav.rb +1 -1
  27. data/lib/typed_eav/partition/definition_batch.rb +70 -0
  28. data/lib/typed_eav/partition.rb +2 -0
  29. data/lib/typed_eav/query_builder.rb +17 -27
  30. data/lib/typed_eav/registry.rb +7 -8
  31. data/lib/typed_eav/version.rb +1 -1
  32. data/lib/typed_eav/versioned.rb +8 -6
  33. data/lib/typed_eav/versioning/subscriber.rb +25 -29
  34. data/lib/typed_eav/versioning.rb +59 -41
  35. data/lib/typed_eav.rb +2 -0
  36. metadata +5 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 255a937ce445032caf1880728a75a7363dda1323f8d20650bb2c73380cdb0c82
4
- data.tar.gz: 3ff1c84f019c73902ae18812b19e8ab254d30813128f80fc7878adf0b43e21d1
3
+ metadata.gz: 312b444b0c7b29eb298af1ef5bba02d3ba56ad811399b3ffcd2dfd2afd88267b
4
+ data.tar.gz: cfa3ca85088eaff46dff6657d098f9fcf38fec9129b53792cfb37cc19e4358ad
5
5
  SHA512:
6
- metadata.gz: f0dc18a3c981530aba9c5be15acf07a1ce1a766845464aaa656876856f113d2b874e70173e9feb51bc2f88596c6bb9deea8a4b473d058e266c8ebc4e4584689c
7
- data.tar.gz: 1d76a14b8ad44f7f17aee8e7973a6a468536711ddfd299cf376cd9d4d41e643c0e84e4b648df1b75bb961cb93764789c9ff9e3c15baffea8f7c323ff64a8f566
6
+ metadata.gz: abcb3cde1dc9d09da1ea014165c245d74aa2ef2f5fd566ecc9f86343b8e30b1ca32bcc10eb9735a06a23641bb0ccef7c1078d2be288a724a20b6d7e44468e6d9
7
+ data.tar.gz: b259edd0ece6e756569a151194368fc0adda83063d56683f9113d6373c0eede174cb545f9d1ca0ccc70e2dd512239494299f3da8608567aa5e47989e3b780eb3
data/CHANGELOG.md CHANGED
@@ -7,6 +7,177 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.7.1] - 2026-09-04
11
+
12
+ A focused patch release for inherited Active Record hosts and multi-partition
13
+ bulk upserts. No new database migrations or public API changes are required.
14
+
15
+ ### Fixed
16
+
17
+ - Resolve inherited and namespaced Active Record host definitions, filters,
18
+ bulk reads, writes, and registry/versioning opt-ins through Rails'
19
+ canonical polymorphic name. STI subclasses now consistently share their
20
+ base class's EAV schema and stored values while queries retain their host
21
+ class restriction. Partition isolation and most-specific field precedence
22
+ are preserved. (#42)
23
+
24
+ ### Performance
25
+
26
+ - Batch BulkUpsert field-definition resolution into one SELECT per transaction
27
+ unit while preserving exact tuple isolation and definition precedence. A
28
+ 20-partition regression case now issues one definition SELECT instead of 20;
29
+ chunked transactions intentionally issue one per chunk. BulkRead and
30
+ BulkUpsert share the internal batched resolver. This is a query-count
31
+ improvement, not a universal throughput claim. (#43)
32
+
33
+ ### Compatibility and verification
34
+
35
+ - BulkUpsert's reduced-semantics acknowledgement, validation, transaction
36
+ boundaries, and callback/versioning behavior are unchanged.
37
+ - Regression coverage includes true STI and namespaced hosts, public
38
+ read/filter/write paths, partition isolation, definition precedence, and
39
+ all/chunk transaction behavior.
40
+
41
+ ## [0.7.0] - 2026-08-18
42
+
43
+ This release hardens TypedEAV's correctness and operational behavior, replaces
44
+ the highest-impact read N+1 path, and adds explicitly bounded bulk-maintenance
45
+ APIs. It keeps the typed-column architecture and existing semantic write path;
46
+ it does not claim that one storage design or batch size wins every workload.
47
+
48
+ ### Highlights
49
+
50
+ - Batch partition resolution in `typed_eav_hash_for(records)` so the accepted
51
+ 1,000-scope BulkRead shape executes three SQL statements instead of 1,002.
52
+ - Replace the six scalar value indexes with smaller partial-covering indexes
53
+ that omit irrelevant NULL cells while preserving index-only entity reads.
54
+ - Add an explicit reduced-semantics PostgreSQL bulk-upsert API and opt-in
55
+ chunked transactions for applications that knowingly prefer throughput or
56
+ bounded commits over the full semantic write envelope.
57
+ - Write `ValueVersion` audit rows in the same source transaction as each
58
+ `Value`, so either both persist or both roll back.
59
+ - Add SQL-narrowed default backfills and resumable, callback-preserving,
60
+ keyset-batched field deletion.
61
+
62
+ ### Changed
63
+
64
+ - Query operands are normalized and validated by the owning Field before SQL is
65
+ built. This keeps scalar, range, array, Currency, Reference, and text-search
66
+ operands aligned with write semantics; Active Record remains responsible for
67
+ SQL bind plumbing.
68
+ - `typed_eav_hash_for(records)` resolves all effective partition definitions in
69
+ one batched query, loads values once, and preloads field associations once.
70
+ Its public return shape, logical-missing behavior, partition precedence, and
71
+ orphan filtering are unchanged.
72
+ - Versioning now installs synchronous `Value` lifecycle callbacks instead of
73
+ writing audit rows from an internal after-commit subscriber. A failed audit
74
+ insert now rolls back the source mutation instead of leaving an unversioned
75
+ committed `Value`.
76
+ - The six scalar indexes now use `(field_id, value) INCLUDE (entity_id) WHERE
77
+ value IS NOT NULL`. The migration creates every replacement concurrently
78
+ before dropping its legacy index; rollback recreates legacy indexes before
79
+ removing replacements.
80
+ - Field and Section partition mutations validate their own pending tuple rather
81
+ than leaking scope changes through shared lookup state. Value pending state is
82
+ also preserved across validation and lifecycle boundaries.
83
+ - JSONB and TypedEAV are documented as workload-dependent storage choices.
84
+ Applications can own expression B-tree indexes for stable JSONB paths and GIN
85
+ indexes for containment; TypedEAV supplies stable typed columns and ordinary
86
+ per-type indexes. No final storage winner is claimed without representative
87
+ workload evidence.
88
+
89
+ ### Added
90
+
91
+ - SQL-narrowed default backfills through an exact-host relation, while retaining
92
+ partition checks, batching, callbacks, validations, idempotence, versions,
93
+ error reporting, and Field-owned logical-missing detection across multi-cell
94
+ storage.
95
+ - `Field::Base#destroy_with_values_in_batches!`, a callback-preserving,
96
+ keyset-batched exact-field deletion path with locked bounded finalization. A
97
+ failed batch leaves the Field available for inspection and retry.
98
+ - `bulk_upsert_typed_eav_values`, an explicitly reduced-semantics PostgreSQL
99
+ upsert. Callers must pass `acknowledge_reduced_semantics: true`; values are
100
+ cast and validated before SQL, while host saves, persistence callbacks,
101
+ versioning, delete shorthand, and per-record savepoints are intentionally
102
+ skipped.
103
+ - `transaction: :chunks, chunk_size: N` for both semantic BulkWrite and the
104
+ reduced upsert. Completed chunks remain committed if a later chunk fails;
105
+ `transaction: :all` remains the default.
106
+
107
+ ### Fixed
108
+
109
+ - Validate Field defaults through the same typed domain rules used by ordinary
110
+ writes, including range, option, reference, array, Currency, and multi-cell
111
+ logical-missing semantics.
112
+ - Fail closed when versioning callbacks are missing, duplicated, installed on
113
+ the wrong lifecycle kind, or configured across different connection pools.
114
+ - Lock and drain only the exact Field's remaining Values during deletion,
115
+ preserving callback/version ordering across retry and race boundaries.
116
+ - Keep Strong Migrations-compatible constraint validation outside a migration
117
+ transaction while preserving the generated-consumer migration path.
118
+ - Reject normalized duplicate bulk-upsert keys such as `:age` and `"age"`
119
+ before issuing SQL.
120
+
121
+ ### Performance
122
+
123
+ - BulkRead characterization reduced the 1,002 statements observed across 1,000
124
+ scopes to three for the same shape. This is a statement-count result, not a
125
+ representative throughput claim.
126
+ - BulkWrite evidence remains bounded to the exercised 100- and 1,000-host
127
+ lanes; no 10k/100k throughput or universal batch-size claim is made.
128
+ - Representative PostgreSQL 15, 16, and 18 migration/catalog checks passed for
129
+ the partial-covering indexes. PostgreSQL 17 planner and benchmark timings are
130
+ retained as co-tenant diagnostic evidence rather than portable latency
131
+ promises.
132
+
133
+ ### Reliability
134
+
135
+ - ValueVersion rows are written in the source transaction, preserving atomic
136
+ rollback with the Value mutation and avoiding a false after-commit rollback
137
+ assumption.
138
+ - The parent-scope check-constraint migration validates existing rows before
139
+ adding constraints with Strong Migrations-compatible nontransactional DDL;
140
+ the unchanged consumer migration canary passed.
141
+
142
+ ### Upgrade notes
143
+
144
+ - Copy/install the latest engine migrations and run the application's normal
145
+ migration command. The parent-scope validation and scalar-index migrations
146
+ use `disable_ddl_transaction!`; do not wrap them in an application-level
147
+ transaction. Concurrent create-before-drop ordering avoids an index-coverage
148
+ gap during normal PostgreSQL deployment.
149
+ - Versioned applications should expect an audit-write failure to abort the
150
+ `Value` mutation. `Value` and `ValueVersion` must share one connection pool;
151
+ versioning fails closed when they do not.
152
+ - Semantic BulkWrite still runs host validations/callbacks, Value persistence
153
+ callbacks, and versioning. Under the default `transaction: :all`, individual
154
+ validation/save failures are isolated by savepoints and successful records
155
+ may commit, while an uncaught exception rolls back the outer transaction.
156
+ `transaction: :chunks` deliberately permits earlier chunks to remain
157
+ committed after a later failure.
158
+ - `bulk_set_typed_eav_values_per_record` uses records as Hash keys; duplicate
159
+ Active Record instances for the same persisted row collapse to one entry.
160
+ Use ordered separate calls when two updates to the same row must both occur.
161
+ - The reduced bulk upsert accepts persisted, unique records and one uniform
162
+ values Hash, returns the number of upserted value rows, and intentionally does
163
+ not provide semantic successes/errors or callback/version guarantees.
164
+ - The supported runtime contract remains Ruby 3.3–4.0, Rails 7.2–8.1, and
165
+ PostgreSQL 15–18.
166
+
167
+ ### Evidence boundaries
168
+
169
+ - The BulkRead result is an exact SQL-statement-count improvement for the
170
+ characterized public path, not a universal throughput or latency guarantee.
171
+ - BulkWrite measurements cover 100- and 1,000-host lanes only. Applications
172
+ should choose chunk sizes from their own callback cost, transaction duration,
173
+ lock contention, and failure-recovery needs.
174
+ - Public chained-IN multi-filter SQL remains the supported strategy. Optional
175
+ `pg_trgm` and dependency statistics stay application-owned and measured;
176
+ TypedEAV does not automatically install extensions, specialized indexes, or
177
+ extended statistics.
178
+ - No representative storage tournament selected TypedEAV, JSONB, per-type EAV,
179
+ or conventional columns as a universal winner.
180
+
10
181
  ## [0.6.0] - 2026-07-13
11
182
 
12
183
  Hardens correctness, query efficiency, installation confidence, and release
@@ -307,6 +478,17 @@ internal helper relocation (see "Changed" below). Anchored by ADRs
307
478
 
308
479
  ### Internal
309
480
 
481
+ - Added the opt-in `Field#destroy_with_values_in_batches!` API for exact-field,
482
+ callback-preserving keyset deletion. It commits bounded Value destroy batches,
483
+ retains the Field across failure, and performs locked bounded finalization;
484
+ ordinary Field destruction and dependency policies remain unchanged.
485
+
486
+ - Atomic ValueVersion writes are installed from the Value callback chains at
487
+ boot, with an identical-pool guard and idempotent recovery if a callback is
488
+ removed. BulkWrite keeps caller context unchanged while correlating version
489
+ groups through its pending marker; EventDispatcher remains a public and
490
+ generic observer broker.
491
+
310
492
  - `TypedEAV::EventDispatcher` is retained as the synchronous broker
311
493
  between `TypedEAV::Hooks` and `ActiveSupport::Notifications`. The
312
494
  cleanup arc explicitly considered collapsing it and rejected that:
@@ -427,7 +609,12 @@ worked examples.
427
609
 
428
610
  Initial release.
429
611
 
612
+ [Unreleased]: https://github.com/dchuk/typed_eav/compare/v0.7.1...HEAD
613
+ [0.7.1]: https://github.com/dchuk/typed_eav/releases/tag/v0.7.1
614
+ [0.7.0]: https://github.com/dchuk/typed_eav/releases/tag/v0.7.0
430
615
  [0.6.0]: https://github.com/dchuk/typed_eav/releases/tag/v0.6.0
616
+ [0.5.0]: https://github.com/dchuk/typed_eav/releases/tag/v0.5.0
617
+ [0.4.0]: https://github.com/dchuk/typed_eav/releases/tag/v0.4.0
431
618
  [0.3.2]: https://github.com/dchuk/typed_eav/releases/tag/v0.3.2
432
619
  [0.3.1]: https://github.com/dchuk/typed_eav/releases/tag/v0.3.1
433
620
  [0.3.0]: https://github.com/dchuk/typed_eav/releases/tag/v0.3.0