Repository: diennea/herddb (java)

Issue:
FOR UPDATE not supported
**FEATURE REQUEST**

1. Please describe the feature you are requesting.

Standard SQL support FOR UPDATE, ex:

    SELECT SEQUENCE_VALUE FROM OPENJPA_SEQUENCE_TABLE WHERE ID = ? FOR UPDATE

HerdDB does not.

2. Indicate the importance of this issue to you (blocker, must-have, should-have, nice-to-have). Are you currently using any workarounds to address this issue?

This prevents to use several JPA mapper without workarounds.



Pull request:
Basic implementation for 'FOR UPDATE' clause
Implementation of SELECT .... FOR UPDATE.

If you issue "SELECT .... FOR UPDATE" the transaction will hold "WRITE" locks for every record returned by the query (both SCANs and GETs).

Traditionally if you do not add "FOR UPDATE" the transaction holds only "READ" locks for every record return by the query, this behaviour is not changed.

Unfortunately the detection of "FOR UPDATE" syntax is quite hacky, because Apache Calcite does not still implement it.

This implementation will ease the compatibility with JPA providers

fixes #631 

Once #637 is in we can change the OpenJPA test case and let it use "FOR UPDATE" syntax 

Patch:
diff --git a/herddb-collections/src/main/java/herddb/collections/CollectionsManager.java b/herddb-collections/src/main/java/herddb/collections/CollectionsManager.java
index 35ab2804..5659c531 100644
--- a/herddb-collections/src/main/java/herddb/collections/CollectionsManager.java
+++ b/herddb-collections/src/main/java/herddb/collections/CollectionsManager.java
@@ -199,7 +199,7 @@ public final class CollectionsManager implements AutoCloseable {
                 .tablespace(TableSpace.DEFAULT)
                 .build();
         CreateTableStatement createTable = new CreateTableStatement(table);
-        tableSpaceManager.executeStatement(createTable, new StatementEvaluationContext(), TransactionContext.NO_TRANSACTION);
+        tableSpaceManager.executeStatement(createTable, StatementEvaluationContext.DEFAULT_EVALUATION_CONTEXT(), TransactionContext.NO_TRANSACTION);
         return table;
     }
 
diff --git a/herddb-collections/src/main/java/herddb/collections/TmpMapImpl.java b/herddb-collections/src/main/java/herddb/collections/TmpMapImpl.java
index 331df19c..33562576 100644
--- a/herddb-collections/src/main/java/herddb/collections/TmpMapImpl.java
+++ b/herddb-collections/src/main/java/herddb/collections/TmpMapImpl.java
@@ -72,6 +72,7 @@ class TmpMapImpl<K, V> implements TmpMap<K, V> {
         private final V value;
 
         public PutStatementEvaluationContext(K key, V value) {
+            super(false);
             this.key = key;
             this.value = value;
         }
@@ -137,7 +138,7 @@ class TmpMapImpl<K, V> implements TmpMap<K, V> {
     @Override
     public void close() {
         DropTableStatement drop = new DropTableStatement(TableSpace.DEFAULT, tmpTableName, true);
-        tableSpaceManager.executeStatement(drop, new StatementEvaluationContext(),
+        tableSpaceManager.executeStatement(drop, StatementEvaluationContext.DEFAULT_EVALUATION_CONTEXT(),
                 herddb.model.TransactionContext.NO_TRANSACTION);
     }
 
@@ -196,7 +197,7 @@ class TmpMapImpl<K, V> implements TmpMap<K, V> {
     public void forEach(BiSink<K, V> sink) throws CollectionsException, SinkException {
 
         try (DataScanner dataScanner =
-                     tableSpaceManager.scan(scan, new StatementEvaluationContext(), TransactionContext.NO_TRANSACTION,
+                     tableSpaceManager.scan(scan, StatementEvaluationContext.DEFAULT_EVALUATION_CONTEXT(), TransactionContext.NO_TRANSACTION,
                              false,
                              false)) {
             while (dataScanner.hasNext()) {
@@ -228,7 +229,7 @@ class TmpMapImpl<K, V> implements TmpMap<K, V> {
     public void clear() throws CollectionsException {
         try {
             tableSpaceManager.executeStatement(new TruncateTableStatement(tmpTableName, tmpTableName),
-                    new StatementEvaluationContext(), TransactionContext.NO_TRANSACTION);
+                    StatementEvaluationContext.DEFAULT_EVALUATION_CONTEXT(), TransactionContext.NO_TRANSACTION);
         } catch (HerdDBInternalException err) {
             throw new CollectionsException(err);
         }
@@ -237,7 +238,7 @@ class TmpMapImpl<K, V> implements TmpMap<K, V> {
     @Override
     public void forEachKey(Sink<K> sink) throws CollectionsException, SinkException {
         try (DataScanner dataScanner =
-                     tableSpaceManager.scan(scan, new StatementEvaluationContext(), TransactionContext.NO_TRANSACTION,
+                     tableSpaceManager.scan(scan, StatementEvaluationContext.DEFAULT_EVALUATION_CONTEXT(), TransactionContext.NO_TRANSACTION,
                              false,
                              false)) {
             while (dataScanner.hasNext()) {
@@ -267,7 +268,7 @@ class TmpMapImpl<K, V> implements TmpMap<K, V> {
             GetStatement get = new GetStatement(TableSpace.DEFAULT, tmpTableName, Bytes.from_array(serializedKey), null,
                     false);
             GetResult getResult = (GetResult) tableSpaceManager.executeStatement(get,
-                    new StatementEvaluationContext(),
+                    StatementEvaluationContext.DEFAULT_EVALUATION_CONTEXT(),
                     herddb.model.TransactionContext.NO_TRANSACTION);
             if (!getResult.found()) {
                 return null;
@@ -285,7 +286,7 @@ class TmpMapImpl<K, V> implements TmpMap<K, V> {
             GetStatement get = new GetStatement(TableSpace.DEFAULT, tmpTableName, Bytes.from_array(serializedKey), null,
                     false);
             GetResult getResult = (GetResult) tableSpaceManager.executeStatement(get,
-                    new StatementEvaluationContext(),
+                    StatementEvaluationContext.DEFAULT_EVALUATION_CONTEXT(),
                     herddb.model.TransactionContext.NO_TRANSACTION);
             return getResult.found();
         } catch (HerdDBInternalException err) {
diff --git a/herddb-core/src/main/java/herddb/core/TableManager.java b/herddb-core/src/main/java/herddb/core/TableManager.java
index 4fe72021..c3102acc 100644
--- a/herddb-core/src/main/java/herddb/core/TableManager.java
+++ b/herddb-core/src/main/java/herddb/core/TableManager.java
@@ -2070,8 +2070,9 @@ public final class TableManager implements AbstractTableManager, Page.Owner {
         }
         Predicate predicate = get.getPredicate();
         boolean requireLock = get.isRequireLock();
+        boolean useWriteLock = requireLock && context.isForceAcquireWriteLock();
         long transactionId = transaction != null ? transaction.transactionId : 0;
-        LockHandle lock = (transaction != null || requireLock) ? lockForRead(key, transaction) : null;
+        LockHandle lock = (transaction != null || requireLock) ? (useWriteLock ? lockForWrite(key, transaction) : lockForRead(key, transaction)) : null;
         CompletableFuture<StatementExecutionResult> res = null;
         try {
             if (transaction != null) {
@@ -2894,6 +2895,9 @@ public final class TableManager implements AbstractTableManager, Page.Owner {
             ScanStatement statement, StatementEvaluationContext context,
             Transaction transaction, boolean lockRequired, boolean forWrite
     ) throws StatementExecutionException {
+
+        forWrite = forWrite || context.isForceAcquireWriteLock();
+
         TupleComparator comparator = statement.getComparator();
         if (!ENABLE_STREAMING_DATA_SCANNER || (comparator != null
                 && this.stats.getTablesize() > HUGE_TABLE_SIZE_FORCE_MATERIALIZED_RESULTSET)) {
diff --git a/herddb-core/src/main/java/herddb/core/TableSpaceManager.java b/herddb-core/src/main/java/herddb/core/TableSpaceManager.java
index 8305cc7e..86504fb0 100644
--- a/herddb-core/src/main/java/herddb/core/TableSpaceManager.java
+++ b/herddb-core/src/main/java/herddb/core/TableSpaceManager.java
@@ -1424,7 +1424,7 @@ public class TableSpaceManager {
             } catch (IllegalArgumentException error) {
                 throw new StatementExecutionException(error);
             }
-            validateAlterTable(newTable, null);
+            validateAlterTable(newTable, StatementEvaluationContext.DEFAULT_EVALUATION_CONTEXT());
             LogEntry entry = LogEntryFactory.alterTable(newTable, null);
             try {
                 CommitLogResult pos = log.log(entry, entry.transactionId <= 0);
diff --git a/herddb-core/src/main/java/herddb/model/StatementEvaluationContext.java b/herddb-core/src/main/java/herddb/model/StatementEvaluationContext.java
index bc686a9b..ab9f78ee 100644
--- a/herddb-core/src/main/java/herddb/model/StatementEvaluationContext.java
+++ b/herddb-core/src/main/java/herddb/model/StatementEvaluationContext.java
@@ -42,13 +42,19 @@ public class StatementEvaluationContext {
     private String defaultTablespace = TableSpace.DEFAULT;
     private volatile long tableSpaceLock;
     private static final ZoneId timezone = ZoneId.systemDefault();
+    private final boolean forceAcquireWriteLock;
 
     // CHECKSTYLE.OFF: MethodName
     public static StatementEvaluationContext DEFAULT_EVALUATION_CONTEXT() {
-        return new StatementEvaluationContext();
+        return new StatementEvaluationContext(false);
     }
     // CHECKSTYLE.ON: MethodName
 
+    protected StatementEvaluationContext(boolean forceAcquireWriteLock) {
+        this.forceAcquireWriteLock = forceAcquireWriteLock;
+    }
+
+
     public String getDefaultTablespace() {
         return defaultTablespace;
     }
@@ -107,4 +113,8 @@ public class StatementEvaluationContext {
         return timezone;
     }
 
+    public boolean isForceAcquireWriteLock() {
+        return forceAcquireWriteLock;
+    }
+
 }
diff --git a/herddb-core/src/main/java/herddb/server/ServerSideConnectionPeer.java b/herddb-core/src/main/java/herddb/server/ServerSideConnectionPeer.java
index 02e885be..cbba042c 100644
--- a/herddb-core/src/main/java/herddb/server/ServerSideConnectionPeer.java
+++ b/herddb-core/src/main/java/herddb/server/ServerSideConnectionPeer.java
@@ -856,7 +856,7 @@ public class ServerSideConnectionPeer implements ServerSideConnection, ChannelEv
 //            LOGGER.log(Level.SEVERE, "statement " + statement);
             CompletableFuture<StatementExecutionResult> res = server
                     .getManager()
-                    .executeStatementAsync(statement, new StatementEvaluationContext(), transactionContext);
+                    .executeStatementAsync(statement, StatementEvaluationContext.DEFAULT_EVALUATION_CONTEXT(), transactionContext);
 //                    LOGGER.log(Level.SEVERE, "query " + query + ", " + parameters + ", result:" + result);
             res.whenComplete((result, err) -> {
                 try {
diff --git a/herddb-core/src/main/java/herddb/sql/CalcitePlanner.java b/herddb-core/src/main/java/herddb/sql/CalcitePlanner.java
index d755b6b1..e4c83938 100644
--- a/herddb-core/src/main/java/herddb/sql/CalcitePlanner.java
+++ b/herddb-core/src/main/java/herddb/sql/CalcitePlanner.java
@@ -243,10 +243,18 @@ public class CalcitePlanner implements AbstractSQLPlanner {
                 + ",query:" + query
                 + ",returnValues:" + returnValues
                 + ",maxRows:" + maxRows;
+        boolean forceAcquireWriteLock;
+        if (query.endsWith(" FOR UPDATE") // this looks very hacky
+                && query.substring(0, 6).toLowerCase().equals("select")) {
+            forceAcquireWriteLock = true;
+            query = query.substring(0, query.length() - " FOR UPDATE".length());
+        } else {
+            forceAcquireWriteLock = false;
+        }
         if (allowCache) {
             ExecutionPlan cached = cache.get(cacheKey);
             if (cached != null) {
-                return new TranslatedQuery(cached, new SQLStatementEvaluationContext(query, parameters));
+                return new TranslatedQuery(cached, new SQLStatementEvaluationContext(query, parameters, forceAcquireWriteLock));
             }
         }
 
@@ -300,7 +308,7 @@ public class CalcitePlanner implements AbstractSQLPlanner {
                         new SQLPlannedOperationStatement(values),
                         values
                 );
-                return new TranslatedQuery(executionPlan, new SQLStatementEvaluationContext(query, parameters));
+                return new TranslatedQuery(executionPlan, new SQLStatementEvaluationContext(query, parameters, forceAcquireWriteLock));
             }
 
             if (query.startsWith("SHOW")) {
@@ -334,7 +342,7 @@ public class CalcitePlanner implements AbstractSQLPlanner {
                     if (allowCache) {
                         cache.put(cacheKey, executionPlan);
                     }
-                    return new TranslatedQuery(executionPlan, new SQLStatementEvaluationContext(query, parameters));
+                    return new TranslatedQuery(executionPlan, new SQLStatementEvaluationContext(query, parameters, forceAcquireWriteLock));
                 }
             }
             if (maxRows > 0) {
@@ -353,7 +361,7 @@ public class CalcitePlanner implements AbstractSQLPlanner {
             if (allowCache) {
                 cache.put(cacheKey, executionPlan);
             }
-            return new TranslatedQuery(executionPlan, new SQLStatementEvaluationContext(query, parameters));
+            return new TranslatedQuery(executionPlan, new SQLStatementEvaluationContext(query, parameters, forceAcquireWriteLock));
         } catch (CalciteContextException ex) {
             LOG.log(Level.INFO, "Error while parsing '" + ex.getOriginalStatement() + "'", ex);
             //TODO can this be done better ?
@@ -423,7 +431,7 @@ public class CalcitePlanner implements AbstractSQLPlanner {
                     new SQLPlannedOperationStatement(values),
                     values
             );
-            return new TranslatedQuery(executionPlan, new SQLStatementEvaluationContext(query, parameters));
+            return new TranslatedQuery(executionPlan, new SQLStatementEvaluationContext(query, parameters, false));
         } else {
             throw new StatementExecutionException(String.format("Incorrect Syntax for SHOW CREATE TABLE tablespace.tablename"));
         }
diff --git a/herddb-core/src/main/java/herddb/sql/DDLSQLPlanner.java b/herddb-core/src/main/java/herddb/sql/DDLSQLPlanner.java
index 74f0c525..72c5683b 100644
--- a/herddb-core/src/main/java/herddb/sql/DDLSQLPlanner.java
+++ b/herddb-core/src/main/java/herddb/sql/DDLSQLPlanner.java
@@ -232,16 +232,16 @@ public class DDLSQLPlanner implements AbstractSQLPlanner {
         if (allowCache) {
             ExecutionPlan cached = cache.get(cacheKey);
             if (cached != null) {
-                return new TranslatedQuery(cached, new SQLStatementEvaluationContext(query, parameters));
+                return new TranslatedQuery(cached, new SQLStatementEvaluationContext(query, parameters, false));
             }
         }
         if (query.startsWith(CalcitePlanner.TABLE_CONSISTENCY_COMMAND)) {
             ExecutionPlan executionPlan = ExecutionPlan.simple(DDLSQLPlanner.this.queryConsistencyCheckStatement(defaultTableSpace, query, parameters));
-            return new TranslatedQuery(executionPlan, new SQLStatementEvaluationContext(query, parameters));
+            return new TranslatedQuery(executionPlan, new SQLStatementEvaluationContext(query, parameters, false));
         }
         if (query.startsWith(CalcitePlanner.TABLESPACE_CONSISTENCY_COMMAND)) {
             ExecutionPlan executionPlan = ExecutionPlan.simple(DDLSQLPlanner.this.queryConsistencyCheckStatement(query));
-            return new TranslatedQuery(executionPlan, new SQLStatementEvaluationContext(query, parameters));
+            return new TranslatedQuery(executionPlan, new SQLStatementEvaluationContext(query, parameters, false));
         }
 
         net.sf.jsqlparser.statement.Statement stmt = parseStatement(query);
@@ -252,7 +252,7 @@ public class DDLSQLPlanner implements AbstractSQLPlanner {
         if (allowCache) {
             cache.put(cacheKey, executionPlan);
         }
-        return new TranslatedQuery(executionPlan, new SQLStatementEvaluationContext(query, parameters));
+        return new TranslatedQuery(executionPlan, new SQLStatementEvaluationContext(query, parameters, false));
 
     }
 
diff --git a/herddb-core/src/main/java/herddb/sql/SQLStatementEvaluationContext.java b/herddb-core/src/main/java/herddb/sql/SQLStatementEvaluationContext.java
index c6165611..b07e3d12 100644
--- a/herddb-core/src/main/java/herddb/sql/SQLStatementEvaluationContext.java
+++ b/herddb-core/src/main/java/herddb/sql/SQLStatementEvaluationContext.java
@@ -42,7 +42,8 @@ public class SQLStatementEvaluationContext extends StatementEvaluationContext {
         return jdbcParameters;
     }
 
-    public SQLStatementEvaluationContext(String query, List<Object> jdbcParameters) {
+    public SQLStatementEvaluationContext(String query, List<Object> jdbcParameters, boolean forceAcquireWriteLock) {
+        super(forceAcquireWriteLock);
         this.query = query;
         this.jdbcParameters = jdbcParameters;
         final int len = jdbcParameters.size();


Test patch:
diff --git a/herddb-core/src/test/java/herddb/core/RawSQLTest.java b/herddb-core/src/test/java/herddb/core/RawSQLTest.java
index e16f2432..0a8ee966 100644
--- a/herddb-core/src/test/java/herddb/core/RawSQLTest.java
+++ b/herddb-core/src/test/java/herddb/core/RawSQLTest.java
@@ -29,7 +29,10 @@ import static org.hamcrest.CoreMatchers.instanceOf;
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotSame;
 import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
@@ -52,6 +55,7 @@ import herddb.model.StatementEvaluationContext;
 import herddb.model.StatementExecutionException;
 import herddb.model.TableDoesNotExistException;
 import herddb.model.TableSpace;
+import herddb.model.Transaction;
 import herddb.model.TransactionContext;
 import herddb.model.TransactionResult;
 import herddb.model.commands.CommitTransactionStatement;
@@ -229,6 +233,150 @@ public class RawSQLTest {
         }
     }
 
+    @Test
+    public void selectForUpdate() throws Exception {
+        String nodeId = "localhost";
+        try (DBManager manager = new DBManager("localhost", new MemoryMetadataStorageManager(), new MemoryDataStorageManager(), new MemoryCommitLogManager(), null, null)) {
+            manager.start();
+            CreateTableSpaceStatement st1 = new CreateTableSpaceStatement("tblspace1", Collections.singleton(nodeId), nodeId, 1, 0, 0);
+            manager.executeStatement(st1, StatementEvaluationContext.DEFAULT_EVALUATION_CONTEXT(), TransactionContext.NO_TRANSACTION);
+            manager.waitForTablespace("tblspace1", 10000);
+
+            execute(manager, "CREATE TABLE tblspace1.tsql (k1 string primary key, n1 int)", Collections.emptyList());
+
+            execute(manager, "INSERT INTO tblspace1.tsql (k1,n1) values(?,?)", Arrays.asList("a", 1));
+
+            // GET .. FOR UPDATE -> WRITE LOCK
+            {
+                long tx;
+                try (DataScanner scan = scan(manager, "SELECT k1,n1 FROM tblspace1.tsql where k1='a' FOR UPDATE", Collections.emptyList(), TransactionContext.AUTOTRANSACTION_TRANSACTION)) {
+                    assertEquals(1, scan.consume().size());
+                    tx = scan.getTransactionId();
+                }
+                Bytes key = Bytes.from_string("a");
+                Transaction transaction = manager.getTableSpaceManager("tblspace1").getTransaction(tx);
+                herddb.utils.LockHandle lock = (herddb.utils.LockHandle) transaction.locks.get("tsql").get(key);
+                System.out.println("LOCK: " + lock);
+                assertNotNull(lock);
+                assertTrue(lock.write);
+                commitTransaction(manager, "tblspace1", tx);
+            }
+
+            // GET .. -> READ LOCK
+            {
+                long tx;
+                try (DataScanner scan = scan(manager, "SELECT k1,n1 FROM tblspace1.tsql where k1='a'", Collections.emptyList(), TransactionContext.AUTOTRANSACTION_TRANSACTION)) {
+                    assertEquals(1, scan.consume().size());
+                    tx = scan.getTransactionId();
+                }
+                Bytes key = Bytes.from_string("a");
+                Transaction transaction = manager.getTableSpaceManager("tblspace1").getTransaction(tx);
+                herddb.utils.LockHandle lock = (herddb.utils.LockHandle) transaction.locks.get("tsql").get(key);
+                System.out.println("LOCK: " + lock);
+                assertNotNull(lock);
+                assertFalse(lock.write);
+                commitTransaction(manager, "tblspace1", tx);
+            }
+
+            // SCAN .. FOR UPDATE -> WRITE LOCK
+            {
+                long tx;
+                try (DataScanner scan = scan(manager, "SELECT k1,n1 FROM tblspace1.tsql FOR UPDATE", Collections.emptyList(), TransactionContext.AUTOTRANSACTION_TRANSACTION)) {
+                    assertEquals(1, scan.consume().size());
+                    tx = scan.getTransactionId();
+                }
+                Bytes key = Bytes.from_string("a");
+                Transaction transaction = manager.getTableSpaceManager("tblspace1").getTransaction(tx);
+                herddb.utils.LockHandle lock = (herddb.utils.LockHandle) transaction.locks.get("tsql").get(key);
+                System.out.println("LOCK: " + lock);
+                assertNotNull(lock);
+                assertTrue(lock.write);
+                commitTransaction(manager, "tblspace1", tx);
+            }
+
+            // SCAN .. -> READ LOCK
+            {
+                long tx;
+                try (DataScanner scan = scan(manager, "SELECT k1,n1 FROM tblspace1.tsql ", Collections.emptyList(), TransactionContext.AUTOTRANSACTION_TRANSACTION)) {
+                    assertEquals(1, scan.consume().size());
+                    tx = scan.getTransactionId();
+                }
+                Bytes key = Bytes.from_string("a");
+                Transaction transaction = manager.getTableSpaceManager("tblspace1").getTransaction(tx);
+                herddb.utils.LockHandle lock = (herddb.utils.LockHandle) transaction.locks.get("tsql").get(key);
+                System.out.println("LOCK: " + lock);
+                assertNotNull(lock);
+                assertFalse(lock.write);
+                commitTransaction(manager, "tblspace1", tx);
+            }
+
+
+            // test that locks are reentrant
+            {
+                long tx;
+                try (DataScanner scan = scan(manager, "SELECT k1,n1 FROM tblspace1.tsql where k1='a' FOR UPDATE", Collections.emptyList(), TransactionContext.AUTOTRANSACTION_TRANSACTION)) {
+                    assertEquals(1, scan.consume().size());
+                    tx = scan.getTransactionId();
+                }
+                Bytes key = Bytes.from_string("a");
+                Transaction transaction = manager.getTableSpaceManager("tblspace1").getTransaction(tx);
+                herddb.utils.LockHandle lock = (herddb.utils.LockHandle) transaction.locks.get("tsql").get(key);
+                System.out.println("LOCK: " + lock);
+                assertNotNull(lock);
+                assertTrue(lock.write);
+
+                long tx2;
+                try (DataScanner scan = scan(manager, "SELECT k1,n1 FROM tblspace1.tsql where k1='a' FOR UPDATE", Collections.emptyList(), new TransactionContext(tx))) {
+                    assertEquals(1, scan.consume().size());
+                    tx2 = scan.getTransactionId();
+                }
+                assertEquals(tx2, tx);
+
+
+                Transaction transaction2 = manager.getTableSpaceManager("tblspace1").getTransaction(tx);
+                herddb.utils.LockHandle lock2 = (herddb.utils.LockHandle) transaction2.locks.get("tsql").get(key);
+                System.out.println("LOCK2: " + lock2);
+                assertNotNull(lock2);
+                assertSame(lock2, lock);
+
+
+                commitTransaction(manager, "tblspace1", tx);
+            }
+
+            // test lock upgrade
+            {
+                long tx;
+                try (DataScanner scan = scan(manager, "SELECT k1,n1 FROM tblspace1.tsql where k1='a'", Collections.emptyList(), TransactionContext.AUTOTRANSACTION_TRANSACTION)) {
+                    assertEquals(1, scan.consume().size());
+                    tx = scan.getTransactionId();
+                }
+                Bytes key = Bytes.from_string("a");
+                Transaction transaction = manager.getTableSpaceManager("tblspace1").getTransaction(tx);
+                herddb.utils.LockHandle lock = (herddb.utils.LockHandle) transaction.locks.get("tsql").get(key);
+                System.out.println("LOCK: " + lock);
+                assertNotNull(lock);
+                assertFalse(lock.write);
+
+                long tx2;
+                try (DataScanner scan = scan(manager, "SELECT k1,n1 FROM tblspace1.tsql where k1='a' FOR UPDATE", Collections.emptyList(), new TransactionContext(tx))) {
+                    assertEquals(1, scan.consume().size());
+                    tx2 = scan.getTransactionId();
+                }
+                assertEquals(tx2, tx);
+
+
+                Transaction transaction2 = manager.getTableSpaceManager("tblspace1").getTransaction(tx);
+                herddb.utils.LockHandle lock2 = (herddb.utils.LockHandle) transaction2.locks.get("tsql").get(key);
+                System.out.println("LOCK2: " + lock2);
+                assertNotNull(lock2);
+                assertNotSame(lock2, lock);
+                assertTrue(lock2.write);
+
+                commitTransaction(manager, "tblspace1", tx);
+            }
+        }
+    }
+
     @Test
     public void escapedStringTest() throws Exception {
         String nodeId = "localhost";
diff --git a/herddb-core/src/test/java/herddb/sql/SQLRecordPredicateTest.java b/herddb-core/src/test/java/herddb/sql/SQLRecordPredicateTest.java
index e9c1418f..cd8ff686 100644
--- a/herddb-core/src/test/java/herddb/sql/SQLRecordPredicateTest.java
+++ b/herddb-core/src/test/java/herddb/sql/SQLRecordPredicateTest.java
@@ -75,7 +75,7 @@ public class SQLRecordPredicateTest {
 
                 assertTrue(pred.getWhere() != null && (pred.getWhere() instanceof CompiledSQLExpression));
 
-                StatementEvaluationContext ctx = new SQLStatementEvaluationContext("the-query", Arrays.asList("my-string"));
+                StatementEvaluationContext ctx = new SQLStatementEvaluationContext("the-query", Arrays.asList("my-string"), false);
 
                 Record record = RecordSerializer.makeRecord(table, "pk", "test", "name", "myname");
                 assertEquals(Boolean.TRUE, pred.evaluate(record, ctx));