Skip to content

diskquota/jdbc: fix renameLayer leaving TILESET.KEY stale (#1526)#1529

Open
groldan wants to merge 2 commits into
GeoWebCache:mainfrom
groldan:fix/1526/main
Open

diskquota/jdbc: fix renameLayer leaving TILESET.KEY stale (#1526)#1529
groldan wants to merge 2 commits into
GeoWebCache:mainfrom
groldan:fix/1526/main

Conversation

@groldan
Copy link
Copy Markdown
Member

@groldan groldan commented May 14, 2026

Renaming a layer in a deployment using the JDBC disk-quota store leaves the quota tables in a half-renamed state: TILESET.LAYER_NAME is updated to the new name, but the embedded layer-name prefix in TILESET.KEY (and the FK reference from TILEPAGE.TILESET_ID) is not.

The next getTileSetById misses the row, getOrCreateTileSet inserts a duplicate, and quota-by-id is split across two rows.

Root cause

Full description in

Fix:

1. Rewrite KEY in the rename SQL

SQLDialect.getRenameLayerStatement now rewrites both LAYER_NAME and the layer-name prefix of KEY:

UPDATE TILESET
   SET LAYER_NAME = :newName,
       KEY = :newName || SUBSTRING(KEY FROM POSITION('#' IN KEY))
 WHERE LAYER_NAME = :oldName

SUBSTRING ... FROM POSITION(...) is standard SQL on PostgreSQL, HSQL, and H2.

2. ON UPDATE CASCADE on the TILEPAGE -> TILESET FK

The FK is now declared REFERENCES ${schema}TILESET(KEY) ON UPDATE CASCADE ON DELETE CASCADE. TILEPAGE.TILESET_ID follows the TILESET.KEY rewrite automatically on fresh schemas.

Pre-existing installations are upgraded in place by SQLDialect.migrateForeignKeys, invoked from initializeTables after
the table-creation pass.

The migration:

  • Locates the existing FK via DatabaseMetaData.getImportedKeys.
  • If the FK is not already importedKeyCascade, drops it and re-adds it with the new rule.
  • Is concurrent-startup safe: if two instances race, the loser's ALTER TABLE DROP CONSTRAINT fails because the winner has already dropped it. The resulting DataAccessException is caught, the live FK state is re-checked via the same metadata query, and a now-cascade FK is accepted as concurrent success rather than propagated.

3. Oracle: legacy behavior preserved

Oracle does not support ON UPDATE CASCADE on foreign keys. OracleDialect overrides both getRenameLayerStatement (keeps LAYER_NAME-only) and migrateForeignKeys (no-op). Today's behavior
is preserved on that dialect - the bug remains there.

The proper Oracle-side fix is DEFERRABLE INITIALLY IMMEDIATE on the FK plus paired UPDATEs in a SET CONSTRAINTS ALL DEFERRED transaction. That belongs in a follow-up fix for #1527: Oracle XE doesn't currently survive the broader SERIALIZABLE / ORA-08176 issue (tracked separately), so the Oracle IT remains @Ignored.

Test coverage

  • JDBCQuotaStoreTest.testRenameLayer / testRenameLayer2 extended to assert that both TILESET.KEY and (via cascade) TILEPAGE.TILESET_ID carry the new layer prefix after rename. These would fail against the pre-fix single-column UPDATE by construction.

  • New AbstractForeignKeyMigrationTest builds a legacy-schema fixture by stripping ON UPDATE CASCADE from the dialect's own DDL, then verifies three paths:

    • migrateAddsOnUpdateCascadeToTilepageForeignKey - legacy FK upgraded to cascade.
    • migrateIsIdempotent - second call is a no-op.
    • migrateIsConcurrentStartupSafe - multiple threads with CyclicBarrier, must return cleanly and the FK must end cascade.

Without the recheck-on-failure step in upgradeTilepageForeignKey, the concurrent test fails on both HSQL (user lacks privilege or object not found: SYS_FK_NNNN) and PostgreSQL (constraint "tilepage_tileset_id_fkey" of relation "tilepage" does not exist).

Runs on HSQLForeignKeyMigrationTest (surefire) and PostgreSQLForeignKeyMigrationIT (failsafe under -Ponline).

Affected versions

Present on main (GeoWebCache 2.x) and the 1.28.x series. Suggested backport target: 1.28.x.


Out of scope:

  • The Oracle-side fix using deferrable constraints - separate.
  • The broader retry-on-serialization-failure layer (GWC #1527)

Requires:


Fixes:

groldan added 2 commits May 13, 2026 21:10
Add failsafe-driven integration tests that run the JDBCQuotaStoreTest
suite against real PostgreSQL and Oracle XE via Testcontainers.

Add a GitHub Actions workflow that runs them on PRs touching the
diskquota module. The legacy fixture-based tests stay in place and
keep skipping cleanly when no fixture file is present.

The Oracle IT is @ignored for now: SERIALIZABLE transactions in
JDBCQuotaStore hit ORA-08176 on freshly-indexed tables, which the
Oracle-recommended retry-on-serialization-failure pattern resolves;
the IT could be re-checked and enabled once retry is implemented.

No production code is modified.

on-behalf-of: @camptocamp <info@camptocamp.com>
…e#1526)

JDBCQuotaStore.renameLayer only updated LAYER_NAME, leaving the
layer-name prefix in TILESET.KEY and TILEPAGE.TILESET_ID stale -
later lookups missed the row, getOrCreateTileSet inserted duplicates,
and quota by id was split across two rows.

The rename SQL now rewrites KEY too; ON UPDATE CASCADE on the
TILEPAGE FK keeps TILESET_ID in lockstep. OracleDialect keeps the
legacy behavior (no ON UPDATE CASCADE on Oracle); a follow-up will
use deferrable constraints there.

on-behalf-of: @camptocamp <info@camptocamp.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant