storage_proxy: cas: reject for tablets-enabled tables

Currently, LWT is not supported with tablets.
In particular the interaction between paxos and tablet
migration is not handled yet.

Therefore, it is better to outright reject LWT queries
for tablets-enabled tables rather than support them
in a flaky way.

This commit also marks tests that depend on LWT
as expeced to fail.

Fixes scylladb/scylladb#18066

Signed-off-by: Benny Halevy <bhalevy@scylladb.com>

Closes scylladb/scylladb#18103
This commit is contained in:
Benny Halevy
2024-03-28 19:50:21 +02:00
committed by Botond Dénes
parent 053a2893cf
commit 0156e97560
10 changed files with 118 additions and 6 deletions

View File

@@ -6153,6 +6153,12 @@ future<bool> storage_proxy::cas(schema_ptr schema, shared_ptr<cas_request> reque
db::consistency_level cl_for_paxos, db::consistency_level cl_for_learn,
clock_type::time_point write_timeout, clock_type::time_point cas_timeout, bool write) {
auto& table = local_db().find_column_family(schema->id());
if (table.uses_tablets()) {
auto msg = format("Cannot use LightWeight Transactions for table {}.{}: LWT is not yet supported with tablets", schema->ks_name(), schema->cf_name());
co_await coroutine::return_exception(exceptions::invalid_request_exception(msg));
}
assert(partition_ranges.size() == 1);
assert(query::is_single_partition(partition_ranges[0]));

View File

@@ -93,8 +93,9 @@ def dotestCreateAndDropIndex(cql, table, indexName, addKeyspaceOnDrop):
f"DROP INDEX {KEYSPACE}.{indexName}")
@pytest.fixture(scope="module")
def table1(cql, test_keyspace):
with create_table(cql, test_keyspace, "(a int primary key, b int)") as table:
# FIXME: LWT is not supported with tablets yet. See #18066
def table1(cql, test_keyspace_vnodes):
with create_table(cql, test_keyspace_vnodes, "(a int primary key, b int)") as table:
yield table
# Reproduces #8717 (CREATE INDEX IF NOT EXISTS was broken):
@@ -449,6 +450,9 @@ TOO_BIG = 1024 * 65
# both singly and in batches (CASSANDRA-10536)
# Reproduces #8627
@pytest.mark.xfail(reason="issue #8627")
@pytest.mark.parametrize("test_keyspace",
[pytest.param("tablets", marks=[pytest.mark.xfail(reason="issue #18066")]), "vnodes"],
indirect=True)
def testIndexOnCompositeValueOver64k(cql, test_keyspace):
too_big = bytearray([1])*TOO_BIG
with create_table(cql, test_keyspace, "(a int, b int, c blob, PRIMARY KEY (a))") as table:
@@ -468,6 +472,9 @@ def testIndexOnCompositeValueOver64k(cql, test_keyspace):
"APPLY BATCH",
too_big)
@pytest.mark.parametrize("test_keyspace",
[pytest.param("tablets", marks=[pytest.mark.xfail(reason="issue #18066")]), "vnodes"],
indirect=True)
def testIndexOnPartitionKeyInsertValueOver64k(cql, test_keyspace):
too_big = bytearray([1])*TOO_BIG
with create_table(cql, test_keyspace, "(a int, b int, c blob, PRIMARY KEY ((a, b)))") as table:
@@ -522,6 +529,9 @@ def testIndexOnPartitionKeyWithStaticColumnAndNoRows(cql, test_keyspace):
execute(cql, table, "UPDATE %s SET s=? WHERE pk1=? AND pk2=?", 9, 1, 20)
assert_rows(execute(cql, table, "SELECT * FROM %s WHERE pk2 = ?", 20), [1, 20, None, 9, None])
@pytest.mark.parametrize("test_keyspace",
[pytest.param("tablets", marks=[pytest.mark.xfail(reason="issue #18066")]), "vnodes"],
indirect=True)
def testIndexOnClusteringColumnInsertValueOver64k(cql, test_keyspace):
too_big = bytearray([1])*TOO_BIG
with create_table(cql, test_keyspace, "(a int, b int, c blob, PRIMARY KEY ((a, b)))") as table:
@@ -554,6 +564,9 @@ def testIndexOnClusteringColumnInsertValueOver64k(cql, test_keyspace):
# Reproduces #8627
@pytest.mark.xfail(reason="issue #8627")
@pytest.mark.parametrize("test_keyspace",
[pytest.param("tablets", marks=[pytest.mark.xfail(reason="issue #18066")]), "vnodes"],
indirect=True)
def testIndexOnFullCollectionEntryInsertCollectionValueOver64k(cql, test_keyspace):
too_big = bytearray([1])*TOO_BIG
map = {0: too_big}

View File

@@ -58,6 +58,9 @@ def testTimestampTTL(cql, test_keyspace):
[1, None, None])
# Migrated from cql_tests.py:TestCQL.invalid_custom_timestamp_test()
@pytest.mark.parametrize("test_keyspace",
[pytest.param("tablets", marks=[pytest.mark.xfail(reason="issue #18066")]), "vnodes"],
indirect=True)
def testInvalidCustomTimestamp(cql, test_keyspace):
# Conditional updates
with create_table(cql, test_keyspace, "(k int, v int, PRIMARY KEY (k, v))") as table:

View File

@@ -1233,6 +1233,9 @@ def testInsertWithCompactStorageAndTwoClusteringColumns(cql, test_keyspace, forc
# InsertUpdateIfConditionTest
# Test for CAS with compact storage table, and CASSANDRA-6813 in particular,
# migrated from cql_tests.py:TestCQL.cas_and_compact_test()
@pytest.mark.parametrize("test_keyspace",
[pytest.param("tablets", marks=[pytest.mark.xfail(reason="issue #18066")]), "vnodes"],
indirect=True)
def testCompactStorage(cql, test_keyspace):
with create_table(cql, test_keyspace, "(partition text, key text, owner text, PRIMARY KEY (partition, key)) WITH COMPACT STORAGE") as table:
execute(cql, table, "INSERT INTO %s (partition, key, owner) VALUES ('a', 'b', null)")

View File

@@ -25,6 +25,9 @@ def is_scylla(cql):
names = [row.table_name for row in cql.execute("SELECT * FROM system_schema.tables WHERE keyspace_name = 'system'")]
yield any('scylla' in name for name in names)
@pytest.mark.parametrize("test_keyspace",
[pytest.param("tablets", marks=[pytest.mark.xfail(reason="issue #18066")]), "vnodes"],
indirect=True)
def testInsertSetIfNotExists(cql, test_keyspace, is_scylla):
with create_table(cql, test_keyspace, "(k int PRIMARY KEY, s set<int>)") as table:
assertRows(execute(cql, table, "INSERT INTO %s (k, s) VALUES (0, {1, 2, 3}) IF NOT EXISTS"),
@@ -472,6 +475,9 @@ def check_invalid_list(cql, table, condition, expected):
assertRows(execute(cql, table, "SELECT * FROM %s"), row(0, ["foo", "bar", "foobar"]))
# Migrated from cql_tests.py:TestCQL.list_item_conditional_test()
@pytest.mark.parametrize("test_keyspace",
[pytest.param("tablets", marks=[pytest.mark.xfail(reason="issue #18066")]), "vnodes"],
indirect=True)
def testListItem(cql, test_keyspace):
for frozen in [False, True]:
typename = "list<text>"
@@ -496,6 +502,9 @@ def testListItem(cql, test_keyspace):
# Test expanded functionality from CASSANDRA-6839,
# migrated from cql_tests.py:TestCQL.expanded_list_item_conditional_test()
@pytest.mark.parametrize("test_keyspace",
[pytest.param("tablets", marks=[pytest.mark.xfail(reason="issue #18066")]), "vnodes"],
indirect=True)
def testExpandedListItem(cql, test_keyspace):
for frozen in [False, True]:
typename = "list<text>"
@@ -670,6 +679,9 @@ def testWholeMap(cql, test_keyspace):
check_invalid_map(cql, table, "m IN null", SyntaxException)
# Migrated from cql_tests.py:TestCQL.map_item_conditional_test()
@pytest.mark.parametrize("test_keyspace",
[pytest.param("tablets", marks=[pytest.mark.xfail(reason="issue #18066")]), "vnodes"],
indirect=True)
def testMapItem(cql, test_keyspace):
for frozen in [False, True]:
typename = "map<text,text>"
@@ -696,6 +708,9 @@ def testMapItem(cql, test_keyspace):
else:
assert list(execute(cql, table, "UPDATE %s set m['foo'] = 'bar', m['bar'] = 'foo' WHERE k = 1 IF m[?] IN (?, ?)", "foo", "blah", None))[0][0] == True
@pytest.mark.parametrize("test_keyspace",
[pytest.param("tablets", marks=[pytest.mark.xfail(reason="issue #18066")]), "vnodes"],
indirect=True)
def testFrozenWithNullValues(cql, test_keyspace):
with create_table(cql, test_keyspace, f"(k int PRIMARY KEY, m frozen<list<text>>)") as table:
execute(cql, table, "INSERT INTO %s (k, m) VALUES (0, null)")
@@ -714,6 +729,9 @@ def testFrozenWithNullValues(cql, test_keyspace):
# Test expanded functionality from CASSANDRA-6839,
# migrated from cql_tests.py:TestCQL.expanded_map_item_conditional_test()
@pytest.mark.parametrize("test_keyspace",
[pytest.param("tablets", marks=[pytest.mark.xfail(reason="issue #18066")]), "vnodes"],
indirect=True)
def testExpandedMapItem(cql, test_keyspace):
for frozen in [False, True]:
typename = "map<text,text>"

View File

@@ -28,6 +28,9 @@ def is_scylla(cql):
yield any('scylla' in name for name in names)
# Migrated from cql_tests.py:TestCQL.static_columns_cas_test()
@pytest.mark.parametrize("test_keyspace",
[pytest.param("tablets", marks=[pytest.mark.xfail(reason="issue #18066")]), "vnodes"],
indirect=True)
def testStaticColumnsCas(cql, test_keyspace, is_scylla):
with create_table(cql, test_keyspace, "(id int, k text, version int static, v text, PRIMARY KEY (id, k))") as table:
# Test that INSERT IF NOT EXISTS concerns only the static column if no clustering nor regular columns
@@ -146,6 +149,9 @@ def testStaticColumnsCas(cql, test_keyspace, is_scylla):
[row(False,1,"k2","newVal"),row(False,1,"k2","newVal")] if is_scylla else [row(False, 1, "k2", "newVal")])
# Test CASSANDRA-10532
@pytest.mark.parametrize("test_keyspace",
[pytest.param("tablets", marks=[pytest.mark.xfail(reason="issue #18066")]), "vnodes"],
indirect=True)
def testStaticColumnsCasDelete(cql, test_keyspace, is_scylla):
with create_table(cql, test_keyspace, "(pk int, ck int, static_col int static, value int, PRIMARY KEY (pk, ck))") as table:
execute(cql, table, "INSERT INTO %s (pk, ck, value) VALUES (?, ?, ?)", 1, 1, 2)
@@ -206,6 +212,9 @@ def testStaticColumnsCasDelete(cql, test_keyspace, is_scylla):
row(1, 5, null, 6),
row(1, 7, null, 8))
@pytest.mark.parametrize("test_keyspace",
[pytest.param("tablets", marks=[pytest.mark.xfail(reason="issue #18066")]), "vnodes"],
indirect=True)
def testStaticColumnsCasUpdate(cql, test_keyspace, is_scylla):
with create_table(cql, test_keyspace, "(pk int, ck int, static_col int static, value int, PRIMARY KEY (pk, ck))") as table:
execute(cql, table, "INSERT INTO %s (pk, ck, value) VALUES (?, ?, ?)", 1, 1, 2)
@@ -258,6 +267,9 @@ def testStaticColumnsCasUpdate(cql, test_keyspace, is_scylla):
row(1, 5, 1, 6),
row(1, 7, 1, 8))
@pytest.mark.parametrize("test_keyspace",
[pytest.param("tablets", marks=[pytest.mark.xfail(reason="issue #18066")]), "vnodes"],
indirect=True)
def testConditionalUpdatesOnStaticColumns(cql, test_keyspace, is_scylla):
with create_table(cql, test_keyspace, "(a int, b int, s int static, d text, PRIMARY KEY (a, b))") as table:
assertInvalidMessage(cql, table, "unset", "UPDATE %s SET s = 6 WHERE a = 6 IF s = ?", unset())
@@ -289,6 +301,9 @@ def testConditionalUpdatesOnStaticColumns(cql, test_keyspace, is_scylla):
assertRows(execute(cql, table, "SELECT * FROM %s WHERE a = 8"),
row(8, null, 8, null))
@pytest.mark.parametrize("test_keyspace",
[pytest.param("tablets", marks=[pytest.mark.xfail(reason="issue #18066")]), "vnodes"],
indirect=True)
def testStaticsWithMultipleConditions(cql, test_keyspace, is_scylla):
with create_table(cql, test_keyspace, "(a int, b int, s1 int static, s2 int static, d int, PRIMARY KEY (a, b))") as table:
for i in range(1,6):
@@ -324,6 +339,9 @@ def testStaticsWithMultipleConditions(cql, test_keyspace, is_scylla):
+ "APPLY BATCH"),
[row(false,None,None,None,None,None),row(false,None,None,None,None,None),row(false,None,None,None,None,None),row(false,None,None,None,None,None)] if is_scylla else [row(false)])
@pytest.mark.parametrize("test_keyspace",
[pytest.param("tablets", marks=[pytest.mark.xfail(reason="issue #18066")]), "vnodes"],
indirect=True)
def testStaticColumnsCasUpdateWithNullStaticColumn(cql, test_keyspace, is_scylla):
with create_table(cql, test_keyspace, "(pk int, ck int, s1 int static, s2 int static, value int, PRIMARY KEY (pk, ck))") as table:
execute(cql, table, "INSERT INTO %s (pk, s1, s2) VALUES (1, 1, 1) USING TIMESTAMP 1000")
@@ -341,6 +359,9 @@ def testStaticColumnsCasUpdateWithNullStaticColumn(cql, test_keyspace, is_scylla
assertRows(execute(cql, table, "UPDATE %s SET s1 = ? WHERE pk = ? IF EXISTS", 2, 2), row(true,2,null,null,1,null) if is_scylla else row(true))
assertRows(execute(cql, table, "SELECT * FROM %s WHERE pk = ?", 2), row(2, null, 2, 1, null))
@pytest.mark.parametrize("test_keyspace",
[pytest.param("tablets", marks=[pytest.mark.xfail(reason="issue #18066")]), "vnodes"],
indirect=True)
def testStaticColumnsCasDeleteWithNullStaticColumn(cql, test_keyspace, is_scylla):
with create_table(cql, test_keyspace, "(pk int, ck int, s1 int static, s2 int static, value int, PRIMARY KEY (pk, ck))") as table:
execute(cql, table, "INSERT INTO %s (pk, s1, s2) VALUES (1, 1, 1) USING TIMESTAMP 1000")

View File

@@ -15,9 +15,10 @@ from cassandra.protocol import InvalidRequest
from util import new_test_table, unique_key_int
@pytest.fixture(scope="module")
def table1(cql, test_keyspace):
# FIXME: LWT is not supported with tablets yet. See #18066
def table1(cql, test_keyspace_vnodes):
schema='p int, c int, r int, s int static, PRIMARY KEY(p, c)'
with new_test_table(cql, test_keyspace, schema) as table:
with new_test_table(cql, test_keyspace_vnodes, schema) as table:
yield table
# An LWT UPDATE whose condition uses non-static columns begins by reading

View File

@@ -11,6 +11,8 @@
# same value for the execution on another shard).
#############################################################################
import pytest
from time import sleep
from util import new_test_table
@@ -44,17 +46,32 @@ def lwt_nondeterm_fn_repeated_execute(cql, test_keyspace, pk_type, fn):
rows = list(cql.execute(select_str))
assert len(rows) == num_iterations * 2
@pytest.mark.parametrize("test_keyspace",
[pytest.param("tablets", marks=[pytest.mark.xfail(reason="issue #18066")]), "vnodes"],
indirect=True)
def test_lwt_uuid_fn_pk_insert(cql, test_keyspace):
lwt_nondeterm_fn_repeated_execute(cql, test_keyspace, "uuid", "uuid")
@pytest.mark.parametrize("test_keyspace",
[pytest.param("tablets", marks=[pytest.mark.xfail(reason="issue #18066")]), "vnodes"],
indirect=True)
def test_lwt_currenttimestamp_fn_pk_insert(cql, test_keyspace):
lwt_nondeterm_fn_repeated_execute(cql, test_keyspace, "timestamp", "currenttimestamp")
@pytest.mark.parametrize("test_keyspace",
[pytest.param("tablets", marks=[pytest.mark.xfail(reason="issue #18066")]), "vnodes"],
indirect=True)
def test_lwt_currenttime_fn_pk_insert(cql, test_keyspace):
lwt_nondeterm_fn_repeated_execute(cql, test_keyspace, "time", "currenttime")
@pytest.mark.parametrize("test_keyspace",
[pytest.param("tablets", marks=[pytest.mark.xfail(reason="issue #18066")]), "vnodes"],
indirect=True)
def test_lwt_currenttimeuuid_fn_pk_insert(cql, test_keyspace):
lwt_nondeterm_fn_repeated_execute(cql, test_keyspace, "timeuuid", "currenttimeuuid")
@pytest.mark.parametrize("test_keyspace",
[pytest.param("tablets", marks=[pytest.mark.xfail(reason="issue #18066")]), "vnodes"],
indirect=True)
def test_lwt_now_fn_pk_insert(cql, test_keyspace):
lwt_nondeterm_fn_repeated_execute(cql, test_keyspace, "timeuuid", "now")

View File

@@ -201,3 +201,21 @@ def test_tablets_are_dropped_when_dropping_index(cql, test_keyspace, drop_index)
except:
pass
raise e
# FIXME: LWT is not supported with tablets yet. See #18066
# Until the issue is fixed, test that a LWT query indeed fails as expected
def test_lwt_support_with_tablets(cql, test_keyspace, skip_without_tablets):
with new_test_table(cql, test_keyspace, "key int PRIMARY KEY, val int") as table:
cql.execute(f"INSERT INTO {table} (key, val) VALUES(1, 0)")
with pytest.raises(InvalidRequest, match=f"{table}.*LWT is not yet supported with tablets"):
cql.execute(f"INSERT INTO {table} (key, val) VALUES(1, 1) IF NOT EXISTS")
# The query is rejected during the execution phase,
# so preparing the LWT query is expected to succeed.
stmt = cql.prepare(f"UPDATE {table} SET val = 1 WHERE KEY = ? IF EXISTS")
with pytest.raises(InvalidRequest, match=f"{table}.*LWT is not yet supported with tablets"):
cql.execute(stmt, [1])
with pytest.raises(InvalidRequest, match=f"{table}.*LWT is not yet supported with tablets"):
cql.execute(f"DELETE FROM {table} WHERE key = 1 IF EXISTS")
res = cql.execute(f"SELECT val FROM {table} WHERE key = 1").one()
assert res.val == 0

View File

@@ -201,7 +201,13 @@ def test_unset_insert_where(cql, table2):
# Similar to test_unset_insert_where() above, just use an LWT write ("IF
# NOT EXISTS"). Test that using an UNSET_VALUE in an LWT condition causes
# a clear error, not silent skip and not a crash as in issue #13001.
def test_unset_insert_where_lwt(cql, table2):
@pytest.mark.parametrize("test_keyspace",
[pytest.param("tablets", marks=[pytest.mark.xfail(reason="issue #18066")]), "vnodes"],
indirect=True)
def test_unset_insert_where_lwt(cql, test_keyspace):
# FIXME: new_test_table is used here due to https://github.com/scylladb/scylladb/issues/18066
# When fixed, this test can go back to using the `table2` fixture.
with new_test_table(cql, test_keyspace, "p int, c int, PRIMARY KEY (p, c)") as table2:
p = unique_key_int()
stmt = cql.prepare(f'INSERT INTO {table2} (p, c) VALUES ({p}, ?) IF NOT EXISTS')
with pytest.raises(InvalidRequest, match="unset"):
@@ -219,7 +225,13 @@ def test_unset_update_where(cql, table3):
# Like test_unset_insert_where_lwt, but using UPDATE
# Python driver doesn't allow sending an UNSET_VALUE for the partition key,
# so only the clustering key is tested.
def test_unset_update_where_lwt(cql, table3):
@pytest.mark.parametrize("test_keyspace",
[pytest.param("tablets", marks=[pytest.mark.xfail(reason="issue #18066")]), "vnodes"],
indirect=True)
def test_unset_update_where_lwt(cql, test_keyspace):
# FIXME: new_test_table is used here due to https://github.com/scylladb/scylladb/issues/18066
# When fixed, this test can go back to using the `table3` fixture.
with new_test_table(cql, test_keyspace, "p int, c int, r int, PRIMARY KEY (p, c)") as table3:
stmt = cql.prepare(f"UPDATE {table3} SET r = 42 WHERE p = 0 AND c = ? IF r = ?")
with pytest.raises(InvalidRequest, match="unset"):