Files
scylladb/test/cql-pytest/test_cast_data.py
Nadav Har'El 78555ba7f1 test/cql-pytest: add tests for data casts and inf in sums
This patch adds tests to reproduce issue #13551. The issue, discovered
by a dtest (cql_cast_test.py), claimed that either cast() or sum(cast())
from varint type broke. So we add two tests in cql-pytest:

1. A new test file, test_cast_data.py, for testing data casts (a
   CAST (...) as ... in a SELECT), starting with testing casts from
   varint to other types.

   The test uncovers a lot of interesting cases (it is heavily
   commented to explain these cases) but nothing there is wrong
   and all tests pass on Scylla.

2. An xfailing test for sum() aggregate of +Inf and -Inf. It turns out
   that this caused #13551. In Cassandra and older Scylla, the sum
   returned a NaN. In Scylla today, it generates a misleading
   error message.

As usual, the tests were run on both Cassandra (4.1.1) and Scylla.

Refs #13551.

Signed-off-by: Nadav Har'El <nyh@scylladb.com>
2023-04-18 13:38:42 +03:00

93 lines
5.0 KiB
Python

# Copyright 2023-present ScyllaDB
#
# SPDX-License-Identifier: AGPL-3.0-or-later
###############################################################################
# Tests for data casts, e.g `SELECT CAST (... AS int) FROM tbl`
#
# Note that this is a different feature from the query casts `(int)123`
# which are tested separately in test_cast.py. Whereas the query casts
# can only reinterpret data losslessly (e.g., cast an integer to a wider
# integer), the data casts tested here can actually convert data from one
# type to another in sometimes surprising ways. For example, a very large
# arbitrary-precision number (varint) can be cast into an integer and result
# in a wraparound, or be cast into a float and result an infinity. In
# this test file we'll check some of these surprising conversions, and
# verify that they are the same in Scylla and Cassandra and don't regress.
#
# See also cql_cast_test.py in dtest.
###############################################################################
import pytest
import math
from util import new_test_table, unique_key_int
from cassandra.protocol import InvalidRequest
@pytest.fixture(scope="module")
def table1(cql, test_keyspace):
with new_test_table(cql, test_keyspace, "p int PRIMARY KEY, cVarint varint") as table:
yield table
# Utility function for emulating a wrapping cast of a big positive number
# into a smaller signed integer of given number of bits. For example,
# casting 511 to 8 bits results in -1.
def signed(number, bits):
p = 2**bits
ret = number % p
if ret > p/2:
return ret - p
else:
return ret
# Test casting a very large varint number to various other types, resulting
# in various non-trivial conversions including truncation or wrap-around of
# numbers.
def test_cast_from_large_varint(cql, table1):
p = unique_key_int()
v = 32767456456456456456545678943512357658768763546575675
cql.execute(f'INSERT INTO {table1} (p, cVarint) VALUES ({p}, {v})')
# We can read back the original number without a cast, or with a cast
# to the same type. The "decimal" type can also hold a varint and return
# the same number.
assert [(v,)] == list(cql.execute(f"SELECT cVarint FROM {table1} WHERE p={p}"))
assert [(v,)] == list(cql.execute(f"SELECT CAST(cVarint AS varint) FROM {table1} WHERE p={p}"))
assert [(v,)] == list(cql.execute(f"SELECT CAST(cVarint AS decimal) FROM {table1} WHERE p={p}"))
# Casting into smaller integer types results in wraparound
assert [(signed(v,8),)] == list(cql.execute(f"SELECT CAST(cVarint AS tinyint) FROM {table1} WHERE p={p}"))
assert [(signed(v,16),)] == list(cql.execute(f"SELECT CAST(cVarint AS smallint) FROM {table1} WHERE p={p}"))
assert [(signed(v,32),)] == list(cql.execute(f"SELECT CAST(cVarint AS int) FROM {table1} WHERE p={p}"))
assert [(signed(v,64),)] == list(cql.execute(f"SELECT CAST(cVarint AS bigint) FROM {table1} WHERE p={p}"))
# Casting to a 32-bit floating point, which only supports numbers up
# to 1e38, results in infinity
assert [(math.inf,)] == list(cql.execute(f"SELECT CAST(cVarint AS float) FROM {table1} WHERE p={p}"))
# Casting to a 64-bit floating point, which supports the range of our
# given number (though not its full precision!) is allowed, and some
# precision is lost. Confusingly, Python's 64-bit floating point is
# called "float", and we can use Python's float() function to convert
# the large number into a 64-bit double to compare the expected result.
assert [(float(v),)] == list(cql.execute(f"SELECT CAST(cVarint AS double) FROM {table1} WHERE p={p}"))
# Casting the number to string types results in printing the string in
# decimal, as expected - same as Python's str() function:
assert [(str(v),)] == list(cql.execute(f"SELECT CAST(cVarint AS ascii) FROM {table1} WHERE p={p}"))
assert [(str(v),)] == list(cql.execute(f"SELECT CAST(cVarint AS text) FROM {table1} WHERE p={p}"))
# "varchar" is supposed to be an alias to "text" and worked just as well,
# but suprisingly casting to varchar doesn't work on Cassandra, so let's
# test it in a separate test below, test_cast_from_large_varint_to_varchar
# Casting a number to all other types is NOT allowed:
for t in ['blob', 'boolean', 'counter', 'date', 'duration', 'inet',
'timestamp', 'timeuuid', 'uuid']:
with pytest.raises(InvalidRequest, match='cast'):
cql.execute(f"SELECT CAST(cVarint AS {t}) FROM {table1} WHERE p={p}")
# In test_cast_from_large_varint we checked that a varint can be cast
# to the "text" type. Since "varchar" is just an alias for "text", casting
# to varchar should work too, but in Cassandra it doesn't so this test
# is marked a Cassandra bug.
def test_cast_from_large_varint_to_varchar(cql, table1, cassandra_bug):
p = unique_key_int()
v = 32767456456456456456545678943512357658768763546575675
cql.execute(f'INSERT INTO {table1} (p, cVarint) VALUES ({p}, {v})')
assert [(str(v),)] == list(cql.execute(f"SELECT CAST(cVarint AS varchar) FROM {table1} WHERE p={p}"))
# TODO: test casts from more types.