-
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 all 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 | ||||
|---|---|---|---|---|---|---|
|
|
@@ -17,6 +17,8 @@ public class JdbcDriver private constructor(public val className: String) { | |||||
|
|
||||||
| @JvmField public val PostgreSQL: JdbcDriver = JdbcDriver("org.postgresql.Driver") | ||||||
|
|
||||||
| @JvmField public val Oracle: JdbcDriver = JdbcDriver("oracle.jdbc.OracleDriver") | ||||||
|
|
||||||
| @JvmStatic | ||||||
| public val entries: List<JdbcDriver> = | ||||||
| listOf(H2, HSQLDB, MariaDB, MsSqlServer, PostgreSQL, Sqlite) | ||||||
|
||||||
| listOf(H2, HSQLDB, MariaDB, MsSqlServer, PostgreSQL, Sqlite) | |
| listOf(H2, HSQLDB, MariaDB, MsSqlServer, Oracle, PostgreSQL, Sqlite) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,270 @@ | ||
| 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 { | ||
| // NOTE: this query can still throw an SQLException under concurrency, | ||
| // because the NOT EXISTS check is not atomic. But this is still fine, | ||
| // as we reduce the error rate, and the call-site does catch the SQLException. | ||
| 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 | ||
| } | ||
|
Comment on lines
+19
to
+54
|
||
| } | ||
|
|
||
| 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.
The new
JdbcDriver.Oracleconstant is not included inentries, which is the backing list forJdbcDriver.invoke(className). As a result, any Java/Kotlin consumer that resolves a driver by class name (e.g.,JdbcDriver("oracle.jdbc.OracleDriver")) will getnulland may reject an otherwise valid Oracle configuration, even though Oracle is now supported elsewhere in the code. This makes Oracle behave inconsistently with the other drivers and will break class-name based configuration flows.Useful? React with 👍 / 👎.