Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
211 commits
Select commit Hold shift + click to select a range
08598df
Add comprehensive test suite for UpgradeGraph class
Feb 4, 2026
031546d
Add DeferredIndexConfig with defaults for deferred index execution
Feb 20, 2026
2ce4961
Add DeferredIndexOperation system tables and bootstrap upgrade step
Feb 20, 2026
c0ca5d9
Add DeferredIndexOperation domain class, enums, and DAO
Feb 21, 2026
ac910c3
Add DeferredAddIndex SchemaChange with visitor wiring and DAO interfa…
Feb 21, 2026
92257b1
Add SchemaEditor.addIndexDeferred() and visitor wiring for Stage 5
Feb 22, 2026
b92d7dc
Add auto-cancel and dependency tracking for deferred index operations
Feb 28, 2026
045d336
Add DeferredIndexExecutor, RecoveryService, and Validator (Stages 7-10)
Mar 1, 2026
5b87bbf
Add cross-platform deferred index dialect support (Stage 11)
Mar 2, 2026
67625fd
Add end-to-end integration tests for deferred index lifecycle (Stage 12)
Mar 2, 2026
a6c1e4d
Fix review findings: timestamp format, boolean column, and ChangeInde…
Mar 2, 2026
9738338
Cap retry backoff delay and fail upgrade on unresolved deferred indexes
Mar 2, 2026
6342d68
Fix review findings #4, #5, #7: backoff cap, validator throw, in-memo…
Mar 2, 2026
a50f492
Use BIG_INTEGER primary keys for both deferred index tables
Mar 3, 2026
fcb61a9
Replace N+1 queries with JOIN in DeferredIndexOperationDAO
Mar 3, 2026
7a691a9
Fix case-sensitivity inconsistencies in deferred index handling
Mar 3, 2026
286d7dc
Widen deferred index table name columns to SchemaValidator.MAX_LENGTH
Mar 3, 2026
47b00bc
Remove dead code and fix stale comment
Mar 3, 2026
66a3c52
Remove @ImplementedBy and @Inject from DAO since it is always constru…
Mar 3, 2026
f2c4248
Distinguish deferred index in human-readable upgrade output
Mar 3, 2026
6405207
Add config validation to deferred index services
Mar 3, 2026
d06bcbb
Add DeferredIndexService facade and make internal classes package-pri…
Mar 3, 2026
b68ffbe
Fix stale assertions in TestUpgradeSteps for deferred index tables
Mar 3, 2026
c801e00
Add unit tests to fill coverage gaps in deferred index feature
Mar 3, 2026
805bd6c
Improve test coverage for deferred index feature
Mar 3, 2026
1b7df21
Refactor deferred index services to use Guice constructor injection
Mar 3, 2026
594f2e3
Add force-immediate config to bypass deferred index creation
Mar 3, 2026
0ec04b8
Add force-deferred config to override immediate index creation
Mar 3, 2026
47591b2
Add coverage for forceImmediateIndexes and forceDeferredIndexes getters
Mar 3, 2026
84941aa
Merge origin/main into experimental/deferred-index-creation
Mar 3, 2026
253301f
Fix review findings: stale rename, negative IDs, SKIPPED status, javadoc
Mar 3, 2026
2b4fa36
Add DEBUG logging to deferred index services
Mar 3, 2026
0723c35
Code review fixes: remove dead code, simplify timestamps, decouple DAO
Mar 3, 2026
babf682
Refactor DeferredIndexChangeServiceImpl: extract SQL builders, add ja…
Mar 3, 2026
3f42844
Rename operationTimeoutSeconds to executionTimeoutSeconds, default 8h
Mar 3, 2026
d5984b1
Extract interfaces for DeferredIndexExecutor, DeferredIndexRecoverySe…
Mar 4, 2026
aa12945
Extract ExecutionResult/ExecutionStatus from DeferredIndexExecutor, r…
Mar 4, 2026
b885f8c
Replace polling with CompletableFuture, validate config in execute()
Mar 4, 2026
8533866
Ensure deferred index tables exist before parallel upgrade steps
Mar 4, 2026
4aa85ea
Rename DeferredIndexValidator to DeferredIndexReadinessCheck, wire in…
Mar 4, 2026
b792b48
Add DeferredIndexExecutorServiceFactory for pluggable thread pool cre…
Mar 4, 2026
cbf4147
Remove test-only constructor from DeferredIndexExecutorImpl
Mar 4, 2026
bfbcd84
Replace ScheduledExecutorService with per-operation progress logging
Mar 5, 2026
b15f6ea
Add getProgress() to DeferredIndexService facade
Mar 5, 2026
b607831
Executor cleanup: INFO/ERROR logging with elapsed time, autocommit re…
Mar 5, 2026
564bb99
Add Javadoc to all non-public methods across deferred index package
Mar 5, 2026
5ae9af7
Code review fixes: remove dead DAO methods, drop operationType column…
Mar 5, 2026
85056d9
Remove shutdown() from DeferredIndexExecutor interface
Mar 5, 2026
874c8b0
Add TDD integration tests for deferred index lifecycle (expect compil…
Mar 5, 2026
a5e7d41
Rename DeferredIndexConfig to DeferredIndexExecutionConfig, add force…
Mar 5, 2026
3ff3a90
Fix Mode 1: move readiness check before sourceSchema capture, add sch…
Mar 5, 2026
106f086
Executor crash recovery + remove recovery service from DeferredIndexS…
Mar 5, 2026
3894fe8
Remove recovery service, dead DAO method, fix lifecycle test index va…
Mar 5, 2026
4764e56
Code review fixes: dedup reconstructIndex, remove dead DAO methods, a…
Mar 5, 2026
ebb04e7
Remove redundant fields from executor, remove dead DAO insertOperatio…
Mar 6, 2026
b198c46
Simplify resetAllInProgressToPending, remove noOp(), fix javadoc wording
Mar 6, 2026
998e11c
Rename run/augment methods, extract awaitCompletion, add stale-index log
Mar 6, 2026
e3c3722
Fix DeferredIndexReadinessCheck Javadoc to describe both modes
Mar 9, 2026
824ae21
Code review fixes: inline upgrade tables, re-defer on ChangeIndex, re…
Mar 9, 2026
d50fd08
Remove unnecessary volatile from executionFuture field
Mar 9, 2026
af1dd91
Code review fixes: harden executor, DAO, config validation, fix flaky…
Mar 18, 2026
7ee6dda
Remove Mode 1/Mode 2, simplify to unified deferred index behavior
Mar 19, 2026
1624541
Add @see Javadoc to all @Override methods, split POJO test into per-f…
Mar 19, 2026
03bbf9a
Remove plan files from repo, add PLAN-*.md to .gitignore
Mar 19, 2026
b1c2148
Add CLAUDE.md to .gitignore
Mar 19, 2026
76b2d7f
Fix stray characters in readiness check Javadoc
Mar 19, 2026
e44d503
Add dialect-level deferred index support, fall back to immediate on u…
Mar 19, 2026
28a7eb1
Add supportsDeferredIndexCreation() override to H2v2 dialect, fix cha…
Mar 20, 2026
66c3f37
Remove DeferredIndexOperationColumn table, store columns as comma-sep…
Mar 24, 2026
0eb027f
Move deferred index config into UpgradeConfigAndContext, validate at …
Mar 24, 2026
3cb8049
Add deferredIndexCreationEnabled kill switch, disabled by default
Mar 24, 2026
31d43cb
Fix SonarCloud code smells: extract constants, clean up imports, refa…
Mar 24, 2026
3161ecf
Code review fixes: constants, Javadoc, private visibility, redundant …
Mar 24, 2026
b7a93fc
Backport code review fixes from comments-based branch
Apr 4, 2026
7d0bb7f
Add given/when/then structure to backported integration tests
Apr 5, 2026
135bc2e
Add DeployedIndexes table schema, POJO, DAO, and status enum
Apr 15, 2026
d213fae
Add isDeferred() and isPhysicallyPresent() to Index, deferred() to bu…
Apr 15, 2026
9fb3cfa
Add DeployedIndexesModelEnricher for schema enrichment and validation
Apr 15, 2026
5602fc3
Add DeployedIndexesChangeService for tracking all index ops during up…
Apr 15, 2026
71fa52c
Refactor visitor to use DeployedIndexesChangeService and model-based DDL
Apr 15, 2026
a0076f4
Wire DeployedIndexes into UpgradePath and Upgrade framework
Apr 16, 2026
1296a91
Add CreateDeployedIndexes upgrade step
Apr 16, 2026
d701220
Remove old deferred index infrastructure
Apr 16, 2026
9613511
Fix TestSchemaChangeSequence for new deferred-as-property model
Apr 16, 2026
dabfd71
Fix all tests for DeployedIndexes architecture
Apr 16, 2026
c315e1b
Implement prepopulation, getDeferredIndexStatements, and SchemaEditor…
Apr 16, 2026
5a6d680
Add integration tests and make performUpgrade return UpgradePath
Apr 16, 2026
58676bf
Complete integration tests, fix RenameIndex deferred preservation, im…
Apr 16, 2026
e0b3fa7
Add comprehensive unit tests and DeployedIndexes table state verifica…
Apr 16, 2026
c09a509
Add enricher unit tests, unique/multi-column deferred integration tests
Apr 16, 2026
11fc009
Add AddTable tracking test, final test iteration
Apr 16, 2026
8a80ba4
Complete all remaining tests: tracker API, sequential upgrade, given/…
Apr 16, 2026
4bedeb0
Remove dead executor config fields and fix stale javadoc references
Apr 16, 2026
7b92a87
Remove all DeferredIndexOperation/DeferredAddIndex/addIndexDeferred l…
Apr 16, 2026
296970a
Consolidate duplicate integration tests, extract schemaWith() helper
Apr 16, 2026
e7ef0d6
Address critical review findings: strengthen assertions, fix state le…
Apr 16, 2026
762fb36
Add missing edge case integration tests
Apr 16, 2026
0dceac8
Add re-upgrade idempotency test
Apr 16, 2026
6d28a96
Add remaining edge case tests: removeTable cleanup, crash recovery
Apr 16, 2026
fbbb652
Rename packages from deployed/deferred to deployedindexes
Apr 16, 2026
2509b44
Update documentation to 100%, add TODO for remaining items
Apr 16, 2026
fb8409e
Remove TODO file from repo (kept locally only)
Apr 16, 2026
f29f2d7
Fix ChangeColumn losing isDeferred() flag during column rename
Apr 16, 2026
b75086f
Remove dead deferredIndexThreadPoolSize config
Apr 16, 2026
6b04501
Dialect + EnrichedIndex cleanups
Apr 16, 2026
4f4104a
Split Index data from operational state (DeployedIndexState)
Apr 16, 2026
582b1e9
DeployedIndexes cleanup: drop upgradeUUID, widen indexColumns, rename…
Apr 16, 2026
5428146
Replace DeployedIndexState nullable-Boolean with IndexPresence enum
Apr 16, 2026
15e9546
Split enricher; rename Result -> EnrichedModel; document invariants
Apr 16, 2026
8edc40a
Return structured DeferredIndexJob from UpgradePath.getDeferredIndexS…
Apr 16, 2026
0821f76
Introduce DeployedIndexesStatementFactory; DAO and ChangeService dele…
Apr 16, 2026
e281cd2
Make DAO and StatementFactory package-private; expose resetInProgress…
Apr 16, 2026
ae55755
Fill unit-test gaps across deployedindexes package
Apr 16, 2026
9f5582a
Code-review fixes: fix tracker WHERE bug, tighten assertions, add tests
Apr 17, 2026
64de1f5
Tidy Upgrade.java: replace FQNs with imports, fold deferred-index-job…
Apr 17, 2026
707301c
P1.1: Delete convenience constructors; thread real DeployedIndexState…
Apr 18, 2026
8bb2524
P1.2: Enricher cleanup — single early-return, honest Morf-table treat…
Apr 18, 2026
1ec487b
P1.3: Introduce IndexKey value type — replace string-concat key in De…
Apr 18, 2026
e6979a8
P1.4: Visitor + schema-change structural improvements
Apr 18, 2026
46dc7cc
P1.5: Upgrade.java extractions + UpgradePath immutability + imports +…
Apr 18, 2026
548d1ad
P1.6: Renames + interface/impl splits for services in the deployedind…
Apr 18, 2026
e959cdf
P1.7: Javadoc pass — IndexPresence disambiguation + cross-refs
Apr 18, 2026
7d408e2
P1 gap-fill: kill-switch-off resolveDeferred test + Morf-table prepop…
Apr 18, 2026
a097ad5
Phase 2: Introduce UpgradeContext; restore SchemaEditor to pure-write
Apr 20, 2026
1e8f9e5
Fix deferred-index handling on dialects without deferred-creation sup…
Apr 20, 2026
c1d9f00
SP1: Slim visitor + service.prime — track only deferred indexes
Apr 20, 2026
e7fda4c
SP2 slim: enricher now primes service + virtualizes unbuilt deferred …
Apr 20, 2026
88bb554
SP3 slim: delete UpgradeContext + prepopulation + 3-arg execute overload
Apr 20, 2026
7218c3f
SP4 slim: document why collectDeferredIndexJobs stays schema-scan, no…
Apr 20, 2026
2c7dd2c
SP5 slim: drop indexDeferred column everywhere
Apr 20, 2026
54037c6
Dead-code audit: collapse isTracked() into isTrackedDeferred()
Apr 20, 2026
dc6fec4
Slim refactor: collapse persistence cluster (4 types -> 2 + helper + …
Apr 28, 2026
5a0630c
Rename DeployedIndexesSql -> DeployedIndexesStatements
Apr 28, 2026
2d94973
Make DeployedIndexesStatements injectable (no static helpers)
Apr 28, 2026
ec07910
Migrate slim to "row-existence = declared deferred" model
Apr 28, 2026
c56cb8c
Split DeployedIndexesModelEnricherImpl.enrich() into named phases
Apr 28, 2026
6593a65
Extract DeferredIndexTrackingPolicy from AbstractSchemaChangeVisitor
Apr 28, 2026
bde2e92
Actually-defer indexes inline on AddTable + AddTableFrom
Apr 28, 2026
5e1fd20
Add missing integration tests for drift + change-to-non-deferred
Apr 28, 2026
88b09d4
Remove redundant performUpgrade in testCompletedDeferredIndexSurvives…
Apr 28, 2026
31ec182
Polish DeferredIndexTrackingPolicy + visitor symmetry
Apr 28, 2026
9f3eda4
Move DeferredIndexTrackingPolicy to upgrade package, make package-pri…
Apr 28, 2026
a000692
Replace inline FQN test references with proper imports
Apr 28, 2026
7031bcb
Test-suite review: polish + restore TestUpgradeSteps
Apr 29, 2026
42df83b
Phase 1 — primitives for background-build branch
Apr 29, 2026
0529c3b
Phase 2 — task API for background-build branch
Apr 29, 2026
1b026fe
Phases 3+4 — narrow drift policy and remove old SQL-based API
Apr 29, 2026
4d39072
Phase 5 — integration test rewrite for new build-task flow
Apr 29, 2026
31b7ac1
Review fixes — schema-qualify PG isIndexValid, reset lock_timeout, po…
Apr 29, 2026
73d277c
Review follow-ups — collect-then-throw drift, multi-task tests, polish
Apr 30, 2026
82905b7
Per-dialect tests for the deferred-index dialect surface
Apr 30, 2026
e18d1d6
PG-only autocommit gating + snapshot getters on DeferredIndexBuildTask
Apr 30, 2026
f2d515d
Rename Deployed* -> Deferred* across the entire feature
Apr 30, 2026
25c1efc
Inline comments on DeferredIndexesModelEnricherImpl#enrich
Apr 30, 2026
09d6a7c
Minor leftover review-pass fixups
May 1, 2026
c15ac28
Revert accidental whitespace-only diffs vs main
May 1, 2026
994ee04
Finish Deployed -> Deferred identifier sweep
May 1, 2026
1327189
Adopt register/unregister vocabulary across deferred-indexes feature
May 1, 2026
cfb33c8
Rename DeferredIndexRegistrationPolicy.effectiveIndex -> normalize
May 1, 2026
592be4d
Drop redundant explicit no-arg ctor on DeferredIndexesStatements
May 1, 2026
6b9bf59
Inline isDeferredIndexesEnabled wrapper in AbstractSchemaChangeVisitor
May 1, 2026
ff697bf
Restore SchemaChangeSequence(List) ctor as @Deprecated
May 1, 2026
7acb8ba
Remove dead null-enricher fallback in Upgrade.enrichSourceSchema
May 1, 2026
d5b0e50
Tighten resolveTargetDeferred in SchemaChangeSequence
May 1, 2026
12db8d9
Add DeferredIndexBuildTask.create static factory; drop the cast
May 1, 2026
d5fa1d8
Replace selectAllColumns helper with select().from(tableRef(TABLE))
May 1, 2026
c6a9dca
Drop redundant feature-flag gate in writeDeferredIndexesDml
May 1, 2026
a09ebee
Split reconcileTable into chooseIndexFor + virtualizeRow helpers
May 1, 2026
e5b100d
Split DeferredIndexBuildTaskImpl into Builder + thin holder
May 1, 2026
c2eb508
Make IndexNameDecorator.isDeferred() delegate to the wrapped index
May 1, 2026
6b16539
Scrub "slim" / "row-existence" references from production code
May 1, 2026
0b52548
Strip remediation suggestions from enricher drift messages
May 1, 2026
02dc2dd
Fix stale capture-before-mutate comment in visit(RemoveIndex)
May 1, 2026
9472684
Trim historical noise from DeferredIndexRegistrationPolicy Javadoc
May 1, 2026
5c5ef06
Rewrite withoutDeferredOnSupportingDialect Javadoc in plain language
May 1, 2026
269e582
Polish 4-arg performUpgrade Javadoc -- @deprecated reason + jobs->tasks
May 1, 2026
aa83864
Replace useless @see #field Javadoc on DeferredIndex POJO
May 1, 2026
6ad5a11
Tweak lock_timeout WARN/DEBUG levels and drop pool-discard over-claim
May 1, 2026
77aa6c5
Add INFO/DEBUG logs to DeferredIndexBuilder for operator visibility
May 1, 2026
e4fec5a
Regenerate non-random UUID on CreateDeferredIndexes
May 1, 2026
cc429a0
Document why CreateDeferredIndexes uses @Sequence(1)
May 1, 2026
7170b5f
Document undocumented methods in the deferred-indexes feature
May 1, 2026
57355d9
Fix integration test fall-out from #131 + clean a substring artifact
May 1, 2026
1d24947
Rename test methods + locals to match register/unregister/normalize API
May 1, 2026
a622860
Strip slim refs / stale references / evolution comments from test files
May 1, 2026
5568024
Remove dead buildDeferredIndexesViaAdopter helper + dead UpgradePath …
May 1, 2026
e9ce664
Consolidate three near-duplicate test pairs
May 1, 2026
6313b9b
Tighten shallow / brittle assertions in deferred-index tests
May 1, 2026
028a4ed
Add InOrder + missing markStarted assertions to failing-path build tests
May 1, 2026
bce2660
Style nits across deferred-index test files (and one production typo)
May 1, 2026
4f872e4
Fill coverage gaps in deferred-index test suite
May 1, 2026
ba701db
Delete redundant testNoDeferredIndexesReturnsEmptyStatements
May 1, 2026
667d429
Strengthen testReUpgradeIsIdempotent to assert no-duplicate-row
May 1, 2026
44a5c2b
Tighten testEnricherHardFailsOnRowForMissingTable assertion
May 1, 2026
db2dd1b
Add physical-state assertions to testCrossStepTableRename
May 1, 2026
f6fbd3e
Fix singular/plural mismatch in testAddTableRegistersIndex...
May 1, 2026
622fa60
Drop stale "jobs" terminology from integration test locals + comments
May 1, 2026
3340980
Reword "the actually-defer fix" Javadoc on inline-AddTable test
May 1, 2026
8b1ed9c
Drop overlapping indexColumns assertion in testCrossStepColumnRename
May 1, 2026
23a6540
Factor inline UpgradeConfigAndContext boilerplate via performUpgradeW…
May 1, 2026
4a3da30
Add given/when/then comments to TestIndexNameDecorator
May 1, 2026
9f118f8
Add given/when/then comments to remaining new branch tests
May 16, 2026
eee9c83
Merge remote-tracking branch 'origin/main' into experimental/deferred…
Aug 5, 2026
ba3da38
Cover PRF-rename x deferred-index intersection
Aug 5, 2026
9cdb0d7
Register PRF-materialised deferred indexes as COMPLETED, not PENDING
Aug 6, 2026
13fc97a
Add statement-level test for registerCompletedIndex
Aug 6, 2026
1ef90d8
Cover the remaining PRF x deferred-index paths
Aug 6, 2026
3709142
Contribute DeferredIndexes to the target schema
Aug 6, 2026
88740ed
Give every deferred-index fixture a unique @Sequence and @UUID
Aug 6, 2026
718dbbb
Give each upgrade walk its own DeferredIndexSession
Aug 6, 2026
865562e
Decide index DDL from observed physical presence, not row status
Aug 6, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,5 @@ target
*.iml
.idea
**/ivy-ide-settings.properties
PLAN-*.md
CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,11 @@ public Upgrade provideUpgrade(ConnectionResources connectionResources,
ViewDeploymentValidator viewDeploymentValidator,
DatabaseUpgradePathValidationService databaseUpgradePathValidationService,
GraphBasedUpgradeBuilderFactory graphBasedUpgradeBuilderFactory,
UpgradeConfigAndContext upgradeConfigAndContext) {
UpgradeConfigAndContext upgradeConfigAndContext,
org.alfasoftware.morf.upgrade.deferredindexes.DeferredIndexesModelEnricher deferredIndexesModelEnricher) {
return new Upgrade(connectionResources, factory, upgradeStatusTableService, viewChangesDeploymentHelper,
viewDeploymentValidator, databaseUpgradePathValidationService, graphBasedUpgradeBuilderFactory,
upgradeConfigAndContext);
upgradeConfigAndContext, deferredIndexesModelEnricher);
}
}

143 changes: 142 additions & 1 deletion morf-core/src/main/java/org/alfasoftware/morf/jdbc/SqlDialect.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.time.Duration;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
Expand Down Expand Up @@ -4047,6 +4048,134 @@ public Collection<String> addIndexStatements(Table table, Index index) {
}


/**
* Whether this dialect supports deferred index creation. When {@code true},
* indexes marked with {@code .deferred()} are queued for background creation
* via the DeferredIndexes table. When {@code false}, deferred requests
* are silently converted to immediate index creation, because the platform's
* {@code CREATE INDEX} blocks DML and deferring would move the lock from the
* upgrade window (when no traffic is flowing) to post-startup (when it is).
*
* <p>The default returns {@code false}. Dialects that support non-blocking
* DDL (e.g. PostgreSQL {@code CONCURRENTLY}, Oracle {@code ONLINE}) should
* override this to return {@code true}.</p>
*
* @return {@code true} if deferred index creation is beneficial on this platform.
*/
public boolean supportsDeferredIndexCreation() {
return false;
}


/**
* Generates the SQL to build a deferred index on an existing table. By default this
* delegates to {@link #addIndexStatements(Table, Index)}, which issues a standard
* {@code CREATE INDEX} statement. Platform-specific dialects may override this method
* to emit non-blocking variants (e.g. {@code CREATE INDEX CONCURRENTLY} on PostgreSQL).
*
* @param table The existing table.
* @param index The new index to build in the background.
* @return A collection of SQL statements.
*/
public Collection<String> deferredIndexDeploymentStatements(Table table, Index index) {
return addIndexStatements(table, index);
}


/**
* Returns whether the deferred-index build path on this dialect requires the JDBC
* connection to be in autocommit mode for the duration of the
* {@link #deferredIndexDeploymentStatements} (and any matching DROP) execution.
*
* <p>The motivating case is PostgreSQL {@code CREATE INDEX CONCURRENTLY}, which refuses
* to run inside a transaction block. Other dialects treat DDL as implicitly committed
* regardless of autocommit setting, so they don't need the build task to disturb the
* borrowed connection's autocommit state. Default {@code false}.</p>
*
* @return {@code true} if the build task must flip autocommit on for this dialect.
*/
public boolean deferredIndexBuildRequiresAutoCommit() {
return false;
}


/**
* Returns a session-scoped statement that bounds how long a subsequent DDL/DML will
* wait for a lock on this dialect, or {@link Optional#empty()} if the dialect doesn't
* benefit from the gate (e.g. its default is already fail-fast).
*
* <p>The deferred-index reconciliation path uses this before issuing {@code DROP INDEX}
* to avoid hanging the adopter's executor when a previous backend is still holding a
* lock (e.g. PostgreSQL {@code CREATE INDEX CONCURRENTLY} from a since-disconnected
* client whose backend hasn't yet been reaped via TCP keepalive).</p>
*
* <p>Default returns {@link Optional#empty()}. PostgreSQL overrides to emit
* {@code SET lock_timeout = X}. Oracle's {@code DDL_LOCK_TIMEOUT} default of {@code 0}
* already fail-fasts; H2's default 1 s is short enough; both accept the default.</p>
*
* @param timeout The maximum time to wait for a lock.
* @return The dialect-specific SQL to set the timeout, or {@link Optional#empty()} to keep defaults.
*/
public Optional<String> setLockTimeoutSql(Duration timeout) {
return Optional.empty();
}


/**
* Returns a session-scoped statement that restores the lock-timeout default after a
* matching {@link #setLockTimeoutSql} call, or {@link Optional#empty()} if the dialect
* doesn't need a reset (default empty matches default empty {@code setLockTimeoutSql}).
*
* <p>The deferred-index reconciliation path uses this in a {@code finally} block after
* issuing {@code DROP INDEX} so the session-scoped {@code SET lock_timeout} doesn't
* bleed back into pooled connections. Without this reset, the next caller borrowing the
* connection would inherit the 10-second timeout — silent breakage of unrelated DDL.</p>
*
* <p>Default returns {@link Optional#empty()}. PostgreSQL overrides to emit
* {@code RESET lock_timeout}.</p>
*
* @return The dialect-specific SQL to clear the timeout, or {@link Optional#empty()}.
*/
public Optional<String> resetLockTimeoutSql() {
return Optional.empty();
}


/**
* Returns whether the named physical index is valid (built and usable).
*
* <p>Used by the deferred-index reconciliation path to decide whether a registration row
* should be promoted to {@code COMPLETED} (a valid index already exists), driven through
* the {@code CREATE INDEX} branch (no index in the catalog), or driven through
* {@code DROP + CREATE} (a previous build left an invalid leftover behind).</p>
*
* <p>Returns:</p>
* <ul>
* <li>{@link Optional#empty()} if the index is not present, or if the dialect cannot
* determine validity. Callers should treat empty as "not present" in the
* reconciliation path.</li>
* <li>{@code Optional.of(true)} if the index exists and is fully usable.</li>
* <li>{@code Optional.of(false)} if the index exists in the catalog but is not usable
* (PostgreSQL {@code indisvalid=false}, Oracle {@code STATUS='UNUSABLE'}). H2 has
* no in-catalog INVALID state.</li>
* </ul>
*
* <p>Default returns {@link Optional#empty()}. Dialects that can answer the question
* (PostgreSQL, Oracle, H2) override this. No special grants are required for the
* per-dialect implementations.</p>
*
* @param connection JDBC connection used to query the catalog.
* @param tableName The table the index is defined on.
* @param indexName The index name.
* @return The validity tri-state.
*/
public Optional<Boolean> isIndexValid(@SuppressWarnings("unused") Connection connection,
@SuppressWarnings("unused") String tableName,
@SuppressWarnings("unused") String indexName) {
return Optional.empty();
}


/**
* Helper method to create all index statements defined for a table
*
Expand All @@ -4070,6 +4199,18 @@ protected List<String> createAllIndexStatements(Table table) {
* @return The SQL to deploy the index on the table.
*/
protected Collection<String> indexDeploymentStatements(Table table, Index index) {
return ImmutableList.of(buildCreateIndexStatement(table, index));
}


/**
* Builds a {@code CREATE [UNIQUE] INDEX} statement.
*
* @param table The table to create the index on.
* @param index The index to create.
* @return the complete CREATE INDEX SQL string.
*/
protected String buildCreateIndexStatement(Table table, Index index) {
StringBuilder statement = new StringBuilder();

statement.append("CREATE ");
Expand All @@ -4086,7 +4227,7 @@ protected Collection<String> indexDeploymentStatements(Table table, Index index)
.append(Joiner.on(", ").join(index.columnNames()))
.append(')');

return ImmutableList.of(statement.toString());
return statement.toString();
}


Expand Down
20 changes: 17 additions & 3 deletions morf-core/src/main/java/org/alfasoftware/morf/metadata/Index.java
Original file line number Diff line number Diff line change
Expand Up @@ -42,16 +42,30 @@ public interface Index {
public boolean isUnique();


/**
* Returns whether this index is deferred, meaning it may be built
* asynchronously after an upgrade rather than inline during the upgrade.
*
* @return True if the index is deferred.
*/
public default boolean isDeferred() {
return false;
}


/**
* Helper for {@link Object#toString()} implementations.
*
* @return String representation of the index.
*/
public default String toStringHelper() {
return new StringBuilder()
StringBuilder sb = new StringBuilder()
.append("Index-").append(getName())
.append("-").append(isUnique() ? "unique" : "")
.append("-").append(Joiner.on(',').join(columnNames()))
.toString();
.append("-").append(Joiner.on(',').join(columnNames()));
if (isDeferred()) {
sb.append("-deferred");
}
return sb.toString();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ class IndexBean implements Index {
*/
private final boolean unique;

/**
* Flags if the index is deferred (built asynchronously after upgrade).
*/
private final boolean deferred;


/**
* Creates an index bean.
Expand All @@ -50,7 +55,7 @@ class IndexBean implements Index {
* @param columnNames Column names to order the index.
*/
IndexBean(String name, boolean unique, String... columnNames) {
this(name, unique, ImmutableList.copyOf(columnNames));
this(name, unique, false, ImmutableList.copyOf(columnNames));
}


Expand All @@ -62,17 +67,18 @@ class IndexBean implements Index {
* @param columnNames Column names to order the index.
*/
IndexBean(String name, boolean unique, Iterable<String> columnNames) {
this(name, unique, ImmutableList.copyOf(columnNames));
this(name, unique, false, ImmutableList.copyOf(columnNames));
}


/**
* Internal constructor.
*/
private IndexBean(String name, boolean unique, ImmutableList<String> columnNames) {
IndexBean(String name, boolean unique, boolean deferred, ImmutableList<String> columnNames) {
super();
this.name = name;
this.unique = unique;
this.deferred = deferred;
this.columnNames = columnNames;
}

Expand All @@ -81,7 +87,7 @@ private IndexBean(String name, boolean unique, ImmutableList<String> columnNames
* @param toCopy Index to copy.
*/
IndexBean(Index toCopy) {
this(toCopy.getName(), toCopy.isUnique(), toCopy.columnNames());
this(toCopy.getName(), toCopy.isUnique(), toCopy.isDeferred(), ImmutableList.copyOf(toCopy.columnNames()));
}


Expand Down Expand Up @@ -110,6 +116,12 @@ public boolean isUnique() {
}


@Override
public boolean isDeferred() {
return deferred;
}


@Override
public String toString() {
return this.toStringHelper();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -615,6 +615,14 @@ public interface IndexBuilder extends Index {
* @return this, for method chaining.
*/
public IndexBuilder unique();


/**
* Mark this index as deferred (built asynchronously after upgrade).
*
* @return this, for method chaining.
*/
public IndexBuilder deferred();
}


Expand Down Expand Up @@ -776,12 +784,12 @@ public ColumnBuilder dataType(DataType dataType) {
private static final class IndexBuilderImpl extends IndexBean implements IndexBuilder {

private IndexBuilderImpl(String name) {
super(name, false, new String[0]);
super(name, false, false, ImmutableList.of());
}


private IndexBuilderImpl(String name, boolean unique, Iterable<String> columnNames) {
super(name, unique, columnNames);
private IndexBuilderImpl(String name, boolean unique, boolean deferred, Iterable<String> columnNames) {
super(name, unique, deferred, ImmutableList.copyOf(columnNames));
}


Expand All @@ -790,7 +798,7 @@ private IndexBuilderImpl(String name, boolean unique, Iterable<String> columnNam
*/
@Override
public IndexBuilder columns(String... columnNames) {
return new IndexBuilderImpl(getName(), isUnique(), Arrays.asList(columnNames));
return new IndexBuilderImpl(getName(), isUnique(), isDeferred(), Arrays.asList(columnNames));
}


Expand All @@ -799,7 +807,7 @@ public IndexBuilder columns(String... columnNames) {
*/
@Override
public IndexBuilder columns(Iterable<String> columnNames) {
return new IndexBuilderImpl(getName(), isUnique(), columnNames);
return new IndexBuilderImpl(getName(), isUnique(), isDeferred(), columnNames);
}


Expand All @@ -808,7 +816,13 @@ public IndexBuilder columns(Iterable<String> columnNames) {
*/
@Override
public IndexBuilder unique() {
return new IndexBuilderImpl(getName(), true, columnNames());
return new IndexBuilderImpl(getName(), true, isDeferred(), columnNames());
}


@Override
public IndexBuilder deferred() {
return new IndexBuilderImpl(getName(), isUnique(), true, columnNames());
}


Expand Down
Loading