From b74721e604c2c8b82c6df7ee7e3411e5d10d81bd Mon Sep 17 00:00:00 2001 From: Jan de Visser Date: Sat, 17 Jul 2021 07:02:28 -0400 Subject: [PATCH] LibSQL: Redesign Value implementation and add new types The implemtation of the Value class was based on lambda member variables implementing type-dependent behaviour. This was done to ensure that Values can be used as stack-only objects; the simplest alternative, virtual methods, forces them onto the heap. The problem with the the lambda approach is that it bloats the Values (which are supposed to be lightweight objects) quite considerably, because every object contains more than a dozen function pointers. The solution to address both problems (we want Values to be able to live on the stack and be as lightweight as possible) chosen here is to encapsulate type-dependent behaviour and state in an implementation class, and let the Value be an AK::Variant of those implementation classes. All methods of Value are now basically straight delegates to the implementation object using the Variant::visit method. One issue complicating matters is the addition of two aggregate types, Tuple and Array, which each contain a Vector of Values. At this point Tuples and Arrays (and potential future aggregate types) can't contain these aggregate types. This is limiting and needs to be addressed. Another area that needs attention is the nomenclature of things; it's a bit of a tangle of 'ValueBlahBlah' and 'ImplBlahBlah'. It makes sense right now I think but admit we probably can do better. Other things included here: - Added the Boolean and Null types (and Tuple and Array, see above). - to_string now always succeeds and returns a String instead of an Optional. This had some impact on other sources. - Added a lot of tests. - Started moving the serialization mechanism more towards where I want it to be, i.e. a 'DataSerializer' object which just takes serialization and deserialization requests and knows for example how to store long strings out-of-line. One last remark: There is obviously a naming clash between the Tuple class and the Tuple Value type. This is intentional; I plan to make the Tuple class a subclass of Value (and hence Key and Row as well). --- Tests/LibSQL/TestSqlDatabase.cpp | 2 +- Tests/LibSQL/TestSqlValueAndTuple.cpp | 425 ++++++- Userland/Libraries/LibSQL/Meta.cpp | 2 +- Userland/Libraries/LibSQL/Serialize.h | 61 +- Userland/Libraries/LibSQL/Tuple.cpp | 13 +- Userland/Libraries/LibSQL/TupleDescriptor.h | 16 + Userland/Libraries/LibSQL/Type.h | 26 +- Userland/Libraries/LibSQL/Value.cpp | 1180 ++++++++++++++----- Userland/Libraries/LibSQL/Value.h | 427 +++++-- 9 files changed, 1724 insertions(+), 428 deletions(-) diff --git a/Tests/LibSQL/TestSqlDatabase.cpp b/Tests/LibSQL/TestSqlDatabase.cpp index a63187ac667..9c67267c5e1 100644 --- a/Tests/LibSQL/TestSqlDatabase.cpp +++ b/Tests/LibSQL/TestSqlDatabase.cpp @@ -66,7 +66,7 @@ void verify_table_contents(SQL::Database& db, int expected_count) for (auto& row : db.select_all(*table)) { StringBuilder builder; builder.appendff("Test{}", row["IntColumn"].to_int().value()); - EXPECT_EQ(row["TextColumn"].to_string().value(), builder.build()); + EXPECT_EQ(row["TextColumn"].to_string(), builder.build()); count++; sum += row["IntColumn"].to_int().value(); } diff --git a/Tests/LibSQL/TestSqlValueAndTuple.cpp b/Tests/LibSQL/TestSqlValueAndTuple.cpp index 2c3d6e25d35..cbedb15836f 100644 --- a/Tests/LibSQL/TestSqlValueAndTuple.cpp +++ b/Tests/LibSQL/TestSqlValueAndTuple.cpp @@ -12,76 +12,203 @@ #include #include -TEST_CASE(text_value) +TEST_CASE(null_value) { - SQL::Value v(SQL::SQLType::Text); + SQL::Value v(SQL::SQLType::Null); + EXPECT(v.type() == SQL::SQLType::Null); + EXPECT(v.is_null()); v = "Test"; - VERIFY(v.to_string().value() == "Test"); + EXPECT(v.is_null()); + EXPECT(v.to_string() == "(null)"); } -TEST_CASE(text_value_to_int) +TEST_CASE(text_value) { - SQL::Value v(SQL::SQLType::Text); - v = "42"; - EXPECT_EQ(v.to_int().value(), 42); + { + SQL::Value v(SQL::SQLType::Text); + EXPECT(v.is_null()); + v = "Test"; + EXPECT(!v.is_null()); + EXPECT(v.to_string() == "Test"); + } + { + SQL::Value v(SQL::SQLType::Text, String("String Test")); + EXPECT(!v.is_null()); + EXPECT(v.to_string() == "String Test"); + } + { + SQL::Value v(SQL::SQLType::Text, "const char * Test"); + EXPECT(!v.is_null()); + EXPECT_EQ(v.to_string(), "const char * Test"); + } + { + SQL::Value v(String("String Test")); + EXPECT(v.type() == SQL::SQLType::Text); + EXPECT(!v.is_null()); + EXPECT(v.to_string() == "String Test"); + } + { + SQL::Value v(SQL::SQLType::Text, SQL::Value(42)); + EXPECT(v.type() == SQL::SQLType::Text); + EXPECT(!v.is_null()); + EXPECT(v.to_string() == "42"); + } +} + +TEST_CASE(assign_null) +{ + SQL::Value v("Test"); + EXPECT(!v.is_null()); + v = SQL::Value::null(); + EXPECT(v.is_null()); +} + +TEST_CASE(text_value_to_other_types) +{ + { + SQL::Value v(SQL::SQLType::Text, "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()); + } + { + SQL::Value v("true"); + EXPECT(v.to_bool().has_value()); + EXPECT(v.to_bool().value()); + } + { + SQL::Value v("false"); + EXPECT(v.to_bool().has_value()); + EXPECT(!v.to_bool().value()); + } } TEST_CASE(text_value_to_int_crash) { - SQL::Value v(SQL::SQLType::Text); - v = "Test"; - EXPECT_CRASH("Can't convert 'Test' to integer", [&]() { (void) (int) v; return Test::Crash::Failure::DidNotCrash; }); + SQL::Value v(SQL::SQLType::Text, "Not a valid integer"); + EXPECT_CRASH("Can't convert 'Not a valid integer' to integer", [&]() { (void) (int) v; return Test::Crash::Failure::DidNotCrash; }); } TEST_CASE(serialize_text_value) { - SQL::Value v(SQL::SQLType::Text); - v = "Test"; - VERIFY(v.to_string().value() == "Test"); + SQL::Value v("Test"); + EXPECT(v.to_string() == "Test"); ByteBuffer buffer; - v.serialize(buffer); + v.serialize_to(buffer); size_t offset = 0; - SQL::Value v2(SQL::SQLType::Text, buffer, offset); - VERIFY((String)v2 == "Test"); + auto v2 = SQL::Value::deserialize_from(buffer, offset); + EXPECT((String)v2 == "Test"); } TEST_CASE(integer_value) { - SQL::Value v(SQL::SQLType::Integer); - v = 42; - VERIFY(v.to_int().value() == 42); + { + SQL::Value v(SQL::SQLType::Integer); + EXPECT(v.is_null()); + v = 42; + EXPECT(!v.is_null()); + EXPECT(v.to_int().value() == 42); + EXPECT(v.to_string() == "42"); + EXPECT(v.to_double().has_value()); + EXPECT(v.to_double().value() - 42.0 < NumericLimits().epsilon()); + EXPECT(v.to_bool().has_value()); + EXPECT(v.to_bool().value()); + } + { + SQL::Value v(0); + EXPECT(!v.is_null()); + EXPECT(v.to_int().value() == 0); + EXPECT(v.to_bool().has_value()); + EXPECT(!v.to_bool().value()); + } + { + SQL::Value v(SQL::SQLType::Integer, "42"); + EXPECT_EQ(v.to_int().value(), 42); + } + { + SQL::Value v(SQL::SQLType::Integer, SQL::Value("42")); + EXPECT_EQ(v.to_int().value(), 42); + } + { + SQL::Value text("42"); + SQL::Value integer(SQL::SQLType::Integer); + integer = text; + EXPECT_EQ(integer.to_int().value(), 42); + } } TEST_CASE(serialize_int_value) { - SQL::Value v(SQL::SQLType::Text); - v = 42; - VERIFY(v.to_int().value() == 42); + SQL::Value v(42); + EXPECT_EQ(v.type(), SQL::SQLType::Integer); + EXPECT_EQ(v.to_int().value(), 42); ByteBuffer buffer; - v.serialize(buffer); + v.serialize_to(buffer); size_t offset = 0; - SQL::Value v2(SQL::SQLType::Text, buffer, offset); - VERIFY(v2 == v); + auto v2 = SQL::Value::deserialize_from(buffer, offset); + EXPECT(!v2.is_null()); + EXPECT_EQ(v2.type(), SQL::SQLType::Integer); + EXPECT_EQ(v2.to_int().value(), 42); + EXPECT(v2 == v); } TEST_CASE(float_value) { - SQL::Value v(SQL::SQLType::Float); - v = 3.14; - VERIFY(v.to_double().value() - 3.14 < 0.001); + { + SQL::Value v(SQL::SQLType::Float); + EXPECT(v.is_null()); + v = 3.14; + EXPECT(!v.is_null()); + 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_EQ(v.to_string(), "3.14"); + EXPECT(!v.to_bool().has_value()); + } + { + SQL::Value v(3.14); + EXPECT(!v.is_null()); + EXPECT(v.to_double().value() - 3.14 < NumericLimits().epsilon()); + } + { + SQL::Value v(3.51); + EXPECT(!v.is_null()); + EXPECT_EQ(v.to_int().value(), 4); + } + { + SQL::Value v(-3.14); + EXPECT_EQ(v.to_int().value(), -3); + } + { + SQL::Value v(-3.51); + EXPECT_EQ(v.to_int().value(), -4); + } + { + SQL::Value v(SQL::SQLType::Float, "3.14"); + EXPECT(v.to_double().value() - 3.14 < NumericLimits().epsilon()); + } } -TEST_CASE(assign_text_value_to_int) +TEST_CASE(serialize_float_value) { - SQL::Value text(SQL::SQLType::Text); - text = "42"; - SQL::Value integer(SQL::SQLType::Integer); - integer = text; - EXPECT_EQ(integer.to_int().value(), 42); + SQL::Value v(3.14); + EXPECT_EQ(v.type(), SQL::SQLType::Float); + EXPECT(v.to_double().value() - 3.14 < NumericLimits().epsilon()); + + ByteBuffer buffer; + v.serialize_to(buffer); + + size_t offset = 0; + auto v2 = SQL::Value::deserialize_from(buffer, offset); + EXPECT(!v2.is_null()); + EXPECT_EQ(v2.type(), SQL::SQLType::Float); + EXPECT(v.to_double().value() - 3.14 < NumericLimits().epsilon()); } TEST_CASE(assign_int_to_text_value) @@ -93,8 +220,7 @@ TEST_CASE(assign_int_to_text_value) TEST_CASE(copy_value) { - SQL::Value text(SQL::SQLType::Text); - text = 42; + SQL::Value text(SQL::SQLType::Text, 42); SQL::Value copy(text); EXPECT_EQ((String)copy, "42"); } @@ -109,6 +235,221 @@ TEST_CASE(compare_text_to_int) EXPECT(integer == text); } +TEST_CASE(bool_value) +{ + { + SQL::Value v(SQL::SQLType::Boolean); + EXPECT(v.is_null()); + v = true; + EXPECT(!v.is_null()); + EXPECT(v.to_bool().has_value()); + EXPECT(v.to_bool().value()); + EXPECT_EQ(v.to_int().value(), 1); + EXPECT_EQ(v.to_string(), "true"); + EXPECT(!v.to_double().has_value()); + } + { + SQL::Value v(SQL::SQLType::Boolean, false); + EXPECT(!v.is_null()); + EXPECT(v.to_bool().has_value()); + EXPECT(!v.to_bool().value()); + EXPECT_EQ(v.to_int().value(), 0); + EXPECT_EQ(v.to_string(), "false"); + EXPECT(!v.to_double().has_value()); + } + { + SQL::Value v(true); + EXPECT_EQ(v.type(), SQL::SQLType::Boolean); + EXPECT(!v.is_null()); + EXPECT(v.to_bool().has_value()); + EXPECT(v.to_bool().value()); + } +} + +TEST_CASE(serialize_boolean_value) +{ + SQL::Value v(true); + EXPECT_EQ(v.type(), SQL::SQLType::Boolean); + EXPECT(bool(v)); + + ByteBuffer buffer; + v.serialize_to(buffer); + + size_t offset = 0; + auto v2 = SQL::Value::deserialize_from(buffer, offset); + EXPECT(!v2.is_null()); + EXPECT_EQ(v2.type(), SQL::SQLType::Boolean); + EXPECT(bool(v2)); + EXPECT_EQ(v, v2); +} + +TEST_CASE(tuple_value) +{ + NonnullRefPtr descriptor = adopt_ref(*new SQL::TupleDescriptor); + descriptor->append({ "col1", SQL::SQLType::Text, SQL::Order::Ascending }); + descriptor->append({ "col2", SQL::SQLType::Integer, SQL::Order::Descending }); + + auto v = SQL::Value::create_tuple(descriptor); + Vector values; + values.append(SQL::Value("Test")); + values.append(SQL::Value(42)); + v = values; + + auto values2 = v.to_vector(); + EXPECT(values2.has_value()); + EXPECT_EQ(values, values2.value()); +} + +TEST_CASE(copy_tuple_value) +{ + NonnullRefPtr descriptor = adopt_ref(*new SQL::TupleDescriptor); + descriptor->append({ "col1", SQL::SQLType::Text, SQL::Order::Ascending }); + descriptor->append({ "col2", SQL::SQLType::Integer, SQL::Order::Descending }); + + auto v = SQL::Value::create_tuple(descriptor); + Vector values; + values.append(SQL::Value("Test")); + values.append(SQL::Value(42)); + v = values; + + auto values2 = v; + EXPECT(values2.type() == v.type()); + EXPECT(!values2.is_null()); + EXPECT_EQ(values, values2.to_vector().value()); +} + +TEST_CASE(tuple_value_wrong_type) +{ + NonnullRefPtr descriptor = adopt_ref(*new SQL::TupleDescriptor); + descriptor->append({ "col1", SQL::SQLType::Text, SQL::Order::Ascending }); + + auto v = SQL::Value::create_tuple(descriptor); + Vector values; + values.append(SQL::Value(42)); + v = values; + EXPECT(v.is_null()); +} + +TEST_CASE(tuple_value_too_many_values) +{ + NonnullRefPtr descriptor = adopt_ref(*new SQL::TupleDescriptor); + descriptor->append({ "col1", SQL::SQLType::Text, SQL::Order::Ascending }); + + auto v = SQL::Value::create_tuple(descriptor); + Vector values; + values.append(SQL::Value("Test")); + values.append(SQL::Value(42)); + v = values; + EXPECT(v.is_null()); +} + +TEST_CASE(tuple_value_not_enough_values) +{ + NonnullRefPtr descriptor = adopt_ref(*new SQL::TupleDescriptor); + descriptor->append({ "col1", SQL::SQLType::Text, SQL::Order::Ascending }); + descriptor->append({ "col2", SQL::SQLType::Integer, SQL::Order::Ascending }); + + auto v = SQL::Value::create_tuple(descriptor); + Vector values; + values.append(SQL::Value("Test")); + v = values; + EXPECT(!v.is_null()); + auto values_opt = v.to_vector(); + EXPECT(values_opt.has_value()); + EXPECT_EQ(values_opt.value().size(), 2u); + auto col2 = values_opt.value()[1]; + EXPECT_EQ(col2.type(), SQL::SQLType::Integer); + EXPECT(col2.is_null()); +} + +TEST_CASE(serialize_tuple_value) +{ + NonnullRefPtr descriptor = adopt_ref(*new SQL::TupleDescriptor); + descriptor->append({ "col1", SQL::SQLType::Text, SQL::Order::Ascending }); + descriptor->append({ "col2", SQL::SQLType::Integer, SQL::Order::Descending }); + + auto v = SQL::Value::create_tuple(descriptor); + Vector values; + values.append(SQL::Value("Test")); + values.append(SQL::Value(42)); + v = values; + + ByteBuffer buffer; + v.serialize_to(buffer); + + size_t offset = 0; + auto v2 = SQL::Value::deserialize_from(buffer, offset); + EXPECT(!v2.is_null()); + EXPECT_EQ(v2.type(), SQL::SQLType::Tuple); + EXPECT_EQ(v, v2); +} + +TEST_CASE(array_value) +{ + auto v = SQL::Value::create_array(SQL::SQLType::Text, 3); + Vector values; + values.append(SQL::Value("Test 1")); + values.append(SQL::Value("Test 2")); + v = values; + + auto values2 = v.to_vector(); + EXPECT(values2.has_value()); + EXPECT_EQ(values, values2.value()); +} + +TEST_CASE(array_value_wrong_type) +{ + auto v = SQL::Value::create_array(SQL::SQLType::Text, 2); + Vector values; + values.append(SQL::Value("Test 1")); + values.append(SQL::Value(42)); + v = values; + EXPECT(v.is_null()); +} + +TEST_CASE(array_value_too_many_values) +{ + auto v = SQL::Value::create_array(SQL::SQLType::Text, 2); + Vector values; + values.append(SQL::Value("Test 1")); + values.append(SQL::Value("Test 2")); + values.append(SQL::Value("Test 3")); + v = values; + EXPECT(v.is_null()); +} + +TEST_CASE(copy_array_value) +{ + auto v = SQL::Value::create_array(SQL::SQLType::Text, 3); + Vector values; + values.append(SQL::Value("Test 1")); + values.append(SQL::Value("Test 2")); + v = values; + + auto values2 = v; + EXPECT(values2.type() == v.type()); + EXPECT(!values2.is_null()); + EXPECT_EQ(values, values2.to_vector().value()); +} + +TEST_CASE(serialize_array_value) +{ + auto v = SQL::Value::create_array(SQL::SQLType::Text, 3); + Vector values; + values.append(SQL::Value("Test 1")); + values.append(SQL::Value("Test 2")); + v = values; + + ByteBuffer buffer; + v.serialize_to(buffer); + + size_t offset = 0; + auto v2 = SQL::Value::deserialize_from(buffer, offset); + EXPECT(!v2.is_null()); + EXPECT_EQ(v2.type(), SQL::SQLType::Array); + EXPECT_EQ(v, v2); +} + TEST_CASE(order_text_values) { SQL::Value v1(SQL::SQLType::Text); @@ -142,8 +483,8 @@ TEST_CASE(tuple) tuple["col1"] = "Test"; tuple["col2"] = 42; - VERIFY(tuple[0] == "Test"); - VERIFY(tuple[1] == 42); + EXPECT(tuple[0] == "Test"); + EXPECT(tuple[1] == 42); } TEST_CASE(serialize_tuple) @@ -163,8 +504,8 @@ TEST_CASE(serialize_tuple) size_t offset = 0; SQL::Tuple tuple2(descriptor, buffer, offset); - VERIFY(tuple2[0] == "Test"); - VERIFY(tuple2[1] == 42); + EXPECT(tuple2[0] == "Test"); + EXPECT(tuple2[1] == 42); } TEST_CASE(copy_tuple) @@ -179,10 +520,10 @@ TEST_CASE(copy_tuple) SQL::Tuple copy; copy = tuple; - VERIFY(tuple == copy); + EXPECT(tuple == copy); SQL::Tuple copy_2(copy); - VERIFY(tuple == copy_2); + EXPECT(tuple == copy_2); } TEST_CASE(compare_tuples) diff --git a/Userland/Libraries/LibSQL/Meta.cpp b/Userland/Libraries/LibSQL/Meta.cpp index 9307624ec99..66d141f7529 100644 --- a/Userland/Libraries/LibSQL/Meta.cpp +++ b/Userland/Libraries/LibSQL/Meta.cpp @@ -16,7 +16,7 @@ SchemaDef::SchemaDef(String name) } SchemaDef::SchemaDef(Key const& key) - : Relation(key["schema_name"].to_string().value()) + : Relation(key["schema_name"].to_string()) { } diff --git a/Userland/Libraries/LibSQL/Serialize.h b/Userland/Libraries/LibSQL/Serialize.h index de9e027e7df..bc03590058f 100644 --- a/Userland/Libraries/LibSQL/Serialize.h +++ b/Userland/Libraries/LibSQL/Serialize.h @@ -7,22 +7,75 @@ #pragma once #include +#include +#include +#include #include namespace SQL { +inline void dump(u8 const* ptr, size_t sz, String const& prefix) +{ + StringBuilder builder; + builder.appendff("{0} {1:04x} | ", prefix, sz); + Vector bytes; + for (auto ix = 0u; ix < sz; ++ix) { + bytes.append(String::formatted("{0:02x}", *(ptr + ix))); + } + StringBuilder bytes_builder; + bytes_builder.join(" ", bytes); + builder.append(bytes_builder.to_string()); + dbgln(builder.to_string()); +} + +inline void write(ByteBuffer& buffer, u8 const* ptr, size_t sz) +{ + if constexpr (SQL_DEBUG) + dump(ptr, sz, "->"); + buffer.append(ptr, sz); +} + +inline u8* read(ByteBuffer& buffer, size_t& at_offset, size_t sz) +{ + auto buffer_ptr = buffer.offset_pointer((int)at_offset); + if constexpr (SQL_DEBUG) + dump(buffer_ptr, sz, "<-"); + at_offset += sz; + return buffer_ptr; +} + template void deserialize_from(ByteBuffer& buffer, size_t& at_offset, T& t) { - auto ptr = buffer.offset_pointer((int)at_offset); - memcpy(&t, ptr, sizeof(T)); - at_offset += sizeof(T); + memcpy(&t, read(buffer, at_offset, sizeof(T)), sizeof(T)); } template void serialize_to(ByteBuffer& buffer, T const& t) { - buffer.append(&t, sizeof(T)); + write(buffer, (u8 const*)(&t), sizeof(T)); +} + +template<> +inline void deserialize_from(ByteBuffer& buffer, size_t& at_offset, String& string) +{ + u32 length; + deserialize_from(buffer, at_offset, length); + if (length > 0) { + string = String((const char*)read(buffer, at_offset, length), length); + } else { + string = ""; + } +} + +template<> +inline void serialize_to(ByteBuffer& buffer, String const& t) +{ + u32 number_of_bytes = min(t.length(), 64); + serialize_to(buffer, number_of_bytes); + if (t.length() > 0) { + write(buffer, (u8 const*)(t.characters()), number_of_bytes); + } } } diff --git a/Userland/Libraries/LibSQL/Tuple.cpp b/Userland/Libraries/LibSQL/Tuple.cpp index fde324b9bb2..8706a07d50f 100644 --- a/Userland/Libraries/LibSQL/Tuple.cpp +++ b/Userland/Libraries/LibSQL/Tuple.cpp @@ -51,7 +51,7 @@ void Tuple::deserialize(ByteBuffer& buffer, size_t& offset) dbgln_if(SQL_DEBUG, "pointer: {}", m_pointer); m_data.clear(); for (auto& part : *m_descriptor) { - m_data.empend(part.type, buffer, offset); + m_data.append(Value::deserialize_from(buffer, offset)); dbgln_if(SQL_DEBUG, "Deserialized element {} = {}", part.name, m_data.last().to_string()); } } @@ -64,11 +64,11 @@ void Tuple::serialize(ByteBuffer& buffer) const for (auto ix = 0u; ix < m_descriptor->size(); ix++) { auto& key_part = m_data[ix]; if constexpr (SQL_DEBUG) { - auto str_opt = key_part.to_string(); + auto key_string = key_part.to_string(); auto& key_part_definition = (*m_descriptor)[ix]; - dbgln("Serialized part {} = {}", key_part_definition.name, (str_opt.has_value()) ? str_opt.value() : "(null)"); + dbgln("Serialized part {} = {}", key_part_definition.name, key_string); } - key_part.serialize(buffer); + key_part.serialize_to(buffer); } } @@ -165,8 +165,7 @@ String Tuple::to_string() const if (!builder.is_empty()) { builder.append('|'); } - auto str_opt = part.to_string(); - builder.append((str_opt.has_value()) ? str_opt.value() : "(null)"); + builder.append(part.to_string()); } if (pointer() != 0) { builder.appendff(":{}", pointer()); @@ -178,7 +177,7 @@ Vector Tuple::to_string_vector() const { Vector ret; for (auto& value : m_data) { - ret.append(value.to_string().value()); + ret.append(value.to_string()); } return ret; } diff --git a/Userland/Libraries/LibSQL/TupleDescriptor.h b/Userland/Libraries/LibSQL/TupleDescriptor.h index 6f6e7b16410..afb93a00f7c 100644 --- a/Userland/Libraries/LibSQL/TupleDescriptor.h +++ b/Userland/Libraries/LibSQL/TupleDescriptor.h @@ -34,6 +34,22 @@ public: } return sz; } + + [[nodiscard]] int compare_ignoring_names(TupleDescriptor const& other) const + { + if (size() != other.size()) + return (int)size() - (int)other.size(); + for (auto ix = 0u; ix < size(); ++ix) { + auto elem = (*this)[ix]; + auto other_elem = other[ix]; + if ((elem.type != other_elem.type) || (elem.order != other_elem.order)) { + return 1; + } + } + return 0; + } + + using Vector::operator==; }; } diff --git a/Userland/Libraries/LibSQL/Type.h b/Userland/Libraries/LibSQL/Type.h index 865276a1de8..fd4b32f21ef 100644 --- a/Userland/Libraries/LibSQL/Type.h +++ b/Userland/Libraries/LibSQL/Type.h @@ -12,17 +12,35 @@ namespace SQL { #define ENUMERATE_SQL_TYPES(S) \ - S("text", 0, Text, String, 64 + sizeof(int)) \ - S("int", 1, Integer, int, sizeof(int)) \ - S("float", 2, Float, double, sizeof(double)) + S("null", 1, Null, int, sizeof(int)) \ + S("text", 2, Text, String, 65 + sizeof(u32)) \ + S("int", 4, Integer, int, sizeof(int)) \ + S("float", 8, Float, double, sizeof(double)) \ + S("bool", 16, Boolean, bool, sizeof(bool)) \ + S("tuple", 32, Tuple, int, sizeof(int)) \ + S("array", 64, Array, int, sizeof(int)) enum class SQLType { #undef __ENUMERATE_SQL_TYPE -#define __ENUMERATE_SQL_TYPE(name, cardinal, type, impl, size) type = (cardinal), +#define __ENUMERATE_SQL_TYPE(name, cardinal, type, impl, size) type = cardinal, ENUMERATE_SQL_TYPES(__ENUMERATE_SQL_TYPE) #undef __ENUMERATE_SQL_TYPE }; +inline static String SQLType_name(SQLType t) +{ + switch (t) { +#undef __ENUMERATE_SQL_TYPE +#define __ENUMERATE_SQL_TYPE(name, cardinal, type, impl, size) \ + case SQLType::type: \ + return name; + ENUMERATE_SQL_TYPES(__ENUMERATE_SQL_TYPE) +#undef __ENUMERATE_SQL_TYPE + default: + VERIFY_NOT_REACHED(); + } +} + inline static size_t size_of(SQLType t) { switch (t) { diff --git a/Userland/Libraries/LibSQL/Value.cpp b/Userland/Libraries/LibSQL/Value.cpp index 122ad7c41bc..c415a130f12 100644 --- a/Userland/Libraries/LibSQL/Value.cpp +++ b/Userland/Libraries/LibSQL/Value.cpp @@ -4,112 +4,187 @@ * SPDX-License-Identifier: BSD-2-Clause */ +#include #include -#include +#include +#include namespace SQL { Value::Value(SQLType sql_type) - : m_impl(0) { setup(sql_type); } -Value::Value(SQLType sql_type, ByteBuffer& buffer, size_t& offset) - : m_impl(0) +void Value::setup(SQLType type) { - setup(sql_type); - m_deserialize(buffer, offset); - m_is_null = false; + switch (type) { +#undef __ENUMERATE_SQL_TYPE +#define __ENUMERATE_SQL_TYPE(name, cardinal, type, impl, size) \ + case SQLType::type: \ + m_impl.set(type##Impl()); \ + break; + ENUMERATE_SQL_TYPES(__ENUMERATE_SQL_TYPE) +#undef __ENUMERATE_SQL_TYPE + default: + VERIFY_NOT_REACHED(); + } } -Value::Value(Value const& other) - : m_impl(0) +Value::Value(SQLType sql_type, Value const& value) + : Value(sql_type) { - setup(other.type()); - m_is_null = other.is_null(); - if (!m_is_null) - m_assign_value(other); + assign(value); } -Value::~Value() +Value::Value(SQLType sql_type, String const& string) + : Value(sql_type) { + assign(string); +} + +Value::Value(SQLType sql_type, char const* string) + : Value(sql_type) +{ + assign(String(string)); +} + +Value::Value(SQLType sql_type, int integer) + : Value(sql_type) +{ + assign(integer); +} + +Value::Value(SQLType sql_type, double dbl) + : Value(sql_type) +{ + assign(dbl); +} + +Value::Value(SQLType sql_type, bool boolean) + : Value(sql_type) +{ + assign(boolean); +} + +Value::Value(String const& string) + : Value(SQLType::Text) +{ + assign(string); +} + +Value::Value(char const* string) + : Value(SQLType::Text) +{ + assign(String(string)); +} + +Value::Value(int integer) + : Value(SQLType::Integer) +{ + assign(integer); +} + +Value::Value(double dbl) + : Value(SQLType::Float) +{ + assign(dbl); +} + +Value::Value(bool boolean) + : Value(SQLType::Boolean) +{ + assign(boolean); +} + +Value Value::create_tuple(NonnullRefPtr const& tuple_descriptor) +{ + return Value(Value::SetImplementationSingleton, TupleImpl(tuple_descriptor)); +} + +Value Value::create_array(SQLType element_type, Optional const& max_size) +{ + return Value(Value::SetImplementationSingleton, ArrayImpl(element_type, max_size)); } Value const& Value::null() { - static Value s_null; + static Value s_null(SQLType::Null); return s_null; } -Value& Value::operator=(Value const& other) +bool Value::is_null() const { - if (this != &other) { - m_is_null = other.is_null(); - if (!m_is_null) { - VERIFY(can_cast(other)); - m_assign_value(other); - } - } - return (*this); + return m_impl.visit([&](auto& impl) { return impl.is_null(); }); } -Value& Value::operator=(String const& value) +SQLType Value::type() const { - m_assign_string(value); - m_is_null = false; - return (*this); + return m_impl.visit([&](auto& impl) { return impl.type(); }); } -Value& Value::operator=(int value) +String Value::type_name() const { - m_assign_int(value); - m_is_null = false; - return (*this); + return m_impl.visit([&](auto& impl) { return impl.type_name(); }); } -Value& Value::operator=(u32 value) +BaseTypeImpl Value::downcast_to_basetype() const { - m_assign_int(static_cast(value)); - m_is_null = false; - return (*this); + return m_impl.downcast(); } -Value& Value::operator=(double value) +String Value::to_string() const { - m_assign_double(value); - m_is_null = false; - return (*this); + if (is_null()) + return "(null)"; + return m_impl.visit([&](auto& impl) { return impl.to_string(); }); } -Value& Value::set_null() +Optional Value::to_int() const { - m_is_null = true; - return (*this); + if (is_null()) + return {}; + return m_impl.visit([&](auto& impl) { return impl.to_int(); }); } -Optional Value::to_string() const +Optional Value::to_u32() const { - if (!m_is_null) - return m_to_string(); + if (is_null()) + return {}; + auto ret = to_int(); + if (ret.has_value()) + return static_cast(ret.value()); + return {}; +} + +Optional Value::to_double() const +{ + if (is_null()) + return {}; + return m_impl.visit([&](auto& impl) { return impl.to_double(); }); +} + +Optional Value::to_bool() const +{ + if (is_null()) + return {}; + return m_impl.visit([&](auto& impl) { return impl.to_bool(); }); +} + +Optional> Value::to_vector() const +{ + if (is_null()) + return {}; + Vector vector; + if (m_impl.visit([&](auto& impl) { return impl.to_vector(vector); })) + return vector; else return {}; } Value::operator String() const { - auto str = to_string(); - VERIFY(str.has_value()); - return str.value(); -} - -Optional Value::to_int() const -{ - if (!m_is_null) { - return m_to_int(); - } else { - return {}; - } + return to_string(); } Value::operator int() const @@ -119,19 +194,6 @@ Value::operator int() const return i.value(); } -Optional Value::to_u32() const -{ - if (!m_is_null) { - auto ret = m_to_int(); - if (ret.has_value()) - return static_cast(ret.value()); - else - return {}; - } else { - return {}; - } -} - Value::operator u32() const { auto i = to_u32(); @@ -139,278 +201,792 @@ Value::operator u32() const return i.value(); } -Optional Value::to_double() const -{ - if (!m_is_null) - return m_to_double(); - else - return {}; -} - Value::operator double() const { - auto dbl = to_double(); - VERIFY(dbl.has_value()); - return dbl.value(); + auto d = to_double(); + VERIFY(d.has_value()); + return d.value(); } -bool Value::can_cast(Value const& other) const +Value::operator bool() const { + auto b = to_bool(); + VERIFY(b.has_value()); + return b.value(); +} + +void Value::assign(Value const& other_value) +{ + m_impl.visit([&](auto& impl) { impl.assign(other_value); }); +} + +void Value::assign(String const& string_value) +{ + m_impl.visit([&](auto& impl) { impl.assign_string(string_value); }); +} + +void Value::assign(int const& int_value) +{ + m_impl.visit([&](auto& impl) { impl.assign_int(int_value); }); +} + +void Value::assign(double const& double_value) +{ + m_impl.visit([&](auto& impl) { impl.assign_double(double_value); }); +} + +void Value::assign(bool const& bool_value) +{ + m_impl.visit([&](auto& impl) { impl.assign_bool(bool_value); }); +} + +void Value::assign(Vector const& values) +{ + m_impl.visit([&](auto& impl) { impl.assign_vector(values); }); +} + +Value& Value::operator=(Value const& other) +{ + if (this != &other) { + if (other.is_null()) { + assign(null()); + } else { + VERIFY(can_cast(other)); + assign(other); + } + } + return (*this); +} + +Value& Value::operator=(String const& value) +{ + assign(value); + return (*this); +} + +Value& Value::operator=(char const* value) +{ + assign(String(value)); + return (*this); +} + +Value& Value::operator=(int value) +{ + assign(value); + return (*this); +} + +Value& Value::operator=(u32 value) +{ + assign(static_cast(value)); + return (*this); +} + +Value& Value::operator=(double value) +{ + assign(value); + return (*this); +} + +Value& Value::operator=(bool value) +{ + assign(value); + return (*this); +} + +Value& Value::operator=(Vector const& vector) +{ + assign(vector); + return (*this); +} + +size_t Value::length() const +{ + return m_impl.visit([&](auto& impl) { return impl.length(); }); +} + +u32 Value::hash() const +{ + return (is_null()) ? 0u : m_impl.visit([&](auto& impl) { return impl.hash(); }); +} + +bool Value::can_cast(Value const& other_value) const +{ + if (type() == other_value.type()) + return true; + return m_impl.visit([&](auto& impl) { return impl.can_cast(other_value); }); +} + +int Value::compare(Value const& other) const +{ + if (is_null()) + return -1; if (other.is_null()) + return 1; + return m_impl.visit([&](auto& impl) { return impl.compare(other); }); +} + +bool Value::operator==(Value const& other) const +{ + return compare(other) == 0; +} + +bool Value::operator==(String const& string_value) const +{ + return to_string() == string_value; +} + +bool Value::operator==(int int_value) const +{ + auto i = to_int(); + if (!i.has_value()) + return false; + return i.value() == int_value; +} + +bool Value::operator==(double double_value) const +{ + auto d = to_double(); + if (!d.has_value()) + return false; + return d.value() == double_value; +} + +bool Value::operator!=(Value const& other) const +{ + return compare(other) != 0; +} + +bool Value::operator<(Value const& other) const +{ + return compare(other) < 0; +} + +bool Value::operator<=(Value const& other) const +{ + return compare(other) <= 0; +} + +bool Value::operator>(Value const& other) const +{ + return compare(other) > 0; +} + +bool Value::operator>=(Value const& other) const +{ + return compare(other) >= 0; +} + +void Value::serialize_to(ByteBuffer& buffer) const +{ + u8 type_flags = (u8)type(); + if (is_null()) + type_flags |= (u8)SQLType::Null; + SQL::serialize_to(buffer, type_flags); + if (!is_null()) + m_impl.visit([&](auto& impl) { impl.serialize(buffer); }); +} + +void Value::deserialize(ByteBuffer& buffer, size_t& offset_at) +{ + m_impl.visit([&](auto& impl) { impl.deserialize(buffer, offset_at); }); +} + +Value Value::deserialize_from(ByteBuffer& buffer, size_t& at_offset) +{ + u8 type_flags; + SQL::deserialize_from(buffer, at_offset, type_flags); + bool is_null = false; + if ((type_flags & (u8)SQLType::Null) && (type_flags != (u8)SQLType::Null)) { + type_flags &= ~((u8)SQLType::Null); + is_null = true; + } + auto type = (SQLType)type_flags; + VERIFY(!is_null || (type != SQLType::Tuple && type != SQLType::Array)); + Value ret(type); + if (!is_null) { + ret.deserialize(buffer, at_offset); + } + return ret; +} + +// ----------------------------------------------------------------- + +bool NullImpl::can_cast(Value const& value) +{ + return value.is_null(); +} + +int NullImpl::compare(Value const& other) +{ + return other.type() == SQLType::Null; +} + +// ----------------------------------------------------------------- + +String TextImpl::to_string() const +{ + return value(); +} + +Optional TextImpl::to_int() const +{ + if (!m_value.has_value()) + return {}; + return value().to_int(); +} + +Optional TextImpl::to_double() const +{ + if (!m_value.has_value()) + return {}; + char* end_ptr; + double ret = strtod(value().characters(), &end_ptr); + if (end_ptr == value().characters()) { + return {}; + } + return ret; +} + +Optional TextImpl::to_bool() const +{ + if (!m_value.has_value()) + return {}; + if (value().equals_ignoring_case("true") || value().equals_ignoring_case("t")) return true; - if (type() == other.type()) - return true; - return m_can_cast(other); + if (value().equals_ignoring_case("false") || value().equals_ignoring_case("f")) + return false; + return {}; } -bool Value::operator==(String const& other) const +void TextImpl::assign(Value const& other_value) { - return operator String() == other; -} - -bool Value::operator==(int other) const -{ - return operator int() == other; -} - -bool Value::operator==(double other) const -{ - return operator double() == other; -} - -void Value::setup(SQLType sql_type) -{ - m_type = sql_type; - switch (sql_type) { - case SQLType::Text: - setup_text(); - break; - case SQLType::Integer: - setup_int(); - break; - case SQLType::Float: - setup_float(); - break; - default: - VERIFY_NOT_REACHED(); + if (other_value.type() == SQLType::Null) { + m_value = {}; + } else { + m_value = other_value.to_string(); } } -void Value::setup_text() +void TextImpl::assign_string(String const& string_value) { - m_impl = String(""); - m_type_name = []() { return "Text"; }; - m_size = []() { return 64 + sizeof(int); }; + m_value = string_value; +} - m_deserialize = [&](ByteBuffer& buffer, size_t& at_offset) { - int len; - memcpy(&len, buffer.offset_pointer((int)at_offset), sizeof(int)); - at_offset += sizeof(int); - m_impl = String((const char*)buffer.offset_pointer((int)at_offset)); - at_offset += 64; - }; +void TextImpl::assign_int(int int_value) +{ + m_value = String::number(int_value); +} - m_serialize = [&](ByteBuffer& buffer) { - char zeroes[64]; +void TextImpl::assign_double(double double_value) +{ + m_value = String::number(double_value); +} - int len = min((int)m_impl.get().length(), 63); - buffer.append(&len, sizeof(int)); - buffer.append(m_impl.get().characters(), len); - memset(zeroes, 0, 64); - buffer.append(zeroes, 64 - len); - }; +void TextImpl::assign_bool(bool bool_value) +{ + m_value = (bool_value) ? "true" : "false"; +} - m_assign_value = [&](Value const& other) { - auto str = other.to_string(); - VERIFY(str.has_value()); - m_impl = str.value(); - }; +size_t TextImpl::length() const +{ + return (is_null()) ? 0 : sizeof(u32) + min(value().length(), 64) + 1; +} - m_assign_string = [&](String const& string) { - m_impl = string; - }; +int TextImpl::compare(Value const& other) const +{ + if (is_null()) + return -1; + auto s1 = value(); + auto s2 = other.to_string(); + if (s1 == s2) + return 0; + return (s1 < s2) ? -1 : 1; +} - m_assign_int = [&](int i) { - m_impl = String::number(i); - }; +u32 TextImpl::hash() const +{ + return value().hash(); +} - m_assign_double = [&](double d) { - m_impl = String::number(d); - }; +String IntegerImpl::to_string() const +{ + return String::formatted("{}", value()); +} - m_to_string = [&]() -> Optional { - return m_impl.get(); - }; +Optional IntegerImpl::to_int() const +{ + return value(); +} - m_to_int = [&]() -> Optional { - return m_impl.get().to_int(); - }; +Optional IntegerImpl::to_double() const +{ + return static_cast(value()); +} - m_to_double = [&]() -> Optional { - char* end_ptr; - double ret = strtod(m_impl.get().characters(), &end_ptr); - if (end_ptr == m_impl.get().characters()) { - return {}; +Optional IntegerImpl::to_bool() const +{ + return value() != 0; +} + +void IntegerImpl::assign(Value const& other_value) +{ + auto i = other_value.to_int(); + if (!i.has_value()) + m_value = {}; + else + m_value = i.value(); +} + +void IntegerImpl::assign_string(String const& string_value) +{ + auto i = string_value.to_int(); + if (!i.has_value()) + m_value = {}; + else + m_value = i.value(); +} + +void IntegerImpl::assign_int(int int_value) +{ + m_value = int_value; +} + +void IntegerImpl::assign_double(double double_value) +{ + m_value = static_cast(round(double_value)); +} + +void IntegerImpl::assign_bool(bool bool_value) +{ + m_value = (bool_value) ? 1 : 0; +} + +bool IntegerImpl::can_cast(Value const& other_value) +{ + return other_value.to_int().has_value(); +} + +int IntegerImpl::compare(Value const& other) const +{ + auto casted = other.to_int(); + if (!casted.has_value()) { + return 1; + } + return value() - casted.value(); +} + +u32 IntegerImpl::hash() const +{ + return int_hash(value()); +} + +String FloatImpl::to_string() const +{ + return String::formatted("{}", value()); +} + +Optional FloatImpl::to_int() const +{ + return static_cast(round(value())); +} + +Optional FloatImpl::to_double() const +{ + return value(); +} + +void FloatImpl::assign(Value const& other_value) +{ + auto i = other_value.to_double(); + if (!i.has_value()) + m_value = {}; + else + m_value = i.value(); +} + +void FloatImpl::assign_string(String const& string_value) +{ + char* end_ptr; + auto dbl = strtod(string_value.characters(), &end_ptr); + if (end_ptr == string_value.characters()) + m_value = {}; + else + m_value = dbl; +} + +void FloatImpl::assign_int(int int_value) +{ + m_value = int_value; +} + +void FloatImpl::assign_double(double double_value) +{ + m_value = double_value; +} + +bool FloatImpl::can_cast(Value const& other_value) +{ + return other_value.to_double().has_value(); +} + +int FloatImpl::compare(Value const& other) const +{ + auto casted = other.to_double(); + if (!casted.has_value()) { + return 1; + } + auto diff = value() - casted.value(); + return (diff < NumericLimits::epsilon()) ? 0 : ((diff > 0) ? 1 : -1); +} + +String BooleanImpl::to_string() const +{ + return (value()) ? "true" : "false"; +} + +Optional BooleanImpl::to_int() const +{ + return (value()) ? 1 : 0; +} + +Optional BooleanImpl::to_double() +{ + return {}; +} + +Optional BooleanImpl::to_bool() const +{ + return value(); +} + +void BooleanImpl::assign(Value const& other_value) +{ + auto b = other_value.to_bool(); + if (!b.has_value()) + m_value = {}; + else + m_value = b.value(); +} + +void BooleanImpl::assign_string(String const& string_value) +{ + return assign(Value(string_value)); +} + +void BooleanImpl::assign_int(int int_value) +{ + m_value = (int_value != 0); +} + +void BooleanImpl::assign_double(double) +{ + m_value = {}; +} + +void BooleanImpl::assign_bool(bool bool_value) +{ + m_value = bool_value; +} + +bool BooleanImpl::can_cast(Value const& other_value) +{ + return other_value.to_bool().has_value(); +} + +int BooleanImpl::compare(Value const& other) const +{ + auto casted = other.to_bool(); + if (!casted.has_value()) { + return 1; + } + return value() ^ casted.value(); // xor - zero if both true or both false, 1 otherwise. +} + +u32 BooleanImpl::hash() const +{ + return int_hash(value()); +} + +void ContainerValueImpl::assign_vector(Vector const& vector_values) +{ + if (!validate_before_assignment(vector_values)) { + m_value = {}; + return; + } + m_value = Vector(); + for (auto& value : vector_values) { + if (!append(value)) { + m_value = {}; + return; } - return ret; - }; + } + if (!validate_after_assignment()) + m_value = {}; +} - m_compare = [&](Value const& other) -> int { - auto s1 = to_string(); - auto s2 = other.to_string(); - VERIFY(s1.has_value()); - if (!s2.has_value()) { - return 1; +bool ContainerValueImpl::to_vector(Vector& vector) const +{ + vector.clear(); + for (auto& value : value()) { + vector.empend(Value(value)); + } + return true; +} + +Vector ContainerValueImpl::to_string_vector() const +{ + Vector ret; + for (auto& value : value()) { + ret.append(Value(value).to_string()); + } + return ret; +} + +String ContainerValueImpl::to_string() const +{ + StringBuilder builder; + builder.append("("); + StringBuilder joined; + joined.join(", ", to_string_vector()); + builder.append(joined.string_view()); + builder.append(")"); + return builder.build(); +} + +u32 ContainerValueImpl::hash() const +{ + u32 ret = 0u; + for (auto& value : value()) { + Value v(value); + // This is an extension of the pair_int_hash function from AK/HashFunctions.h: + if (!ret) + ret = v.hash(); + else + ret = int_hash((ret * 209) ^ (v.hash() * 413)); + } + return ret; +} + +bool ContainerValueImpl::append(Value const& value) +{ + if (value.type() == SQLType::Tuple || value.type() == SQLType::Array) + return false; + return append(value.downcast_to_basetype()); +} + +bool ContainerValueImpl::append(BaseTypeImpl const& impl) +{ + if (m_max_size.has_value() && (size() >= m_max_size.value())) + return false; + if (!validate(impl)) + return false; + m_value.value().empend(impl); + return true; +} + +void ContainerValueImpl::serialize_values(ByteBuffer& buffer) const +{ + serialize_to(buffer, (u32)size()); + for (auto& value : value()) { + Value(value).serialize_to(buffer); + } +} + +void ContainerValueImpl::deserialize_values(ByteBuffer& buffer, size_t& at_offset) +{ + u32 sz; + deserialize_from(buffer, at_offset, sz); + m_value = Vector(); + for (auto ix = 0u; ix < sz; ix++) { + append(Value::deserialize_from(buffer, at_offset)); + } +} + +void TupleImpl::assign(Value const& other) +{ + if (other.type() != SQLType::Tuple) { + m_value = {}; + return; + } + auto& other_impl = other.get_impl({}); + auto other_descriptor = other_impl.m_descriptor; + if (m_descriptor && other_descriptor && m_descriptor->compare_ignoring_names(*other_descriptor)) { + m_value = {}; + return; + } + assign_vector(other.to_vector().value()); +} + +size_t TupleImpl::length() const +{ + return (m_descriptor) ? m_descriptor->data_length() : 0; +} + +bool TupleImpl::can_cast(Value const& other_value) const +{ + if (other_value.type() != SQLType::Tuple) + return false; + return (m_descriptor == other_value.get_impl({}).m_descriptor); +} + +int TupleImpl::compare(Value const& other) const +{ + if (other.type() != SQLType::Tuple) + return 1; + + auto& other_impl = other.get_impl({}); + if (m_descriptor && other_impl.m_descriptor && m_descriptor->compare_ignoring_names(*other_impl.m_descriptor)) + return 1; + + auto other_values = other_impl.value(); + if (size() != other_impl.size()) + return (int)value().size() - (int)other_impl.size(); + for (auto ix = 0u; ix < value().size(); ix++) { + auto ret = Value(value()[ix]).compare(Value(other_impl.value()[ix])); + if (ret != 0) { + if (m_descriptor && (ix < m_descriptor->size()) && (*m_descriptor)[ix].order == Order::Descending) + ret = -ret; + return ret; } - if (s1.value() == s2.value()) - return 0; - return (s1.value() < s2.value()) ? -1 : 1; - }; + } + return 0; +} - m_can_cast = [](Value const&) -> bool { +void TupleImpl::serialize(ByteBuffer& buffer) const +{ + if (m_descriptor) { + serialize_to(buffer, (u32)m_descriptor->size()); + for (auto& tuple_element : *m_descriptor) { + u8 elem_type = (u8)tuple_element.type; + serialize_to(buffer, elem_type); + u8 elem_order = (u8)tuple_element.order; + serialize_to(buffer, elem_order); + } + } else { + serialize_to(buffer, (u32)-1); + } + serialize_values(buffer); +} + +void TupleImpl::deserialize(ByteBuffer& buffer, size_t& at_offset) +{ + u32 sz; + deserialize_from(buffer, at_offset, sz); + if (sz != (u32)-1) { + NonnullRefPtr serialized_descriptor = adopt_ref(*new TupleDescriptor); + for (auto ix = 0u; ix < sz; ix++) { + u8 elem_type, elem_order; + deserialize_from(buffer, at_offset, elem_type); + deserialize_from(buffer, at_offset, elem_order); + serialized_descriptor->empend("", (SQLType)elem_type, (Order)elem_order); + } + m_descriptor = serialized_descriptor; + m_max_size = m_descriptor->size(); + } else { + m_descriptor = nullptr; + m_max_size = {}; + } + deserialize_values(buffer, at_offset); +} + +bool TupleImpl::validate(BaseTypeImpl const& value) +{ + if (!m_descriptor) return true; - }; - - m_hash = [&]() { - return m_impl.get().hash(); - }; + auto required_type = (*m_descriptor)[size()].type; + return Value(value).type() == required_type; } -void Value::setup_int() +bool TupleImpl::validate_after_assignment() { - m_impl.set(0); - m_type_name = []() { return "Integer"; }; - m_size = []() { return sizeof(int); }; - - m_deserialize = [&](ByteBuffer& buffer, size_t& at_offset) { - memcpy(m_impl.get_pointer(), buffer.offset_pointer((int)at_offset), sizeof(int)); - at_offset += sizeof(int); - }; - - m_serialize = [&](ByteBuffer& buffer) { - buffer.append(m_impl.get_pointer(), sizeof(int)); - }; - - m_assign_value = [&](Value const& other) { - auto i = other.to_int(); - VERIFY(i.has_value()); - m_impl = i.value(); - }; - - m_assign_string = [&](String const& string) { - auto i = string.to_int(); - VERIFY(i.has_value()); - m_impl = i.value(); - }; - - m_assign_int = [&](int i) { - m_impl.set(move(i)); - }; - - m_assign_double = [&](double d) { - m_impl.set((int)d); - }; - - m_to_string = [&]() -> Optional { - StringBuilder builder; - builder.appendff("{}", m_impl.get()); - return builder.build(); - }; - - m_to_int = [&]() -> Optional { - return m_impl.get(); - }; - - m_to_double = [&]() -> Optional { - return static_cast(m_impl.get()); - }; - - m_compare = [&](Value const& other) -> int { - auto casted = other.to_int(); - if (!casted.has_value()) { - return 1; - } - return m_impl.get() - casted.value(); - }; - - m_can_cast = [](Value const& other) -> bool { - auto i = other.to_int(); - return i.has_value(); - }; - - m_hash = [&]() -> u32 { - return int_hash(m_impl.get()); - }; + if (!m_descriptor) + return true; + for (auto ix = value().size(); ix < m_descriptor->size(); ++ix) { + auto required_type = (*m_descriptor)[ix].type; + append(Value(required_type)); + } + return true; } -void Value::setup_float() +void ArrayImpl::assign(Value const& other) { - m_impl.set(0.0); - m_type_name = []() { return "Float"; }; - m_size = []() { return sizeof(double); }; + if (other.type() != SQLType::Array) { + m_value = {}; + return; + } + auto& other_impl = other.get_impl({}); + if (m_max_size != other_impl.m_max_size || m_element_type != other_impl.m_element_type) { + m_value = {}; + return; + } + assign_vector(other.to_vector().value()); +} - m_deserialize = [&](ByteBuffer& buffer, size_t& at_offset) { - memcpy(m_impl.get_pointer(), buffer.offset_pointer((int)at_offset), sizeof(double)); - at_offset += sizeof(double); - }; +size_t ArrayImpl::length() const +{ + size_t ret = 0; + for (auto& value : value()) { + ret += Value(value).length(); + } + return ret; +} - m_serialize = [&](ByteBuffer& buffer) { - buffer.append(m_impl.get_pointer(), sizeof(double)); - }; +bool ArrayImpl::can_cast(Value const& other_value) const +{ + if (other_value.type() != SQLType::Array) + return false; + auto& other_impl = other_value.get_impl({}); + return (m_max_size != other_impl.m_max_size || m_element_type != other_impl.m_element_type); +} - m_to_string = [&]() -> Optional { - StringBuilder builder; - builder.appendff("{}", m_impl.get()); - return builder.build(); - }; - - m_to_int = [&]() -> Optional { - return (int)m_impl.get(); - }; - - m_to_double = [&]() -> Optional { - return m_impl.get(); - }; - - m_assign_value = [&](Value const& other) { - auto dbl = other.to_double(); - VERIFY(dbl.has_value()); - m_impl.set(move(dbl.value())); - }; - - m_assign_string = [&](String const& string) { - char* end_ptr; - auto dbl = strtod(string.characters(), &end_ptr); - VERIFY(end_ptr != string.characters()); - m_impl.set(move(dbl)); - }; - - m_assign_int = [&](int i) { - m_impl.set(static_cast(i)); - }; - - m_assign_double = [&](double d) { - m_impl.set(move(d)); - }; - - m_compare = [&](Value const& other) -> int { - auto casted = other.to_double(); - if (!casted.has_value()) { - return 1; +int ArrayImpl::compare(Value const& other) const +{ + if (other.type() != SQLType::Array) + return 1; + auto other_impl = other.get_impl({}); + if (other_impl.m_element_type != m_element_type) + return 1; + if (other_impl.m_max_size.has_value() && m_max_size.has_value() && other_impl.m_max_size != m_max_size) + return (int)m_max_size.value() - (int)other_impl.m_max_size.value(); + if (size() != other_impl.size()) + return (int)size() - (int)other_impl.size(); + for (auto ix = 0u; ix < size(); ix++) { + auto ret = Value(value()[ix]).compare(Value(other_impl.value()[ix])); + if (ret != 0) { + return ret; } - auto diff = m_impl.get() - casted.value(); - return (diff < NumericLimits::epsilon()) ? 0 : ((diff > 0) ? 1 : -1); - }; + } + return 0; +} - m_can_cast = [](Value const& other) -> bool { - auto dbl = other.to_double(); - return dbl.has_value(); - }; +void ArrayImpl::serialize(ByteBuffer& buffer) const +{ + serialize_to(buffer, (u8)m_element_type); + if (m_max_size.has_value()) + serialize_to(buffer, (u32)m_max_size.value()); + else + serialize_to(buffer, (u32)0); + serialize_values(buffer); +} - // Using floats in hash functions is a bad idea. Let's disable that for now. - m_hash = []() -> u32 { - VERIFY_NOT_REACHED(); - }; +void ArrayImpl::deserialize(ByteBuffer& buffer, size_t& at_offset) +{ + u8 elem_type; + deserialize_from(buffer, at_offset, elem_type); + m_element_type = (SQLType)elem_type; + u32 max_sz; + deserialize_from(buffer, at_offset, max_sz); + if (max_sz) + m_max_size = max_sz; + else + m_max_size = {}; + deserialize_values(buffer, at_offset); +} + +bool ArrayImpl::validate(BaseTypeImpl const& impl) +{ + return Value(impl).type() == m_element_type; } } diff --git a/Userland/Libraries/LibSQL/Value.h b/Userland/Libraries/LibSQL/Value.h index 655aac93f0c..624e32e4aea 100644 --- a/Userland/Libraries/LibSQL/Value.h +++ b/Userland/Libraries/LibSQL/Value.h @@ -6,14 +6,290 @@ #pragma once +#include #include -#include +#include #include #include +#include +#include #include +#include namespace SQL { +class Value; + +class BaseImpl { +public: + explicit BaseImpl(SQLType type = SQLType::Null) + : m_type(type) + { + } + + [[nodiscard]] SQLType type() const { return m_type; } + [[nodiscard]] String type_name() const { return SQLType_name(type()); } + +private: + SQLType m_type { SQLType::Null }; +}; + +class NullImpl : public BaseImpl { +public: + explicit NullImpl() + : BaseImpl(SQLType::Null) + { + } + + [[nodiscard]] static bool is_null() { return true; } + [[nodiscard]] static String to_string() { return "(null)"; } + [[nodiscard]] static Optional to_int() { return {}; } + [[nodiscard]] static Optional to_double() { return {}; } + [[nodiscard]] static Optional to_bool() { return {}; } + [[nodiscard]] static bool to_vector(Vector&) { return false; } + static void assign(Value const&) { } + static void assign_string(String const&) { } + static void assign_int(int) { } + static void assign_double(double) { } + static void assign_bool(bool) { } + static void assign_vector(Vector const&) { } + [[nodiscard]] static size_t length() { return 0; } + [[nodiscard]] static bool can_cast(Value const&); + [[nodiscard]] static int compare(Value const&); + static void serialize(ByteBuffer&) { } + static void deserialize(ByteBuffer&, size_t&) { } + [[nodiscard]] static u32 hash() { return 0; } +}; + +template +class Impl : public BaseImpl { +public: + [[nodiscard]] bool is_null() const + { + return !m_value.has_value(); + } + + [[nodiscard]] T const& value() const + { + VERIFY(m_value.has_value()); + return m_value.value(); + } + + [[nodiscard]] size_t length() const + { + return sizeof(T); + } + + void serialize(ByteBuffer& buffer) const + { + serialize_to(buffer, value()); + } + + void deserialize(ByteBuffer& buffer, size_t& at_offset) + { + T value; + deserialize_from(buffer, at_offset, value); + m_value = value; + } + +protected: + explicit Impl(SQLType sql_type) + : BaseImpl(sql_type) + { + } + + Optional m_value {}; +}; + +class TextImpl : public Impl { +public: + explicit TextImpl() + : Impl(SQLType::Text) + { + } + + [[nodiscard]] String to_string() const; + [[nodiscard]] Optional to_int() const; + [[nodiscard]] Optional to_double() const; + [[nodiscard]] Optional to_bool() const; + [[nodiscard]] static bool to_vector(Vector&) { return false; } + void assign(Value const&); + void assign_string(String const&); + void assign_int(int); + void assign_double(double); + void assign_bool(bool); + void assign_vector(Vector const&) { m_value = {}; } + [[nodiscard]] size_t length() const; + [[nodiscard]] static bool can_cast(Value const&) { return true; } + [[nodiscard]] int compare(Value const& other) const; + [[nodiscard]] u32 hash() const; +}; + +class IntegerImpl : public Impl { +public: + IntegerImpl() + : Impl(SQLType::Integer) + { + } + + [[nodiscard]] String to_string() const; + [[nodiscard]] Optional to_int() const; + [[nodiscard]] Optional to_double() const; + [[nodiscard]] Optional to_bool() const; + [[nodiscard]] static bool to_vector(Vector&) { return false; } + void assign(Value const&); + void assign_string(String const&); + void assign_int(int); + void assign_double(double); + void assign_bool(bool); + void assign_vector(Vector const&) { m_value = {}; } + [[nodiscard]] static bool can_cast(Value const&); + [[nodiscard]] int compare(Value const& other) const; + [[nodiscard]] u32 hash() const; +}; + +class FloatImpl : public Impl { +public: + explicit FloatImpl() + : Impl(SQLType::Float) + { + } + + [[nodiscard]] String to_string() const; + [[nodiscard]] Optional to_int() const; + [[nodiscard]] Optional to_double() const; + [[nodiscard]] static Optional to_bool() { return {}; } + [[nodiscard]] static bool to_vector(Vector&) { return false; } + void assign(Value const&); + void assign_string(String const&); + void assign_int(int); + void assign_double(double); + void assign_bool(bool) { m_value = {}; } + void assign_vector(Vector const&) { m_value = {}; } + [[nodiscard]] static bool can_cast(Value const&); + [[nodiscard]] int compare(Value const& other) const; + + // Using floats in hash functions is a bad idea. Let's disable that for now. + [[nodiscard]] static u32 hash() { VERIFY_NOT_REACHED(); } +}; + +class BooleanImpl : public Impl { +public: + explicit BooleanImpl() + : Impl(SQLType::Boolean) + { + } + + [[nodiscard]] String to_string() const; + [[nodiscard]] Optional to_int() const; + [[nodiscard]] static Optional to_double(); + [[nodiscard]] Optional to_bool() const; + [[nodiscard]] static bool to_vector(Vector&) { return false; } + void assign(Value const&); + void assign_string(String const&); + void assign_int(int); + void assign_double(double); + void assign_bool(bool); + void assign_vector(Vector const&) { m_value = {}; } + [[nodiscard]] static bool can_cast(Value const&); + [[nodiscard]] int compare(Value const& other) const; + [[nodiscard]] u32 hash() const; +}; + +using BaseTypeImpl = Variant; + +class ContainerValueImpl : public Impl> { +public: + virtual ~ContainerValueImpl() = default; + + [[nodiscard]] String to_string() const; + [[nodiscard]] static Optional to_int() { return {}; } + [[nodiscard]] static Optional to_double() { return {}; } + [[nodiscard]] static Optional to_bool() { return {}; } + [[nodiscard]] bool to_vector(Vector&) const; + void assign_string(String const&) { m_value = {}; } + void assign_int(int) { m_value = {}; } + void assign_double(double) { m_value = {}; } + void assign_bool(bool) { m_value = {}; } + void assign_vector(Vector const&); + [[nodiscard]] u32 hash() const; + + virtual bool validate_before_assignment(Vector const&) { return true; } + virtual bool validate(BaseTypeImpl const&) { return true; } + virtual bool validate_after_assignment() { return true; } + [[nodiscard]] Vector to_string_vector() const; + [[nodiscard]] size_t size() const { return is_null() ? 0 : value().size(); } + bool append(Value const&); + bool append(BaseTypeImpl const& value); + void serialize_values(ByteBuffer& buffer) const; + void deserialize_values(ByteBuffer&, size_t& at_offset); + +protected: + explicit ContainerValueImpl(SQLType sql_type, Optional const& max_size = {}) + : Impl(sql_type) + , m_max_size(max_size) + { + } + + Optional m_max_size {}; +}; + +class TupleImpl : public ContainerValueImpl { +public: + explicit TupleImpl(NonnullRefPtr const& descriptor, bool is_null = true) + : ContainerValueImpl(SQLType::Tuple, is_null) + , m_descriptor(descriptor) + { + m_max_size = m_descriptor->size(); + } + + explicit TupleImpl() + : ContainerValueImpl(SQLType::Tuple, {}) + { + } + + void assign(Value const&); + [[nodiscard]] size_t length() const; + [[nodiscard]] bool can_cast(Value const&) const; + [[nodiscard]] int compare(Value const& other) const; + + virtual bool validate(BaseTypeImpl const&) override; + virtual bool validate_after_assignment() override; + void serialize(ByteBuffer& buffer) const; + void deserialize(ByteBuffer& buffer, size_t&); + +private: + RefPtr m_descriptor; +}; + +class ArrayImpl : public ContainerValueImpl { +public: + explicit ArrayImpl(SQLType element_type, Optional const& max_size = {}) + : ContainerValueImpl(SQLType::Array, max_size) + , m_element_type(element_type) + { + } + + explicit ArrayImpl() + : ContainerValueImpl(SQLType::Array, {}) + , m_element_type(SQLType::Null) + { + } + + void assign(Value const&); + [[nodiscard]] size_t length() const; + [[nodiscard]] bool can_cast(Value const&) const; + [[nodiscard]] int compare(Value const& other) const; + void serialize(ByteBuffer& buffer) const; + void deserialize(ByteBuffer& buffer, size_t&); + virtual bool validate(BaseTypeImpl const&) override; + +private: + SQLType m_element_type { SQLType::Text }; +}; + +using ValueTypeImpl = Variant; + /** * 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 @@ -21,88 +297,105 @@ namespace SQL { */ class Value { public: - explicit Value(SQLType sql_type = SQLType::Text); - Value(SQLType sql_type, ByteBuffer& buffer, size_t& offset); - Value(Value const& other); - ~Value(); + Value(Value&) = default; + Value(Value const&) = default; - static Value const& null(); + explicit Value(SQLType sql_type = SQLType::Null); - Value& operator=(Value&& other) noexcept + template + explicit Value(Variant impl) + : m_impl(impl) { - (*this) = other; - return (*this); } + + enum SetImplementation { + SetImplementationSingleton + }; + + template + Value(SetImplementation, I&& impl) + { + m_impl.set(forward(impl)); + } + + Value(SQLType, Value const&); + Value(SQLType, String const&); + Value(SQLType, char const*); + Value(SQLType, int); + Value(SQLType, double); + Value(SQLType, bool); + explicit Value(String const&); + explicit Value(char const*); + explicit Value(int); + explicit Value(double); + explicit Value(bool); + + ~Value() = default; + + [[nodiscard]] bool is_null() const; + [[nodiscard]] SQLType type() const; + [[nodiscard]] String type_name() const; + [[nodiscard]] BaseTypeImpl downcast_to_basetype() const; + + template + Impl const& get_impl(Badge) const { return m_impl.get(); } + + [[nodiscard]] String to_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; + + explicit operator String() const; + explicit operator int() const; + explicit operator u32() const; + explicit operator double() const; + explicit operator bool() const; + + void assign(Value const& other_value); + void assign(String const& string_value); + void assign(int const& int_value); + void assign(double const& double_value); + void assign(bool const& bool_value); + void assign(Vector const& values); + Value& operator=(Value const& other); + Value& operator=(String const&); - Value& operator=(String&& string) - { - operator=(string); - return *this; - } + Value& operator=(char const*); Value& operator=(int); Value& operator=(u32); Value& operator=(double); - Value& set_null(); + Value& operator=(bool); + Value& operator=(Vector const&); - Optional to_string() const; - explicit operator String() const; - Optional to_int() const; - explicit operator int() const; - Optional to_double() const; - explicit operator double() const; - Optional to_u32() const; - explicit operator u32() const; - - [[nodiscard]] SQLType type() const { return m_type; } - [[nodiscard]] const char* type_name() const { return m_type_name(); } - [[nodiscard]] size_t size() const { return m_size(); } - [[nodiscard]] int compare(Value const& other) const { return m_compare(other); } - [[nodiscard]] bool is_null() const { return m_is_null; } + [[nodiscard]] size_t length() const; + [[nodiscard]] u32 hash() const; [[nodiscard]] bool can_cast(Value const&) const; - [[nodiscard]] u32 hash() const { return (is_null()) ? 0 : m_hash(); } + void serialize_to(ByteBuffer&) const; + void deserialize(ByteBuffer&, size_t&); - bool operator==(Value const& other) const { return m_compare(other) == 0; } - bool operator==(String const& other) const; - bool operator==(int other) const; - bool operator==(double other) const; - bool operator!=(Value const& other) const { return m_compare(other) != 0; } - bool operator<(Value const& other) const { return m_compare(other) < 0; } - bool operator<=(Value const& other) const { return m_compare(other) <= 0; } - bool operator>(Value const& other) const { return m_compare(other) > 0; } - bool operator>=(Value const& other) const { return m_compare(other) >= 0; } + [[nodiscard]] int compare(Value const&) const; + bool operator==(Value const&) const; + bool operator==(String const&) const; + bool operator==(int) const; + bool operator==(double) const; + bool operator!=(Value const&) const; + bool operator<(Value const&) const; + bool operator<=(Value const&) const; + bool operator>(Value const&) const; + bool operator>=(Value const&) const; - void serialize(ByteBuffer& buffer) const - { - VERIFY(!is_null()); - m_serialize(buffer); - } + static Value const& null(); + static Value create_tuple(NonnullRefPtr const&); + static Value create_array(SQLType element_type, Optional const& max_size = {}); + static Value deserialize_from(ByteBuffer&, size_t&); private: - void setup(SQLType sql_type); - void setup_text(); - void setup_int(); - void setup_float(); + void setup(SQLType type); - Function()> m_to_string; - Function()> m_to_int; - Function()> m_to_double; - Function m_assign_value; - Function m_assign_string; - Function m_assign_int; - Function m_assign_double; - Function m_compare; - Function m_serialize; - Function m_deserialize; - Function m_size; - Function m_type_name; - Function m_can_cast; - Function m_hash; - - SQLType m_type { SQLType::Text }; - bool m_is_null { true }; - - Variant m_impl; + ValueTypeImpl m_impl {}; }; }