From 52bbc1065c8e701e0f92bdec329bf79413283850 Mon Sep 17 00:00:00 2001 From: Jan Ciolek Date: Wed, 12 Oct 2022 13:09:46 +0200 Subject: [PATCH] cql3: allow lists of IN elements to be NULL Requests like `col IN NULL` used to cause an error - Invalid null value for colum col. We would like to allow NULLs everywhere. When a NULL occurs on either side of a binary operator, the whole operation should just evaluate to NULL. Signed-off-by: Jan Ciolek Closes #11775 --- cql3/expr/expression.cc | 9 ++++++--- cql3/restrictions/statement_restrictions.cc | 7 +++++-- .../select_single_column_relation_test.py | 3 +-- test/cql-pytest/test_null.py | 14 +++++--------- 4 files changed, 17 insertions(+), 16 deletions(-) diff --git a/cql3/expr/expression.cc b/cql3/expr/expression.cc index 1c9b0a42cb..4ebf0b0e2f 100644 --- a/cql3/expr/expression.cc +++ b/cql3/expr/expression.cc @@ -460,8 +460,9 @@ bool like(const column_value& cv, const raw_value_view& pattern, const evaluatio /// True iff the column value is in the set defined by rhs. bool is_one_of(const expression& col, const expression& rhs, const evaluation_inputs& inputs) { const cql3::raw_value in_list = evaluate(rhs, inputs); - statements::request_validations::check_false( - in_list.is_null(), "Invalid null value for column {}", col); + if (in_list.is_null()) { + return false; + } return boost::algorithm::any_of(get_list_elements(in_list), [&] (const managed_bytes_opt& b) { return equal(col, b, inputs); @@ -697,7 +698,9 @@ value_list get_IN_values( if (in_list.is_unset_value()) { throw exceptions::invalid_request_exception(format("Invalid unset value for column {}", column_name)); } - statements::request_validations::check_false(in_list.is_null(), "Invalid null value for column {}", column_name); + if (in_list.is_null()) { + return value_list(); + } utils::chunked_vector list_elems = get_list_elements(in_list); return to_sorted_vector(std::move(list_elems) | non_null | deref, comparator); } diff --git a/cql3/restrictions/statement_restrictions.cc b/cql3/restrictions/statement_restrictions.cc index e844e0c51f..4e1e01ed2d 100644 --- a/cql3/restrictions/statement_restrictions.cc +++ b/cql3/restrictions/statement_restrictions.cc @@ -1169,8 +1169,11 @@ struct multi_column_range_accumulator { intersect_all(to_range(binop.op, clustering_key_prefix(std::move(values)))); } else if (binop.op == oper_t::IN) { const cql3::raw_value tup = expr::evaluate(binop.rhs, options); - statements::request_validations::check_false(tup.is_null(), "Invalid null value for IN restriction"); - process_in_values(expr::get_list_of_tuples_elements(tup, *type_of(binop.rhs))); + utils::chunked_vector> tuple_elems; + if (tup.is_value()) { + tuple_elems = expr::get_list_of_tuples_elements(tup, *type_of(binop.rhs)); + } + process_in_values(std::move(tuple_elems)); } else { on_internal_error(rlogger, format("multi_column_range_accumulator: unexpected atom {}", binop)); } diff --git a/test/cql-pytest/cassandra_tests/validation/operations/select_single_column_relation_test.py b/test/cql-pytest/cassandra_tests/validation/operations/select_single_column_relation_test.py index 9922d3c0cd..f74cce09d5 100644 --- a/test/cql-pytest/cassandra_tests/validation/operations/select_single_column_relation_test.py +++ b/test/cql-pytest/cassandra_tests/validation/operations/select_single_column_relation_test.py @@ -89,8 +89,7 @@ def testClusteringColumnRelations(cql, test_keyspace): ["first", 2, 6, 2], ["first", 3, 7, 3]) - assert_invalid_message(cql, table, "Invalid null value for column b", - "select * from %s where a = ? and b in ? and c in ?", "first", None, [7, 6]) + assert_empty(execute(cql, table, "select * from %s where a = ? and b in ? and c in ?", "first", None, [7, 6])) assert_rows(execute(cql, table, "select * from %s where a = ? and c >= ? and b in (?, ?)", "first", 6, 3, 2), ["first", 2, 6, 2], diff --git a/test/cql-pytest/test_null.py b/test/cql-pytest/test_null.py index 593f9a4692..f7a973b96a 100644 --- a/test/cql-pytest/test_null.py +++ b/test/cql-pytest/test_null.py @@ -52,21 +52,17 @@ def test_insert_null_key(cql, table1): # Tests handling of "key_column in ?" where ? is bound to null. # Reproduces issue #8265. -def test_primary_key_in_null(cql, table1): - 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]) +def test_primary_key_in_null(scylla_only, cql, table1): + assert list(cql.execute(cql.prepare(f"SELECT p FROM {table1} WHERE p IN ?"), [None])) == [] + assert list(cql.execute(cql.prepare(f"SELECT p FROM {table1} WHERE p='' AND c IN ?"), [None])) == [] + assert list(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]) + assert list(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):