Files
scylladb/test/cql-pytest/test_null.py
Dejan Mircevski 1fdaeca7d0 cql3: Reject updates with NULL key values
We were silently ignoring INSERTs with NULL values for primary-key
columns, which Cassandra rejects.  Fix it by rejecting any
modification_statement that would operate on empty partition or
clustering range.

This is the most direct fix, because range and slice are calculated in
one place for all modification statements.  It covers not only NULL
cases, but also impossible restrictions like c>0 AND c<0.
Unfortunately, Cassandra doesn't treat all modification statements
consistently, so this fix cannot fully match its behavior.  We err on
the side of tolerance, accepting some DELETE statements that Cassandra
rejects.  We add a TODO for rejecting such DELETEs later.

Fixes #7852.

Tests: unit (dev), cql-pytest against Cassandra 4.0

Signed-off-by: Dejan Mircevski <dejan@scylladb.com>

Closes #9286
2021-09-05 10:23:28 +03:00

121 lines
6.6 KiB
Python

# Copyright 2020-present ScyllaDB
#
# This file is part of Scylla.
#
# Scylla is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Scylla is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with Scylla. If not, see <http://www.gnu.org/licenses/>.
#############################################################################
# Tests for finer points of the meaning of "null" in various places
#############################################################################
import pytest
import re
from cassandra.protocol import SyntaxException, AlreadyExists, InvalidRequest, ConfigurationException, ReadFailure
from util import unique_name, random_string, new_test_table
@pytest.fixture(scope="module")
def table1(cql, test_keyspace):
table = test_keyspace + "." + unique_name()
cql.execute(f"CREATE TABLE {table} (p text, c text, v text, primary key (p, c))")
yield table
cql.execute("DROP TABLE " + table)
# An item cannot be inserted without a key. Verify that before we get into
# the really interesting test below - trying to pass "null" as the value of
# the key.
# See also issue #3665.
def test_insert_missing_key(cql, table1):
s = random_string()
# A clustering key is missing. Cassandra uses the message "Some clustering
# keys are missing: c", and Scylla: "Missing mandatory PRIMARY KEY part c"
with pytest.raises(InvalidRequest, match=re.compile('missing', re.IGNORECASE)):
cql.execute(f"INSERT INTO {table1} (p) VALUES ('{s}')")
# Similarly, a missing partition key
with pytest.raises(InvalidRequest, match=re.compile('missing', re.IGNORECASE)):
cql.execute(f"INSERT INTO {table1} (c) VALUES ('{s}')")
# A null key, like a missing one, is also not allowed.
# This reproduces issue #7852.
def test_insert_null_key(cql, table1):
s = random_string()
with pytest.raises(InvalidRequest, match=re.compile('Invalid (null|clustering)')):
cql.execute(f"INSERT INTO {table1} (p,c) VALUES ('{s}', null)")
with pytest.raises(InvalidRequest, match=re.compile('Invalid (null|partition)')):
cql.execute(f"INSERT INTO {table1} (p,c) VALUES (null, '{s}')")
# Try the same thing with prepared statement, where a "None" stands for
# a null. Note that this is completely different from UNSET_VALUE - only
# with the latter should the insertion be ignored.
stmt = cql.prepare(f"INSERT INTO {table1} (p,c) VALUES (?, ?)")
with pytest.raises(InvalidRequest, match=re.compile('Invalid (null|clustering)')):
cql.execute(stmt, [s, None])
with pytest.raises(InvalidRequest, match=re.compile('Invalid (null|partition)')):
cql.execute(stmt, [None, s])
def test_primary_key_in_null(cql, table1):
'''Tests handling of "key_column in ?" where ? is bound to null.'''
with pytest.raises(InvalidRequest, match='null value'):
cql.execute(cql.prepare(f"SELECT p FROM {table1} WHERE p IN ?"), [None])
with pytest.raises(InvalidRequest, match='null value'):
cql.execute(cql.prepare(f"SELECT p FROM {table1} WHERE p='' AND c IN ?"), [None])
with pytest.raises(InvalidRequest, match='Invalid null value for IN restriction'):
cql.execute(cql.prepare(f"SELECT p FROM {table1} WHERE p='' AND (c) IN ?"), [None])
# Cassandra says "IN predicates on non-primary-key columns (v) is not yet supported".
def test_regular_column_in_null(scylla_only, cql, table1):
'''Tests handling of "regular_column in ?" where ? is bound to null.'''
# Without any rows in the table, SELECT will shortcircuit before evaluating the WHERE clause.
cql.execute(f"INSERT INTO {table1} (p,c) VALUES ('p', 'c')")
with pytest.raises(InvalidRequest, match='null value'):
cql.execute(cql.prepare(f"SELECT v FROM {table1} WHERE v IN ? ALLOW FILTERING"), [None])
# Though nonsensical, this operation is allowed by Cassandra. Ensure we allow it, too.
def test_delete_impossible_clustering_range(cql, table1):
cql.execute(f"DELETE FROM {table1} WHERE p='p' and c<'a' and c>'a'")
@pytest.mark.xfail(reason="Unimplemented for clustering key")
def test_delete_null_key(cql, table1):
with pytest.raises(InvalidRequest, match=re.compile('Invalid (null|partition)')):
cql.execute(f"DELETE FROM {table1} WHERE p=null")
with pytest.raises(InvalidRequest, match=re.compile('Invalid (null|partition)')):
cql.execute(cql.prepare(f"DELETE FROM {table1} WHERE p=?"), [None])
with pytest.raises(InvalidRequest, match=re.compile('Invalid (null|clustering)')):
cql.execute(f"DELETE FROM {table1} WHERE p='p' AND c=null")
with pytest.raises(InvalidRequest, match=re.compile('Invalid (null|clustering)')):
cql.execute(cql.prepare(f"DELETE FROM {table1} WHERE p='p' AND c=?"), [None])
# Test what SELECT does with the restriction "WHERE v=NULL".
# In SQL, "WHERE v=NULL" doesn't match anything - because nothing is equal
# to null - not even null. SQL also provides a more useful restriction
# "WHERE v IS NULL" which matches all rows where v is unset.
# Scylla and Cassandra do *not* support the "IS NULL" syntax yet (they do
# have "IS NOT NULL" but only in a definition of a materialized view),
# so it is commonly requested that "WHERE v=NULL" should do what "IS NULL"
# is supposed to do - see issues #4776 and #8489 for Scylla and
# CASSANDRA-10715 for Cassandra, where this feature was requested.
# Nevertheless, in Scylla we decided to follow SQL: "WHERE v=NULL" should
# matche nothing, not even rows where v is unset. This is what the following
# test verifies.
# This test fails on Cassandra (hence cassandra_bug) because Cassandra
# refuses the "WHERE v=NULL" relation, rather than matching nothing.
# We consider this a mistake, and not something we want to emulate in Scylla.
def test_filtering_eq_null(cassandra_bug, cql, table1):
p = random_string()
cql.execute(f"INSERT INTO {table1} (p,c,v) VALUES ('{p}', '1', 'hello')")
cql.execute(f"INSERT INTO {table1} (p,c,v) VALUES ('{p}', '2', '')")
cql.execute(f"INSERT INTO {table1} (p,c) VALUES ('{p}', '3')")
# As explained above, none of the above-inserted rows should match -
# not even the one with an unset v:
assert list(cql.execute(f"SELECT c FROM {table1} WHERE p='{p}' AND v=NULL ALLOW FILTERING")) == []