From d1b4cbfbc3956fc72bd183dbc219c4e7e8bdfb98 Mon Sep 17 00:00:00 2001 From: Nadav Har'El Date: Tue, 15 Feb 2022 23:52:43 +0200 Subject: [PATCH] test/cql-pytest: add reproducer for LWT bug with static-column conditions This patch adds a reproducing test for issue #10081. That issue is about a conditional (LWT) UPDATE operation that chose a non-existent row via WHERE, and its condition refers to both static and regular columns: In that case, the code incorrectly assumes that because it didn't read any row, all columns are null - and forgets that the static column is *not* null. The test, test_lwt.py::test_lwt_missing_row_with_static passes on Cassandra but fails on Scylla, so is marked xfail. Refs #10081 Signed-off-by: Nadav Har'El Message-Id: <20220215215243.660087-1-nyh@scylladb.com> --- test/cql-pytest/test_lwt.py | 65 +++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 test/cql-pytest/test_lwt.py diff --git a/test/cql-pytest/test_lwt.py b/test/cql-pytest/test_lwt.py new file mode 100644 index 0000000000..f28d5595e2 --- /dev/null +++ b/test/cql-pytest/test_lwt.py @@ -0,0 +1,65 @@ +# Copyright 2020-present ScyllaDB +# +# SPDX-License-Identifier: AGPL-3.0-or-later + +############################################################################# +# Various tests for Light-Weight Transactions (LWT) support in Scylla. +# Note that we have many more LWT tests in the cql-repl framework: +# ../cql/lwt*_test.cql, ../cql/cassandra_cql_test.cql. +############################################################################# + +import re +import pytest +from cassandra.protocol import InvalidRequest + +from util import new_test_table, unique_key_int + +@pytest.fixture(scope="module") +def table1(cql, test_keyspace): + schema='p int, c int, r int, s int static, PRIMARY KEY(p, c)' + with new_test_table(cql, test_keyspace, schema) as table: + yield table + +# An LWT UPDATE whose condition uses non-static columns begins by reading +# the clustering row which must be specified by the WHERE. If there is a +# static column in the partition, it is read as well. The value of the all +# these columns - regular and static - is then passed to the condition. +# As discovered in issue #10081, if the row determined by WHERE does NOT +# exist, Scylla still needs to read the static column, but forgets to do so. +# this test reproduces this issue. +@pytest.mark.xfail(reason="Issue #10081") +def test_lwt_missing_row_with_static(cql, table1): + p = unique_key_int() + # Insert into partition p just the static column - and no clustering rows. + cql.execute(f'INSERT INTO {table1}(p, s) values ({p}, 1)') + # Now, do an update with WHERE p={p} AND c=1. This clustering row does + # *not* exist, so we expect to see r=null - and s=1 from before. + r = list(cql.execute(f'UPDATE {table1} SET s=2,r=1 WHERE p={p} AND c=1 IF s=1 and r=null')) + assert len(r) == 1 + assert r[0].applied == True + # At this point we should have one row, for c=1 + assert list(cql.execute(f'SELECT * FROM {table1} WHERE p={p}')) == [(p, 1, 2, 1)] + +# The fact that to reproduce #10081 above we needed the condition (IF) to +# mention a non-static column as well, suggests that Scylla has a different code +# path for the case that the condition has *only* static columns. In fact, +# in that case, the WHERE doesn't even need to specify the clustering key - +# the partition key should be enough. The following test confirms that this +# is indeed the case. +def test_lwt_static_condition(cql, table1): + p = unique_key_int() + cql.execute(f'INSERT INTO {table1}(p, s) values ({p}, 1)') + # When the condition only mentions static (partition-wide) columns, + # it is allowed not to specify the clustering key in the WHERE: + r = list(cql.execute(f'UPDATE {table1} SET s=2 WHERE p={p} IF s=1')) + assert len(r) == 1 + assert r[0].applied == True + assert list(cql.execute(f'SELECT * FROM {table1} WHERE p={p}')) == [(p, None, 2, None)] + # When the condition also mentions a non-static column, WHERE must point + # to a clustering column, i.e., mention the clustering key. If the + # clustering key is missing, we get an InvalidRequest error, where the + # message is slightly different between Scylla and Cassandra ("Missing + # mandatory PRIMARY KEY part c" and "Some clustering keys are missing: c", + # respectively. + with pytest.raises(InvalidRequest, match=re.compile('missing', re.IGNORECASE)): + cql.execute(f'UPDATE {table1} SET s=2 WHERE p={p} IF r=1')