diff --git a/README.md b/README.md index 5eb0c34..8d661c0 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,7 @@ public class DLockConfig { public KeyLock keyLock(DataSource dataSource) { return new JDBCKeyLockBuilder() .dataSource(dataSource) - .databaseType(DatabaseType.H2) // or ORACLE + .databaseType(DatabaseType.H2) // or ORACLE, POSTGRESQL .createDatabase(true) // Automatically creates the DLCK table .build(); } @@ -199,7 +199,7 @@ When using the `KeyLock` API, keep the following constraints in mind: * [**dlock-api**](./dlock-api): Core interfaces (`KeyLock`, `LockHandle`). * [**dlock-core**](./dlock-core): Base implementation logic (expiration policies, utilities). -* [**dlock-jdbc**](./dlock-jdbc): JDBC implementation (H2, Oracle support). +* [**dlock-jdbc**](./dlock-jdbc): JDBC implementation (H2, Oracle, PostgreSQL support). * [**dlock-spring**](./dlock-spring): Spring integration (`@Lock` aspect). ```mermaid diff --git a/dlock-jdbc/src/main/java/com/dlock/jdbc/builder/JDBCKeyLockBuilder.java b/dlock-jdbc/src/main/java/com/dlock/jdbc/builder/JDBCKeyLockBuilder.java index 52be632..aed5080 100644 --- a/dlock-jdbc/src/main/java/com/dlock/jdbc/builder/JDBCKeyLockBuilder.java +++ b/dlock-jdbc/src/main/java/com/dlock/jdbc/builder/JDBCKeyLockBuilder.java @@ -42,6 +42,9 @@ public JDBCKeyLockBuilder databaseType(DatabaseType databaseType) { } public JDBCKeyLockBuilder lockTableName(String lockTableName) { + if (lockTableName == null || !lockTableName.matches("^[a-zA-Z0-9_]+$")) { + throw new IllegalArgumentException("Table name must only contain alphanumeric characters and underscores"); + } this.lockTableName = lockTableName; return this; } diff --git a/dlock-jdbc/src/main/java/com/dlock/jdbc/repository/JDBCLockRepository.java b/dlock-jdbc/src/main/java/com/dlock/jdbc/repository/JDBCLockRepository.java index 894f1df..3ddade7 100644 --- a/dlock-jdbc/src/main/java/com/dlock/jdbc/repository/JDBCLockRepository.java +++ b/dlock-jdbc/src/main/java/com/dlock/jdbc/repository/JDBCLockRepository.java @@ -26,6 +26,7 @@ public class JDBCLockRepository implements LockRepository { private final String findByHandleSQL; private final String findByKeySQL; private final String removeByHandleSQL; + private final int insertParamsCount; public JDBCLockRepository(ScriptResolver scriptResolver, DataSource dataSource) { this.dataSource = dataSource; @@ -33,6 +34,7 @@ public JDBCLockRepository(ScriptResolver scriptResolver, DataSource dataSource) this.findByHandleSQL = scriptResolver.resolveScript("lock.findByHandle"); this.findByKeySQL = scriptResolver.resolveScript("lock.findByKey"); this.removeByHandleSQL = scriptResolver.resolveScript("lock.removeByHandle"); + this.insertParamsCount = countOccurrences(insertSQL, '?'); } @Override @@ -134,12 +136,19 @@ private boolean executeInsert(Connection connection, WriteLockRecord lockRecord) ps.setString(2, lockRecord.lockHandleId()); ps.setLong(3, lockRecord.expirationSeconds()); - ps.setString(4, lockRecord.lockKey()); + + if (insertParamsCount == 4) { + ps.setString(4, lockRecord.lockKey()); + } return ps.executeUpdate() == 1; } } + private int countOccurrences(String str, char ch) { + return (int) str.chars().filter(c -> c == ch).count(); + } + /** Delete SQL PreparedStatement. */ private boolean executeRemove(Connection connection, String lockHandleId) throws SQLException { try (PreparedStatement ps = connection.prepareStatement(removeByHandleSQL)) { diff --git a/dlock-jdbc/src/main/resources/db/POSTGRESQL-sql.properties b/dlock-jdbc/src/main/resources/db/POSTGRESQL-sql.properties index c710636..4f83d8d 100644 --- a/dlock-jdbc/src/main/resources/db/POSTGRESQL-sql.properties +++ b/dlock-jdbc/src/main/resources/db/POSTGRESQL-sql.properties @@ -1,4 +1,4 @@ -lock.findByKey=SELECT "LCK_KEY", "LCK_HNDL_ID", "CREATED_TIME", "EXPIRE_SEC", CURRENT_TIMESTAMP FROM "@@tableName@@" WHERE "LCK_KEY" = ? -lock.findByHandle=SELECT "LCK_KEY", "LCK_HNDL_ID", "CREATED_TIME", "EXPIRE_SEC", CURRENT_TIMESTAMP FROM "@@tableName@@" WHERE "LCK_HNDL_ID" = ? -lock.insert=INSERT INTO "@@tableName@@" ("LCK_KEY", "LCK_HNDL_ID", "CREATED_TIME", "EXPIRE_SEC") SELECT ?, ?, CURRENT_TIMESTAMP, ? WHERE NOT EXISTS (SELECT 1 FROM "@@tableName@@" WHERE "LCK_KEY" = ?) -lock.removeByHandle=DELETE FROM "@@tableName@@" WHERE "LCK_HNDL_ID" = ? +lock.findByKey=SELECT "LCK_KEY", "LCK_HNDL_ID", "CREATED_TIME", "EXPIRE_SEC", CURRENT_TIMESTAMP FROM "@@tableName@@" WHERE "LCK_KEY" = ? +lock.findByHandle=SELECT "LCK_KEY", "LCK_HNDL_ID", "CREATED_TIME", "EXPIRE_SEC", CURRENT_TIMESTAMP FROM "@@tableName@@" WHERE "LCK_HNDL_ID" = ? +lock.insert=INSERT INTO "@@tableName@@" ("LCK_KEY", "LCK_HNDL_ID", "CREATED_TIME", "EXPIRE_SEC") VALUES (?, ?, CURRENT_TIMESTAMP, ?) ON CONFLICT ("LCK_KEY") DO NOTHING +lock.removeByHandle=DELETE FROM "@@tableName@@" WHERE "LCK_HNDL_ID" = ? diff --git a/dlock-jdbc/src/test/groovy/com/dlock/jdbc/builder/JDBCKeyLockBuilderTest.groovy b/dlock-jdbc/src/test/groovy/com/dlock/jdbc/builder/JDBCKeyLockBuilderTest.groovy new file mode 100644 index 0000000..7e30818 --- /dev/null +++ b/dlock-jdbc/src/test/groovy/com/dlock/jdbc/builder/JDBCKeyLockBuilderTest.groovy @@ -0,0 +1,48 @@ +package com.dlock.jdbc.builder + +import spock.lang.Specification +import spock.lang.Unroll + +class JDBCKeyLockBuilderTest extends Specification { + + @Unroll + def "should accept valid lockTableName: #tableName"() { + given: + def builder = new JDBCKeyLockBuilder() + + when: + builder.lockTableName(tableName) + + then: + noExceptionThrown() + + where: + tableName << ["DLCK", "MyTable_1", "My_Table"] + } + + @Unroll + def "should reject invalid lockTableName: #tableName"() { + given: + def builder = new JDBCKeyLockBuilder() + + when: + builder.lockTableName(tableName) + + then: + thrown(IllegalArgumentException) + + where: + tableName << ["My-Table", "My.Table", "My Table", "DROP TABLE", ""] + } + + def "should reject null lockTableName"() { + given: + def builder = new JDBCKeyLockBuilder() + + when: + builder.lockTableName(null) + + then: + thrown(IllegalArgumentException) + } +} diff --git a/dlock-spring/README.md b/dlock-spring/README.md index 551291d..05c8e73 100644 --- a/dlock-spring/README.md +++ b/dlock-spring/README.md @@ -24,7 +24,7 @@ public class DLockConfig { public KeyLock keyLock(DataSource dataSource) { return new JDBCKeyLockBuilder() .dataSource(dataSource) - .databaseType(DatabaseType.H2) + .databaseType(DatabaseType.H2) // or POSTGRESQL, ORACLE .build(); } }