-
Notifications
You must be signed in to change notification settings - Fork 1
Add Oracle support #10
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 2 commits
e73838a
a65156a
0c5c5e0
8a4e256
686e0d2
905c586
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,267 @@ | ||
| package org.funfix.delayedqueue.jvm.internals.jdbc.oracle | ||
|
|
||
| import java.sql.SQLException | ||
| import java.time.Duration | ||
| import java.time.Instant | ||
| import org.funfix.delayedqueue.jvm.JdbcDriver | ||
| import org.funfix.delayedqueue.jvm.internals.jdbc.DBTableRow | ||
| import org.funfix.delayedqueue.jvm.internals.jdbc.DBTableRowWithId | ||
| import org.funfix.delayedqueue.jvm.internals.jdbc.SQLVendorAdapter | ||
| import org.funfix.delayedqueue.jvm.internals.jdbc.SafeConnection | ||
| import org.funfix.delayedqueue.jvm.internals.jdbc.prepareStatement | ||
| import org.funfix.delayedqueue.jvm.internals.jdbc.toDBTableRowWithId | ||
| import org.funfix.delayedqueue.jvm.internals.utils.Raise | ||
|
|
||
| /** Oracle-specific adapter. */ | ||
| internal class OracleAdapter(driver: JdbcDriver, tableName: String) : | ||
| SQLVendorAdapter(driver, tableName) { | ||
|
|
||
| context(_: Raise<InterruptedException>, _: Raise<SQLException>) | ||
| override fun insertOneRow(conn: SafeConnection, row: DBTableRow): Boolean { | ||
| val sql = | ||
| """ | ||
| INSERT INTO "$tableName" | ||
| ( | ||
| "pKey", | ||
| "pKind", | ||
| "payload", | ||
| "scheduledAt", | ||
| "scheduledAtInitially", | ||
| "createdAt" | ||
| ) | ||
| SELECT ?, ?, ?, ?, ?, ? | ||
| FROM dual | ||
| WHERE NOT EXISTS ( | ||
| SELECT 1 | ||
| FROM "$tableName" | ||
| WHERE "pKey" = ? AND "pKind" = ? | ||
| ) | ||
| """ | ||
|
|
||
| return conn.prepareStatement(sql) { stmt -> | ||
| stmt.setString(1, row.pKey) | ||
| stmt.setString(2, row.pKind) | ||
| stmt.setBytes(3, row.payload) | ||
| stmt.setEpochMillis(4, row.scheduledAt) | ||
| stmt.setEpochMillis(5, row.scheduledAtInitially) | ||
| stmt.setEpochMillis(6, row.createdAt) | ||
| stmt.setString(7, row.pKey) | ||
| stmt.setString(8, row.pKind) | ||
| stmt.executeUpdate() > 0 | ||
| } | ||
| } | ||
|
|
||
| context(_: Raise<InterruptedException>, _: Raise<SQLException>) | ||
| override fun selectForUpdateOneRow( | ||
| conn: SafeConnection, | ||
| kind: String, | ||
| key: String, | ||
| ): DBTableRowWithId? { | ||
| val sql = | ||
| """ | ||
| SELECT | ||
| "id", | ||
| "pKey", | ||
| "pKind", | ||
| "payload", | ||
| "scheduledAt", | ||
| "scheduledAtInitially", | ||
| "lockUuid", | ||
| "createdAt" | ||
| FROM "$tableName" | ||
| WHERE "pKey" = ? AND "pKind" = ? AND ROWNUM <= 1 | ||
| FOR UPDATE | ||
| """ | ||
|
|
||
| return conn.prepareStatement(sql) { stmt -> | ||
| stmt.setString(1, key) | ||
| stmt.setString(2, kind) | ||
| stmt.executeQuery().use { rs -> | ||
| if (rs.next()) { | ||
| rs.toDBTableRowWithId() | ||
| } else { | ||
| null | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| context(_: Raise<InterruptedException>, _: Raise<SQLException>) | ||
| override fun selectByKey(conn: SafeConnection, kind: String, key: String): DBTableRowWithId? { | ||
| val sql = | ||
| """ | ||
| SELECT | ||
| "id", | ||
| "pKey", | ||
| "pKind", | ||
| "payload", | ||
| "scheduledAt", | ||
| "scheduledAtInitially", | ||
| "lockUuid", | ||
| "createdAt" | ||
| FROM "$tableName" | ||
| WHERE "pKey" = ? AND "pKind" = ? AND ROWNUM <= 1 | ||
| """ | ||
|
|
||
| return conn.prepareStatement(sql) { stmt -> | ||
| stmt.setString(1, key) | ||
| stmt.setString(2, kind) | ||
| stmt.executeQuery().use { rs -> | ||
| if (rs.next()) { | ||
| rs.toDBTableRowWithId() | ||
| } else { | ||
| null | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| context(_: Raise<InterruptedException>, _: Raise<SQLException>) | ||
| override fun selectFirstAvailableWithLock( | ||
| conn: SafeConnection, | ||
| kind: String, | ||
| now: Instant, | ||
| ): DBTableRowWithId? { | ||
| val sql = | ||
| """ | ||
| SELECT | ||
| "id", | ||
| "pKey", | ||
| "pKind", | ||
| "payload", | ||
| "scheduledAt", | ||
| "scheduledAtInitially", | ||
| "lockUuid", | ||
| "createdAt" | ||
| FROM "$tableName" | ||
| WHERE ROWID IN ( | ||
| SELECT ROWID | ||
| FROM "$tableName" | ||
| WHERE "pKind" = ? AND "scheduledAt" <= ? | ||
| ORDER BY "scheduledAt" | ||
| FETCH FIRST 1 ROWS ONLY | ||
| ) | ||
| FOR UPDATE SKIP LOCKED | ||
|
Comment on lines
+139
to
+147
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The Oracle query chooses a single candidate row in the subquery ( Useful? React with 👍 / 👎. |
||
| """ | ||
|
|
||
| return conn.prepareStatement(sql) { stmt -> | ||
| stmt.setString(1, kind) | ||
| stmt.setEpochMillis(2, now) | ||
| stmt.executeQuery().use { rs -> | ||
| if (rs.next()) { | ||
| rs.toDBTableRowWithId() | ||
| } else { | ||
| null | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| context(_: Raise<InterruptedException>, _: Raise<SQLException>) | ||
| override fun acquireManyOptimistically( | ||
| conn: SafeConnection, | ||
| kind: String, | ||
| limit: Int, | ||
| lockUuid: String, | ||
| timeout: Duration, | ||
| now: Instant, | ||
| ): Int { | ||
| require(limit > 0) { "Limit must be > 0" } | ||
| val expireAt = now.plus(timeout) | ||
|
|
||
| val selectSql = | ||
| """ | ||
| SELECT "id" | ||
| FROM "$tableName" | ||
| WHERE ROWID IN ( | ||
| SELECT ROWID | ||
| FROM "$tableName" | ||
| WHERE "pKind" = ? AND "scheduledAt" <= ? | ||
| ORDER BY "scheduledAt" | ||
| FETCH FIRST $limit ROWS ONLY | ||
| ) | ||
| FOR UPDATE SKIP LOCKED | ||
| """ | ||
|
|
||
| val ids = | ||
| conn.prepareStatement(selectSql) { stmt -> | ||
| stmt.setString(1, kind) | ||
| stmt.setEpochMillis(2, now) | ||
| stmt.executeQuery().use { rs -> | ||
| val results = mutableListOf<Long>() | ||
| while (rs.next()) { | ||
| results.add(rs.getLong("id")) | ||
| } | ||
| results | ||
| } | ||
| } | ||
|
|
||
| if (ids.isEmpty()) return 0 | ||
|
|
||
| val placeholders = ids.joinToString(",") { "?" } | ||
| val updateSql = | ||
| """ | ||
| UPDATE "$tableName" | ||
| SET | ||
| "lockUuid" = ?, | ||
| "scheduledAt" = ? | ||
| WHERE "id" IN ($placeholders) | ||
| """ | ||
|
|
||
| return conn.prepareStatement(updateSql) { stmt -> | ||
| stmt.setString(1, lockUuid) | ||
| stmt.setEpochMillis(2, expireAt) | ||
| ids.forEachIndexed { index, id -> stmt.setLong(index + 3, id) } | ||
| stmt.executeUpdate() | ||
| } | ||
| } | ||
|
|
||
| context(_: Raise<InterruptedException>, _: Raise<SQLException>) | ||
| override fun selectAllAvailableWithLock( | ||
| conn: SafeConnection, | ||
| lockUuid: String, | ||
| count: Int, | ||
| offsetId: Long?, | ||
| ): List<DBTableRowWithId> { | ||
| val offsetClause = offsetId?.let { "AND \"id\" > ?" } ?: "" | ||
| val sql = | ||
| """ | ||
| SELECT | ||
| "id", | ||
| "pKey", | ||
| "pKind", | ||
| "payload", | ||
| "scheduledAt", | ||
| "scheduledAtInitially", | ||
| "lockUuid", | ||
| "createdAt" | ||
| FROM ( | ||
| SELECT | ||
| "id", | ||
| "pKey", | ||
| "pKind", | ||
| "payload", | ||
| "scheduledAt", | ||
| "scheduledAtInitially", | ||
| "lockUuid", | ||
| "createdAt" | ||
| FROM "$tableName" | ||
| WHERE "lockUuid" = ? $offsetClause | ||
| ORDER BY "id" | ||
| ) | ||
| WHERE ROWNUM <= $count | ||
| """ | ||
|
|
||
| return conn.prepareStatement(sql) { stmt -> | ||
| stmt.setString(1, lockUuid) | ||
| offsetId?.let { stmt.setLong(2, it) } | ||
| stmt.executeQuery().use { rs -> | ||
| val results = mutableListOf<DBTableRowWithId>() | ||
| while (rs.next()) { | ||
| results.add(rs.toDBTableRowWithId()) | ||
| } | ||
| results | ||
| } | ||
| } | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
insertOneRowis documented to return false when the key already exists, but this Oracle implementation can still throw a duplicate-keySQLExceptionunder concurrency (the NOT EXISTS check isn’t fully race-free). Consider catching duplicate-key exceptions here (using the existingfiltersForDriver(driver).duplicateKey) and returning false, or at least add an explicit comment like the MS-SQL adapter does so callers don’t rely on the return value alone.