typed_eav 0.5.0 → 0.7.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 (37) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +207 -0
  3. data/README.md +271 -61
  4. data/app/models/typed_eav/field/base.rb +90 -33
  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/option.rb +0 -8
  10. data/app/models/typed_eav/section.rb +11 -5
  11. data/app/models/typed_eav/value.rb +95 -32
  12. data/db/migrate/20260430000000_add_parent_scope_to_typed_eav_partitions.rb +1 -1
  13. data/db/migrate/20260712000000_enforce_parent_scope_invariant.rb +54 -0
  14. data/db/migrate/20260816000000_use_partial_covering_scalar_indexes.rb +198 -0
  15. data/lib/generators/typed_eav/scaffold/templates/controllers/typed_eav_controller.rb +1 -9
  16. data/lib/typed_eav/bulk_read.rb +41 -9
  17. data/lib/typed_eav/bulk_upsert.rb +141 -0
  18. data/lib/typed_eav/bulk_write.rb +65 -39
  19. data/lib/typed_eav/config.rb +19 -21
  20. data/lib/typed_eav/csv_mapper.rb +1 -1
  21. data/lib/typed_eav/engine.rb +16 -27
  22. data/lib/typed_eav/entity_query.rb +42 -8
  23. data/lib/typed_eav/event_dispatcher.rb +21 -30
  24. data/lib/typed_eav/field/typed_storage.rb +48 -0
  25. data/lib/typed_eav/field_deletion.rb +75 -0
  26. data/lib/typed_eav/filter_query.rb +9 -7
  27. data/lib/typed_eav/has_typed_eav/instance_methods.rb +11 -8
  28. data/lib/typed_eav/partition.rb +8 -3
  29. data/lib/typed_eav/query_builder.rb +17 -27
  30. data/lib/typed_eav/registry.rb +7 -8
  31. data/lib/typed_eav/schema_portability/import_index.rb +58 -0
  32. data/lib/typed_eav/schema_portability.rb +22 -25
  33. data/lib/typed_eav/version.rb +1 -1
  34. data/lib/typed_eav/versioning/subscriber.rb +25 -29
  35. data/lib/typed_eav/versioning.rb +59 -41
  36. data/lib/typed_eav.rb +2 -0
  37. metadata +19 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2584a7a3e9eab294e874931c8202f2dae2082be6a036bd419fce7df3fdd94f56
4
- data.tar.gz: 7d24090e4969e89bb268f89510b46ce034d8f9f045054c0d6d80828c4cd3006c
3
+ metadata.gz: 37b913418c998e4620c90efb72a0be8c93b838a2977db0347c9198b743ced2f3
4
+ data.tar.gz: 0a8a7f3a622712cc29b5738b8b0f4a54e3df3ffc56657277a69ebb956d3c46bf
5
5
  SHA512:
6
- metadata.gz: 4aeaa932e2dff4d5ab22ae95c66d98953b93163fc9580445f05a365ce1489a56cfdbaf998e47548d5fe468227341eec170409eafad5191449cb96201039900d0
7
- data.tar.gz: 1b626f58647506b2d0317e382d0fd2d8ebf7daae1c11e84dc9d58b1929107af9c62854c19c39a6244e9957d1e20ccb7b3c924c7aee9a77aa19414453c356e534
6
+ metadata.gz: c5bdc06300d8bb92538c93cfe23ac3afb6bed9719a29f6f7590740e6d4f56ac14998e21b4230c58fa2eed10e89dfe053af86144f3cbcf9637049c3d5b811d90b
7
+ data.tar.gz: 4760251616a62f91b7ed931dafc8b92a25653047cab8fcbd21dd027fe6353fdd2e7209a0629233306c69f77ec8eec08459854e8cc81443595d0b68e0503f4e8b
data/CHANGELOG.md CHANGED
@@ -5,6 +5,197 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [Unreleased]
9
+
10
+ No unreleased changes.
11
+
12
+ ## [0.7.0] - 2026-08-18
13
+
14
+ This release hardens TypedEAV's correctness and operational behavior, replaces
15
+ the highest-impact read N+1 path, and adds explicitly bounded bulk-maintenance
16
+ APIs. It keeps the typed-column architecture and existing semantic write path;
17
+ it does not claim that one storage design or batch size wins every workload.
18
+
19
+ ### Highlights
20
+
21
+ - Batch partition resolution in `typed_eav_hash_for(records)` so the accepted
22
+ 1,000-scope BulkRead shape executes three SQL statements instead of 1,002.
23
+ - Replace the six scalar value indexes with smaller partial-covering indexes
24
+ that omit irrelevant NULL cells while preserving index-only entity reads.
25
+ - Add an explicit reduced-semantics PostgreSQL bulk-upsert API and opt-in
26
+ chunked transactions for applications that knowingly prefer throughput or
27
+ bounded commits over the full semantic write envelope.
28
+ - Write `ValueVersion` audit rows in the same source transaction as each
29
+ `Value`, so either both persist or both roll back.
30
+ - Add SQL-narrowed default backfills and resumable, callback-preserving,
31
+ keyset-batched field deletion.
32
+
33
+ ### Changed
34
+
35
+ - Query operands are normalized and validated by the owning Field before SQL is
36
+ built. This keeps scalar, range, array, Currency, Reference, and text-search
37
+ operands aligned with write semantics; Active Record remains responsible for
38
+ SQL bind plumbing.
39
+ - `typed_eav_hash_for(records)` resolves all effective partition definitions in
40
+ one batched query, loads values once, and preloads field associations once.
41
+ Its public return shape, logical-missing behavior, partition precedence, and
42
+ orphan filtering are unchanged.
43
+ - Versioning now installs synchronous `Value` lifecycle callbacks instead of
44
+ writing audit rows from an internal after-commit subscriber. A failed audit
45
+ insert now rolls back the source mutation instead of leaving an unversioned
46
+ committed `Value`.
47
+ - The six scalar indexes now use `(field_id, value) INCLUDE (entity_id) WHERE
48
+ value IS NOT NULL`. The migration creates every replacement concurrently
49
+ before dropping its legacy index; rollback recreates legacy indexes before
50
+ removing replacements.
51
+ - Field and Section partition mutations validate their own pending tuple rather
52
+ than leaking scope changes through shared lookup state. Value pending state is
53
+ also preserved across validation and lifecycle boundaries.
54
+ - JSONB and TypedEAV are documented as workload-dependent storage choices.
55
+ Applications can own expression B-tree indexes for stable JSONB paths and GIN
56
+ indexes for containment; TypedEAV supplies stable typed columns and ordinary
57
+ per-type indexes. No final storage winner is claimed without representative
58
+ workload evidence.
59
+
60
+ ### Added
61
+
62
+ - SQL-narrowed default backfills through an exact-host relation, while retaining
63
+ partition checks, batching, callbacks, validations, idempotence, versions,
64
+ error reporting, and Field-owned logical-missing detection across multi-cell
65
+ storage.
66
+ - `Field::Base#destroy_with_values_in_batches!`, a callback-preserving,
67
+ keyset-batched exact-field deletion path with locked bounded finalization. A
68
+ failed batch leaves the Field available for inspection and retry.
69
+ - `bulk_upsert_typed_eav_values`, an explicitly reduced-semantics PostgreSQL
70
+ upsert. Callers must pass `acknowledge_reduced_semantics: true`; values are
71
+ cast and validated before SQL, while host saves, persistence callbacks,
72
+ versioning, delete shorthand, and per-record savepoints are intentionally
73
+ skipped.
74
+ - `transaction: :chunks, chunk_size: N` for both semantic BulkWrite and the
75
+ reduced upsert. Completed chunks remain committed if a later chunk fails;
76
+ `transaction: :all` remains the default.
77
+
78
+ ### Fixed
79
+
80
+ - Validate Field defaults through the same typed domain rules used by ordinary
81
+ writes, including range, option, reference, array, Currency, and multi-cell
82
+ logical-missing semantics.
83
+ - Fail closed when versioning callbacks are missing, duplicated, installed on
84
+ the wrong lifecycle kind, or configured across different connection pools.
85
+ - Lock and drain only the exact Field's remaining Values during deletion,
86
+ preserving callback/version ordering across retry and race boundaries.
87
+ - Keep Strong Migrations-compatible constraint validation outside a migration
88
+ transaction while preserving the generated-consumer migration path.
89
+ - Reject normalized duplicate bulk-upsert keys such as `:age` and `"age"`
90
+ before issuing SQL.
91
+
92
+ ### Performance
93
+
94
+ - BulkRead characterization reduced the 1,002 statements observed across 1,000
95
+ scopes to three for the same shape. This is a statement-count result, not a
96
+ representative throughput claim.
97
+ - BulkWrite evidence remains bounded to the exercised 100- and 1,000-host
98
+ lanes; no 10k/100k throughput or universal batch-size claim is made.
99
+ - Representative PostgreSQL 15, 16, and 18 migration/catalog checks passed for
100
+ the partial-covering indexes. PostgreSQL 17 planner and benchmark timings are
101
+ retained as co-tenant diagnostic evidence rather than portable latency
102
+ promises.
103
+
104
+ ### Reliability
105
+
106
+ - ValueVersion rows are written in the source transaction, preserving atomic
107
+ rollback with the Value mutation and avoiding a false after-commit rollback
108
+ assumption.
109
+ - The parent-scope check-constraint migration validates existing rows before
110
+ adding constraints with Strong Migrations-compatible nontransactional DDL;
111
+ the unchanged consumer migration canary passed.
112
+
113
+ ### Upgrade notes
114
+
115
+ - Copy/install the latest engine migrations and run the application's normal
116
+ migration command. The parent-scope validation and scalar-index migrations
117
+ use `disable_ddl_transaction!`; do not wrap them in an application-level
118
+ transaction. Concurrent create-before-drop ordering avoids an index-coverage
119
+ gap during normal PostgreSQL deployment.
120
+ - Versioned applications should expect an audit-write failure to abort the
121
+ `Value` mutation. `Value` and `ValueVersion` must share one connection pool;
122
+ versioning fails closed when they do not.
123
+ - Semantic BulkWrite still runs host validations/callbacks, Value persistence
124
+ callbacks, and versioning. Under the default `transaction: :all`, individual
125
+ validation/save failures are isolated by savepoints and successful records
126
+ may commit, while an uncaught exception rolls back the outer transaction.
127
+ `transaction: :chunks` deliberately permits earlier chunks to remain
128
+ committed after a later failure.
129
+ - `bulk_set_typed_eav_values_per_record` uses records as Hash keys; duplicate
130
+ Active Record instances for the same persisted row collapse to one entry.
131
+ Use ordered separate calls when two updates to the same row must both occur.
132
+ - The reduced bulk upsert accepts persisted, unique records and one uniform
133
+ values Hash, returns the number of upserted value rows, and intentionally does
134
+ not provide semantic successes/errors or callback/version guarantees.
135
+ - The supported runtime contract remains Ruby 3.3–4.0, Rails 7.2–8.1, and
136
+ PostgreSQL 15–18.
137
+
138
+ ### Evidence boundaries
139
+
140
+ - The BulkRead result is an exact SQL-statement-count improvement for the
141
+ characterized public path, not a universal throughput or latency guarantee.
142
+ - BulkWrite measurements cover 100- and 1,000-host lanes only. Applications
143
+ should choose chunk sizes from their own callback cost, transaction duration,
144
+ lock contention, and failure-recovery needs.
145
+ - Public chained-IN multi-filter SQL remains the supported strategy. Optional
146
+ `pg_trgm` and dependency statistics stay application-owned and measured;
147
+ TypedEAV does not automatically install extensions, specialized indexes, or
148
+ extended statistics.
149
+ - No representative storage tournament selected TypedEAV, JSONB, per-type EAV,
150
+ or conventional columns as a universal winner.
151
+
152
+ ## [0.6.0] - 2026-07-13
153
+
154
+ Hardens correctness, query efficiency, installation confidence, and release
155
+ safety while defining the supported runtime window. Existing public method
156
+ signatures and valid-record behavior remain unchanged. The compatibility floor
157
+ is intentionally raised to the release lines exercised by CI.
158
+
159
+ ### Changed
160
+
161
+ - Define the supported runtime contract as Ruby 3.3–4.0, Rails 7.2–8.1, and
162
+ PostgreSQL 15–18. Runtime dependency bounds now reject unverified future
163
+ Ruby and Rails release lines; prereleases remain outside the support
164
+ guarantee. Issue #26.
165
+
166
+ ### Fixed
167
+
168
+ - Prevent single-value reads from returning a stale value from a losing
169
+ partition, and enforce the parent-scope partition invariant in PostgreSQL.
170
+ Issues #22 and #23.
171
+ - Exercise packaged migrations in a generated Rails consumer application so
172
+ missing or non-idempotent installation paths fail before release. Issue #25.
173
+
174
+ ### Performance
175
+
176
+ - Remove obsolete option-cache invalidation and batch-load fields for already
177
+ loaded typed values. Issues #28 and #29.
178
+ - Bound scaffold visibility queries and preload existing values during default
179
+ backfills. Issues #30 and #31.
180
+ - Keep all-partitions filter unions inside SQL and batch schema-import lookups
181
+ and option writes. Issues #32 and #33.
182
+
183
+ ### Documentation
184
+
185
+ - Refresh maintainer, schema, and architecture guidance for the 0.5+ behavior
186
+ and current database invariants. Issue #34.
187
+
188
+ ### Release engineering
189
+
190
+ - Gate trusted publishing on the exact dereferenced tag commit, the complete
191
+ supported compatibility matrix, lint, package-content inspection, and a
192
+ real-host migration installation using the same checksummed gem artifact
193
+ that is sent to RubyGems. Issue #27.
194
+
195
+ ### References
196
+
197
+ - Issues #22, #23, and #25–#34.
198
+
8
199
  ## [0.5.0] - 2026-06-01
9
200
 
10
201
  Adds a human display label for fields, distinct from the immutable machine
@@ -258,6 +449,17 @@ internal helper relocation (see "Changed" below). Anchored by ADRs
258
449
 
259
450
  ### Internal
260
451
 
452
+ - Added the opt-in `Field#destroy_with_values_in_batches!` API for exact-field,
453
+ callback-preserving keyset deletion. It commits bounded Value destroy batches,
454
+ retains the Field across failure, and performs locked bounded finalization;
455
+ ordinary Field destruction and dependency policies remain unchanged.
456
+
457
+ - Atomic ValueVersion writes are installed from the Value callback chains at
458
+ boot, with an identical-pool guard and idempotent recovery if a callback is
459
+ removed. BulkWrite keeps caller context unchanged while correlating version
460
+ groups through its pending marker; EventDispatcher remains a public and
461
+ generic observer broker.
462
+
261
463
  - `TypedEAV::EventDispatcher` is retained as the synchronous broker
262
464
  between `TypedEAV::Hooks` and `ActiveSupport::Notifications`. The
263
465
  cleanup arc explicitly considered collapsing it and rejected that:
@@ -378,6 +580,11 @@ worked examples.
378
580
 
379
581
  Initial release.
380
582
 
583
+ [Unreleased]: https://github.com/dchuk/typed_eav/compare/v0.7.0...HEAD
584
+ [0.7.0]: https://github.com/dchuk/typed_eav/releases/tag/v0.7.0
585
+ [0.6.0]: https://github.com/dchuk/typed_eav/releases/tag/v0.6.0
586
+ [0.5.0]: https://github.com/dchuk/typed_eav/releases/tag/v0.5.0
587
+ [0.4.0]: https://github.com/dchuk/typed_eav/releases/tag/v0.4.0
381
588
  [0.3.2]: https://github.com/dchuk/typed_eav/releases/tag/v0.3.2
382
589
  [0.3.1]: https://github.com/dchuk/typed_eav/releases/tag/v0.3.1
383
590
  [0.3.0]: https://github.com/dchuk/typed_eav/releases/tag/v0.3.0