From 0fd6159e013d81d729871dcbc47cfcfb347cac44 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 7 Feb 2026 20:51:48 +0000 Subject: [PATCH 1/3] Initial plan From bcb307c8b1b1782e617e2fcd95b8b43895276149 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 7 Feb 2026 21:00:16 +0000 Subject: [PATCH 2/3] Fix non-deterministic ordering in all database adapters Add secondary sort by id column to ORDER BY clauses in both acquireManyOptimistically and selectFirstAvailableWithLock methods across all database adapters (H2, HSQLDB, SQLite, PostgreSQL, MariaDB, MS SQL Server, Oracle). This ensures deterministic ordering when multiple messages have the same scheduledAt timestamp, which became a problem after H2 upgrade from 2.3.232 to 2.4.240. Co-authored-by: alexandru <11753+alexandru@users.noreply.github.com> --- .../funfix/delayedqueue/jvm/internals/jdbc/h2/H2Adapter.kt | 4 ++-- .../delayedqueue/jvm/internals/jdbc/hsqldb/HSQLDBAdapter.kt | 4 ++-- .../delayedqueue/jvm/internals/jdbc/mariadb/MariaDBAdapter.kt | 4 ++-- .../jvm/internals/jdbc/mssql/MsSqlServerAdapter.kt | 4 ++-- .../delayedqueue/jvm/internals/jdbc/oracle/OracleAdapter.kt | 4 ++-- .../jvm/internals/jdbc/postgres/PostgreSQLAdapter.kt | 4 ++-- .../delayedqueue/jvm/internals/jdbc/sqlite/SqliteAdapter.kt | 4 ++-- 7 files changed, 14 insertions(+), 14 deletions(-) diff --git a/delayedqueue-jvm/src/main/kotlin/org/funfix/delayedqueue/jvm/internals/jdbc/h2/H2Adapter.kt b/delayedqueue-jvm/src/main/kotlin/org/funfix/delayedqueue/jvm/internals/jdbc/h2/H2Adapter.kt index ca5caa5..83657c2 100644 --- a/delayedqueue-jvm/src/main/kotlin/org/funfix/delayedqueue/jvm/internals/jdbc/h2/H2Adapter.kt +++ b/delayedqueue-jvm/src/main/kotlin/org/funfix/delayedqueue/jvm/internals/jdbc/h2/H2Adapter.kt @@ -97,7 +97,7 @@ internal class H2Adapter(driver: JdbcDriver, tableName: String) : "createdAt" FROM "$tableName" WHERE "pKind" = ? AND "scheduledAt" <= ? - ORDER BY "scheduledAt" + ORDER BY "scheduledAt", "id" LIMIT 1 FOR UPDATE """ @@ -136,7 +136,7 @@ internal class H2Adapter(driver: JdbcDriver, tableName: String) : SELECT TOP $limit "id" FROM "$tableName" WHERE "pKind" = ? AND "scheduledAt" <= ? - ORDER BY "scheduledAt" + ORDER BY "scheduledAt", "id" ) """ diff --git a/delayedqueue-jvm/src/main/kotlin/org/funfix/delayedqueue/jvm/internals/jdbc/hsqldb/HSQLDBAdapter.kt b/delayedqueue-jvm/src/main/kotlin/org/funfix/delayedqueue/jvm/internals/jdbc/hsqldb/HSQLDBAdapter.kt index dee4c1b..d4a2cce 100644 --- a/delayedqueue-jvm/src/main/kotlin/org/funfix/delayedqueue/jvm/internals/jdbc/hsqldb/HSQLDBAdapter.kt +++ b/delayedqueue-jvm/src/main/kotlin/org/funfix/delayedqueue/jvm/internals/jdbc/hsqldb/HSQLDBAdapter.kt @@ -74,7 +74,7 @@ internal class HSQLDBAdapter(driver: JdbcDriver, tableName: String) : "createdAt" FROM "$tableName" WHERE "pKind" = ? AND "scheduledAt" <= ? - ORDER BY "scheduledAt" + ORDER BY "scheduledAt", "id" FETCH FIRST 1 ROWS ONLY """ @@ -112,7 +112,7 @@ internal class HSQLDBAdapter(driver: JdbcDriver, tableName: String) : SELECT "id" FROM "$tableName" WHERE "pKind" = ? AND "scheduledAt" <= ? - ORDER BY "scheduledAt" + ORDER BY "scheduledAt", "id" LIMIT $limit ) """ diff --git a/delayedqueue-jvm/src/main/kotlin/org/funfix/delayedqueue/jvm/internals/jdbc/mariadb/MariaDBAdapter.kt b/delayedqueue-jvm/src/main/kotlin/org/funfix/delayedqueue/jvm/internals/jdbc/mariadb/MariaDBAdapter.kt index 5a0f7ec..51b1f96 100644 --- a/delayedqueue-jvm/src/main/kotlin/org/funfix/delayedqueue/jvm/internals/jdbc/mariadb/MariaDBAdapter.kt +++ b/delayedqueue-jvm/src/main/kotlin/org/funfix/delayedqueue/jvm/internals/jdbc/mariadb/MariaDBAdapter.kt @@ -93,7 +93,7 @@ internal class MariaDBAdapter(driver: JdbcDriver, tableName: String) : `createdAt` FROM `$tableName` WHERE `pKind` = ? AND `scheduledAt` <= ? - ORDER BY `scheduledAt` + ORDER BY `scheduledAt`, `id` LIMIT 1 FOR UPDATE SKIP LOCKED """ @@ -133,7 +133,7 @@ internal class MariaDBAdapter(driver: JdbcDriver, tableName: String) : SELECT `id` FROM `$tableName` WHERE `pKind` = ? AND `scheduledAt` <= ? - ORDER BY `scheduledAt` + ORDER BY `scheduledAt`, `id` LIMIT $limit FOR UPDATE SKIP LOCKED ) AS subq diff --git a/delayedqueue-jvm/src/main/kotlin/org/funfix/delayedqueue/jvm/internals/jdbc/mssql/MsSqlServerAdapter.kt b/delayedqueue-jvm/src/main/kotlin/org/funfix/delayedqueue/jvm/internals/jdbc/mssql/MsSqlServerAdapter.kt index fa1587c..ac141c2 100644 --- a/delayedqueue-jvm/src/main/kotlin/org/funfix/delayedqueue/jvm/internals/jdbc/mssql/MsSqlServerAdapter.kt +++ b/delayedqueue-jvm/src/main/kotlin/org/funfix/delayedqueue/jvm/internals/jdbc/mssql/MsSqlServerAdapter.kt @@ -112,7 +112,7 @@ internal class MsSqlServerAdapter(driver: JdbcDriver, tableName: String) : WITH (UPDLOCK, READPAST) WHERE [pKind] = ? AND [scheduledAt] <= ? - ORDER BY [scheduledAt] + ORDER BY [scheduledAt], [id] """ return conn.prepareStatement(sql) { stmt -> @@ -153,7 +153,7 @@ internal class MsSqlServerAdapter(driver: JdbcDriver, tableName: String) : WITH (UPDLOCK, READPAST) WHERE [pKind] = ? AND [scheduledAt] <= ? - ORDER BY [scheduledAt] + ORDER BY [scheduledAt], [id] ) """ diff --git a/delayedqueue-jvm/src/main/kotlin/org/funfix/delayedqueue/jvm/internals/jdbc/oracle/OracleAdapter.kt b/delayedqueue-jvm/src/main/kotlin/org/funfix/delayedqueue/jvm/internals/jdbc/oracle/OracleAdapter.kt index 04cb3ed..b03310f 100644 --- a/delayedqueue-jvm/src/main/kotlin/org/funfix/delayedqueue/jvm/internals/jdbc/oracle/OracleAdapter.kt +++ b/delayedqueue-jvm/src/main/kotlin/org/funfix/delayedqueue/jvm/internals/jdbc/oracle/OracleAdapter.kt @@ -141,7 +141,7 @@ internal class OracleAdapter(driver: JdbcDriver, tableName: String) : SELECT ROWID FROM "$tableName" WHERE "pKind" = ? AND "scheduledAt" <= ? - ORDER BY "scheduledAt" + ORDER BY "scheduledAt", "id" FETCH FIRST 1 ROWS ONLY ) FOR UPDATE SKIP LOCKED @@ -180,7 +180,7 @@ internal class OracleAdapter(driver: JdbcDriver, tableName: String) : SELECT ROWID FROM "$tableName" WHERE "pKind" = ? AND "scheduledAt" <= ? - ORDER BY "scheduledAt" + ORDER BY "scheduledAt", "id" FETCH FIRST $limit ROWS ONLY ) FOR UPDATE SKIP LOCKED diff --git a/delayedqueue-jvm/src/main/kotlin/org/funfix/delayedqueue/jvm/internals/jdbc/postgres/PostgreSQLAdapter.kt b/delayedqueue-jvm/src/main/kotlin/org/funfix/delayedqueue/jvm/internals/jdbc/postgres/PostgreSQLAdapter.kt index 9b37a24..e8f8b0a 100644 --- a/delayedqueue-jvm/src/main/kotlin/org/funfix/delayedqueue/jvm/internals/jdbc/postgres/PostgreSQLAdapter.kt +++ b/delayedqueue-jvm/src/main/kotlin/org/funfix/delayedqueue/jvm/internals/jdbc/postgres/PostgreSQLAdapter.kt @@ -99,7 +99,7 @@ internal class PostgreSQLAdapter(driver: JdbcDriver, tableName: String) : "createdAt" FROM "$tableName" WHERE "pKind" = ? AND "scheduledAt" <= ? - ORDER BY "scheduledAt" + ORDER BY "scheduledAt", "id" LIMIT 1 FOR UPDATE SKIP LOCKED """ @@ -140,7 +140,7 @@ internal class PostgreSQLAdapter(driver: JdbcDriver, tableName: String) : FROM "$tableName" WHERE "pKind" = ? AND "scheduledAt" <= ? - ORDER BY "scheduledAt" + ORDER BY "scheduledAt", "id" LIMIT $limit FOR UPDATE SKIP LOCKED ) diff --git a/delayedqueue-jvm/src/main/kotlin/org/funfix/delayedqueue/jvm/internals/jdbc/sqlite/SqliteAdapter.kt b/delayedqueue-jvm/src/main/kotlin/org/funfix/delayedqueue/jvm/internals/jdbc/sqlite/SqliteAdapter.kt index 43fdd3c..72c8d06 100644 --- a/delayedqueue-jvm/src/main/kotlin/org/funfix/delayedqueue/jvm/internals/jdbc/sqlite/SqliteAdapter.kt +++ b/delayedqueue-jvm/src/main/kotlin/org/funfix/delayedqueue/jvm/internals/jdbc/sqlite/SqliteAdapter.kt @@ -81,7 +81,7 @@ internal class SqliteAdapter(driver: JdbcDriver, tableName: String) : "createdAt" FROM "$tableName" WHERE "pKind" = ? AND "scheduledAt" <= ? - ORDER BY "scheduledAt" + ORDER BY "scheduledAt", "id" LIMIT 1 """ @@ -120,7 +120,7 @@ internal class SqliteAdapter(driver: JdbcDriver, tableName: String) : SELECT "id" FROM "$tableName" WHERE "pKind" = ? AND "scheduledAt" <= ? - ORDER BY "scheduledAt" + ORDER BY "scheduledAt", "id" LIMIT $limit ) """ From 92122b05f71d6346ff551b796f1b082332dfd90b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 7 Feb 2026 21:06:41 +0000 Subject: [PATCH 3/3] Update indexes to include id column for optimal query performance Add 'id' column to KindPlusScheduledAtIndex in all database migrations to ensure ORDER BY (scheduledAt, id) queries are fully index-covered. Also add migration #2 to update existing databases by dropping and recreating the index with the additional column. This ensures both deterministic ordering and optimal query performance without requiring additional sort operations. Co-authored-by: alexandru <11753+alexandru@users.noreply.github.com> --- .../jvm/internals/jdbc/h2/H2Migrations.kt | 37 ++++++++++++++- .../internals/jdbc/hsqldb/HSQLDBMigrations.kt | 37 ++++++++++++++- .../jdbc/mariadb/MariaDBMigrations.kt | 36 ++++++++++++++- .../jdbc/mssql/MsSqlServerMigrations.kt | 38 +++++++++++++++- .../internals/jdbc/oracle/OracleMigrations.kt | 45 ++++++++++++++++++- .../jdbc/postgres/PostgreSQLMigrations.kt | 37 ++++++++++++++- .../internals/jdbc/sqlite/SqliteMigrations.kt | 37 ++++++++++++++- 7 files changed, 253 insertions(+), 14 deletions(-) diff --git a/delayedqueue-jvm/src/main/kotlin/org/funfix/delayedqueue/jvm/internals/jdbc/h2/H2Migrations.kt b/delayedqueue-jvm/src/main/kotlin/org/funfix/delayedqueue/jvm/internals/jdbc/h2/H2Migrations.kt index bd5cb9e..95ef431 100644 --- a/delayedqueue-jvm/src/main/kotlin/org/funfix/delayedqueue/jvm/internals/jdbc/h2/H2Migrations.kt +++ b/delayedqueue-jvm/src/main/kotlin/org/funfix/delayedqueue/jvm/internals/jdbc/h2/H2Migrations.kt @@ -31,11 +31,44 @@ internal object H2Migrations { ON "$tableName" ("pKind", "pKey"); CREATE INDEX "${tableName}__KindPlusScheduledAtIndex" - ON "$tableName" ("pKind", "scheduledAt"); + ON "$tableName" ("pKind", "scheduledAt", "id"); CREATE INDEX "${tableName}__LockUuidPlusIdIndex" ON "$tableName" ("lockUuid", "id"); """, - ) + ), + // Migration 2: Update index to include 'id' column for deterministic ordering + Migration( + sql = + """ + DROP INDEX IF EXISTS "${tableName}__KindPlusScheduledAtIndex"; + CREATE INDEX "${tableName}__KindPlusScheduledAtIndex" + ON "$tableName" ("pKind", "scheduledAt", "id"); + """, + needsExecution = { conn -> + // Check if index exists but doesn't have the 'id' column + val metadata = conn.underlying.metaData + var hasIndex = false + var hasIdColumn = false + + metadata.getIndexInfo(null, null, tableName, false, false).use { rs -> + while (rs.next()) { + val indexName = rs.getString("INDEX_NAME") + if (indexName == "${tableName}__KindPlusScheduledAtIndex") { + hasIndex = true + val columnName = rs.getString("COLUMN_NAME") + val ordinalPosition = rs.getShort("ORDINAL_POSITION") + // Check if 'id' is in position 3 + if (columnName == "id" && ordinalPosition == 3.toShort()) { + hasIdColumn = true + } + } + } + } + + // Run migration if index exists but doesn't have id column + hasIndex && !hasIdColumn + }, + ), ) } diff --git a/delayedqueue-jvm/src/main/kotlin/org/funfix/delayedqueue/jvm/internals/jdbc/hsqldb/HSQLDBMigrations.kt b/delayedqueue-jvm/src/main/kotlin/org/funfix/delayedqueue/jvm/internals/jdbc/hsqldb/HSQLDBMigrations.kt index ee6811e..b3aad26 100644 --- a/delayedqueue-jvm/src/main/kotlin/org/funfix/delayedqueue/jvm/internals/jdbc/hsqldb/HSQLDBMigrations.kt +++ b/delayedqueue-jvm/src/main/kotlin/org/funfix/delayedqueue/jvm/internals/jdbc/hsqldb/HSQLDBMigrations.kt @@ -31,11 +31,44 @@ internal object HSQLDBMigrations { ON "$tableName" ("pKind", "pKey"); CREATE INDEX "${tableName}__KindPlusScheduledAtIndex" - ON "$tableName" ("pKind", "scheduledAt"); + ON "$tableName" ("pKind", "scheduledAt", "id"); CREATE INDEX "${tableName}__LockUuidPlusIdIndex" ON "$tableName" ("lockUuid", "id"); """, - ) + ), + // Migration 2: Update index to include 'id' column for deterministic ordering + Migration( + sql = + """ + DROP INDEX "${tableName}__KindPlusScheduledAtIndex" IF EXISTS; + CREATE INDEX "${tableName}__KindPlusScheduledAtIndex" + ON "$tableName" ("pKind", "scheduledAt", "id"); + """, + needsExecution = { conn -> + // Check if index exists but doesn't have the 'id' column + val metadata = conn.underlying.metaData + var hasIndex = false + var hasIdColumn = false + + metadata.getIndexInfo(null, null, tableName, false, false).use { rs -> + while (rs.next()) { + val indexName = rs.getString("INDEX_NAME") + if (indexName == "${tableName}__KindPlusScheduledAtIndex") { + hasIndex = true + val columnName = rs.getString("COLUMN_NAME") + val ordinalPosition = rs.getShort("ORDINAL_POSITION") + // Check if 'id' is in position 3 + if (columnName == "id" && ordinalPosition == 3.toShort()) { + hasIdColumn = true + } + } + } + } + + // Run migration if index exists but doesn't have id column + hasIndex && !hasIdColumn + }, + ), ) } diff --git a/delayedqueue-jvm/src/main/kotlin/org/funfix/delayedqueue/jvm/internals/jdbc/mariadb/MariaDBMigrations.kt b/delayedqueue-jvm/src/main/kotlin/org/funfix/delayedqueue/jvm/internals/jdbc/mariadb/MariaDBMigrations.kt index 8838ee0..8c004b6 100644 --- a/delayedqueue-jvm/src/main/kotlin/org/funfix/delayedqueue/jvm/internals/jdbc/mariadb/MariaDBMigrations.kt +++ b/delayedqueue-jvm/src/main/kotlin/org/funfix/delayedqueue/jvm/internals/jdbc/mariadb/MariaDBMigrations.kt @@ -29,9 +29,41 @@ internal object MariaDBMigrations { UNIQUE KEY `${tableName}__KindPlusKeyUniqueIndex` (`pKind`, `pKey`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; - CREATE INDEX `${tableName}__KindPlusScheduledAtIndex` ON `$tableName`(`pKind`, `scheduledAt`); + CREATE INDEX `${tableName}__KindPlusScheduledAtIndex` ON `$tableName`(`pKind`, `scheduledAt`, `id`); CREATE INDEX `${tableName}__LockUuidPlusIdIndex` ON `$tableName`(`lockUuid`, `id`) """, - ) + ), + // Migration 2: Update index to include 'id' column for deterministic ordering + Migration( + sql = + """ + DROP INDEX IF EXISTS `${tableName}__KindPlusScheduledAtIndex` ON `$tableName`; + CREATE INDEX `${tableName}__KindPlusScheduledAtIndex` ON `$tableName`(`pKind`, `scheduledAt`, `id`); + """, + needsExecution = { conn -> + // Check if index exists but doesn't have the 'id' column + val metadata = conn.underlying.metaData + var hasIndex = false + var hasIdColumn = false + + metadata.getIndexInfo(null, null, tableName, false, false).use { rs -> + while (rs.next()) { + val indexName = rs.getString("INDEX_NAME") + if (indexName == "${tableName}__KindPlusScheduledAtIndex") { + hasIndex = true + val columnName = rs.getString("COLUMN_NAME") + val ordinalPosition = rs.getShort("ORDINAL_POSITION") + // Check if 'id' is in position 3 + if (columnName == "id" && ordinalPosition == 3.toShort()) { + hasIdColumn = true + } + } + } + } + + // Run migration if index exists but doesn't have id column + hasIndex && !hasIdColumn + }, + ), ) } diff --git a/delayedqueue-jvm/src/main/kotlin/org/funfix/delayedqueue/jvm/internals/jdbc/mssql/MsSqlServerMigrations.kt b/delayedqueue-jvm/src/main/kotlin/org/funfix/delayedqueue/jvm/internals/jdbc/mssql/MsSqlServerMigrations.kt index 8c0e2ed..27a63fb 100644 --- a/delayedqueue-jvm/src/main/kotlin/org/funfix/delayedqueue/jvm/internals/jdbc/mssql/MsSqlServerMigrations.kt +++ b/delayedqueue-jvm/src/main/kotlin/org/funfix/delayedqueue/jvm/internals/jdbc/mssql/MsSqlServerMigrations.kt @@ -32,11 +32,45 @@ internal object MsSqlServerMigrations { ON [$tableName] ([pKind], [pKey]); CREATE INDEX [${tableName}__KindPlusScheduledAtIndex] - ON [$tableName]([pKind], [scheduledAt]); + ON [$tableName]([pKind], [scheduledAt], [id]); CREATE INDEX [${tableName}__LockUuidPlusIdIndex] ON [$tableName]([lockUuid], [id]); """, - ) + ), + // Migration 2: Update index to include 'id' column for deterministic ordering + Migration( + sql = + """ + IF EXISTS (SELECT * FROM sys.indexes WHERE name = '${tableName}__KindPlusScheduledAtIndex') + DROP INDEX [${tableName}__KindPlusScheduledAtIndex] ON [$tableName]; + CREATE INDEX [${tableName}__KindPlusScheduledAtIndex] + ON [$tableName]([pKind], [scheduledAt], [id]); + """, + needsExecution = { conn -> + // Check if index exists but doesn't have the 'id' column + val metadata = conn.underlying.metaData + var hasIndex = false + var hasIdColumn = false + + metadata.getIndexInfo(null, null, tableName, false, false).use { rs -> + while (rs.next()) { + val indexName = rs.getString("INDEX_NAME") + if (indexName == "${tableName}__KindPlusScheduledAtIndex") { + hasIndex = true + val columnName = rs.getString("COLUMN_NAME") + val ordinalPosition = rs.getShort("ORDINAL_POSITION") + // Check if 'id' is in position 3 + if (columnName == "id" && ordinalPosition == 3.toShort()) { + hasIdColumn = true + } + } + } + } + + // Run migration if index exists but doesn't have id column + hasIndex && !hasIdColumn + }, + ), ) } diff --git a/delayedqueue-jvm/src/main/kotlin/org/funfix/delayedqueue/jvm/internals/jdbc/oracle/OracleMigrations.kt b/delayedqueue-jvm/src/main/kotlin/org/funfix/delayedqueue/jvm/internals/jdbc/oracle/OracleMigrations.kt index b30a11f..f12eb98 100644 --- a/delayedqueue-jvm/src/main/kotlin/org/funfix/delayedqueue/jvm/internals/jdbc/oracle/OracleMigrations.kt +++ b/delayedqueue-jvm/src/main/kotlin/org/funfix/delayedqueue/jvm/internals/jdbc/oracle/OracleMigrations.kt @@ -31,11 +31,52 @@ internal object OracleMigrations { ON "$tableName"("pKey", "pKind"); CREATE INDEX "${tableName}__KindPlusScheduledAtIndex" - ON "$tableName"("pKind", "scheduledAt"); + ON "$tableName"("pKind", "scheduledAt", "id"); CREATE INDEX "${tableName}__LockUuidPlusIdIndex" ON "$tableName"("lockUuid", "id"); """, - ) + ), + // Migration 2: Update index to include 'id' column for deterministic ordering + Migration( + sql = + """ + BEGIN + EXECUTE IMMEDIATE 'DROP INDEX "${tableName}__KindPlusScheduledAtIndex"'; + EXCEPTION + WHEN OTHERS THEN + IF SQLCODE != -1418 THEN + RAISE; + END IF; + END; + / + CREATE INDEX "${tableName}__KindPlusScheduledAtIndex" + ON "$tableName"("pKind", "scheduledAt", "id"); + """, + needsExecution = { conn -> + // Check if index exists but doesn't have the 'id' column + val metadata = conn.underlying.metaData + var hasIndex = false + var hasIdColumn = false + + metadata.getIndexInfo(null, null, tableName, false, false).use { rs -> + while (rs.next()) { + val indexName = rs.getString("INDEX_NAME") + if (indexName == "${tableName}__KindPlusScheduledAtIndex") { + hasIndex = true + val columnName = rs.getString("COLUMN_NAME") + val ordinalPosition = rs.getShort("ORDINAL_POSITION") + // Check if 'id' is in position 3 + if (columnName == "id" && ordinalPosition == 3.toShort()) { + hasIdColumn = true + } + } + } + } + + // Run migration if index exists but doesn't have id column + hasIndex && !hasIdColumn + }, + ), ) } diff --git a/delayedqueue-jvm/src/main/kotlin/org/funfix/delayedqueue/jvm/internals/jdbc/postgres/PostgreSQLMigrations.kt b/delayedqueue-jvm/src/main/kotlin/org/funfix/delayedqueue/jvm/internals/jdbc/postgres/PostgreSQLMigrations.kt index cd6e8fc..5e46501 100644 --- a/delayedqueue-jvm/src/main/kotlin/org/funfix/delayedqueue/jvm/internals/jdbc/postgres/PostgreSQLMigrations.kt +++ b/delayedqueue-jvm/src/main/kotlin/org/funfix/delayedqueue/jvm/internals/jdbc/postgres/PostgreSQLMigrations.kt @@ -31,11 +31,44 @@ internal object PostgreSQLMigrations { ON "$tableName"("pKey", "pKind"); CREATE INDEX "${tableName}__KindPlusScheduledAtIndex" - ON "$tableName"("pKind", "scheduledAt"); + ON "$tableName"("pKind", "scheduledAt", "id"); CREATE INDEX "${tableName}__LockUuidPlusIdIndex" ON "$tableName"("lockUuid", "id"); """, - ) + ), + // Migration 2: Update index to include 'id' column for deterministic ordering + Migration( + sql = + """ + DROP INDEX IF EXISTS "${tableName}__KindPlusScheduledAtIndex"; + CREATE INDEX "${tableName}__KindPlusScheduledAtIndex" + ON "$tableName"("pKind", "scheduledAt", "id"); + """, + needsExecution = { conn -> + // Check if index exists but doesn't have the 'id' column + val metadata = conn.underlying.metaData + var hasIndex = false + var hasIdColumn = false + + metadata.getIndexInfo(null, null, tableName, false, false).use { rs -> + while (rs.next()) { + val indexName = rs.getString("INDEX_NAME") + if (indexName == "${tableName}__KindPlusScheduledAtIndex") { + hasIndex = true + val columnName = rs.getString("COLUMN_NAME") + val ordinalPosition = rs.getShort("ORDINAL_POSITION") + // Check if 'id' is in position 3 + if (columnName == "id" && ordinalPosition == 3.toShort()) { + hasIdColumn = true + } + } + } + } + + // Run migration if index exists but doesn't have id column + hasIndex && !hasIdColumn + }, + ), ) } diff --git a/delayedqueue-jvm/src/main/kotlin/org/funfix/delayedqueue/jvm/internals/jdbc/sqlite/SqliteMigrations.kt b/delayedqueue-jvm/src/main/kotlin/org/funfix/delayedqueue/jvm/internals/jdbc/sqlite/SqliteMigrations.kt index fca8d39..401c7f3 100644 --- a/delayedqueue-jvm/src/main/kotlin/org/funfix/delayedqueue/jvm/internals/jdbc/sqlite/SqliteMigrations.kt +++ b/delayedqueue-jvm/src/main/kotlin/org/funfix/delayedqueue/jvm/internals/jdbc/sqlite/SqliteMigrations.kt @@ -31,11 +31,44 @@ internal object SqliteMigrations { ON $tableName (pKind, pKey); CREATE INDEX ${tableName}__KindPlusScheduledAtIndex - ON $tableName (pKind, scheduledAt); + ON $tableName (pKind, scheduledAt, id); CREATE INDEX ${tableName}__LockUuidPlusIdIndex ON $tableName (lockUuid, id); """, - ) + ), + // Migration 2: Update index to include 'id' column for deterministic ordering + Migration( + sql = + """ + DROP INDEX IF EXISTS ${tableName}__KindPlusScheduledAtIndex; + CREATE INDEX ${tableName}__KindPlusScheduledAtIndex + ON $tableName (pKind, scheduledAt, id); + """, + needsExecution = { conn -> + // Check if index exists but doesn't have the 'id' column + val metadata = conn.underlying.metaData + var hasIndex = false + var hasIdColumn = false + + metadata.getIndexInfo(null, null, tableName, false, false).use { rs -> + while (rs.next()) { + val indexName = rs.getString("INDEX_NAME") + if (indexName == "${tableName}__KindPlusScheduledAtIndex") { + hasIndex = true + val columnName = rs.getString("COLUMN_NAME") + val ordinalPosition = rs.getShort("ORDINAL_POSITION") + // Check if 'id' is in position 3 + if (columnName == "id" && ordinalPosition == 3.toShort()) { + hasIdColumn = true + } + } + } + } + + // Run migration if index exists but doesn't have id column + hasIndex && !hasIdColumn + }, + ), ) }