Files
scylladb/test/cqlpy/cassandra_tests/validation/operations/create_test.py
Karol Nowacki 4577c66a04 cql, schema: Extend name length limit from 48 to 192 bytes
This commit increases the maximum length of names for keyspaces, tables, materialized views, and indexes from 48 to 192 bytes.
The previous 48-bytes limit was inherited from Cassandra 3 for compatibility. However, this validation was removed in Cassandra 4 and 5 (see CASSANDRA-20389)
and some usage scenarios (such as some feature store workflows generating long table names) now depend on this relaxed constraint.
This change brings ScyllaDB's behavior in line with modern Cassandra versions and better supports these use cases.

The new limit of 192 bytes is derived from underlying filesystem limitations to prevent runtime errors when creating directories for table data.
When a new table is created, ScyllaDB generates a directory for its SSTables. The directory name is constructed from the table name, a dash, and a 32-character UUID.
For a CDC-enabled table, an associated log table is also created, which has the suffix `_scylla_cdc_log` appended to its name.
The directory name for this log table becomes the longest possible representation.
Additionally we reserve 15 bytes for future use, allowing for potential future extensions without breaking existing schemas.
To guarantee that directory creation never fails due to exceeding filesystem name limits, the maximum name length is calculated as follows:
  255 bytes (common filesystem limit for a path component)
-  32 bytes (for the 32-character UUID string)
-   1 byte  (for the '-' separator)
-  15 bytes (for the '_scylla_cdc_log' suffix)
-  15 bytes (reserved for future use)
----------
= 192 bytes (Maximum allowed name length)
This calculation is similar in principle to the one proposed for Cassandra to fix related directory creation failures (see apache/cassandra/pull/4038).

This patch also updates/adds all associated tests to validate the new 192-byte limit.
The documentation has been updated accordingly.
2025-06-18 14:08:38 +02:00

661 lines
39 KiB
Python

# This file was translated from the original Java test from the Apache
# Cassandra source repository, as of commit 8d91b469afd3fcafef7ef85c10c8acc11703ba2d
#
# The original Apache Cassandra license:
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from ...porting import *
from cassandra.util import Duration
from cassandra.protocol import SyntaxException, InvalidRequest, ConfigurationException
from uuid import UUID
def testCreateTableWithSmallintColumns(cql, test_keyspace):
short_max_value = 2**15-1
short_min_value = -2**15
with create_table(cql, test_keyspace, "(a text, b smallint, c smallint, primary key (a, b))") as table:
execute(cql, table, "INSERT INTO %s (a, b, c) VALUES ('1', 1, 2)")
execute(cql, table, "INSERT INTO %s (a, b, c) VALUES (?, ?, ?)", "2", short_max_value, short_min_value)
assertRows(execute(cql, table, "SELECT * FROM %s"),
["2", short_max_value, short_min_value],
["1", 1, 2])
# This cannot be tested in Python, it doesn't send the wrong types...
#assertInvalidMessage(cql, table, "Expected 2 bytes for a smallint (4)",
# "INSERT INTO %s (a, b, c) VALUES (?, ?, ?)", "3", 1, 2)
#assertInvalidMessage(cql, table, "Expected 2 bytes for a smallint (0)",
# "INSERT INTO %s (a, b, c) VALUES (?, ?, ?)", "3", 1, b"")
def testCreateTinyintColumns(cql, test_keyspace):
byte_max_value = 127
byte_min_value = -128
with create_table(cql, test_keyspace, "(a text, b tinyint, c tinyint, primary key (a, b))") as table:
execute(cql, table, "INSERT INTO %s (a, b, c) VALUES ('1', 1, 2)")
execute(cql, table, "INSERT INTO %s (a, b, c) VALUES (?, ?, ?)", "2", byte_max_value, byte_min_value)
assertRows(execute(cql, table, "SELECT * FROM %s"),
["2", byte_max_value, byte_min_value],
["1", 1, 2])
# This cannot be tested in Python, it doesn't send the wrong types...
#assertInvalidMessage(cql, table, "Expected 1 byte for a tinyint (4)",
# "INSERT INTO %s (a, b, c) VALUES (?, ?, ?)", "3", 1, 2)
#assertInvalidMessage(cql, table, "Expected 1 byte for a tinyint (0)",
# "INSERT INTO %s (a, b, c) VALUES (?, ?, ?)", "3", (byte) 1, ByteBufferUtil.EMPTY_BYTE_BUFFER)
@pytest.mark.xfail(reason="Issue #8001")
def testCreateTableWithDurationColumns(cql, test_keyspace):
t = unique_name()
# Messages in Scylla and Cassandra are slightly different - Cassandra
# refers to "column 'a'", Scylla to "part a".
assertInvalidMessageRE(cql, test_keyspace, "duration type is not supported for PRIMARY KEY.*a",
f"CREATE TABLE %s.{t} (a duration PRIMARY KEY, b int);")
assertInvalidMessageRE(cql, test_keyspace, "duration type is not supported for PRIMARY KEY.*b",
f"CREATE TABLE %s.{t} (a text, b duration, c duration, primary key (a, b));")
assertInvalidMessageRE(cql, test_keyspace, "duration type is not supported for PRIMARY KEY.*b",
f"CREATE TABLE %s.{t} (a text, b duration, c duration, primary key (a, b)) with clustering order by (b DESC);")
with create_table(cql, test_keyspace, "(a int, b int, c duration, primary key (a, b))") as table:
execute(cql, table, "INSERT INTO %s (a, b, c) VALUES (1, 1, 1y2mo)")
execute(cql, table, "INSERT INTO %s (a, b, c) VALUES (1, 2, -1y2mo)")
execute(cql, table, "INSERT INTO %s (a, b, c) VALUES (1, 3, 1Y2MO)")
execute(cql, table, "INSERT INTO %s (a, b, c) VALUES (1, 4, 2w)")
execute(cql, table, "INSERT INTO %s (a, b, c) VALUES (1, 5, 2d10h)")
execute(cql, table, "INSERT INTO %s (a, b, c) VALUES (1, 6, 30h20m)")
execute(cql, table, "INSERT INTO %s (a, b, c) VALUES (1, 7, 20m)")
execute(cql, table, "INSERT INTO %s (a, b, c) VALUES (1, 8, 567ms)")
execute(cql, table, "INSERT INTO %s (a, b, c) VALUES (1, 9, 1950us)")
# Reproduces #8001:
execute(cql, table, "INSERT INTO %s (a, b, c) VALUES (1, 10, 1950µs)")
execute(cql, table, "INSERT INTO %s (a, b, c) VALUES (1, 11, 1950000NS)")
execute(cql, table, "INSERT INTO %s (a, b, c) VALUES (1, 12, -1950000ns)")
execute(cql, table, "INSERT INTO %s (a, b, c) VALUES (1, 13, 1y3mo2h10m)")
execute(cql, table, "INSERT INTO %s (a, b, c) VALUES (1, 14, -P1Y2M)")
execute(cql, table, "INSERT INTO %s (a, b, c) VALUES (1, 15, P2D)")
execute(cql, table, "INSERT INTO %s (a, b, c) VALUES (1, 16, PT20M)")
execute(cql, table, "INSERT INTO %s (a, b, c) VALUES (1, 17, P2W)")
execute(cql, table, "INSERT INTO %s (a, b, c) VALUES (1, 18, P1Y3MT2H10M)")
execute(cql, table, "INSERT INTO %s (a, b, c) VALUES (1, 19, P0000-00-00T30:20:00)")
execute(cql, table, "INSERT INTO %s (a, b, c) VALUES (1, 20, P0001-03-00T02:10:00)")
execute(cql, table, "INSERT INTO %s (a, b, c) VALUES (?, ?, ?)", 1, 21, Duration(12, 10, 0))
execute(cql, table, "INSERT INTO %s (a, b, c) VALUES (?, ?, ?)", 1, 22, Duration(-12, -10, 0))
NANOS_PER_MICRO = 1000
NANOS_PER_MILLI = 1000 * NANOS_PER_MICRO
NANOS_PER_SECOND = 1000 * NANOS_PER_MILLI
NANOS_PER_MINUTE = 60 * NANOS_PER_SECOND
NANOS_PER_HOUR = 60 * NANOS_PER_MINUTE
MONTHS_PER_YEAR = 12
assertRows(execute(cql, table, "SELECT * FROM %s"),
[1, 1, Duration(14, 0, 0)],
[1, 2, Duration(-14, 0, 0)],
[1, 3, Duration(14, 0, 0)],
[1, 4, Duration(0, 14, 0)],
[1, 5, Duration(0, 2, 10 * NANOS_PER_HOUR)],
[1, 6, Duration(0, 0, 30 * NANOS_PER_HOUR + 20 * NANOS_PER_MINUTE)],
[1, 7, Duration(0, 0, 20 * NANOS_PER_MINUTE)],
[1, 8, Duration(0, 0, 567 * NANOS_PER_MILLI)],
[1, 9, Duration(0, 0, 1950 * NANOS_PER_MICRO)],
#Reproduces #8001:
[1, 10, Duration(0, 0, 1950 * NANOS_PER_MICRO)],
[1, 11, Duration(0, 0, 1950000)],
[1, 12, Duration(0, 0, -1950000)],
[1, 13, Duration(15, 0, 130 * NANOS_PER_MINUTE)],
[1, 14, Duration(-14, 0, 0)],
[1, 15, Duration(0, 2, 0)],
[1, 16, Duration(0, 0, 20 * NANOS_PER_MINUTE)],
[1, 17, Duration(0, 14, 0)],
[1, 18, Duration(15, 0, 130 * NANOS_PER_MINUTE)],
[1, 19, Duration(0, 0, 30 * NANOS_PER_HOUR + 20 * NANOS_PER_MINUTE)],
[1, 20, Duration(15, 0, 130 * NANOS_PER_MINUTE)],
[1, 21, Duration(12, 10, 0)],
[1, 22, Duration(-12, -10, 0)])
# Cassandra and Scylla print different messages: Cassandra prints
# "Slice restrictions are not supported on duration columns", Scylla
# prints "Duration type is unordered for c".
assertInvalidMessageRE(cql, table, "Slice restrictions are not supported on duration columns|Duration type is unordered for c",
"SELECT * FROM %s WHERE c > 1y ALLOW FILTERING")
assertInvalidMessageRE(cql, table, "Slice restrictions are not supported on duration columns|Duration type is unordered for c",
"SELECT * FROM %s WHERE c <= 1y ALLOW FILTERING")
# Cannot be tested in Python
#assertInvalidMessage(cql, table, "Expected at least 3 bytes for a duration (1)",
# "INSERT INTO %s (a, b, c) VALUES (?, ?, ?)", 2, 1, 1)
#assertInvalidMessage(cql, table, "Expected at least 3 bytes for a duration (0)",
# "INSERT INTO %s (a, b, c) VALUES (?, ?, ?)", 2, 1, b"")
assertInvalidMessageRE(cql, table, "Invalid duration. The.*number of days must be less.*or equal to 2147483647",
"INSERT INTO %s (a, b, c) VALUES (1, 2, " + str(2**63-1) + "d)")
assertInvalidMessageRE(cql, table, "The duration months, days.*and nanoseconds must be all of the same sign \\(2, -2, 0\\)",
"INSERT INTO %s (a, b, c) VALUES (?, ?, ?)", 2, 1, Duration(2, -2, 0))
assertInvalidMessageRE(cql, table, "The duration months, days.*and nanoseconds must be all of the same sign \\(-2, 0, 2000000\\)",
"INSERT INTO %s (a, b, c) VALUES (?, ?, ?)", 2, 1, Duration(-2, 0, 2000000))
assertInvalidMessageRE(cql, table, "The duration months.*must be a 32 bit.*integer",
"INSERT INTO %s (a, b, c) VALUES (?, ?, ?)", 2, 1, Duration(9223372036854775807, 1, 0))
assertInvalidMessageRE(cql, table, "The duration days.*must be a 32 bit.*integer",
"INSERT INTO %s (a, b, c) VALUES (?, ?, ?)", 2, 1, Duration(0, 9223372036854775807, 0))
# Test with duration column name
with create_table(cql, test_keyspace, "(a text PRIMARY KEY, duration duration)") as table:
pass
# Test duration within Map
assertInvalidMessage(cql, test_keyspace, "Durations are not allowed as map keys: map<duration, text>",
f"CREATE TABLE %s.{t}(pk int PRIMARY KEY, m map<duration, text>)")
with create_table(cql, test_keyspace, "(pk int PRIMARY KEY, m map<text, duration>)") as table:
execute(cql, table, "INSERT INTO %s (pk, m) VALUES (1, {'one month' : 1mo, '60 days' : 60d})")
assertRows(execute(cql, table, "SELECT * FROM %s"),
[1, {"one month": Duration(1,0,0), "60 days": Duration(0,60,0)}])
assertInvalidMessageRE(cql, test_keyspace, "duration type is not supported for PRIMARY KEY (column 'm'|part m)",
f"CREATE TABLE %s.{t}(m frozen<map<text, duration>> PRIMARY KEY, v int)")
assertInvalidMessageRE(cql, test_keyspace, "duration type is not supported for PRIMARY KEY (column 'm'|part m)",
f"CREATE TABLE %s.{t}(pk int, m frozen<map<text, duration>>, v int, PRIMARY KEY (pk, m))")
# Test duration within Set
assertInvalidMessage(cql, test_keyspace, "Durations are not allowed inside sets: set<duration>",
f"CREATE TABLE %s.{t}(pk int PRIMARY KEY, s set<duration>)")
assertInvalidMessage(cql, test_keyspace, "Durations are not allowed inside sets: frozen<set<duration>>",
f"CREATE TABLE %s.{t}(s frozen<set<duration>> PRIMARY KEY, v int)")
# Test duration within List
with create_table(cql, test_keyspace, "(pk int PRIMARY KEY, l list<duration>)") as table:
execute(cql, table, "INSERT INTO %s (pk, l) VALUES (1, [1mo, 60d])")
assertRows(execute(cql, table, "SELECT * FROM %s"),
[1, [Duration(1,0,0), Duration(0,60,0)]])
assertInvalidMessageRE(cql, test_keyspace, "duration type is not supported for PRIMARY KEY (column 'l'|part l)",
f"CREATE TABLE %s.{t}(l frozen<list<duration>> PRIMARY KEY, v int)")
# Test duration within Tuple
with create_table(cql, test_keyspace, "(pk int PRIMARY KEY, t tuple<int, duration>)") as table:
execute(cql, table, "INSERT INTO %s (pk, t) VALUES (1, (1, 1mo))")
assertRows(execute(cql, table, "SELECT * FROM %s"),
[1, (1, Duration(1,0,0))])
assertInvalidMessageRE(cql, test_keyspace, "duration type is not supported for PRIMARY KEY (column 't'|part t)",
f"CREATE TABLE %s.{t}(t frozen<tuple<int, duration>> PRIMARY KEY, v int)")
# Test duration within UDT
with create_type(cql, test_keyspace, "(a duration)") as myType:
with create_table(cql, test_keyspace, f"(pk int PRIMARY KEY, u {myType})") as table:
execute(cql, table, "INSERT INTO %s (pk, u) VALUES (1, {a : 1mo})")
assertRows(execute(cql, table, "SELECT * FROM %s"),
[1, userType("a", Duration(1,0,0))])
assertInvalidMessageRE(cql, test_keyspace, "duration type is not supported for PRIMARY KEY (column 'u'|part u)",
f"CREATE TABLE %s.{t}(pk int, u frozen<{myType}>, v int, PRIMARY KEY(pk, u))")
# Test duration with several level of depth
assertInvalidMessageRE(cql, test_keyspace, "duration type is not supported for PRIMARY KEY (column 'm'|part m)",
f"CREATE TABLE %s.{t}(pk int, m frozen<map<text, list<tuple<int, duration>>>>, v int, PRIMARY KEY (pk, m))")
# Creation and basic operations on a static table,
# migrated from cql_tests.py:TestCQL.static_cf_test()
def testStaticTable(cql, test_keyspace):
with create_table(cql, test_keyspace, "(userid uuid PRIMARY KEY, firstname text, lastname text, age int)") as table:
id1 = UUID("550e8400-e29b-41d4-a716-446655440000")
id2 = UUID("f47ac10b-58cc-4372-a567-0e02b2c3d479")
execute(cql, table, "INSERT INTO %s (userid, firstname, lastname, age) VALUES (?, ?, ?, ?)", id1, "Frodo", "Baggins", 32)
execute(cql, table, "UPDATE %s SET firstname = ?, lastname = ?, age = ? WHERE userid = ?", "Samwise", "Gamgee", 33, id2)
assertRows(execute(cql, table, "SELECT firstname, lastname FROM %s WHERE userid = ?", id1),
["Frodo", "Baggins"])
assertRows(execute(cql, table, "SELECT * FROM %s WHERE userid = ?", id1),
[id1, 32, "Frodo", "Baggins"])
assertRows(execute(cql, table, "SELECT * FROM %s"),
[id2, 33, "Samwise", "Gamgee"],
[id1, 32, "Frodo", "Baggins"]
)
batch = "BEGIN BATCH " + \
"INSERT INTO %s (userid, age) VALUES (?, ?) " + \
"UPDATE %s SET age = ? WHERE userid = ? " + \
"DELETE firstname, lastname FROM %s WHERE userid = ? " + \
"DELETE firstname, lastname FROM %s WHERE userid = ? " + \
"APPLY BATCH"
execute(cql, table, batch, id1, 36, 37, id2, id1, id2)
assertRows(execute(cql, table, "SELECT * FROM %s"),
[id2, 37, None, None],
[id1, 36, None, None])
# Creation and basic operations on a composite table,
# migrated from cql_tests.py:TestCQL.sparse_cf_test()
def testSparseCompositeTable(cql, test_keyspace):
with create_table(cql, test_keyspace, "(userid uuid, posted_month int, posted_day int, body text, posted_by text, PRIMARY KEY (userid, posted_month, posted_day))") as table:
id1 = UUID("550e8400-e29b-41d4-a716-446655440000")
id2 = UUID("f47ac10b-58cc-4372-a567-0e02b2c3d479")
execute(cql, table, "INSERT INTO %s (userid, posted_month, posted_day, body, posted_by) VALUES (?, 1, 12, 'Something else', 'Frodo Baggins')", id1)
execute(cql, table, "INSERT INTO %s (userid, posted_month, posted_day, body, posted_by) VALUES (?, 1, 24, 'Something something', 'Frodo Baggins')", id1)
execute(cql, table, "UPDATE %s SET body = 'Yo Froddo', posted_by = 'Samwise Gamgee' WHERE userid = ? AND posted_month = 1 AND posted_day = 3", id2)
execute(cql, table, "UPDATE %s SET body = 'Yet one more message' WHERE userid = ? AND posted_month = 1 and posted_day = 30", id1)
assertRows(execute(cql, table, "SELECT body, posted_by FROM %s WHERE userid = ? AND posted_month = 1 AND posted_day = 24", id1),
["Something something", "Frodo Baggins"])
assertRows(execute(cql, table, "SELECT posted_day, body, posted_by FROM %s WHERE userid = ? AND posted_month = 1 AND posted_day > 12", id1),
row(24, "Something something", "Frodo Baggins"),
row(30, "Yet one more message", null))
assertRows(execute(cql, table, "SELECT posted_day, body, posted_by FROM %s WHERE userid = ? AND posted_month = 1", id1),
row(12, "Something else", "Frodo Baggins"),
row(24, "Something something", "Frodo Baggins"),
row(30, "Yet one more message", null))
# Check invalid create table statements,
# migrated from cql_tests.py:TestCQL.create_invalid_test()
def testInvalidCreateTableStatements(cql, test_keyspace):
assertInvalidThrow(cql, test_keyspace, SyntaxException, "CREATE TABLE %s.test ()")
assertInvalid(cql, test_keyspace, "CREATE TABLE %s.test (c1 text, c2 text, c3 text)")
assertInvalid(cql, test_keyspace, "CREATE TABLE %s.test (key1 text PRIMARY KEY, key2 text PRIMARY KEY)")
assertInvalid(cql, test_keyspace, "CREATE TABLE %s.test (key text PRIMARY KEY, key int)")
assertInvalid(cql, test_keyspace, "CREATE TABLE %s.test (key text PRIMARY KEY, c int, c text)")
# Check obsolete properties from CQL2 are rejected
# migrated from cql_tests.py:TestCQL.invalid_old_property_test()
def testObsoleteTableProperties(cql, test_keyspace):
assertInvalidThrow(cql, test_keyspace, SyntaxException, "CREATE TABLE %s.table0 (foo text PRIMARY KEY, c int) WITH default_validation=timestamp")
with create_table(cql, test_keyspace, "(foo text PRIMARY KEY, c int)") as table:
assertInvalidThrow(cql, table, SyntaxException, "ALTER TABLE %s WITH default_validation=int")
# Test create and drop keyspace
# migrated from cql_tests.py:TestCQL.keyspace_test()
def testKeyspace(cql):
n = unique_name()
assertInvalidThrow(cql, n, SyntaxException, "CREATE KEYSPACE %s testXYZ ")
execute(cql, n, "CREATE KEYSPACE %s WITH replication = { 'class' : 'SimpleStrategy', 'replication_factor' : 1 }")
execute(cql, n, "DROP KEYSPACE %s")
# The original Cassandra test checked that a 53-character keyspace name doesn't work
# but Scylla increased the 48-character limit to 192 characters, so this test was modified
# to check length of 500, to produce the same outcome on Scylla and Cassandra.
too_long_keyspace_name = "k" * 500
assertInvalid(cql, "",
f"CREATE KEYSPACE {too_long_keyspace_name} WITH replication = {{ 'class' : 'SimpleStrategy', 'replication_factor' : 1 }}")
# FIXME: Cassandra throws InvalidRequest here, but Scylla uses
# ConfigurationException. We shouldn't have done that... But I consider
# this difference to be so trivial I didn't want to consider this a bug.
# Maybe we should reconsider, and not allow ConfigurationException...
assertInvalidThrow(cql, "", (InvalidRequest, ConfigurationException), "DROP KEYSPACE non_existing")
execute(cql, n, "CREATE KEYSPACE %s WITH replication = { 'class' : 'SimpleStrategy', 'replication_factor' : 1 }")
# clean-up
execute(cql, n, "DROP KEYSPACE %s")
# Test {@link ConfigurationException} is thrown on create keyspace with invalid DC option in replication configuration .
def testCreateKeyspaceWithNTSOnlyAcceptsConfiguredDataCenterNames(cql, this_dc):
n = unique_name()
assertInvalidThrow(cql, n, ConfigurationException, "CREATE KEYSPACE %s WITH replication = { 'class' : 'NetworkTopologyStrategy', 'INVALID_DC' : 2 }")
execute(cql, n, "CREATE KEYSPACE %s WITH replication = {'class' : 'NetworkTopologyStrategy', '" + this_dc + "' : 1 }")
execute(cql, n, "DROP KEYSPACE IF EXISTS %s")
# Mix valid and invalid, should throw an exception
assertInvalidThrow(cql, n, ConfigurationException, "CREATE KEYSPACE %s WITH replication={ 'class' : 'NetworkTopologyStrategy', '" + this_dc + "' : 2 , 'INVALID_DC': 1}")
execute(cql, n, "DROP KEYSPACE IF EXISTS %s")
# Test {@link ConfigurationException} is not thrown on create NetworkTopologyStrategy keyspace without any options.
@pytest.mark.xfail(reason="Issue #16028")
def testCreateKeyspaceWithNetworkTopologyStrategyNoOptions(cql):
n = unique_name()
execute(cql, n, "CREATE KEYSPACE %s with replication = { 'class': 'NetworkTopologyStrategy' }")
# clean-up
execute(cql, n, "DROP KEYSPACE IF EXISTS %s")
# Test {@link ConfigurationException} is not thrown on create SimpleStrategy keyspace without any options.
# Reproduces one aspect of #8892 (a default_keyspace_rf allows creating
# a keyspace without specifying a replication factor).
@pytest.mark.xfail(reason="Issue #8892")
def testCreateKeyspaceWithSimpleStrategyNoOptions(cql):
n = unique_name()
execute(cql, n, "CREATE KEYSPACE %s WITH replication = { 'class' : 'SimpleStrategy' }")
# clean-up
execute(cql, n, "DROP KEYSPACE IF EXISTS %s")
def testCreateKeyspaceWithMultipleInstancesOfSameDCThrowsException(cql, this_dc):
n = unique_name()
assertInvalidThrow(cql, n, SyntaxException, "CREATE KEYSPACE %s WITH replication = {'class' : 'NetworkTopologyStrategy', '" + this_dc + "' : 2, '" + this_dc + "' : 3 }")
execute(cql, n, "DROP KEYSPACE IF EXISTS %s")
# Test create and drop table
# migrated from cql_tests.py:TestCQL.table_test()
def testTable(cql, test_keyspace):
with create_table(cql, test_keyspace, "(k int, c int, PRIMARY KEY (k),)") as table:
pass
with create_table(cql, test_keyspace, "(k int PRIMARY KEY, c int)") as table1:
execute(cql, table1, f"DROP TABLE %s")
execute(cql, table1, f"CREATE TABLE %s ( k int PRIMARY KEY, c1 int, c2 int, ) ")
table4 = unique_name()
# repeated column
# Different messages in Cassandra and Scylla
assertInvalidMessageRE(cql, test_keyspace, "Duplicate column 'k' declaration for table|Multiple definition of identifier k", f"CREATE TABLE %s.{table4} (k int PRIMARY KEY, c int, k text)")
# Migrated from cql_tests.py:TestCQL.multiordering_validation_test()
def testTable(cql, test_keyspace):
tableName = test_keyspace + "." + unique_name()
assertInvalid(cql, tableName, "CREATE TABLE %s (k int, c1 int, c2 int, PRIMARY KEY (k, c1, c2)) WITH CLUSTERING ORDER BY (c2 DESC)")
tableName = test_keyspace + "." + unique_name()
assertInvalid(cql, tableName, "CREATE TABLE %s (k int, c1 int, c2 int, PRIMARY KEY (k, c1, c2)) WITH CLUSTERING ORDER BY (c2 ASC, c1 DESC)")
tableName = test_keyspace + "." + unique_name()
assertInvalid(cql, tableName, "CREATE TABLE test (k int, c1 int, c2 int, PRIMARY KEY (k, c1, c2)) WITH CLUSTERING ORDER BY (c1 DESC, c2 DESC, c3 DESC)")
execute(cql, tableName, "CREATE TABLE %s (k int, c1 int, c2 int, PRIMARY KEY (k, c1, c2)) WITH CLUSTERING ORDER BY (c1 DESC, c2 DESC)")
execute(cql, tableName, "DROP TABLE %s")
execute(cql, tableName, "CREATE TABLE %s (k int, c1 int, c2 int, PRIMARY KEY (k, c1, c2)) WITH CLUSTERING ORDER BY (c1 ASC, c2 DESC)")
execute(cql, tableName, "DROP TABLE %s")
# Tests for triggers (see issue #2205) are commented out, because using
# them in Cassandra requires adding a Java class, which we can't do.
# It is also unlikely that in the present form, they will ever be added
# to Scylla.
# @Test
# public void testCreateTrigger() throws Throwable
# {
# createTable("CREATE TABLE %s (a int, b int, c int, PRIMARY KEY (a))")
# execute(cql, table, "CREATE TRIGGER trigger_1 ON %s USING '" + TestTrigger.class.getName() + "'")
# assertTriggerExists("trigger_1")
# execute(cql, table, "CREATE TRIGGER trigger_2 ON %s USING '" + TestTrigger.class.getName() + "'")
# assertTriggerExists("trigger_2")
# assertInvalid(cql, table, "CREATE TRIGGER trigger_1 ON %s USING '" + TestTrigger.class.getName() + "'")
# execute(cql, table, "CREATE TRIGGER \"Trigger 3\" ON %s USING '" + TestTrigger.class.getName() + "'")
# assertTriggerExists("Trigger 3")
# }
#
# @Test
# public void testCreateTriggerIfNotExists() throws Throwable
# {
# createTable("CREATE TABLE %s (a int, b int, c int, PRIMARY KEY (a, b))")
#
# execute(cql, table, "CREATE TRIGGER IF NOT EXISTS trigger_1 ON %s USING '" + TestTrigger.class.getName() + "'")
# assertTriggerExists("trigger_1")
#
# execute(cql, table, "CREATE TRIGGER IF NOT EXISTS trigger_1 ON %s USING '" + TestTrigger.class.getName() + "'")
# assertTriggerExists("trigger_1")
# }
#
# @Test
# public void testDropTrigger() throws Throwable
# {
# createTable("CREATE TABLE %s (a int, b int, c int, PRIMARY KEY (a))")
#
# execute(cql, table, "CREATE TRIGGER trigger_1 ON %s USING '" + TestTrigger.class.getName() + "'")
# assertTriggerExists("trigger_1")
#
# execute(cql, table, "DROP TRIGGER trigger_1 ON %s")
# assertTriggerDoesNotExists("trigger_1")
#
# execute(cql, table, "CREATE TRIGGER trigger_1 ON %s USING '" + TestTrigger.class.getName() + "'")
# assertTriggerExists("trigger_1")
#
# assertInvalid(cql, table, "DROP TRIGGER trigger_2 ON %s")
#
# execute(cql, table, "CREATE TRIGGER \"Trigger 3\" ON %s USING '" + TestTrigger.class.getName() + "'")
# assertTriggerExists("Trigger 3")
#
# execute(cql, table, "DROP TRIGGER \"Trigger 3\" ON %s")
# assertTriggerDoesNotExists("Trigger 3")
# }
#
# @Test
# public void testDropTriggerIfExists() throws Throwable
# {
# createTable("CREATE TABLE %s (a int, b int, c int, PRIMARY KEY (a))")
#
# execute(cql, table, "DROP TRIGGER IF EXISTS trigger_1 ON %s")
# assertTriggerDoesNotExists("trigger_1")
#
# execute(cql, table, "CREATE TRIGGER trigger_1 ON %s USING '" + TestTrigger.class.getName() + "'")
# assertTriggerExists("trigger_1")
#
# execute(cql, table, "DROP TRIGGER IF EXISTS trigger_1 ON %s")
# assertTriggerDoesNotExists("trigger_1")
# }
#
# private void assertTriggerExists(String name)
# {
# TableMetadata metadata = Schema.instance.getTableMetadata(keyspace(), currentTable())
# assertTrue("the trigger does not exist", metadata.triggers.get(name).isPresent())
# }
#
# private void assertTriggerDoesNotExists(String name)
# {
# TableMetadata metadata = Schema.instance.getTableMetadata(keyspace(), currentTable())
# assertFalse("the trigger exists", metadata.triggers.get(name).isPresent())
# }
#
# public static class TestTrigger implements ITrigger
# {
# public TestTrigger() { }
# public Collection<Mutation> augment(Partition update)
# {
# return Collections.emptyList()
# }
# }
#
#}
# Test commented out because it uses internal Cassandra Java APIs, not CQL
# @Test
# // tests CASSANDRA-4278
# public void testHyphenDatacenters() throws Throwable
# {
# IEndpointSnitch snitch = DatabaseDescriptor.getEndpointSnitch()
#
# // Register an EndpointSnitch which returns fixed values for test.
# DatabaseDescriptor.setEndpointSnitch(new AbstractEndpointSnitch()
# {
# @Override
# public String getRack(InetAddressAndPort endpoint) { return RACK1; }
#
# @Override
# public String getDatacenter(InetAddressAndPort endpoint) { return "us-east-1"; }
#
# @Override
# public int compareEndpoints(InetAddressAndPort target, Replica a1, Replica a2) { return 0; }
# })
#
# // this forces the dc above to be added to the list of known datacenters (fixes static init problem
# // with this group of tests), ok to remove at some point if doing so doesn't break the test
# StorageService.instance.getTokenMetadata().updateHostId(UUID.randomUUID(), InetAddressAndPort.getByName("127.0.0.255"))
# execute(cql, table, "CREATE KEYSPACE Foo WITH replication = { 'class' : 'NetworkTopologyStrategy', 'us-east-1' : 1 };")
#
# // Restore the previous EndpointSnitch
# DatabaseDescriptor.setEndpointSnitch(snitch)
#
# // clean up
# execute(cql, table, "DROP KEYSPACE IF EXISTS Foo")
# }
# tests CASSANDRA-9565
def testDoubleWith(cql, test_keyspace):
for stmt in ["CREATE KEYSPACE WITH WITH DURABLE_WRITES = true",
"CREATE KEYSPACE ks WITH WITH DURABLE_WRITES = true"]:
assertInvalidSyntaxMessage(cql, test_keyspace, "no viable alternative at input 'WITH'", stmt)
# Scylla does not support CEP-11 (Pluggable memtable implementations),
# and is unlikely to ever support it in a way compatible with Cassandra.
# Also, the tests for it use some internal Java APIs instead of CQL.
# So they are commented out.
# public static class InvalidMemtableFactoryMethod
# {
# @SuppressWarnings("unused")
# public static String factory(Map<String, String> options)
# {
# return "invalid"
# }
# }
#
# public static class InvalidMemtableFactoryField
# {
# @SuppressWarnings("unused")
# public static String FACTORY = "invalid"
# }
#
# @Test
# public void testCreateTableWithMemtable() throws Throwable
# {
# createTable("CREATE TABLE %s (a text, b int, c int, primary key (a, b))")
# assertSame(MemtableParams.DEFAULT.factory(), getCurrentColumnFamilyStore().metadata().params.memtable.factory())
#
# assertSchemaOption("memtable", null)
#
# testMemtableConfig("skiplist", SkipListMemtable.FACTORY, SkipListMemtable.class)
# testMemtableConfig("skiplist_remapped", SkipListMemtable.FACTORY, SkipListMemtable.class)
# testMemtableConfig("test_fullname", TestMemtable.FACTORY, SkipListMemtable.class)
# testMemtableConfig("test_shortname", SkipListMemtable.FACTORY, SkipListMemtable.class)
# testMemtableConfig("default", MemtableParams.DEFAULT.factory(), SkipListMemtable.class)
#
# assertThrowsConfigurationException("The 'class_name' option must be specified.",
# "CREATE TABLE %s (a text, b int, c int, primary key (a, b))"
# + " WITH memtable = 'test_empty_class';")
#
# assertThrowsConfigurationException("The 'class_name' option must be specified.",
# "CREATE TABLE %s (a text, b int, c int, primary key (a, b))"
# + " WITH memtable = 'test_missing_class';")
#
# assertThrowsConfigurationException("Memtable class org.apache.cassandra.db.memtable.SkipListMemtable does not accept any further parameters, but {invalid=throw} were given.",
# "CREATE TABLE %s (a text, b int, c int, primary key (a, b))"
# + " WITH memtable = 'test_invalid_param';")
#
# assertThrowsConfigurationException("Could not create memtable factory for class NotExisting",
# "CREATE TABLE %s (a text, b int, c int, primary key (a, b))"
# + " WITH memtable = 'test_unknown_class';")
#
# assertThrowsConfigurationException("Memtable class org.apache.cassandra.db.memtable.TestMemtable does not accept any further parameters, but {invalid=throw} were given.",
# "CREATE TABLE %s (a text, b int, c int, primary key (a, b))"
# + " WITH memtable = 'test_invalid_extra_param';")
#
# assertThrowsConfigurationException("Could not create memtable factory for class " + InvalidMemtableFactoryMethod.class.getName(),
# "CREATE TABLE %s (a text, b int, c int, primary key (a, b))"
# + " WITH memtable = 'test_invalid_factory_method';")
#
# assertThrowsConfigurationException("Could not create memtable factory for class " + InvalidMemtableFactoryField.class.getName(),
# "CREATE TABLE %s (a text, b int, c int, primary key (a, b))"
# + " WITH memtable = 'test_invalid_factory_field';")
#
# assertThrowsConfigurationException("Memtable configuration \"unknown\" not found.",
# "CREATE TABLE %s (a text, b int, c int, primary key (a, b))"
# + " WITH memtable = 'unknown';")
# }
#
# private void testMemtableConfig(String memtableConfig, Memtable.Factory factoryInstance, Class<? extends Memtable> memtableClass) throws Throwable
# {
# createTable("CREATE TABLE %s (a text, b int, c int, primary key (a, b))"
# + " WITH memtable = '" + memtableConfig + "';")
# assertSame(factoryInstance, getCurrentColumnFamilyStore().metadata().params.memtable.factory())
# Assert.assertTrue(memtableClass.isInstance(getCurrentColumnFamilyStore().getTracker().getView().getCurrentMemtable()))
#
# assertSchemaOption("memtable", MemtableParams.DEFAULT.configurationKey().equals(memtableConfig) ? null : memtableConfig)
# }
#
def assertSchemaOption(cql, table, option, expected):
[ks, cf] = table.split('.')
assertRows(execute(cql, table, "SELECT " + option + " FROM system_schema.tables WHERE keyspace_name = '" + ks + "' and table_name = '" + cf + "';"), row(expected))
@pytest.mark.xfail(reason="Issue #8948, #6442")
def testCreateTableWithCompression(cql, test_keyspace):
with create_table(cql, test_keyspace, "(a text, b int, c int, primary key (a, b))") as table:
assertSchemaOption(cql, table, "compression", {"chunk_length_in_kb": "16", "class": "org.apache.cassandra.io.compress.LZ4Compressor"})
with create_table(cql, test_keyspace, "(a text, b int, c int, primary key (a, b)) WITH compression = { 'class' : 'SnappyCompressor', 'chunk_length_in_kb' : 32 };") as table:
assertSchemaOption(cql, table, "compression", {"chunk_length_in_kb": "32", "class": "org.apache.cassandra.io.compress.SnappyCompressor"})
with create_table(cql, test_keyspace, "(a text, b int, c int, primary key (a, b)) WITH compression = { 'class' : 'SnappyCompressor', 'chunk_length_in_kb' : 32, 'enabled': true };") as table:
assertSchemaOption(cql, table, "compression", {"chunk_length_in_kb": "32", "class": "org.apache.cassandra.io.compress.SnappyCompressor"})
with create_table(cql, test_keyspace, "(a text, b int, c int, primary key (a, b)) WITH compression = { 'sstable_compression' : 'SnappyCompressor', 'chunk_length_in_kb' : 32 };") as table:
assertSchemaOption(cql, table, "compression", {"chunk_length_in_kb": "32", "class": "org.apache.cassandra.io.compress.SnappyCompressor"})
with create_table(cql, test_keyspace, "(a text, b int, c int, primary key (a, b)) WITH compression = { 'sstable_compression' : 'SnappyCompressor', 'min_compress_ratio' : 2 };") as table:
assertSchemaOption(cql, table, "compression", {"chunk_length_in_kb": "16", "class": "org.apache.cassandra.io.compress.SnappyCompressor", "min_compress_ratio": "2.0"})
with create_table(cql, test_keyspace, "(a text, b int, c int, primary key (a, b)) WITH compression = { 'sstable_compression' : 'SnappyCompressor', 'min_compress_ratio' : 1 };") as table:
assertSchemaOption(cql, table, "compression", {"chunk_length_in_kb": "16", "class": "org.apache.cassandra.io.compress.SnappyCompressor", "min_compress_ratio": "1.0"})
#
with create_table(cql, test_keyspace, "(a text, b int, c int, primary key (a, b)) WITH compression = { 'sstable_compression' : 'SnappyCompressor', 'min_compress_ratio' : 0 };") as table:
assertSchemaOption(cql, table, "compression", {"chunk_length_in_kb": "16", "class": "org.apache.cassandra.io.compress.SnappyCompressor"})
with create_table(cql, test_keyspace, "(a text, b int, c int, primary key (a, b)) WITH compression = { 'sstable_compression' : '', 'chunk_length_kb' : 32 };") as table:
assertSchemaOption(cql, table, "compression", {"enabled": "false"})
with create_table(cql, test_keyspace, "(a text, b int, c int, primary key (a, b)) WITH compression = { 'enabled' : 'false' };") as table:
assertSchemaOption(cql, table, "compression", {"enabled": "false"})
table = test_keyspace + '.' + unique_name()
assertThrowsConfigurationException(cql, table, "Missing sub-option 'class' for the 'compression' option.",
"CREATE TABLE %s (a text, b int, c int, primary key (a, b))"
+ " WITH compression = {'chunk_length_in_kb' : 32};")
assertThrowsConfigurationException(cql, table, "The 'class' option must not be empty. To disable compression use 'enabled' : false",
"CREATE TABLE %s (a text, b int, c int, primary key (a, b))"
+ " WITH compression = { 'class' : ''};")
assertThrowsConfigurationException(cql, table, "If the 'enabled' option is set to false no other options must be specified",
"CREATE TABLE %s (a text, b int, c int, primary key (a, b))"
+ " WITH compression = { 'enabled' : 'false', 'class' : 'SnappyCompressor'};")
assertThrowsConfigurationException(cql, table, "If the 'enabled' option is set to false no other options must be specified",
"CREATE TABLE %s (a text, b int, c int, primary key (a, b))"
+ " WITH compression = { 'enabled' : 'false', 'chunk_length_in_kb' : 32};")
assertThrowsConfigurationException(cql, table, "The 'sstable_compression' option must not be used if the compression algorithm is already specified by the 'class' option",
"CREATE TABLE %s (a text, b int, c int, primary key (a, b))"
+ " WITH compression = { 'sstable_compression' : 'SnappyCompressor', 'class' : 'SnappyCompressor'};")
assertThrowsConfigurationException(cql, table, "The 'chunk_length_kb' option must not be used if the chunk length is already specified by the 'chunk_length_in_kb' option",
"CREATE TABLE %s (a text, b int, c int, primary key (a, b))"
+ " WITH compression = { 'class' : 'SnappyCompressor', 'chunk_length_kb' : 32 , 'chunk_length_in_kb' : 32 };")
assertThrowsConfigurationException(cql, table, "chunk_length_in_kb must be a power of 2",
"CREATE TABLE %s (a text, b int, c int, primary key (a, b))"
+ " WITH compression = { 'class' : 'SnappyCompressor', 'chunk_length_in_kb' : 31 };")
assertThrowsConfigurationException(cql, table, "Invalid negative or null chunk_length_in_kb",
"CREATE TABLE %s (a text, b int, c int, primary key (a, b))"
+ " WITH compression = { 'class' : 'SnappyCompressor', 'chunk_length_in_kb' : -1 };")
assertThrowsConfigurationException(cql, table, "Invalid negative min_compress_ratio",
"CREATE TABLE %s (a text, b int, c int, primary key (a, b))"
+ " WITH compression = { 'class' : 'SnappyCompressor', 'min_compress_ratio' : -1 };")
assertThrowsConfigurationException(cql, table, "Unknown compression options unknownOption",
"CREATE TABLE %s (a text, b int, c int, primary key (a, b))"
+ " WITH compression = { 'class' : 'SnappyCompressor', 'unknownOption' : 32 };")
def assertThrowsConfigurationException(cql, table, message, cmd, *args):
with pytest.raises(ConfigurationException, match=re.escape(message)):
execute(cql, table, cmd, *args)