From 72e41a7dbd07196314244c899e74bb193b2f976e Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Sun, 11 Dec 2022 11:44:11 -0500 Subject: [PATCH] LibSQL: Support 64-bit integer values and handle overflow errors Currently, integers are stored in LibSQL as 32-bit signed integers, even if the provided type is unsigned. This resulted in a series of unchecked unsigned-to-signed conversions, and prevented storing 64-bit values. Further, mathematical operations were performed without similar checks, and without checking for overflow. This changes SQL::Value to behave like SQLite for INTEGER types. In SQLite, the INTEGER type does not imply a size or signedness of the underlying type. Instead, SQLite determines on-the-fly what type is needed as values are created and updated. To do so, the SQL::Value variant can now hold an i64 or u64 integer. If a specific type is requested, invalid conversions are now explictly an error (e.g. converting a stored -1 to a u64 will fail). When binary mathematical operations are performed, we now try to coerce the RHS value to a type that works with the LHS value, failing the operation if that isn't possible. Any overflow or invalid operation (e.g. bitshifting a 64-bit value by more than 64 bytes) is an error. --- Tests/LibSQL/TestSqlBtreeIndex.cpp | 2 +- Tests/LibSQL/TestSqlDatabase.cpp | 4 +- Tests/LibSQL/TestSqlHashIndex.cpp | 2 +- Tests/LibSQL/TestSqlStatementExecution.cpp | 56 +- Tests/LibSQL/TestSqlValueAndTuple.cpp | 798 ++++++++++++++++++- Userland/Libraries/LibSQL/AST/Expression.cpp | 16 +- Userland/Libraries/LibSQL/AST/Select.cpp | 4 +- Userland/Libraries/LibSQL/Database.cpp | 2 +- Userland/Libraries/LibSQL/Heap.h | 2 +- Userland/Libraries/LibSQL/Meta.cpp | 6 +- Userland/Libraries/LibSQL/Result.h | 1 + Userland/Libraries/LibSQL/Value.cpp | 544 ++++++++----- Userland/Libraries/LibSQL/Value.h | 87 +- 13 files changed, 1241 insertions(+), 283 deletions(-) diff --git a/Tests/LibSQL/TestSqlBtreeIndex.cpp b/Tests/LibSQL/TestSqlBtreeIndex.cpp index 986998dda1a..3ed41f6f865 100644 --- a/Tests/LibSQL/TestSqlBtreeIndex.cpp +++ b/Tests/LibSQL/TestSqlBtreeIndex.cpp @@ -211,7 +211,7 @@ void insert_into_and_scan_btree(int num_keys) if (prev.size()) { EXPECT(prev < key); } - auto key_value = key[0].to_int(); + auto key_value = key[0].to_int(); for (auto ix = 0; ix < num_keys; ix++) { if (keys[ix] == key_value) { EXPECT_EQ(key.pointer(), pointers[ix]); diff --git a/Tests/LibSQL/TestSqlDatabase.cpp b/Tests/LibSQL/TestSqlDatabase.cpp index 4700f09efe4..de960e3e88c 100644 --- a/Tests/LibSQL/TestSqlDatabase.cpp +++ b/Tests/LibSQL/TestSqlDatabase.cpp @@ -68,10 +68,10 @@ void verify_table_contents(SQL::Database& db, int expected_count) EXPECT(!rows_or_error.is_error()); for (auto& row : rows_or_error.value()) { StringBuilder builder; - builder.appendff("Test{}", row["IntColumn"].to_int().value()); + builder.appendff("Test{}", row["IntColumn"].to_int().value()); EXPECT_EQ(row["TextColumn"].to_deprecated_string(), builder.build()); count++; - sum += row["IntColumn"].to_int().value(); + sum += row["IntColumn"].to_int().value(); } EXPECT_EQ(count, expected_count); EXPECT_EQ(sum, (expected_count * (expected_count - 1)) / 2); diff --git a/Tests/LibSQL/TestSqlHashIndex.cpp b/Tests/LibSQL/TestSqlHashIndex.cpp index 2219b16034d..f648d8506dc 100644 --- a/Tests/LibSQL/TestSqlHashIndex.cpp +++ b/Tests/LibSQL/TestSqlHashIndex.cpp @@ -268,7 +268,7 @@ void insert_into_and_scan_hash_index(int num_keys) int count = 0; for (auto iter = hash_index->begin(); !iter.is_end(); iter++, count++) { auto key = (*iter); - auto key_value = key[0].to_int(); + auto key_value = key[0].to_int(); VERIFY(key_value.has_value()); for (auto ix = 0; ix < num_keys; ix++) { diff --git a/Tests/LibSQL/TestSqlStatementExecution.cpp b/Tests/LibSQL/TestSqlStatementExecution.cpp index cc2fd9e425b..62c5abc037d 100644 --- a/Tests/LibSQL/TestSqlStatementExecution.cpp +++ b/Tests/LibSQL/TestSqlStatementExecution.cpp @@ -105,7 +105,7 @@ TEST_CASE(insert_into_table) EXPECT(!rows_or_error.is_error()); for (auto& row : rows_or_error.value()) { EXPECT_EQ(row["TEXTCOLUMN"].to_deprecated_string(), "Test"); - EXPECT_EQ(row["INTCOLUMN"].to_int().value(), 42); + EXPECT_EQ(row["INTCOLUMN"].to_int(), 42); count++; } EXPECT_EQ(count, 1); @@ -318,7 +318,7 @@ TEST_CASE(select_with_where) result = execute(database, "SELECT TextColumn, IntColumn FROM TestSchema.TestTable WHERE IntColumn > 44;"); EXPECT_EQ(result.size(), 2u); for (auto& row : result) { - EXPECT(row.row[1].to_int().value() > 44); + EXPECT(row.row[1].to_int().value() > 44); } } @@ -348,10 +348,10 @@ TEST_CASE(select_cross_join) EXPECT_EQ(result.size(), 25u); for (auto& row : result) { EXPECT(row.row.size() == 4); - EXPECT(row.row[1].to_int().value() >= 42); - EXPECT(row.row[1].to_int().value() <= 46); - EXPECT(row.row[3].to_int().value() >= 40); - EXPECT(row.row[3].to_int().value() <= 48); + EXPECT(row.row[1].to_int().value() >= 42); + EXPECT(row.row[1].to_int().value() <= 46); + EXPECT(row.row[3].to_int().value() >= 40); + EXPECT(row.row[3].to_int().value() <= 48); } } @@ -383,7 +383,7 @@ TEST_CASE(select_inner_join) "WHERE TestTable1.IntColumn = TestTable2.IntColumn;"); EXPECT_EQ(result.size(), 1u); EXPECT_EQ(result[0].row.size(), 3u); - EXPECT_EQ(result[0].row[0].to_int().value(), 42); + EXPECT_EQ(result[0].row[0].to_int(), 42); EXPECT_EQ(result[0].row[1].to_deprecated_string(), "Test_1"); EXPECT_EQ(result[0].row[2].to_deprecated_string(), "Test_12"); } @@ -463,11 +463,11 @@ TEST_CASE(select_with_order) result = execute(database, "SELECT TextColumn, IntColumn FROM TestSchema.TestTable ORDER BY IntColumn;"); EXPECT_EQ(result.size(), 5u); - EXPECT_EQ(result[0].row[1].to_int().value(), 40); - EXPECT_EQ(result[1].row[1].to_int().value(), 41); - EXPECT_EQ(result[2].row[1].to_int().value(), 42); - EXPECT_EQ(result[3].row[1].to_int().value(), 44); - EXPECT_EQ(result[4].row[1].to_int().value(), 47); + EXPECT_EQ(result[0].row[1].to_int(), 40); + EXPECT_EQ(result[1].row[1].to_int(), 41); + EXPECT_EQ(result[2].row[1].to_int(), 42); + EXPECT_EQ(result[3].row[1].to_int(), 44); + EXPECT_EQ(result[4].row[1].to_int(), 47); result = execute(database, "SELECT TextColumn, IntColumn FROM TestSchema.TestTable ORDER BY TextColumn;"); EXPECT_EQ(result.size(), 5u); @@ -547,15 +547,15 @@ TEST_CASE(select_with_order_two_columns) result = execute(database, "SELECT TextColumn, IntColumn FROM TestSchema.TestTable ORDER BY TextColumn, IntColumn;"); EXPECT_EQ(result.size(), 5u); EXPECT_EQ(result[0].row[0].to_deprecated_string(), "Test_1"); - EXPECT_EQ(result[0].row[1].to_int().value(), 47); + EXPECT_EQ(result[0].row[1].to_int(), 47); EXPECT_EQ(result[1].row[0].to_deprecated_string(), "Test_2"); - EXPECT_EQ(result[1].row[1].to_int().value(), 40); + EXPECT_EQ(result[1].row[1].to_int(), 40); EXPECT_EQ(result[2].row[0].to_deprecated_string(), "Test_2"); - EXPECT_EQ(result[2].row[1].to_int().value(), 42); + EXPECT_EQ(result[2].row[1].to_int(), 42); EXPECT_EQ(result[3].row[0].to_deprecated_string(), "Test_4"); - EXPECT_EQ(result[3].row[1].to_int().value(), 41); + EXPECT_EQ(result[3].row[1].to_int(), 41); EXPECT_EQ(result[4].row[0].to_deprecated_string(), "Test_5"); - EXPECT_EQ(result[4].row[1].to_int().value(), 44); + EXPECT_EQ(result[4].row[1].to_int(), 44); } TEST_CASE(select_with_order_by_column_not_in_result) @@ -626,16 +626,16 @@ TEST_CASE(select_with_order_limit_and_offset) } auto result = execute(database, "SELECT TextColumn, IntColumn FROM TestSchema.TestTable ORDER BY IntColumn LIMIT 10 OFFSET 10;"); EXPECT_EQ(result.size(), 10u); - EXPECT_EQ(result[0].row[1].to_int().value(), 10); - EXPECT_EQ(result[1].row[1].to_int().value(), 11); - EXPECT_EQ(result[2].row[1].to_int().value(), 12); - EXPECT_EQ(result[3].row[1].to_int().value(), 13); - EXPECT_EQ(result[4].row[1].to_int().value(), 14); - EXPECT_EQ(result[5].row[1].to_int().value(), 15); - EXPECT_EQ(result[6].row[1].to_int().value(), 16); - EXPECT_EQ(result[7].row[1].to_int().value(), 17); - EXPECT_EQ(result[8].row[1].to_int().value(), 18); - EXPECT_EQ(result[9].row[1].to_int().value(), 19); + EXPECT_EQ(result[0].row[1].to_int(), 10); + EXPECT_EQ(result[1].row[1].to_int(), 11); + EXPECT_EQ(result[2].row[1].to_int(), 12); + EXPECT_EQ(result[3].row[1].to_int(), 13); + EXPECT_EQ(result[4].row[1].to_int(), 14); + EXPECT_EQ(result[5].row[1].to_int(), 15); + EXPECT_EQ(result[6].row[1].to_int(), 16); + EXPECT_EQ(result[7].row[1].to_int(), 17); + EXPECT_EQ(result[8].row[1].to_int(), 18); + EXPECT_EQ(result[9].row[1].to_int(), 19); } TEST_CASE(select_with_limit_out_of_bounds) @@ -707,7 +707,7 @@ TEST_CASE(binary_operator_execution) auto const& result_row = result.at(i).row; EXPECT_EQ(result_row.size(), 1u); - auto result_column = result_row[0].to_int(); + auto result_column = result_row[0].to_int(); result_values.append(result_column.value()); } diff --git a/Tests/LibSQL/TestSqlValueAndTuple.cpp b/Tests/LibSQL/TestSqlValueAndTuple.cpp index fdcf7451675..fd95a6b67b1 100644 --- a/Tests/LibSQL/TestSqlValueAndTuple.cpp +++ b/Tests/LibSQL/TestSqlValueAndTuple.cpp @@ -19,8 +19,8 @@ TEST_CASE(null_value) EXPECT_EQ(v.type(), SQL::SQLType::Null); EXPECT_EQ(v.to_deprecated_string(), "(null)"sv); EXPECT(!v.to_bool().has_value()); - EXPECT(!v.to_int().has_value()); - EXPECT(!v.to_u32().has_value()); + EXPECT(!v.to_int().has_value()); + EXPECT(!v.to_int().has_value()); EXPECT(!v.to_double().has_value()); } @@ -72,8 +72,8 @@ TEST_CASE(text_value_to_other_types) SQL::Value v("42"); EXPECT_EQ(v.type(), SQL::SQLType::Text); - EXPECT(v.to_int().has_value()); - EXPECT_EQ(v.to_int().value(), 42); + EXPECT(v.to_int().has_value()); + EXPECT_EQ(v.to_int().value(), 42); EXPECT(v.to_double().has_value()); EXPECT((v.to_double().value() - 42.0) < NumericLimits().epsilon()); @@ -97,8 +97,8 @@ TEST_CASE(text_value_to_other_types) EXPECT_EQ(v.type(), SQL::SQLType::Text); EXPECT(!v.to_bool().has_value()); - EXPECT(!v.to_int().has_value()); - EXPECT(!v.to_u32().has_value()); + EXPECT(!v.to_int().has_value()); + EXPECT(!v.to_int().has_value()); EXPECT(!v.to_double().has_value()); } { @@ -147,8 +147,8 @@ TEST_CASE(integer_value) v = 42; EXPECT_EQ(v.type(), SQL::SQLType::Integer); - EXPECT(v.to_int().has_value()); - EXPECT_EQ(v.to_int().value(), 42); + EXPECT(v.to_int().has_value()); + EXPECT_EQ(v.to_int().value(), 42); EXPECT_EQ(v.to_deprecated_string(), "42"sv); EXPECT(v.to_double().has_value()); @@ -161,8 +161,8 @@ TEST_CASE(integer_value) SQL::Value v(0); EXPECT_EQ(v.type(), SQL::SQLType::Integer); - EXPECT(v.to_int().has_value()); - EXPECT_EQ(v.to_int().value(), 0); + EXPECT(v.to_int().has_value()); + EXPECT_EQ(v.to_int().value(), 0); EXPECT(v.to_bool().has_value()); EXPECT(!v.to_bool().value()); @@ -171,16 +171,16 @@ TEST_CASE(integer_value) SQL::Value v(42); EXPECT_EQ(v.type(), SQL::SQLType::Integer); - EXPECT(v.to_int().has_value()); - EXPECT_EQ(v.to_int().value(), 42); + EXPECT(v.to_int().has_value()); + EXPECT_EQ(v.to_int().value(), 42); } { SQL::Value text("42"); SQL::Value integer(SQL::SQLType::Integer); integer = text; - EXPECT(integer.to_int().has_value()); - EXPECT_EQ(integer.to_int().value(), 42); + EXPECT(integer.to_int().has_value()); + EXPECT_EQ(integer.to_int().value(), 42); } } @@ -200,6 +200,52 @@ TEST_CASE(serialize_int_value) EXPECT_EQ(v2, v); } +TEST_CASE(serialize_downsized_int_value) +{ + auto run_test_for_value = [](auto value) { + using T = decltype(value); + SQL::Value v(value); + + SQL::Serializer serializer; + serializer.serialize(v); + serializer.rewind(); + + auto type_flags = serializer.deserialize(); + auto type_data = type_flags & 0xf0; + auto type = static_cast(type_flags & 0x0f); + + EXPECT_NE(type_data, 0); + EXPECT_EQ(type, SQL::SQLType::Integer); + + auto deserialized = serializer.deserialize(); + EXPECT_EQ(deserialized, value); + }; + + run_test_for_value(NumericLimits::min()); + run_test_for_value(NumericLimits::max()); + + run_test_for_value(NumericLimits::min()); + run_test_for_value(NumericLimits::max()); + + run_test_for_value(NumericLimits::min()); + run_test_for_value(NumericLimits::max()); + + run_test_for_value(NumericLimits::min()); + run_test_for_value(NumericLimits::max()); + + run_test_for_value(NumericLimits::min()); + run_test_for_value(NumericLimits::max()); + + run_test_for_value(NumericLimits::min()); + run_test_for_value(NumericLimits::max()); + + run_test_for_value(NumericLimits::min()); + run_test_for_value(NumericLimits::max()); + + run_test_for_value(NumericLimits::min()); + run_test_for_value(NumericLimits::max()); +} + TEST_CASE(float_value) { { @@ -213,8 +259,8 @@ TEST_CASE(float_value) EXPECT(v.to_double().has_value()); EXPECT((v.to_double().value() - 3.14) < NumericLimits().epsilon()); - EXPECT(v.to_int().has_value()); - EXPECT_EQ(v.to_int().value(), 3); + EXPECT(v.to_int().has_value()); + EXPECT_EQ(v.to_int().value(), 3); EXPECT_EQ(v.to_deprecated_string(), "3.14"); EXPECT(v.to_bool().has_value()); @@ -226,8 +272,8 @@ TEST_CASE(float_value) EXPECT(v.to_double().has_value()); EXPECT(v.to_double().value() < NumericLimits().epsilon()); - EXPECT(v.to_int().has_value()); - EXPECT_EQ(v.to_int().value(), 0); + EXPECT(v.to_int().has_value()); + EXPECT_EQ(v.to_int().value(), 0); EXPECT_EQ(v.to_deprecated_string(), "0"sv); EXPECT(v.to_bool().has_value()); @@ -242,22 +288,22 @@ TEST_CASE(float_value) SQL::Value v(3.51); EXPECT_EQ(v.type(), SQL::SQLType::Float); - EXPECT(v.to_int().has_value()); - EXPECT_EQ(v.to_int().value(), 4); + EXPECT(v.to_int().has_value()); + EXPECT_EQ(v.to_int().value(), 4); } { SQL::Value v(-3.14); EXPECT_EQ(v.type(), SQL::SQLType::Float); - EXPECT(v.to_int().has_value()); - EXPECT_EQ(v.to_int().value(), -3); + EXPECT(v.to_int().has_value()); + EXPECT_EQ(v.to_int().value(), -3); } { SQL::Value v(-3.51); EXPECT_EQ(v.type(), SQL::SQLType::Float); - EXPECT(v.to_int().has_value()); - EXPECT_EQ(v.to_int().value(), -4); + EXPECT(v.to_int().has_value()); + EXPECT_EQ(v.to_int().value(), -4); } } @@ -284,12 +330,79 @@ TEST_CASE(copy_value) EXPECT_EQ(copy, "42"sv); } -TEST_CASE(compare_text_to_int) +TEST_CASE(to_int) { SQL::Value text("42"); SQL::Value integer(42); EXPECT_EQ(text, integer); EXPECT_EQ(integer, text); + + SQL::Value int_64 { static_cast(123) }; + EXPECT_EQ(int_64.to_int(), 123); + EXPECT_EQ(int_64.to_int(), 123); + EXPECT_EQ(int_64.to_int(), 123); + EXPECT_EQ(int_64.to_int(), 123u); + EXPECT_EQ(int_64.to_int(), 123u); + EXPECT_EQ(int_64.to_int(), 123u); + EXPECT_EQ(int_64.to_int(), 123u); + + SQL::Value uint_64 { static_cast(123) }; + EXPECT_EQ(uint_64.to_int(), 123); + EXPECT_EQ(uint_64.to_int(), 123); + EXPECT_EQ(uint_64.to_int(), 123); + EXPECT_EQ(uint_64.to_int(), 123); + EXPECT_EQ(uint_64.to_int(), 123u); + EXPECT_EQ(uint_64.to_int(), 123u); + EXPECT_EQ(uint_64.to_int(), 123u); +} + +TEST_CASE(to_int_failures) +{ + SQL::Value large_int_64 { NumericLimits::max() }; + EXPECT(!large_int_64.to_int().has_value()); + EXPECT(!large_int_64.to_int().has_value()); + EXPECT(!large_int_64.to_int().has_value()); + EXPECT(!large_int_64.to_int().has_value()); + EXPECT(!large_int_64.to_int().has_value()); + EXPECT(!large_int_64.to_int().has_value()); + + SQL::Value large_int_32 { NumericLimits::max() }; + EXPECT(!large_int_32.to_int().has_value()); + EXPECT(!large_int_32.to_int().has_value()); + EXPECT(!large_int_32.to_int().has_value()); + EXPECT(!large_int_32.to_int().has_value()); + + SQL::Value small_int_64 { NumericLimits::min() }; + EXPECT(!small_int_64.to_int().has_value()); + EXPECT(!small_int_64.to_int().has_value()); + EXPECT(!small_int_64.to_int().has_value()); + EXPECT(!small_int_64.to_int().has_value()); + EXPECT(!small_int_64.to_int().has_value()); + EXPECT(!small_int_64.to_int().has_value()); + EXPECT(!small_int_64.to_int().has_value()); + + SQL::Value small_int_32 { NumericLimits::min() }; + EXPECT(!small_int_32.to_int().has_value()); + EXPECT(!small_int_32.to_int().has_value()); + EXPECT(!small_int_32.to_int().has_value()); + EXPECT(!small_int_32.to_int().has_value()); + EXPECT(!small_int_32.to_int().has_value()); + EXPECT(!small_int_32.to_int().has_value()); + + SQL::Value large_uint_64 { NumericLimits::max() }; + EXPECT(!large_uint_64.to_int().has_value()); + EXPECT(!large_uint_64.to_int().has_value()); + EXPECT(!large_uint_64.to_int().has_value()); + EXPECT(!large_uint_64.to_int().has_value()); + EXPECT(!large_uint_64.to_int().has_value()); + EXPECT(!large_uint_64.to_int().has_value()); + EXPECT(!large_uint_64.to_int().has_value()); + + SQL::Value large_uint_32 { NumericLimits::max() }; + EXPECT(!large_uint_32.to_int().has_value()); + EXPECT(!large_uint_32.to_int().has_value()); + EXPECT(!large_uint_32.to_int().has_value()); + EXPECT(!large_uint_32.to_int().has_value()); } TEST_CASE(bool_value) @@ -305,8 +418,8 @@ TEST_CASE(bool_value) EXPECT(v.to_bool().has_value()); EXPECT(v.to_bool().value()); - EXPECT(v.to_int().has_value()); - EXPECT_EQ(v.to_int().value(), 1); + EXPECT(v.to_int().has_value()); + EXPECT_EQ(v.to_int().value(), 1); EXPECT_EQ(v.to_deprecated_string(), "true"sv); EXPECT(v.to_double().has_value()); @@ -319,8 +432,8 @@ TEST_CASE(bool_value) EXPECT(v.to_bool().has_value()); EXPECT(!v.to_bool().value()); - EXPECT(v.to_int().has_value()); - EXPECT_EQ(v.to_int().value(), 0); + EXPECT(v.to_int().has_value()); + EXPECT_EQ(v.to_int().value(), 0); EXPECT_EQ(v.to_deprecated_string(), "false"sv); EXPECT(v.to_double().has_value()); @@ -333,8 +446,8 @@ TEST_CASE(bool_value) EXPECT(v.to_bool().has_value()); EXPECT(v.to_bool().value()); - EXPECT(v.to_int().has_value()); - EXPECT_EQ(v.to_int().value(), 1); + EXPECT(v.to_int().has_value()); + EXPECT_EQ(v.to_int().value(), 1); EXPECT_EQ(v.to_deprecated_string(), "true"sv); EXPECT(v.to_double().has_value()); @@ -567,3 +680,626 @@ TEST_CASE(compare_tuples) EXPECT(tuple3 >= tuple1); EXPECT(tuple3 > tuple1); } + +TEST_CASE(add) +{ + { + SQL::Value value1 { 21 }; + SQL::Value value2 { 42 }; + + auto result = value1.add(value2); + EXPECT(!result.is_error()); + EXPECT_EQ(result.value().type(), SQL::SQLType::Integer); + EXPECT_EQ(result.value(), 63); + } + { + SQL::Value value1 { 21 }; + SQL::Value value2 { static_cast(42) }; + + auto result = value1.add(value2); + EXPECT(!result.is_error()); + EXPECT_EQ(result.value().type(), SQL::SQLType::Integer); + EXPECT_EQ(result.value(), 63); + } + { + SQL::Value value1 { static_cast(21) }; + SQL::Value value2 { 42 }; + + auto result = value1.add(value2); + EXPECT(!result.is_error()); + EXPECT_EQ(result.value().type(), SQL::SQLType::Integer); + EXPECT_EQ(result.value(), 63); + } + { + SQL::Value value1 { static_cast(21) }; + SQL::Value value2 { 42 }; + + auto result = value1.add(value2); + EXPECT(!result.is_error()); + EXPECT_EQ(result.value().type(), SQL::SQLType::Integer); + EXPECT_EQ(result.value(), 63); + } + { + SQL::Value value1 { static_cast(21.5) }; + SQL::Value value2 { 42 }; + + auto result = value1.add(value2); + EXPECT(!result.is_error()); + EXPECT_EQ(result.value().type(), SQL::SQLType::Float); + EXPECT((result.value().to_double().value() - 63.5) < NumericLimits().epsilon()); + } +} + +TEST_CASE(add_error) +{ + { + // Fails to coerce value2 to the signedness of value1. + SQL::Value value1 { 1 }; + SQL::Value value2 { NumericLimits::max() }; + + auto result = value1.add(value2); + EXPECT(result.is_error()); + EXPECT_EQ(result.error().error(), SQL::SQLErrorCode::IntegerOverflow); + } + { + // Fails to coerce value2 to the signedness of value1. + SQL::Value value1 { static_cast(1) }; + SQL::Value value2 { -1 }; + + auto result = value1.add(value2); + EXPECT(result.is_error()); + EXPECT_EQ(result.error().error(), SQL::SQLErrorCode::IntegerOverflow); + } + { + // The operation itself would overflow. + SQL::Value value1 { static_cast(1) }; + SQL::Value value2 { NumericLimits::max() }; + + auto result = value1.add(value2); + EXPECT(result.is_error()); + EXPECT_EQ(result.error().error(), SQL::SQLErrorCode::IntegerOverflow); + } + { + // Cannot convert value to a number. + SQL::Value value1 { 1 }; + SQL::Value value2 { "foo"sv }; + + auto result = value1.add(value2); + EXPECT(result.is_error()); + EXPECT_EQ(result.error().error(), SQL::SQLErrorCode::NumericOperatorTypeMismatch); + } +} + +TEST_CASE(subtract) +{ + { + SQL::Value value1 { 21 }; + SQL::Value value2 { 42 }; + + auto result = value1.subtract(value2); + EXPECT(!result.is_error()); + EXPECT_EQ(result.value().type(), SQL::SQLType::Integer); + EXPECT_EQ(result.value(), -21); + } + { + SQL::Value value1 { 21 }; + SQL::Value value2 { static_cast(42) }; + + auto result = value1.subtract(value2); + EXPECT(!result.is_error()); + EXPECT_EQ(result.value().type(), SQL::SQLType::Integer); + EXPECT_EQ(result.value(), -21); + } + { + SQL::Value value1 { static_cast(42) }; + SQL::Value value2 { 21 }; + + auto result = value1.subtract(value2); + EXPECT(!result.is_error()); + EXPECT_EQ(result.value().type(), SQL::SQLType::Integer); + EXPECT_EQ(result.value(), 21); + } + { + SQL::Value value1 { static_cast(21) }; + SQL::Value value2 { 42 }; + + auto result = value1.subtract(value2); + EXPECT(!result.is_error()); + EXPECT_EQ(result.value().type(), SQL::SQLType::Integer); + EXPECT_EQ(result.value(), -21); + } + { + SQL::Value value1 { static_cast(21.5) }; + SQL::Value value2 { 42 }; + + auto result = value1.subtract(value2); + EXPECT(!result.is_error()); + EXPECT_EQ(result.value().type(), SQL::SQLType::Float); + EXPECT((result.value().to_double().value() - 20.5) < NumericLimits().epsilon()); + } +} + +TEST_CASE(subtract_error) +{ + { + // Fails to coerce value2 to the signedness of value1. + SQL::Value value1 { 1 }; + SQL::Value value2 { NumericLimits::max() }; + + auto result = value1.subtract(value2); + EXPECT(result.is_error()); + EXPECT_EQ(result.error().error(), SQL::SQLErrorCode::IntegerOverflow); + } + { + // Fails to coerce value2 to the signedness of value1. + SQL::Value value1 { static_cast(1) }; + SQL::Value value2 { -1 }; + + auto result = value1.subtract(value2); + EXPECT(result.is_error()); + EXPECT_EQ(result.error().error(), SQL::SQLErrorCode::IntegerOverflow); + } + { + // The operation itself would overflow. + SQL::Value value1 { static_cast(0) }; + SQL::Value value2 { static_cast(1) }; + + auto result = value1.subtract(value2); + EXPECT(result.is_error()); + EXPECT_EQ(result.error().error(), SQL::SQLErrorCode::IntegerOverflow); + } + { + // Cannot convert value to a number. + SQL::Value value1 { 1 }; + SQL::Value value2 { "foo"sv }; + + auto result = value1.subtract(value2); + EXPECT(result.is_error()); + EXPECT_EQ(result.error().error(), SQL::SQLErrorCode::NumericOperatorTypeMismatch); + } +} + +TEST_CASE(multiply) +{ + { + SQL::Value value1 { 2 }; + SQL::Value value2 { 21 }; + + auto result = value1.multiply(value2); + EXPECT(!result.is_error()); + EXPECT_EQ(result.value().type(), SQL::SQLType::Integer); + EXPECT_EQ(result.value(), 42); + } + { + SQL::Value value1 { 2 }; + SQL::Value value2 { static_cast(21) }; + + auto result = value1.multiply(value2); + EXPECT(!result.is_error()); + EXPECT_EQ(result.value().type(), SQL::SQLType::Integer); + EXPECT_EQ(result.value(), 42); + } + { + SQL::Value value1 { static_cast(2) }; + SQL::Value value2 { 21 }; + + auto result = value1.multiply(value2); + EXPECT(!result.is_error()); + EXPECT_EQ(result.value().type(), SQL::SQLType::Integer); + EXPECT_EQ(result.value(), 42); + } + { + SQL::Value value1 { static_cast(2) }; + SQL::Value value2 { 21 }; + + auto result = value1.multiply(value2); + EXPECT(!result.is_error()); + EXPECT_EQ(result.value().type(), SQL::SQLType::Integer); + EXPECT_EQ(result.value(), 42); + } + { + SQL::Value value1 { static_cast(2.5) }; + SQL::Value value2 { 21 }; + + auto result = value1.multiply(value2); + EXPECT(!result.is_error()); + EXPECT_EQ(result.value().type(), SQL::SQLType::Float); + EXPECT((result.value().to_double().value() - 52.5) < NumericLimits().epsilon()); + } +} + +TEST_CASE(multiply_error) +{ + { + // Fails to coerce value2 to the signedness of value1. + SQL::Value value1 { 1 }; + SQL::Value value2 { NumericLimits::max() }; + + auto result = value1.multiply(value2); + EXPECT(result.is_error()); + EXPECT_EQ(result.error().error(), SQL::SQLErrorCode::IntegerOverflow); + } + { + // Fails to coerce value2 to the signedness of value1. + SQL::Value value1 { static_cast(1) }; + SQL::Value value2 { -1 }; + + auto result = value1.multiply(value2); + EXPECT(result.is_error()); + EXPECT_EQ(result.error().error(), SQL::SQLErrorCode::IntegerOverflow); + } + { + // The operation itself would overflow. + SQL::Value value1 { NumericLimits::max() }; + SQL::Value value2 { 2 }; + + auto result = value1.multiply(value2); + EXPECT(result.is_error()); + EXPECT_EQ(result.error().error(), SQL::SQLErrorCode::IntegerOverflow); + } + { + // Cannot convert value to a number. + SQL::Value value1 { 1 }; + SQL::Value value2 { "foo"sv }; + + auto result = value1.multiply(value2); + EXPECT(result.is_error()); + EXPECT_EQ(result.error().error(), SQL::SQLErrorCode::NumericOperatorTypeMismatch); + } +} + +TEST_CASE(divide) +{ + { + SQL::Value value1 { 42 }; + SQL::Value value2 { -2 }; + + auto result = value1.divide(value2); + EXPECT(!result.is_error()); + EXPECT_EQ(result.value().type(), SQL::SQLType::Integer); + EXPECT_EQ(result.value(), -21); + } + { + SQL::Value value1 { 42 }; + SQL::Value value2 { static_cast(2) }; + + auto result = value1.divide(value2); + EXPECT(!result.is_error()); + EXPECT_EQ(result.value().type(), SQL::SQLType::Integer); + EXPECT_EQ(result.value(), 21); + } + { + SQL::Value value1 { static_cast(42) }; + SQL::Value value2 { 2 }; + + auto result = value1.divide(value2); + EXPECT(!result.is_error()); + EXPECT_EQ(result.value().type(), SQL::SQLType::Integer); + EXPECT_EQ(result.value(), 21); + } + { + SQL::Value value1 { static_cast(42) }; + SQL::Value value2 { 2 }; + + auto result = value1.divide(value2); + EXPECT(!result.is_error()); + EXPECT_EQ(result.value().type(), SQL::SQLType::Integer); + EXPECT_EQ(result.value(), 21); + } + { + SQL::Value value1 { static_cast(43) }; + SQL::Value value2 { 2 }; + + auto result = value1.divide(value2); + EXPECT(!result.is_error()); + EXPECT_EQ(result.value().type(), SQL::SQLType::Float); + EXPECT((result.value().to_double().value() - 21.5) < NumericLimits().epsilon()); + } +} + +TEST_CASE(divide_error) +{ + { + // The operation itself would overflow. + SQL::Value value1 { 1 }; + SQL::Value value2 { 0 }; + + auto result = value1.divide(value2); + EXPECT(result.is_error()); + EXPECT_EQ(result.error().error(), SQL::SQLErrorCode::IntegerOverflow); + } + { + // Cannot convert value to a number. + SQL::Value value1 { 1 }; + SQL::Value value2 { "foo"sv }; + + auto result = value1.divide(value2); + EXPECT(result.is_error()); + EXPECT_EQ(result.error().error(), SQL::SQLErrorCode::NumericOperatorTypeMismatch); + } +} + +TEST_CASE(modulo) +{ + { + SQL::Value value1 { 21 }; + SQL::Value value2 { 2 }; + + auto result = value1.modulo(value2); + EXPECT(!result.is_error()); + EXPECT_EQ(result.value().type(), SQL::SQLType::Integer); + EXPECT_EQ(result.value(), 1); + } + { + SQL::Value value1 { 21 }; + SQL::Value value2 { static_cast(2) }; + + auto result = value1.modulo(value2); + EXPECT(!result.is_error()); + EXPECT_EQ(result.value().type(), SQL::SQLType::Integer); + EXPECT_EQ(result.value(), 1); + } + { + SQL::Value value1 { static_cast(21) }; + SQL::Value value2 { 2 }; + + auto result = value1.modulo(value2); + EXPECT(!result.is_error()); + EXPECT_EQ(result.value().type(), SQL::SQLType::Integer); + EXPECT_EQ(result.value(), 1); + } + { + SQL::Value value1 { static_cast(21) }; + SQL::Value value2 { 2 }; + + auto result = value1.modulo(value2); + EXPECT(!result.is_error()); + EXPECT_EQ(result.value().type(), SQL::SQLType::Integer); + EXPECT_EQ(result.value(), 1); + } +} + +TEST_CASE(modulo_error) +{ + { + // Fails to coerce value2 to the signedness of value1. + SQL::Value value1 { 1 }; + SQL::Value value2 { NumericLimits::max() }; + + auto result = value1.modulo(value2); + EXPECT(result.is_error()); + EXPECT_EQ(result.error().error(), SQL::SQLErrorCode::IntegerOverflow); + } + { + // Fails to coerce value2 to the signedness of value1. + SQL::Value value1 { static_cast(1) }; + SQL::Value value2 { -1 }; + + auto result = value1.modulo(value2); + EXPECT(result.is_error()); + EXPECT_EQ(result.error().error(), SQL::SQLErrorCode::IntegerOverflow); + } + { + // The operation itself would overflow. + SQL::Value value1 { 21 }; + SQL::Value value2 { 0 }; + + auto result = value1.modulo(value2); + EXPECT(result.is_error()); + EXPECT_EQ(result.error().error(), SQL::SQLErrorCode::IntegerOverflow); + } + { + // Cannot convert value to an integer. + SQL::Value value1 { 1 }; + SQL::Value value2 { "foo"sv }; + + auto result = value1.modulo(value2); + EXPECT(result.is_error()); + EXPECT_EQ(result.error().error(), SQL::SQLErrorCode::NumericOperatorTypeMismatch); + } + { + // Cannot convert value to an integer. + SQL::Value value1 { static_cast(21.5) }; + SQL::Value value2 { 2 }; + + auto result = value1.modulo(value2); + EXPECT(result.is_error()); + EXPECT_EQ(result.error().error(), SQL::SQLErrorCode::NumericOperatorTypeMismatch); + } +} + +TEST_CASE(shift_left) +{ + { + SQL::Value value1 { 0b0011'0000 }; + SQL::Value value2 { 2 }; + + auto result = value1.shift_left(value2); + EXPECT(!result.is_error()); + EXPECT_EQ(result.value().type(), SQL::SQLType::Integer); + EXPECT_EQ(result.value(), 0b1100'0000); + } + { + SQL::Value value1 { 0b0011'0000 }; + SQL::Value value2 { static_cast(2) }; + + auto result = value1.shift_left(value2); + EXPECT(!result.is_error()); + EXPECT_EQ(result.value().type(), SQL::SQLType::Integer); + EXPECT_EQ(result.value(), 0b1100'0000); + } + { + SQL::Value value1 { static_cast(0b0011'0000) }; + SQL::Value value2 { 2 }; + + auto result = value1.shift_left(value2); + EXPECT(!result.is_error()); + EXPECT_EQ(result.value().type(), SQL::SQLType::Integer); + EXPECT_EQ(result.value(), 0b1100'0000); + } + { + SQL::Value value1 { static_cast(0b0011'0000) }; + SQL::Value value2 { 2 }; + + auto result = value1.shift_left(value2); + EXPECT(!result.is_error()); + EXPECT_EQ(result.value().type(), SQL::SQLType::Integer); + EXPECT_EQ(result.value(), 0b1100'0000); + } +} + +TEST_CASE(shift_left_error) +{ + { + // Fails to coerce value2 to the signedness of value1. + SQL::Value value1 { 1 }; + SQL::Value value2 { NumericLimits::max() }; + + auto result = value1.shift_left(value2); + EXPECT(result.is_error()); + EXPECT_EQ(result.error().error(), SQL::SQLErrorCode::IntegerOverflow); + } + { + // Fails to coerce value2 to the signedness of value1. + SQL::Value value1 { static_cast(1) }; + SQL::Value value2 { -1 }; + + auto result = value1.shift_left(value2); + EXPECT(result.is_error()); + EXPECT_EQ(result.error().error(), SQL::SQLErrorCode::IntegerOverflow); + } + { + // The operation itself would overflow. + SQL::Value value1 { 21 }; + SQL::Value value2 { -1 }; + + auto result = value1.shift_left(value2); + EXPECT(result.is_error()); + EXPECT_EQ(result.error().error(), SQL::SQLErrorCode::IntegerOverflow); + } + { + // The operation itself would overflow. + SQL::Value value1 { 21 }; + SQL::Value value2 { 64 }; + + auto result = value1.shift_left(value2); + EXPECT(result.is_error()); + EXPECT_EQ(result.error().error(), SQL::SQLErrorCode::IntegerOverflow); + } + { + // Cannot convert value to an integer. + SQL::Value value1 { 1 }; + SQL::Value value2 { "foo"sv }; + + auto result = value1.shift_left(value2); + EXPECT(result.is_error()); + EXPECT_EQ(result.error().error(), SQL::SQLErrorCode::NumericOperatorTypeMismatch); + } + { + // Cannot convert value to an integer. + SQL::Value value1 { static_cast(21.5) }; + SQL::Value value2 { 2 }; + + auto result = value1.shift_left(value2); + EXPECT(result.is_error()); + EXPECT_EQ(result.error().error(), SQL::SQLErrorCode::NumericOperatorTypeMismatch); + } +} + +TEST_CASE(shift_right) +{ + { + SQL::Value value1 { 0b0011'0000 }; + SQL::Value value2 { 2 }; + + auto result = value1.shift_right(value2); + EXPECT(!result.is_error()); + EXPECT_EQ(result.value().type(), SQL::SQLType::Integer); + EXPECT_EQ(result.value(), 0b0000'1100); + } + { + SQL::Value value1 { 0b0011'0000 }; + SQL::Value value2 { static_cast(2) }; + + auto result = value1.shift_right(value2); + EXPECT(!result.is_error()); + EXPECT_EQ(result.value().type(), SQL::SQLType::Integer); + EXPECT_EQ(result.value(), 0b0000'1100); + } + { + SQL::Value value1 { static_cast(0b0011'0000) }; + SQL::Value value2 { 2 }; + + auto result = value1.shift_right(value2); + EXPECT(!result.is_error()); + EXPECT_EQ(result.value().type(), SQL::SQLType::Integer); + EXPECT_EQ(result.value(), 0b0000'1100); + } + { + SQL::Value value1 { static_cast(0b0011'0000) }; + SQL::Value value2 { 2 }; + + auto result = value1.shift_right(value2); + EXPECT(!result.is_error()); + EXPECT_EQ(result.value().type(), SQL::SQLType::Integer); + EXPECT_EQ(result.value(), 0b0000'1100); + } +} + +TEST_CASE(shift_right_error) +{ + { + // Fails to coerce value2 to the signedness of value1. + SQL::Value value1 { 1 }; + SQL::Value value2 { NumericLimits::max() }; + + auto result = value1.shift_right(value2); + EXPECT(result.is_error()); + EXPECT_EQ(result.error().error(), SQL::SQLErrorCode::IntegerOverflow); + } + { + // Fails to coerce value2 to the signedness of value1. + SQL::Value value1 { static_cast(1) }; + SQL::Value value2 { -1 }; + + auto result = value1.shift_right(value2); + EXPECT(result.is_error()); + EXPECT_EQ(result.error().error(), SQL::SQLErrorCode::IntegerOverflow); + } + { + // The operation itself would overflow. + SQL::Value value1 { 21 }; + SQL::Value value2 { -1 }; + + auto result = value1.shift_right(value2); + EXPECT(result.is_error()); + EXPECT_EQ(result.error().error(), SQL::SQLErrorCode::IntegerOverflow); + } + { + // The operation itself would overflow. + SQL::Value value1 { 21 }; + SQL::Value value2 { 64 }; + + auto result = value1.shift_right(value2); + EXPECT(result.is_error()); + EXPECT_EQ(result.error().error(), SQL::SQLErrorCode::IntegerOverflow); + } + { + // Cannot convert value to an integer. + SQL::Value value1 { 1 }; + SQL::Value value2 { "foo"sv }; + + auto result = value1.shift_right(value2); + EXPECT(result.is_error()); + EXPECT_EQ(result.error().error(), SQL::SQLErrorCode::NumericOperatorTypeMismatch); + } + { + // Cannot convert value to an integer. + SQL::Value value1 { static_cast(21.5) }; + SQL::Value value2 { 2 }; + + auto result = value1.shift_right(value2); + EXPECT(result.is_error()); + EXPECT_EQ(result.error().error(), SQL::SQLErrorCode::NumericOperatorTypeMismatch); + } +} diff --git a/Userland/Libraries/LibSQL/AST/Expression.cpp b/Userland/Libraries/LibSQL/AST/Expression.cpp index 0a39d4a30b5..f2506d6e5d7 100644 --- a/Userland/Libraries/LibSQL/AST/Expression.cpp +++ b/Userland/Libraries/LibSQL/AST/Expression.cpp @@ -128,15 +128,7 @@ ResultOr UnaryOperatorExpression::evaluate(ExecutionContext& context) con return expression_value; return Result { SQLCommand::Unknown, SQLErrorCode::NumericOperatorTypeMismatch, UnaryOperator_name(type()) }; case UnaryOperator::Minus: - if (expression_value.type() == SQLType::Integer) { - expression_value = -expression_value.to_int().value(); - return expression_value; - } - if (expression_value.type() == SQLType::Float) { - expression_value = -expression_value.to_double().value(); - return expression_value; - } - return Result { SQLCommand::Unknown, SQLErrorCode::NumericOperatorTypeMismatch, UnaryOperator_name(type()) }; + return expression_value.negate(); case UnaryOperator::Not: if (expression_value.type() == SQLType::Boolean) { expression_value = !expression_value.to_bool().value(); @@ -144,11 +136,7 @@ ResultOr UnaryOperatorExpression::evaluate(ExecutionContext& context) con } return Result { SQLCommand::Unknown, SQLErrorCode::BooleanOperatorTypeMismatch, UnaryOperator_name(type()) }; case UnaryOperator::BitwiseNot: - if (expression_value.type() == SQLType::Integer) { - expression_value = ~expression_value.to_u32().value(); - return expression_value; - } - return Result { SQLCommand::Unknown, SQLErrorCode::IntegerOperatorTypeMismatch, UnaryOperator_name(type()) }; + return expression_value.bitwise_not(); default: VERIFY_NOT_REACHED(); } diff --git a/Userland/Libraries/LibSQL/AST/Select.cpp b/Userland/Libraries/LibSQL/AST/Select.cpp index 8dfa6a93cc8..df06aed99ef 100644 --- a/Userland/Libraries/LibSQL/AST/Select.cpp +++ b/Userland/Libraries/LibSQL/AST/Select.cpp @@ -119,7 +119,7 @@ ResultOr Select::execute(ExecutionContext& context) const auto limit = TRY(m_limit_clause->limit_expression()->evaluate(context)); if (!limit.is_null()) { - auto limit_value_maybe = limit.to_u32(); + auto limit_value_maybe = limit.to_int(); if (!limit_value_maybe.has_value()) return Result { SQLCommand::Select, SQLErrorCode::SyntaxError, "LIMIT clause must evaluate to an integer value"sv }; @@ -129,7 +129,7 @@ ResultOr Select::execute(ExecutionContext& context) const if (m_limit_clause->offset_expression() != nullptr) { auto offset = TRY(m_limit_clause->offset_expression()->evaluate(context)); if (!offset.is_null()) { - auto offset_value_maybe = offset.to_u32(); + auto offset_value_maybe = offset.to_int(); if (!offset_value_maybe.has_value()) return Result { SQLCommand::Select, SQLErrorCode::SyntaxError, "OFFSET clause must evaluate to an integer value"sv }; diff --git a/Userland/Libraries/LibSQL/Database.cpp b/Userland/Libraries/LibSQL/Database.cpp index 93a154118d9..ee5f3c0a20e 100644 --- a/Userland/Libraries/LibSQL/Database.cpp +++ b/Userland/Libraries/LibSQL/Database.cpp @@ -165,7 +165,7 @@ ResultOr> Database::get_table(DeprecatedString const& sc auto table_hash = table_def->hash(); auto column_key = ColumnDef::make_key(table_def); - for (auto it = m_table_columns->find(column_key); !it.is_end() && ((*it)["table_hash"].to_u32().value() == table_hash); ++it) + for (auto it = m_table_columns->find(column_key); !it.is_end() && ((*it)["table_hash"].to_int() == table_hash); ++it) table_def->append_column(*it); return table_def; diff --git a/Userland/Libraries/LibSQL/Heap.h b/Userland/Libraries/LibSQL/Heap.h index 72915862222..492141fa08a 100644 --- a/Userland/Libraries/LibSQL/Heap.h +++ b/Userland/Libraries/LibSQL/Heap.h @@ -33,7 +33,7 @@ class Heap : public Core::Object { C_OBJECT(Heap); public: - static constexpr inline u32 current_version = 2; + static constexpr inline u32 current_version = 3; virtual ~Heap() override; diff --git a/Userland/Libraries/LibSQL/Meta.cpp b/Userland/Libraries/LibSQL/Meta.cpp index 3924a601f50..35bf4a808ba 100644 --- a/Userland/Libraries/LibSQL/Meta.cpp +++ b/Userland/Libraries/LibSQL/Meta.cpp @@ -59,9 +59,9 @@ Key ColumnDef::key() const { auto key = Key(index_def()); key["table_hash"] = parent_relation()->hash(); - key["column_number"] = (int)column_number(); + key["column_number"] = column_number(); key["column_name"] = name(); - key["column_type"] = (int)type(); + key["column_type"] = to_underlying(type()); return key; } @@ -183,7 +183,7 @@ void TableDef::append_column(DeprecatedString name, SQLType sql_type) void TableDef::append_column(Key const& column) { - auto column_type = column["column_type"].to_int(); + auto column_type = column["column_type"].to_int>(); VERIFY(column_type.has_value()); append_column(column["column_name"].to_deprecated_string(), static_cast(*column_type)); diff --git a/Userland/Libraries/LibSQL/Result.h b/Userland/Libraries/LibSQL/Result.h index d5d8f781a72..e965ada00b1 100644 --- a/Userland/Libraries/LibSQL/Result.h +++ b/Userland/Libraries/LibSQL/Result.h @@ -48,6 +48,7 @@ constexpr char const* command_tag(SQLCommand command) S(DatabaseDoesNotExist, "Database '{}' does not exist") \ S(DatabaseUnavailable, "Database Unavailable") \ S(IntegerOperatorTypeMismatch, "Cannot apply '{}' operator to non-numeric operands") \ + S(IntegerOverflow, "Operation would cause integer overflow") \ S(InternalError, "{}") \ S(InvalidDatabaseName, "Invalid database name '{}'") \ S(InvalidNumberOfPlaceholderValues, "Number of values does not match number of placeholders") \ diff --git a/Userland/Libraries/LibSQL/Value.cpp b/Userland/Libraries/LibSQL/Value.cpp index 5f366189939..86f2ecf94e4 100644 --- a/Userland/Libraries/LibSQL/Value.cpp +++ b/Userland/Libraries/LibSQL/Value.cpp @@ -12,11 +12,87 @@ #include #include #include -#include #include namespace SQL { +// We use the upper 4 bits of the encoded type to store extra information about the type. This +// includes if the value is null, and the encoded size of any integer type. Of course, this encoding +// only works if the SQL type itself fits in the lower 4 bits. +enum class SQLTypeWithCount { +#undef __ENUMERATE_SQL_TYPE +#define __ENUMERATE_SQL_TYPE(name, type) type, + ENUMERATE_SQL_TYPES(__ENUMERATE_SQL_TYPE) +#undef __ENUMERATE_SQL_TYPE + Count, +}; + +static_assert(to_underlying(SQLTypeWithCount::Count) <= 0x0f, "Too many SQL types for current encoding"); + +// Adding to this list is fine, but changing the order of any value here will result in LibSQL +// becoming unable to read existing .db files. If the order must absolutely be changed, be sure +// to bump Heap::current_version. +enum class TypeData : u8 { + Null = 1 << 4, + Int8 = 2 << 4, + Int16 = 3 << 4, + Int32 = 4 << 4, + Int64 = 5 << 4, + Uint8 = 6 << 4, + Uint16 = 7 << 4, + Uint32 = 8 << 4, + Uint64 = 9 << 4, +}; + +template +static decltype(auto) downsize_integer(Integer auto value, Callback&& callback) +{ + if constexpr (IsSigned) { + if (AK::is_within_range(value)) + return callback(static_cast(value), TypeData::Int8); + if (AK::is_within_range(value)) + return callback(static_cast(value), TypeData::Int16); + if (AK::is_within_range(value)) + return callback(static_cast(value), TypeData::Int32); + return callback(value, TypeData::Int64); + } else { + if (AK::is_within_range(value)) + return callback(static_cast(value), TypeData::Uint8); + if (AK::is_within_range(value)) + return callback(static_cast(value), TypeData::Uint16); + if (AK::is_within_range(value)) + return callback(static_cast(value), TypeData::Uint32); + return callback(value, TypeData::Uint64); + } +} + +template +static decltype(auto) downsize_integer(Value const& value, Callback&& callback) +{ + VERIFY(value.is_int()); + + if (value.value().has()) + return downsize_integer(value.value().get(), forward(callback)); + return downsize_integer(value.value().get(), forward(callback)); +} + +template +static ResultOr perform_integer_operation(Value const& lhs, Value const& rhs, Callback&& callback) +{ + VERIFY(lhs.is_int()); + VERIFY(rhs.is_int()); + + if (lhs.value().has()) { + if (auto rhs_value = rhs.to_int(); rhs_value.has_value()) + return callback(lhs.to_int().value(), rhs_value.value()); + } else { + if (auto rhs_value = rhs.to_int(); rhs_value.has_value()) + return callback(lhs.to_int().value(), rhs_value.value()); + } + + return Result { SQLCommand::Unknown, SQLErrorCode::IntegerOverflow }; +} + Value::Value(SQLType type) : m_type(type) { @@ -28,22 +104,23 @@ Value::Value(DeprecatedString value) { } -Value::Value(int value) - : m_type(SQLType::Integer) - , m_value(value) -{ -} - -Value::Value(u32 value) - : m_type(SQLType::Integer) - , m_value(static_cast(value)) // FIXME: Handle signed overflow. -{ -} - Value::Value(double value) - : m_type(SQLType::Float) - , m_value(value) { + if (trunc(value) == value) { + if (AK::is_within_range(value)) { + m_type = SQLType::Integer; + m_value = static_cast(value); + return; + } + if (AK::is_within_range(value)) { + m_type = SQLType::Integer; + m_value = static_cast(value); + return; + } + } + + m_type = SQLType::Float; + m_value = value; } Value::Value(NonnullRefPtr descriptor, Vector values) @@ -122,6 +199,11 @@ bool Value::is_null() const return !m_value.has_value(); } +bool Value::is_int() const +{ + return m_value.has_value() && (m_value->has() || m_value->has()); +} + DeprecatedString Value::to_deprecated_string() const { if (is_null()) @@ -129,7 +211,7 @@ DeprecatedString Value::to_deprecated_string() const return m_value->visit( [](DeprecatedString const& value) -> DeprecatedString { return value; }, - [](int value) -> DeprecatedString { return DeprecatedString::number(value); }, + [](Integer auto value) -> DeprecatedString { return DeprecatedString::number(value); }, [](double value) -> DeprecatedString { return DeprecatedString::number(value); }, [](bool value) -> DeprecatedString { return value ? "true"sv : "false"sv; }, [](TupleValue const& value) -> DeprecatedString { @@ -143,33 +225,6 @@ DeprecatedString Value::to_deprecated_string() const }); } -Optional Value::to_int() const -{ - if (is_null()) - return {}; - - return m_value->visit( - [](DeprecatedString const& value) -> Optional { return value.to_int(); }, - [](int value) -> Optional { return value; }, - [](double value) -> Optional { - if (value > static_cast(NumericLimits::max())) - return {}; - if (value < static_cast(NumericLimits::min())) - return {}; - return static_cast(round(value)); - }, - [](bool value) -> Optional { return static_cast(value); }, - [](TupleValue const&) -> Optional { return {}; }); -} - -Optional Value::to_u32() const -{ - // FIXME: Handle negative values. - if (auto result = to_int(); result.has_value()) - return static_cast(result.value()); - return {}; -} - Optional Value::to_double() const { if (is_null()) @@ -184,7 +239,7 @@ Optional Value::to_double() const return {}; return result; }, - [](int value) -> Optional { return static_cast(value); }, + [](Integer auto value) -> Optional { return static_cast(value); }, [](double value) -> Optional { return value; }, [](bool value) -> Optional { return static_cast(value); }, [](TupleValue const&) -> Optional { return {}; }); @@ -203,7 +258,7 @@ Optional Value::to_bool() const return false; return {}; }, - [](int value) -> Optional { return static_cast(value); }, + [](Integer auto value) -> Optional { return static_cast(value); }, [](double value) -> Optional { return fabs(value) > NumericLimits::epsilon(); }, [](bool value) -> Optional { return value; }, [](TupleValue const& value) -> Optional { @@ -242,20 +297,6 @@ Value& Value::operator=(DeprecatedString value) return *this; } -Value& Value::operator=(int value) -{ - m_type = SQLType::Integer; - m_value = value; - return *this; -} - -Value& Value::operator=(u32 value) -{ - m_type = SQLType::Integer; - m_value = static_cast(value); // FIXME: Handle signed overflow. - return *this; -} - Value& Value::operator=(double value) { m_type = SQLType::Float; @@ -318,7 +359,11 @@ size_t Value::length() const // FIXME: This seems to be more of an encoded byte size rather than a length. return m_value->visit( [](DeprecatedString const& value) -> size_t { return sizeof(u32) + value.length(); }, - [](int value) -> size_t { return sizeof(value); }, + [](Integer auto value) -> size_t { + return downsize_integer(value, [](auto integer, auto) { + return sizeof(integer); + }); + }, [](double value) -> size_t { return sizeof(value); }, [](bool value) -> size_t { return sizeof(value); }, [](TupleValue const& value) -> size_t { @@ -338,7 +383,14 @@ u32 Value::hash() const return m_value->visit( [](DeprecatedString const& value) -> u32 { return value.hash(); }, - [](int value) -> u32 { return int_hash(value); }, + [](Integer auto value) -> u32 { + return downsize_integer(value, [](auto integer, auto) { + if constexpr (sizeof(decltype(integer)) == 8) + return u64_hash(integer); + else + return int_hash(integer); + }); + }, [](double) -> u32 { VERIFY_NOT_REACHED(); }, [](bool value) -> u32 { return int_hash(value); }, [](TupleValue const& value) -> u32 { @@ -364,8 +416,8 @@ int Value::compare(Value const& other) const return m_value->visit( [&](DeprecatedString const& value) -> int { return value.view().compare(other.to_deprecated_string()); }, - [&](int value) -> int { - auto casted = other.to_int(); + [&](Integer auto value) -> int { + auto casted = other.to_int>(); if (!casted.has_value()) return 1; @@ -427,16 +479,6 @@ bool Value::operator==(StringView value) const return to_deprecated_string() == value; } -bool Value::operator==(int value) const -{ - return to_int() == value; -} - -bool Value::operator==(u32 value) const -{ - return to_u32() == value; -} - bool Value::operator==(double value) const { return to_double() == value; @@ -467,133 +509,218 @@ bool Value::operator>=(Value const& value) const return compare(value) >= 0; } -static Result invalid_type_for_numeric_operator(AST::BinaryOperator op) +template +static Result invalid_type_for_numeric_operator(Operator op) { - return { SQLCommand::Unknown, SQLErrorCode::NumericOperatorTypeMismatch, BinaryOperator_name(op) }; + if constexpr (IsSame) + return { SQLCommand::Unknown, SQLErrorCode::NumericOperatorTypeMismatch, BinaryOperator_name(op) }; + else if constexpr (IsSame) + return { SQLCommand::Unknown, SQLErrorCode::NumericOperatorTypeMismatch, UnaryOperator_name(op) }; + else + static_assert(DependentFalse); } ResultOr Value::add(Value const& other) const { - if (auto double_maybe = to_double(); double_maybe.has_value()) { - if (auto other_double_maybe = other.to_double(); other_double_maybe.has_value()) - return Value(double_maybe.value() + other_double_maybe.value()); - if (auto int_maybe = other.to_int(); int_maybe.has_value()) - return Value(double_maybe.value() + (double)int_maybe.value()); - } else if (auto int_maybe = to_int(); int_maybe.has_value()) { - if (auto other_double_maybe = other.to_double(); other_double_maybe.has_value()) - return Value(other_double_maybe.value() + (double)int_maybe.value()); - if (auto other_int_maybe = other.to_int(); other_int_maybe.has_value()) - return Value(int_maybe.value() + other_int_maybe.value()); + if (is_int() && other.is_int()) { + return perform_integer_operation(*this, other, [](auto lhs, auto rhs) -> ResultOr { + Checked result { lhs }; + result.add(rhs); + + if (result.has_overflow()) + return Result { SQLCommand::Unknown, SQLErrorCode::IntegerOverflow }; + return Value { result.value_unchecked() }; + }); } - return invalid_type_for_numeric_operator(AST::BinaryOperator::Plus); + + auto lhs = to_double(); + auto rhs = other.to_double(); + + if (!lhs.has_value() || !rhs.has_value()) + return invalid_type_for_numeric_operator(AST::BinaryOperator::Plus); + return Value { lhs.value() + rhs.value() }; } ResultOr Value::subtract(Value const& other) const { - if (auto double_maybe = to_double(); double_maybe.has_value()) { - if (auto other_double_maybe = other.to_double(); other_double_maybe.has_value()) - return Value(double_maybe.value() - other_double_maybe.value()); - if (auto int_maybe = other.to_int(); int_maybe.has_value()) - return Value(double_maybe.value() - (double)int_maybe.value()); - } else if (auto int_maybe = to_int(); int_maybe.has_value()) { - if (auto other_double_maybe = other.to_double(); other_double_maybe.has_value()) - return Value((double)int_maybe.value() - other_double_maybe.value()); - if (auto other_int_maybe = other.to_int(); other_int_maybe.has_value()) - return Value(int_maybe.value() - other_int_maybe.value()); + if (is_int() && other.is_int()) { + return perform_integer_operation(*this, other, [](auto lhs, auto rhs) -> ResultOr { + Checked result { lhs }; + result.sub(rhs); + + if (result.has_overflow()) + return Result { SQLCommand::Unknown, SQLErrorCode::IntegerOverflow }; + return Value { result.value_unchecked() }; + }); } - return invalid_type_for_numeric_operator(AST::BinaryOperator::Minus); + + auto lhs = to_double(); + auto rhs = other.to_double(); + + if (!lhs.has_value() || !rhs.has_value()) + return invalid_type_for_numeric_operator(AST::BinaryOperator::Minus); + return Value { lhs.value() - rhs.value() }; } ResultOr Value::multiply(Value const& other) const { - if (auto double_maybe = to_double(); double_maybe.has_value()) { - if (auto other_double_maybe = other.to_double(); other_double_maybe.has_value()) - return Value(double_maybe.value() * other_double_maybe.value()); - if (auto int_maybe = other.to_int(); int_maybe.has_value()) - return Value(double_maybe.value() * (double)int_maybe.value()); - } else if (auto int_maybe = to_int(); int_maybe.has_value()) { - if (auto other_double_maybe = other.to_double(); other_double_maybe.has_value()) - return Value((double)int_maybe.value() * other_double_maybe.value()); - if (auto other_int_maybe = other.to_int(); other_int_maybe.has_value()) - return Value(int_maybe.value() * other_int_maybe.value()); + if (is_int() && other.is_int()) { + return perform_integer_operation(*this, other, [](auto lhs, auto rhs) -> ResultOr { + Checked result { lhs }; + result.mul(rhs); + + if (result.has_overflow()) + return Result { SQLCommand::Unknown, SQLErrorCode::IntegerOverflow }; + return Value { result.value_unchecked() }; + }); } - return invalid_type_for_numeric_operator(AST::BinaryOperator::Multiplication); + + auto lhs = to_double(); + auto rhs = other.to_double(); + + if (!lhs.has_value() || !rhs.has_value()) + return invalid_type_for_numeric_operator(AST::BinaryOperator::Multiplication); + return Value { lhs.value() * rhs.value() }; } ResultOr Value::divide(Value const& other) const { - if (auto double_maybe = to_double(); double_maybe.has_value()) { - if (auto other_double_maybe = other.to_double(); other_double_maybe.has_value()) - return Value(double_maybe.value() / other_double_maybe.value()); - if (auto int_maybe = other.to_int(); int_maybe.has_value()) - return Value(double_maybe.value() / (double)int_maybe.value()); - } else if (auto int_maybe = to_int(); int_maybe.has_value()) { - if (auto other_double_maybe = other.to_double(); other_double_maybe.has_value()) - return Value((double)int_maybe.value() / other_double_maybe.value()); - if (auto other_int_maybe = other.to_int(); other_int_maybe.has_value()) - return Value(int_maybe.value() / other_int_maybe.value()); - } - return invalid_type_for_numeric_operator(AST::BinaryOperator::Division); + auto lhs = to_double(); + auto rhs = other.to_double(); + + if (!lhs.has_value() || !rhs.has_value()) + return invalid_type_for_numeric_operator(AST::BinaryOperator::Division); + if (rhs == 0.0) + return Result { SQLCommand::Unknown, SQLErrorCode::IntegerOverflow }; + + return Value { lhs.value() / rhs.value() }; } ResultOr Value::modulo(Value const& other) const { - auto int_maybe_1 = to_int(); - auto int_maybe_2 = other.to_int(); - if (!int_maybe_1.has_value() || !int_maybe_2.has_value()) + if (!is_int() || !other.is_int()) return invalid_type_for_numeric_operator(AST::BinaryOperator::Modulo); - return Value(int_maybe_1.value() % int_maybe_2.value()); + + return perform_integer_operation(*this, other, [](auto lhs, auto rhs) -> ResultOr { + Checked result { lhs }; + result.mod(rhs); + + if (result.has_overflow()) + return Result { SQLCommand::Unknown, SQLErrorCode::IntegerOverflow }; + return Value { result.value_unchecked() }; + }); +} + +ResultOr Value::negate() const +{ + if (type() == SQLType::Integer) { + auto value = to_int(); + if (!value.has_value()) + return invalid_type_for_numeric_operator(AST::UnaryOperator::Minus); + + return Value { value.value() * -1 }; + } + + if (type() == SQLType::Float) + return Value { -to_double().value() }; + + return invalid_type_for_numeric_operator(AST::UnaryOperator::Minus); } ResultOr Value::shift_left(Value const& other) const { - auto u32_maybe = to_u32(); - auto num_bytes_maybe = other.to_int(); - if (!u32_maybe.has_value() || !num_bytes_maybe.has_value()) + if (!is_int() || !other.is_int()) return invalid_type_for_numeric_operator(AST::BinaryOperator::ShiftLeft); - return Value(u32_maybe.value() << num_bytes_maybe.value()); + + return perform_integer_operation(*this, other, [](auto lhs, auto rhs) -> ResultOr { + using LHS = decltype(lhs); + using RHS = decltype(rhs); + + static constexpr auto max_shift = static_cast(sizeof(LHS) * 8); + if (rhs < 0 || rhs >= max_shift) + return Result { SQLCommand::Unknown, SQLErrorCode::IntegerOverflow }; + + return Value { lhs << rhs }; + }); } ResultOr Value::shift_right(Value const& other) const { - auto u32_maybe = to_u32(); - auto num_bytes_maybe = other.to_int(); - if (!u32_maybe.has_value() || !num_bytes_maybe.has_value()) + if (!is_int() || !other.is_int()) return invalid_type_for_numeric_operator(AST::BinaryOperator::ShiftRight); - return Value(u32_maybe.value() >> num_bytes_maybe.value()); + + return perform_integer_operation(*this, other, [](auto lhs, auto rhs) -> ResultOr { + using LHS = decltype(lhs); + using RHS = decltype(rhs); + + static constexpr auto max_shift = static_cast(sizeof(LHS) * 8); + if (rhs < 0 || rhs >= max_shift) + return Result { SQLCommand::Unknown, SQLErrorCode::IntegerOverflow }; + + return Value { lhs >> rhs }; + }); } ResultOr Value::bitwise_or(Value const& other) const { - auto u32_maybe_1 = to_u32(); - auto u32_maybe_2 = other.to_u32(); - if (!u32_maybe_1.has_value() || !u32_maybe_2.has_value()) + if (!is_int() || !other.is_int()) return invalid_type_for_numeric_operator(AST::BinaryOperator::BitwiseOr); - return Value(u32_maybe_1.value() | u32_maybe_2.value()); + + return perform_integer_operation(*this, other, [](auto lhs, auto rhs) { + return Value { lhs | rhs }; + }); } ResultOr Value::bitwise_and(Value const& other) const { - auto u32_maybe_1 = to_u32(); - auto u32_maybe_2 = other.to_u32(); - if (!u32_maybe_1.has_value() || !u32_maybe_2.has_value()) + if (!is_int() || !other.is_int()) return invalid_type_for_numeric_operator(AST::BinaryOperator::BitwiseAnd); - return Value(u32_maybe_1.value() & u32_maybe_2.value()); + + return perform_integer_operation(*this, other, [](auto lhs, auto rhs) { + return Value { lhs & rhs }; + }); } -static constexpr auto sql_type_null_as_flag = static_cast(SQLType::Null); +ResultOr Value::bitwise_not() const +{ + if (!is_int()) + return invalid_type_for_numeric_operator(AST::UnaryOperator::BitwiseNot); + + return downsize_integer(*this, [](auto value, auto) { + return Value { ~value }; + }); +} + +static u8 encode_type_flags(Value const& value) +{ + auto type_flags = to_underlying(value.type()); + + if (value.is_null()) { + type_flags |= to_underlying(TypeData::Null); + } else if (value.is_int()) { + downsize_integer(value, [&](auto, auto type_data) { + type_flags |= to_underlying(type_data); + }); + } + + return type_flags; +} void Value::serialize(Serializer& serializer) const { - auto type_flags = static_cast(type()); - if (is_null()) - type_flags |= sql_type_null_as_flag; - + auto type_flags = encode_type_flags(*this); serializer.serialize(type_flags); if (is_null()) return; + if (is_int()) { + downsize_integer(*this, [&](auto integer, auto) { + serializer.serialize(integer); + }); + return; + } + m_value->visit( [&](TupleValue const& value) { serializer.serialize(*value.descriptor); @@ -608,16 +735,11 @@ void Value::serialize(Serializer& serializer) const void Value::deserialize(Serializer& serializer) { auto type_flags = serializer.deserialize(); - bool has_value = true; - if ((type_flags & sql_type_null_as_flag) && (type_flags != sql_type_null_as_flag)) { - type_flags &= ~sql_type_null_as_flag; - has_value = false; - } + auto type_data = static_cast(type_flags & 0xf0); + m_type = static_cast(type_flags & 0x0f); - m_type = static_cast(type_flags); - - if (!has_value) + if (type_data == TypeData::Null) return; switch (m_type) { @@ -628,7 +750,35 @@ void Value::deserialize(Serializer& serializer) m_value = serializer.deserialize(); break; case SQLType::Integer: - m_value = serializer.deserialize(0); + switch (type_data) { + case TypeData::Int8: + m_value = static_cast(serializer.deserialize(0)); + break; + case TypeData::Int16: + m_value = static_cast(serializer.deserialize(0)); + break; + case TypeData::Int32: + m_value = static_cast(serializer.deserialize(0)); + break; + case TypeData::Int64: + m_value = static_cast(serializer.deserialize(0)); + break; + case TypeData::Uint8: + m_value = static_cast(serializer.deserialize(0)); + break; + case TypeData::Uint16: + m_value = static_cast(serializer.deserialize(0)); + break; + case TypeData::Uint32: + m_value = static_cast(serializer.deserialize(0)); + break; + case TypeData::Uint64: + m_value = static_cast(serializer.deserialize(0)); + break; + default: + VERIFY_NOT_REACHED(); + break; + } break; case SQLType::Float: m_value = serializer.deserialize(0.0); @@ -673,11 +823,9 @@ ResultOr> Value::infer_tuple_descriptor(Vector bool IPC::encode(Encoder& encoder, SQL::Value const& value) { - auto type_flags = to_underlying(value.type()); - if (value.is_null()) - type_flags |= SQL::sql_type_null_as_flag; - + auto type_flags = encode_type_flags(value); encoder << type_flags; + if (value.is_null()) return true; @@ -688,7 +836,9 @@ bool IPC::encode(Encoder& encoder, SQL::Value const& value) encoder << value.to_deprecated_string(); break; case SQL::SQLType::Integer: - encoder << value.to_int().value(); + SQL::downsize_integer(value, [&](auto integer, auto) { + encoder << integer; + }); break; case SQL::SQLType::Float: encoder << value.to_double().value(); @@ -704,46 +854,72 @@ bool IPC::encode(Encoder& encoder, SQL::Value const& value) return true; } +template +static ErrorOr decode_scalar(IPC::Decoder& decoder, SQL::Value& value) +{ + T decoded {}; + TRY(decoder.decode(decoded)); + value = move(decoded); + return {}; +} + template<> ErrorOr IPC::decode(Decoder& decoder, SQL::Value& value) { - UnderlyingType type_flags; + u8 type_flags { 0 }; TRY(decoder.decode(type_flags)); - if ((type_flags & SQL::sql_type_null_as_flag) && (type_flags != SQL::sql_type_null_as_flag)) { - type_flags &= ~SQL::sql_type_null_as_flag; + auto type_data = static_cast(type_flags & 0xf0); + auto type = static_cast(type_flags & 0x0f); - value = SQL::Value(static_cast(type_flags)); + if (type_data == SQL::TypeData::Null) { + value = SQL::Value(type); return {}; } - switch (static_cast(type_flags)) { + switch (type) { case SQL::SQLType::Null: break; - case SQL::SQLType::Text: { - DeprecatedString text; - TRY(decoder.decode(text)); - value = move(text); + case SQL::SQLType::Text: + TRY(decode_scalar(decoder, value)); break; - } - case SQL::SQLType::Integer: { - int number { 0 }; - TRY(decoder.decode(number)); - value = number; + case SQL::SQLType::Integer: + switch (type_data) { + case SQL::TypeData::Int8: + TRY(decode_scalar(decoder, value)); + break; + case SQL::TypeData::Int16: + TRY(decode_scalar(decoder, value)); + break; + case SQL::TypeData::Int32: + TRY(decode_scalar(decoder, value)); + break; + case SQL::TypeData::Int64: + TRY(decode_scalar(decoder, value)); + break; + case SQL::TypeData::Uint8: + TRY(decode_scalar(decoder, value)); + break; + case SQL::TypeData::Uint16: + TRY(decode_scalar(decoder, value)); + break; + case SQL::TypeData::Uint32: + TRY(decode_scalar(decoder, value)); + break; + case SQL::TypeData::Uint64: + TRY(decode_scalar(decoder, value)); + break; + default: + VERIFY_NOT_REACHED(); + break; + } break; - } - case SQL::SQLType::Float: { - double number { 0.0 }; - TRY(decoder.decode(number)); - value = number; + case SQL::SQLType::Float: + TRY(decode_scalar(decoder, value)); break; - } - case SQL::SQLType::Boolean: { - bool boolean { false }; - TRY(decoder.decode(boolean)); - value = boolean; + case SQL::SQLType::Boolean: + TRY(decode_scalar(decoder, value)); break; - } case SQL::SQLType::Tuple: { Vector tuple; TRY(decoder.decode(tuple)); diff --git a/Userland/Libraries/LibSQL/Value.h b/Userland/Libraries/LibSQL/Value.h index 83d2ae03f93..e7568192b6e 100644 --- a/Userland/Libraries/LibSQL/Value.h +++ b/Userland/Libraries/LibSQL/Value.h @@ -7,6 +7,7 @@ #pragma once +#include #include #include #include @@ -17,58 +18,107 @@ #include #include #include +#include namespace SQL { +template +concept Boolean = SameAs, bool>; + +template +concept Integer = (Integral && !Boolean); + /** * A `Value` is an atomic piece of SQL data`. A `Value` has a basic type * (Text/String, Integer, Float, etc). Richer types are implemented in higher * level layers, but the resulting data is stored in these `Value` objects. */ class Value { + template + using IntegerType = Conditional, i64, u64>; + public: explicit Value(SQLType sql_type = SQLType::Null); explicit Value(DeprecatedString); - explicit Value(int); - explicit Value(u32); explicit Value(double); Value(Value const&); Value(Value&&); ~Value(); - static ResultOr create_tuple(NonnullRefPtr); - static ResultOr create_tuple(Vector); + explicit Value(Integer auto value) + : m_type(SQLType::Integer) + , m_value(static_cast>(value)) + { + } - template - requires(SameAs, bool>) explicit Value(T value) + explicit Value(Boolean auto value) : m_type(SQLType::Boolean) , m_value(value) { } + static ResultOr create_tuple(NonnullRefPtr); + static ResultOr create_tuple(Vector); + [[nodiscard]] SQLType type() const; [[nodiscard]] StringView type_name() const; [[nodiscard]] bool is_type_compatible_with(SQLType) const; [[nodiscard]] bool is_null() const; + [[nodiscard]] bool is_int() const; + + [[nodiscard]] auto const& value() const + { + VERIFY(m_value.has_value()); + return *m_value; + } [[nodiscard]] DeprecatedString to_deprecated_string() const; - [[nodiscard]] Optional to_int() const; - [[nodiscard]] Optional to_u32() const; [[nodiscard]] Optional to_double() const; [[nodiscard]] Optional to_bool() const; [[nodiscard]] Optional> to_vector() const; + template + [[nodiscard]] Optional to_int() const + { + if (is_null()) + return {}; + + return m_value->visit( + [](DeprecatedString const& value) -> Optional { + if constexpr (IsSigned) + return value.to_int(); + else + return value.to_uint(); + }, + [](Integer auto value) -> Optional { + if (!AK::is_within_range(value)) + return {}; + return static_cast(value); + }, + [](double value) -> Optional { + if (!AK::is_within_range(value)) + return {}; + return static_cast(round(value)); + }, + [](bool value) -> Optional { return static_cast(value); }, + [](TupleValue const&) -> Optional { return {}; }); + } + Value& operator=(Value); Value& operator=(DeprecatedString); - Value& operator=(int); - Value& operator=(u32); Value& operator=(double); + Value& operator=(Integer auto value) + { + m_type = SQLType::Integer; + m_value = static_cast>(value); + return *this; + } + ResultOr assign_tuple(NonnullRefPtr); ResultOr assign_tuple(Vector); - template - requires(SameAs, bool>) Value& operator=(T value) + Value& operator=(Boolean auto value) { m_type = SQLType::Boolean; m_value = value; @@ -83,9 +133,14 @@ public: [[nodiscard]] int compare(Value const&) const; bool operator==(Value const&) const; bool operator==(StringView) const; - bool operator==(int) const; - bool operator==(u32) const; bool operator==(double) const; + + template + bool operator==(T value) + { + return to_int() == value; + } + bool operator!=(Value const&) const; bool operator<(Value const&) const; bool operator<=(Value const&) const; @@ -97,10 +152,12 @@ public: ResultOr multiply(Value const&) const; ResultOr divide(Value const&) const; ResultOr modulo(Value const&) const; + ResultOr negate() const; ResultOr shift_left(Value const&) const; ResultOr shift_right(Value const&) const; ResultOr bitwise_or(Value const&) const; ResultOr bitwise_and(Value const&) const; + ResultOr bitwise_not() const; [[nodiscard]] TupleElementDescriptor descriptor() const; @@ -112,7 +169,7 @@ private: Vector values; }; - using ValueType = Variant; + using ValueType = Variant; static ResultOr> infer_tuple_descriptor(Vector const& values); Value(NonnullRefPtr descriptor, Vector values);