IGNITE-28724 Calcite engine. Support SELECT ... FOR UPDATE query - #13366
IGNITE-28724 Calcite engine. Support SELECT ... FOR UPDATE query#13366vldpyatkov wants to merge 10 commits into
Conversation
| List<SqlNode> ofCols = null; | ||
| SqlIdentifier col; | ||
| Long waitSeconds = null; | ||
| String waitValue; | ||
| } | ||
| { | ||
| qry = OrderedQueryOrExpr(ExprContext.ACCEPT_QUERY) { s = span(); } | ||
| [ | ||
| LOOKAHEAD(<FOR> <UPDATE>) | ||
| <FOR> <UPDATE> | ||
| [ | ||
| LOOKAHEAD(<OF>) | ||
| <OF> | ||
| { | ||
| ofCols = new ArrayList<SqlNode>(); | ||
| } | ||
| col = CompoundIdentifier() { ofCols.add(col); } | ||
| ( | ||
| <COMMA> col = CompoundIdentifier() { ofCols.add(col); } | ||
| )* | ||
| { ofList = new SqlNodeList(ofCols, s.pos()); } | ||
| ] | ||
| [ | ||
| LOOKAHEAD(<WAIT>) | ||
| <WAIT> <UNSIGNED_INTEGER_LITERAL> | ||
| { | ||
| waitValue = token.image; | ||
|
|
||
| try { | ||
| waitSeconds = Long.parseLong(waitValue); | ||
| } |
There was a problem hiding this comment.
| List<SqlNode> ofCols = null; | |
| SqlIdentifier col; | |
| Long waitSeconds = null; | |
| String waitValue; | |
| } | |
| { | |
| qry = OrderedQueryOrExpr(ExprContext.ACCEPT_QUERY) { s = span(); } | |
| [ | |
| LOOKAHEAD(<FOR> <UPDATE>) | |
| <FOR> <UPDATE> | |
| [ | |
| LOOKAHEAD(<OF>) | |
| <OF> | |
| { | |
| ofCols = new ArrayList<SqlNode>(); | |
| } | |
| col = CompoundIdentifier() { ofCols.add(col); } | |
| ( | |
| <COMMA> col = CompoundIdentifier() { ofCols.add(col); } | |
| )* | |
| { ofList = new SqlNodeList(ofCols, s.pos()); } | |
| ] | |
| [ | |
| LOOKAHEAD(<WAIT>) | |
| <WAIT> <UNSIGNED_INTEGER_LITERAL> | |
| { | |
| waitValue = token.image; | |
| try { | |
| waitSeconds = Long.parseLong(waitValue); | |
| } | |
| SqlIdentifier col; | |
| Long waitSeconds = null; | |
| } | |
| { | |
| qry = OrderedQueryOrExpr(ExprContext.ACCEPT_QUERY) { s = span(); } | |
| [ | |
| LOOKAHEAD(<FOR> <UPDATE>) | |
| <FOR> <UPDATE> | |
| [ | |
| LOOKAHEAD(<OF>) | |
| <OF> | |
| { | |
| List<SqlNode> columns = new ArrayList<SqlNode>(); | |
| } | |
| col = CompoundIdentifier() { columns.add(col); } | |
| ( | |
| <COMMA> col = CompoundIdentifier() { columns.add(col); } | |
| )* | |
| { ofList = new SqlNodeList(columns, s.pos()); } | |
| ] | |
| [ | |
| LOOKAHEAD(<WAIT>) | |
| <WAIT> <UNSIGNED_INTEGER_LITERAL> | |
| { | |
| String waitValue = token.image; | |
| try { | |
| waitSeconds = Long.parseLong(waitValue); | |
| } |
There was a problem hiding this comment.
The previous variant is fit for existing code, but I can rewrite this close as you wish)
There was a problem hiding this comment.
I thought again.
I guess I won't be able to move the variable declarations inside of the Java code here. Because it is a common approach in this file, and I don't want to be the first who violates it.
| { | ||
| final Span s; | ||
| SqlNode qry; | ||
| SqlNodeList ofList = null; |
There was a problem hiding this comment.
The same, but I see you want to put this in order. Ok, I will try.
There was a problem hiding this comment.
Fixed variable names and made the Java code slightly simpler.
| IgniteResource.INSTANCE.selectForUpdateRequiresPessimisticTx().str(), | ||
| IgniteQueryErrorCode.UNSUPPORTED_OPERATION); | ||
|
|
||
| // waitSeconds: null = use tx remaining time (0), 0 = NOWAIT (-1), positive = ms. |
There was a problem hiding this comment.
(0) and (-1) in braces are confused, what is the purpose of such a comment ? plz extend it or remove
There was a problem hiding this comment.
I translate it right now. If you get in waitSeconds after parsing the query string, null it, which means you have to pass wait milliseconds 0, which means wait until the transaction is alive. If you get 0 (in waitSeconds) you have to pass -1L (or any negative digit) with means nowait, or if you get a digit, you have to convert seconds to milliseconds and pass the result.
I will extend the comment here.
| waitMs = waitSeconds * 1000L; | ||
|
|
||
| // Zero means that retries are limited only by the transaction or query timeout. | ||
| long deadline = waitMs > 0 |
There was a problem hiding this comment.
| long deadline = waitMs > 0 | |
| long attemptsTimeout = waitMs > 0 |
There was a problem hiding this comment.
I do not think there is a good change because the deadline is not a timeout; it is a time after which the query execution has to be finished.
|
|
||
| if (userTx == null || !userTx.pessimistic()) | ||
| throw new IgniteSQLException( | ||
| IgniteResource.INSTANCE.selectForUpdateRequiresPessimisticTx().str(), |
There was a problem hiding this comment.
seems we need to align this 'strange' as for me message with :
org.apache.ignite.internal.processors.cache.GridCacheAdapter#lockTxEntriesAsync ->
new IgniteCheckedException("Failed to acquire transactional lock in optimistic transaction."));
or ignore this check here and it will be raized a bit later, need to make sure that SqlException will be raized.
There was a problem hiding this comment.
The message is not only for optomistic transaction is used. It will be appear when anyone try to liunch SELECT FOR UPDATE without transaction at all. For this reason is has a general statement.
|
|
||
| IgniteTable igniteTable = (IgniteTable)schemaPlus.getTable(target.tableName()); | ||
|
|
||
| if (igniteTable == null) |
There was a problem hiding this comment.
is it sys view case ? if so - it need to be handled more informative i suppose
There was a problem hiding this comment.
I added a dedicated test for the system table. It fills in the other code fragment.
This exception is rather for completeness than for the specific case. I think we can be there in case where table removed concurrently.
| IgniteInternalFuture<?> lockFut = GridTestUtils.runAsync(() -> { | ||
| try (Transaction tx = ignite0.transactions().txStart(PESSIMISTIC, READ_COMMITTED)) { | ||
| assertRows( | ||
| sql("SELECT p.id FROM Person p JOIN Dept d ON p.deptId = d.id WHERE p.id = 1 FOR UPDATE"), |
There was a problem hiding this comment.
why you avoid to inherit from AbstractBasicIntegrationTest ? and use all infrastructure like :
assertQuery("SELECT p.id FROM Person p JOIN Dept d ON p.deptId = d.id WHERE p.id = 1 FOR UPDATE")
.returns(1)
.check();
There was a problem hiding this comment.
These tests do not read the transaction in SQL.
So I can try, but it leads to fixing the base class.
| /** | ||
| * Integration tests for {@code SELECT ... FOR UPDATE} syntax. | ||
| */ | ||
| public class SelectForUpdateIntegrationTest extends GridCommonAbstractTest { |
There was a problem hiding this comment.
- sys view tests are absent
- virtual tables are absent table(system_range(1, 4)
- non PK involved tests are absent, or i miss ?
There was a problem hiding this comment.
- Added a test
- Added a test
- There are a lot of tests without condition on PK.
| * | ||
| * @return Result cursor when locking succeeds, or {@code null} when the SELECT must be executed again. | ||
| */ | ||
| @Nullable private FieldsQueryCursor<List<?>> tryExecuteForUpdate( |
There was a problem hiding this comment.
The method is a bit hard to read; could you split the different parts into separate methods?
There was a problem hiding this comment.
I split the method into three parts:
- Collect lock batches,
- Locks acquiring,
- Prepare result.
| } | ||
|
|
||
| /** */ | ||
| private void validateVersionColumnDmlTarget(SqlIdentifier id) { |
There was a problem hiding this comment.
I don't think other system fields should be modifiable during an update either. Could you check that? If so, we need to fix it for all of them preferably in a separate ticket, ideally one addressed before this one, to avoid bloating the changes here.
There was a problem hiding this comment.
For the other system fields, the circumstances are more complicated.
For example, field _val can be used if it is the only field for a simple type like String or Integer.
A _key field is available to use in the INSERT command.
| @Override public boolean isUpdateAllowed(RelOptTable tbl, int colIdx) { | ||
| final CacheColumnDescriptor desc = descriptors[colIdx]; | ||
|
|
||
| if (QueryUtils.VER_FIELD_NAME.equals(desc.name())) |
There was a problem hiding this comment.
Maybe we could introduce a boolean field or something similar to avoid checking strings?
There was a problem hiding this comment.
I fied it as desc instanceof SystemDescriptor, it not clear because we call _key and _val also system fileds, but they are instances of the other class (KeyValDescriptor).
Anyway is is correct right now.
| // Both WHEN MATCHED and WHEN NOT MATCHED clauses in MERGE. | ||
| assert rowColumnsCnt == descriptors.length * 2 + updateColList.size() : "Unexpected columns count: " + | ||
| rowColumnsCnt; | ||
| assert rowColumnsCnt == 2 * descriptors.length + updateColList.size() : "Unexpected columns count: " |
There was a problem hiding this comment.
It looks like you haven't changed anything here other than repositioning the multiplication operator.
There was a problem hiding this comment.
Yes, I changed it many times and finally returned to the origin.
| protected List<List<?>> sql(IgniteEx ignite, String sql, Object... params) { | ||
| // {@code sql} can contain more than one query. | ||
| List<FieldsQueryCursor<List<?>>> allCurs = queryProcessor(ignite).query(queryContext(), "PUBLIC", sql, params); | ||
| Transaction tx = ignite.transactions().tx(); |
There was a problem hiding this comment.
I suggest not keeping these changes in this class but moving them to the appropriate ones, or creating a subclass of AbstractBasicIntegrationTest and defining this logic there.
There was a problem hiding this comment.
I like this change. Moreover, I think finally we understand that the AbstractBasicIntegrationTest.java is not the best base class.
Probably, we will make a refactoring in the future in order to it uses a public APY.
|
|
||
| /** */ | ||
| @Test | ||
| public void testSystemColumnsAreHiddenFromSelectStar() throws Exception { |
There was a problem hiding this comment.
Column name validation is missing.
There was a problem hiding this comment.
This check is based on a column count.
The person table has fields id, name, and age, so this assertEquals(3, rows.get(0).size()) passes.
https://issues.apache.org/jira/browse/IGNITE-28724