Skip to content

Commit

Permalink
test/cql-pytest: regression test for old bug with CAST(f AS TEXT) pre…
Browse files Browse the repository at this point in the history
…cision

When casting a float or double column to a string with `CAST(f AS TEXT)`,
Scylla is expected to print the number with enough digits so that reading
that string back to a float or double restores the original number
exactly. This expectation isn't documented anywhere, but makes sense,
and is what Cassandra does.

Before commit 71bbd74, this wasn't the
case in Scylla: `CAST(f AS TEXT)` always printed 6 digits of precision,
which was a bit under enough for a float (which can have 7 decimal digits
of precision), but very much not enough for a double (which can need 15
digits). The origin of this magic "6 digits" number was that Scylla uses
seastar::to_sstring() to print the float and double values, and before
the aforementioned commit those functions used sprintf with the "%g"
format - which always prints 6 decimal digits of precision! After that
commit, to_sstring() now uses a different approach (based on fmt) to
print the float and double values, that prints all significant digits.

This patch adds a regression test for this bug: We write float and double
values to the database, cast them to text, and then recover the float
or double number from that text - and check that we get back exactly the
same float or double object. The test *fails* before the aforementioned
commit, and passes after it. It also passes on Cassandra.

Refs scylladb#15127

Signed-off-by: Nadav Har'El <nyh@scylladb.com>
  • Loading branch information
nyh committed Aug 23, 2023
1 parent a4e7f9b commit 7ece52f
Showing 1 changed file with 50 additions and 0 deletions.
50 changes: 50 additions & 0 deletions test/cql-pytest/test_cast_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import math
from util import new_test_table, unique_key_int
from cassandra.protocol import InvalidRequest
from ctypes import c_float

@pytest.fixture(scope="module")
def table1(cql, test_keyspace):
Expand Down Expand Up @@ -151,4 +152,53 @@ def test_cast_to_counter(cql, table3):
with pytest.raises(InvalidRequest, match='cannot be cast'):
cql.execute(f"SELECT CAST({col} AS counter) FROM {table3} WHERE p={p}")

# Test casting 32-bit floating-point to text. The output string should have
# enough digits to faithfully reproduce the original floating-point value
# when read back. We don't check in this test whether maybe it outputs too
# many digits that don't make any difference when read back.
# Reproduces issue #15127.
def test_cast_float_to_text(cql, table3):
p = unique_key_int()
# The numbers below (based on pi) have excessive precision, they are
# truncated to Python's 64-bit precision, and later further truncated
# to 32-bit floats when assigned to a Scylla "float" column.
numbers = [ 3.141592653589793238462643383279502884197,
-3.141592653589793238462643383279502884197,
-31415926535897932384.62643383279502884197,
0.00003141592653589793238462643383279502884197]
for n in numbers:
cql.execute(f'INSERT INTO {table3} (p, f) VALUES ({p}, {n})')
# Sanity check: If we read "f" back, we get n truncated to 32-bit
# precision. We use c_float to truncate n to 32-bit precision.
expected_f = c_float(n)
read_f = c_float(list(cql.execute(f"SELECT f FROM {table3} WHERE p={p}"))[0][0])
assert expected_f.value == read_f.value # that's how to compare c_float objects
# Finally test CAST(f AS text): test that the resulting textual
# representation has enough digits to restore f without loss of
# precision. If we ask Scylla to cast f to text, and then convert
# this text back to c_float, the result should be equal again to
# expected_f. If the cast printed too few digits, it would not be
# equal.
ftext = list(cql.execute(f"SELECT CAST(f AS text) FROM {table3} WHERE p={p}"))[0][0]
read_f = c_float(float(ftext))
assert expected_f.value == read_f.value

# Same test as test_cast_float_to_text() above, just for 64-bit floats.
# Reproduces issue #15127.
def test_cast_double_to_text(cql, table3):
p = unique_key_int()
numbers = [ 3.141592653589793238462643383279502884197,
-3.141592653589793238462643383279502884197,
-31415926535897932384.62643383279502884197,
0.00003141592653589793238462643383279502884197]
for n in numbers:
cql.execute(f'INSERT INTO {table3} (p, db) VALUES ({p}, {n})')
# If we read "db" back, we get n truncated to 64-bit precision.
assert n == list(cql.execute(f"SELECT db FROM {table3} WHERE p={p}"))[0][0]
# If we ask Scylla to cast db to text, and then convert this text back
# to 64-bit float, the result should be equal again to n. If the cast
# printed too few digits, it would not be equal.
ftext = list(cql.execute(f"SELECT CAST(db AS text) FROM {table3} WHERE p={p}"))[0][0]
assert n == float(ftext)

# TODO: test casts from more types.

0 comments on commit 7ece52f

Please sign in to comment.