From 2a465291703f553b281639cc13f4a834ff4125ab Mon Sep 17 00:00:00 2001 From: Jan de Visser Date: Thu, 17 Jun 2021 13:23:52 -0400 Subject: [PATCH] LibSQL: Basic dynamic value classes for SQL Storage layer This patch adds the basic dynamic value classes used by the SQL Storage layer. The most elementary class is Value, which holds a typed Value which can be converted to standard C++ types. A Tuple is a collection of Values described by a TupleDescriptor, which specifies the names, types, and ordering of the elements in the Tuple. Tuples and Values can be serialized and deserialized to and from ByteBuffers. This is mechanism which is used to save them to disk. Tuples are used as keys in SQL indexes and rows in SQL tables. Also included is a test file. --- Tests/LibSQL/TestSqlValueAndTuple.cpp | 215 ++++++++++ Userland/Libraries/LibSQL/CMakeLists.txt | 12 +- Userland/Libraries/LibSQL/Forward.h | 4 + Userland/Libraries/LibSQL/Serialize.h | 28 ++ Userland/Libraries/LibSQL/Tuple.cpp | 245 ++++++++++++ Userland/Libraries/LibSQL/Tuple.h | 87 +++++ Userland/Libraries/LibSQL/TupleDescriptor.h | 39 ++ Userland/Libraries/LibSQL/Type.h | 40 ++ Userland/Libraries/LibSQL/Value.cpp | 413 ++++++++++++++++++++ Userland/Libraries/LibSQL/Value.h | 108 +++++ 10 files changed, 1186 insertions(+), 5 deletions(-) create mode 100644 Tests/LibSQL/TestSqlValueAndTuple.cpp create mode 100644 Userland/Libraries/LibSQL/Serialize.h create mode 100644 Userland/Libraries/LibSQL/Tuple.cpp create mode 100644 Userland/Libraries/LibSQL/Tuple.h create mode 100644 Userland/Libraries/LibSQL/TupleDescriptor.h create mode 100644 Userland/Libraries/LibSQL/Type.h create mode 100644 Userland/Libraries/LibSQL/Value.cpp create mode 100644 Userland/Libraries/LibSQL/Value.h diff --git a/Tests/LibSQL/TestSqlValueAndTuple.cpp b/Tests/LibSQL/TestSqlValueAndTuple.cpp new file mode 100644 index 00000000000..7740e5ce48a --- /dev/null +++ b/Tests/LibSQL/TestSqlValueAndTuple.cpp @@ -0,0 +1,215 @@ +/* + * Copyright (c) 2021, Jan de Visser + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include + +#include +#include +#include +#include +#include + +TEST_CASE(text_value) +{ + SQL::Value v(SQL::SQLType::Text); + v = "Test"; + VERIFY(v.to_string().value() == "Test"); +} + +TEST_CASE(text_value_to_int) +{ + SQL::Value v(SQL::SQLType::Text); + v = "42"; + EXPECT_EQ(v.to_int().value(), 42); +} + +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; }); +} + +TEST_CASE(serialize_text_value) +{ + SQL::Value v(SQL::SQLType::Text); + v = "Test"; + VERIFY(v.to_string().value() == "Test"); + + ByteBuffer buffer; + v.serialize(buffer); + + size_t offset = 0; + SQL::Value v2(SQL::SQLType::Text, buffer, offset); + VERIFY((String)v2 == "Test"); +} + +TEST_CASE(integer_value) +{ + SQL::Value v(SQL::SQLType::Integer); + v = 42; + VERIFY(v.to_int().value() == 42); +} + +TEST_CASE(serialize_int_value) +{ + SQL::Value v(SQL::SQLType::Text); + v = 42; + VERIFY(v.to_int().value() == 42); + + ByteBuffer buffer; + v.serialize(buffer); + + size_t offset = 0; + SQL::Value v2(SQL::SQLType::Text, buffer, offset); + VERIFY(v2 == v); +} + +TEST_CASE(float_value) +{ + SQL::Value v(SQL::SQLType::Float); + v = 3.14; + VERIFY(v.to_double().value() - 3.14 < 0.001); +} + +TEST_CASE(assign_text_value_to_int) +{ + SQL::Value text(SQL::SQLType::Text); + text = "42"; + SQL::Value integer(SQL::SQLType::Integer); + integer = text; + EXPECT_EQ(integer.to_int().value(), 42); +} + +TEST_CASE(assign_int_to_text_value) +{ + SQL::Value text(SQL::SQLType::Text); + text = 42; + EXPECT_EQ((String)text, "42"); +} + +TEST_CASE(copy_value) +{ + SQL::Value text(SQL::SQLType::Text); + text = 42; + SQL::Value copy(text); + EXPECT_EQ((String)copy, "42"); +} + +TEST_CASE(compare_text_to_int) +{ + SQL::Value text(SQL::SQLType::Text); + text = 42; + SQL::Value integer(SQL::SQLType::Integer); + integer = 42; + EXPECT(text == integer); + EXPECT(integer == text); +} + +TEST_CASE(order_text_values) +{ + SQL::Value v1(SQL::SQLType::Text); + v1 = "Test_A"; + SQL::Value v2(SQL::SQLType::Text); + v2 = "Test_B"; + EXPECT(v1 <= v2); + EXPECT(v1 < v2); + EXPECT(v2 >= v1); + EXPECT(v2 > v1); +} + +TEST_CASE(order_int_values) +{ + SQL::Value v1(SQL::SQLType::Integer); + v1 = 12; + SQL::Value v2(SQL::SQLType::Integer); + v2 = 42; + EXPECT(v1 <= v2); + EXPECT(v1 < v2); + EXPECT(v2 >= v1); + EXPECT(v2 > v1); +} + +TEST_CASE(tuple) +{ + SQL::TupleDescriptor descriptor; + descriptor.append({ "col1", SQL::SQLType::Text, SQL::Order::Ascending }); + descriptor.append({ "col2", SQL::SQLType::Integer, SQL::Order::Descending }); + SQL::Tuple tuple(descriptor); + + tuple["col1"] = "Test"; + tuple["col2"] = 42; + VERIFY(tuple[0] == "Test"); + VERIFY(tuple[1] == 42); +} + +TEST_CASE(serialize_tuple) +{ + SQL::TupleDescriptor descriptor; + descriptor.append({ "col1", SQL::SQLType::Text, SQL::Order::Ascending }); + descriptor.append({ "col2", SQL::SQLType::Integer, SQL::Order::Descending }); + SQL::Tuple tuple(descriptor); + + tuple["col1"] = "Test"; + tuple["col2"] = 42; + + auto buffer = ByteBuffer(); + tuple.serialize(buffer); + EXPECT_EQ((String)tuple[0], "Test"); + EXPECT_EQ((int)tuple[1], 42); + + size_t offset = 0; + SQL::Tuple tuple2(descriptor, buffer, offset); + VERIFY(tuple2[0] == "Test"); + VERIFY(tuple2[1] == 42); +} + +TEST_CASE(copy_tuple) +{ + SQL::TupleDescriptor descriptor; + descriptor.append({ "col1", SQL::SQLType::Text, SQL::Order::Ascending }); + descriptor.append({ "col2", SQL::SQLType::Integer, SQL::Order::Descending }); + SQL::Tuple tuple(descriptor); + + tuple["col1"] = "Test"; + tuple["col2"] = 42; + + SQL::Tuple copy; + copy = tuple; + VERIFY(tuple == copy); + + SQL::Tuple copy_2(copy); + VERIFY(tuple == copy_2); +} + +TEST_CASE(compare_tuples) +{ + SQL::TupleDescriptor descriptor; + descriptor.append({ "col1", SQL::SQLType::Text, SQL::Order::Ascending }); + descriptor.append({ "col2", SQL::SQLType::Integer, SQL::Order::Descending }); + + SQL::Tuple tuple1(descriptor); + tuple1["col1"] = "Test"; + tuple1["col2"] = 42; + + SQL::Tuple tuple2(descriptor); + tuple2["col1"] = "Test"; + tuple2["col2"] = 12; + + SQL::Tuple tuple3(descriptor); + tuple3["col1"] = "Text"; + tuple3["col2"] = 12; + + EXPECT(tuple1 <= tuple2); + EXPECT(tuple1 < tuple2); + EXPECT(tuple2 >= tuple1); + EXPECT(tuple2 > tuple1); + + EXPECT(tuple1 <= tuple3); + EXPECT(tuple1 < tuple3); + EXPECT(tuple3 >= tuple1); + EXPECT(tuple3 > tuple1); +} diff --git a/Userland/Libraries/LibSQL/CMakeLists.txt b/Userland/Libraries/LibSQL/CMakeLists.txt index faec52cb2a1..f4d79cd413f 100644 --- a/Userland/Libraries/LibSQL/CMakeLists.txt +++ b/Userland/Libraries/LibSQL/CMakeLists.txt @@ -1,9 +1,11 @@ set(SOURCES - Lexer.cpp - Parser.cpp - SyntaxHighlighter.cpp - Token.cpp -) + Lexer.cpp + Parser.cpp + SyntaxHighlighter.cpp + Token.cpp + Tuple.cpp + Value.cpp + ) serenity_lib(LibSQL sql) target_link_libraries(LibSQL LibCore LibSyntax) diff --git a/Userland/Libraries/LibSQL/Forward.h b/Userland/Libraries/LibSQL/Forward.h index 14c08d0923d..fffb12748fb 100644 --- a/Userland/Libraries/LibSQL/Forward.h +++ b/Userland/Libraries/LibSQL/Forward.h @@ -22,6 +22,8 @@ class ColumnNameExpression; class CommonTableExpression; class CommonTableExpressionList; class CreateTable; +class TupleDescriptor; +struct TupleElement; class Delete; class DropColumn; class DropTable; @@ -58,7 +60,9 @@ class Statement; class StringLiteral; class TableOrSubquery; class Token; +class Tuple; class TypeName; class UnaryOperatorExpression; class Update; +class Value; } diff --git a/Userland/Libraries/LibSQL/Serialize.h b/Userland/Libraries/LibSQL/Serialize.h new file mode 100644 index 00000000000..de9e027e7df --- /dev/null +++ b/Userland/Libraries/LibSQL/Serialize.h @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2021, Jan de Visser + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include + +namespace SQL { + +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); +} + +template +void serialize_to(ByteBuffer& buffer, T const& t) +{ + buffer.append(&t, sizeof(T)); +} + +} diff --git a/Userland/Libraries/LibSQL/Tuple.cpp b/Userland/Libraries/LibSQL/Tuple.cpp new file mode 100644 index 00000000000..406484e924b --- /dev/null +++ b/Userland/Libraries/LibSQL/Tuple.cpp @@ -0,0 +1,245 @@ +/* + * Copyright (c) 2021, Jan de Visser + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include + +#include +#include +#include +#include +#include +#include + +namespace SQL { + +Tuple::Tuple() + : m_descriptor() + , m_data() +{ +} + +Tuple::Tuple(TupleDescriptor const& descriptor, u32 pointer) + : m_descriptor(descriptor) + , m_data() + , m_pointer(pointer) +{ + for (auto& element : descriptor) { + m_data.append(Value(element.type)); + } +} + +Tuple::Tuple(TupleDescriptor const& descriptor, ByteBuffer& buffer, size_t& offset) + : Tuple(descriptor) +{ + deserialize(buffer, offset); +} + +Tuple::Tuple(TupleDescriptor const& descriptor, ByteBuffer& buffer) + : Tuple(descriptor) +{ + size_t offset = 0; + deserialize(buffer, offset); +} + +void Tuple::deserialize(ByteBuffer& buffer, size_t& offset) +{ + dbgln_if(SQL_DEBUG, "deserialize tuple at offset {}", offset); + deserialize_from(buffer, offset, m_pointer); + dbgln_if(SQL_DEBUG, "pointer: {}", m_pointer); + m_data.clear(); + for (auto& part : m_descriptor) { + m_data.append(Value(part.type, buffer, offset)); + dbgln_if(SQL_DEBUG, "Deserialized element {} = {}", part.name, m_data.last().to_string().value()); + } +} + +void Tuple::serialize(ByteBuffer& buffer) const +{ + VERIFY(m_descriptor.size() == m_data.size()); + dbgln_if(SQL_DEBUG, "Serializing tuple pointer {}", pointer()); + serialize_to(buffer, pointer()); + 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_part_definition = m_descriptor[ix]; + dbgln("Serialized part {} = {}", key_part_definition.name, (str_opt.has_value()) ? str_opt.value() : "(null)"); + } + key_part.serialize(buffer); + } +} + +Tuple::Tuple(Tuple const& other) + : m_descriptor() + , m_data() +{ + copy_from(other); +} + +Tuple& Tuple::operator=(Tuple const& other) +{ + if (this != &other) { + copy_from(other); + } + return *this; +} + +Optional Tuple::index_of(String name) const +{ + auto n = move(name); + for (auto ix = 0u; ix < m_descriptor.size(); ix++) { + auto& part = m_descriptor[ix]; + if (part.name == n) { + return (int)ix; + } + } + return {}; +} + +Value const& Tuple::operator[](size_t ix) const +{ + VERIFY(ix < m_data.size()); + return m_data[ix]; +} + +Value& Tuple::operator[](size_t ix) +{ + VERIFY(ix < m_data.size()); + return m_data[ix]; +} + +Value const& Tuple::operator[](String const& name) const +{ + auto index = index_of(name); + VERIFY(index.has_value()); + return (*this)[index.value()]; +} + +Value& Tuple::operator[](String const& name) +{ + auto index = index_of(name); + VERIFY(index.has_value()); + return (*this)[index.value()]; +} + +void Tuple::append(const Value& value) +{ + VERIFY(m_descriptor.size() == 0); + m_data.append(value); +} + +Tuple& Tuple::operator+=(Value const& value) +{ + append(value); + return *this; +} + +bool Tuple::is_compatible(Tuple const& other) const +{ + if ((m_descriptor.size() == 0) && (other.m_descriptor.size() == 0)) { + return true; + } + if (m_descriptor.size() != other.m_descriptor.size()) { + return false; + } + for (auto ix = 0u; ix < m_descriptor.size(); ix++) { + auto& my_part = m_descriptor[ix]; + auto& other_part = other.m_descriptor[ix]; + if (my_part.type != other_part.type) { + return false; + } + if (my_part.order != other_part.order) { + return false; + } + } + return true; +} + +String Tuple::to_string() const +{ + StringBuilder builder; + for (auto& part : m_data) { + if (!builder.is_empty()) { + builder.append('|'); + } + auto str_opt = part.to_string(); + builder.append((str_opt.has_value()) ? str_opt.value() : "(null)"); + } + if (pointer() != 0) { + builder.appendff(":{}", pointer()); + } + return builder.build(); +} + +size_t Tuple::size() const +{ + size_t sz = sizeof(u32); + for (auto& part : m_data) { + sz += part.size(); + } + return sz; +} + +void Tuple::copy_from(const Tuple& other) +{ + m_descriptor.clear(); + for (TupleElement const& part : other.m_descriptor) { + m_descriptor.append(part); + } + m_data.clear(); + for (auto& part : other.m_data) { + m_data.append(part); + } + m_pointer = other.pointer(); +} + +int Tuple::compare(const Tuple& other) const +{ + auto num_values = min(m_data.size(), other.m_data.size()); + VERIFY(num_values > 0); + for (auto ix = 0u; ix < num_values; ix++) { + auto ret = m_data[ix].compare(other.m_data[ix]); + if (ret != 0) { + if ((ix < m_descriptor.size()) && m_descriptor[ix].order == Order::Descending) + ret = -ret; + return ret; + } + } + return 0; +} + +int Tuple::match(const Tuple& other) const +{ + auto other_index = 0u; + for (auto& part : other.descriptor()) { + auto other_value = other[other_index]; + if (other_value.is_null()) + return 0; + auto my_index = index_of(part.name); + if (!my_index.has_value()) + return -1; + auto ret = m_data[my_index.value()].compare(other_value); + if (ret != 0) + return (m_descriptor[my_index.value()].order == Order::Descending) ? -ret : ret; + other_index++; + } + return 0; +} + +u32 Tuple::hash() const +{ + u32 ret = 0u; + for (auto& value : m_data) { + // This is an extension of the pair_int_hash function from AK/HashFunctions.h: + if (!ret) + ret = value.hash(); + else + ret = int_hash((ret * 209) ^ (value.hash() * 413)); + } + return ret; +} + +} diff --git a/Userland/Libraries/LibSQL/Tuple.h b/Userland/Libraries/LibSQL/Tuple.h new file mode 100644 index 00000000000..19dc2d4dd9b --- /dev/null +++ b/Userland/Libraries/LibSQL/Tuple.h @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2021, Jan de Visser + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include + +namespace SQL { + +/** + * A Key is an element of a random-access data structure persisted in a Heap. + * Key objects stored in such a structure have a definition controlling the + * number of parts or columns the key has, the types of the parts, and the + * sort order of these parts. Besides having an optional definition, a Key + * consists of one Value object per part. In addition, keys have a u32 pointer + * member which points to a Heap location. + * + * Key objects without a definition can be used to locate/find objects in + * a searchable data collection. + * + * FIXME Currently the Key definition is passed as an `IndexDefinition` meta + * data object, meaning that names are associated with both the definition + * and the parts of the key. These names are not used, meaning that key + * definitions should probably be constructed in a different way. + */ +class Tuple { +public: + Tuple(); + explicit Tuple(TupleDescriptor const&, u32 pointer = 0); + Tuple(TupleDescriptor const&, ByteBuffer&, size_t&); + Tuple(TupleDescriptor const&, ByteBuffer&); + Tuple(Tuple const&); + virtual ~Tuple() = default; + + Tuple& operator=(Tuple const&); + + [[nodiscard]] String to_string() const; + explicit operator String() const { return to_string(); } + + bool operator<(Tuple const& other) const { return compare(other) < 0; } + bool operator<=(Tuple const& other) const { return compare(other) <= 0; } + bool operator==(Tuple const& other) const { return compare(other) == 0; } + bool operator!=(Tuple const& other) const { return compare(other) != 0; } + bool operator>(Tuple const& other) const { return compare(other) > 0; } + bool operator>=(Tuple const& other) const { return compare(other) >= 0; } + + [[nodiscard]] bool is_null() const { return m_data.is_empty(); } + [[nodiscard]] bool has(String const& name) const { return index_of(name).has_value(); } + + Value const& operator[](size_t ix) const; + Value& operator[](size_t ix); + Value const& operator[](String const& name) const; + Value& operator[](String const& name); + void append(Value const&); + Tuple& operator+=(Value const&); + [[nodiscard]] bool is_compatible(Tuple const&) const; + + [[nodiscard]] u32 pointer() const { return m_pointer; } + void set_pointer(u32 ptr) { m_pointer = ptr; } + + [[nodiscard]] size_t size() const; + [[nodiscard]] size_t length() const { return m_descriptor.size(); } + [[nodiscard]] TupleDescriptor descriptor() const { return m_descriptor; } + [[nodiscard]] int compare(Tuple const&) const; + [[nodiscard]] int match(Tuple const&) const; + [[nodiscard]] u32 hash() const; + virtual void serialize(ByteBuffer&) const; + [[nodiscard]] virtual size_t data_length() const { return descriptor().data_length(); } + +protected: + [[nodiscard]] Optional index_of(String) const; + void copy_from(Tuple const&); + void deserialize(ByteBuffer&, size_t&); + +private: + TupleDescriptor m_descriptor; + Vector m_data; + u32 m_pointer { 0 }; +}; + +} diff --git a/Userland/Libraries/LibSQL/TupleDescriptor.h b/Userland/Libraries/LibSQL/TupleDescriptor.h new file mode 100644 index 00000000000..2fc34172f84 --- /dev/null +++ b/Userland/Libraries/LibSQL/TupleDescriptor.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2021, Jan de Visser + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include + +namespace SQL { + +struct TupleElement { + String name { "" }; + SQLType type { SQLType::Text }; + Order order { Order::Ascending }; + + bool operator==(TupleElement const&) const = default; +}; + +class TupleDescriptor : public Vector { +public: + TupleDescriptor() = default; + TupleDescriptor(TupleDescriptor const&) = default; + ~TupleDescriptor() = default; + + [[nodiscard]] size_t data_length() const + { + size_t sz = sizeof(u32); + for (auto& part : *this) { + sz += size_of(part.type); + } + return sz; + } +}; + +} diff --git a/Userland/Libraries/LibSQL/Type.h b/Userland/Libraries/LibSQL/Type.h new file mode 100644 index 00000000000..c9350d899e3 --- /dev/null +++ b/Userland/Libraries/LibSQL/Type.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2021, Jan de Visser + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include + +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)) + +enum class SQLType { +#undef __ENUMERATE_SQL_TYPE +#define __ENUMERATE_SQL_TYPE(name, cardinal, type, impl, size) type = (cardinal), + ENUMERATE_SQL_TYPES(__ENUMERATE_SQL_TYPE) +#undef __ENUMERATE_SQL_TYPE +}; + +inline static size_t size_of(SQLType t) +{ + switch (t) { +#undef __ENUMERATE_SQL_TYPE +#define __ENUMERATE_SQL_TYPE(name, cardinal, type, impl, size) \ + case SQLType::type: \ + return size; + ENUMERATE_SQL_TYPES(__ENUMERATE_SQL_TYPE) +#undef __ENUMERATE_SQL_TYPE + default: + VERIFY_NOT_REACHED(); + } +} + +} diff --git a/Userland/Libraries/LibSQL/Value.cpp b/Userland/Libraries/LibSQL/Value.cpp new file mode 100644 index 00000000000..06bf3b41ccd --- /dev/null +++ b/Userland/Libraries/LibSQL/Value.cpp @@ -0,0 +1,413 @@ +/* + * Copyright (c) 2021, Jan de Visser + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include + +namespace SQL { + +Value::Value(SQLType sql_type) +{ + setup(sql_type); +} + +Value::Value(SQLType sql_type, ByteBuffer& buffer, size_t& offset) +{ + setup(sql_type); + m_deserialize(buffer, offset); + m_is_null = false; +} + +Value::Value(Value const& other) +{ + setup(other.type()); + m_is_null = other.is_null(); + if (!m_is_null) + m_assign_value(other); +} + +Value::~Value() +{ +} + +Value const& Value::null() +{ + static Value s_null; + return s_null; +} + +Value& Value::operator=(Value const& other) +{ + if (this != &other) { + m_is_null = other.is_null(); + if (!m_is_null) { + VERIFY(can_cast(other)); + m_assign_value(other); + } + } + return (*this); +} + +Value& Value::operator=(String const& value) +{ + m_assign_string(value); + m_is_null = false; + return (*this); +} + +Value& Value::operator=(int value) +{ + m_assign_int(value); + m_is_null = false; + return (*this); +} + +Value& Value::operator=(u32 value) +{ + m_assign_int(static_cast(value)); + m_is_null = false; + return (*this); +} + +Value& Value::operator=(double value) +{ + m_assign_double(value); + m_is_null = false; + return (*this); +} + +Value& Value::set_null() +{ + m_is_null = true; + return (*this); +} + +Optional Value::to_string() const +{ + if (!m_is_null) + return m_to_string(); + 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 {}; + } +} + +Value::operator int() const +{ + auto i = to_int(); + VERIFY(i.has_value()); + 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(); + VERIFY(i.has_value()); + 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(); +} + +bool Value::can_cast(Value const& other) const +{ + if (other.is_null()) + return true; + if (type() == other.type()) + return true; + return m_can_cast(other); +} + +bool Value::operator==(String const& other) const +{ + 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(); + } +} + +void Value::setup_text() +{ + m_impl = String(""); + m_type_name = []() { return "Text"; }; + m_size = []() { return 64 + sizeof(int); }; + + 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; + }; + + m_serialize = [&](ByteBuffer& buffer) { + char zeroes[64]; + + 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); + }; + + m_assign_value = [&](Value const& other) { + auto str = other.to_string(); + VERIFY(str.has_value()); + m_impl = str.value(); + }; + + m_assign_string = [&](String const& string) { + m_impl = string; + }; + + m_assign_int = [&](int i) { + m_impl = String::number(i); + }; + + m_assign_double = [&](double d) { + m_impl = String::number(d); + }; + + m_to_string = [&]() -> Optional { + return m_impl.get(); + }; + + m_to_int = [&]() -> Optional { + return m_impl.get().to_int(); + }; + + m_to_double = [&]() -> Optional { + char* end_ptr; + double ret = strtod(m_impl.get().characters(), &end_ptr); + if (end_ptr == m_impl.get().characters()) { + return {}; + } + return ret; + }; + + 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; + } + if (s1.value() == s2.value()) + return 0; + return (s1.value() < s2.value()) ? -1 : 1; + }; + + m_can_cast = [](Value const&) -> bool { + return true; + }; + + m_hash = [&]() { + return m_impl.get().hash(); + }; +} + +void Value::setup_int() +{ + 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()); + }; +} + +void Value::setup_float() +{ + m_impl.set(0.0); + m_type_name = []() { return "Float"; }; + m_size = []() { return sizeof(double); }; + + 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); + }; + + m_serialize = [&](ByteBuffer& buffer) { + buffer.append(m_impl.get_pointer(), sizeof(double)); + }; + + 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; + } + auto diff = m_impl.get() - casted.value(); + return (diff < NumericLimits::epsilon()) ? 0 : ((diff > 0) ? 1 : -1); + }; + + m_can_cast = [](Value const& other) -> bool { + auto dbl = other.to_double(); + return dbl.has_value(); + }; + + // Using floats in hash functions is a bad idea. Let's disable that for now. + m_hash = []() -> u32 { + VERIFY_NOT_REACHED(); + }; +} + +} diff --git a/Userland/Libraries/LibSQL/Value.h b/Userland/Libraries/LibSQL/Value.h new file mode 100644 index 00000000000..74c90a8feab --- /dev/null +++ b/Userland/Libraries/LibSQL/Value.h @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2021, Jan de Visser + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include +#include + +namespace SQL { + +/** + * 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 { +public: + explicit Value(SQLType sql_type = SQLType::Text); + Value(SQLType sql_type, ByteBuffer& buffer, size_t& offset); + Value(Value const& other); + ~Value(); + + static Value const& null(); + + Value& operator=(Value&& other) noexcept + { + (*this) = other; + return (*this); + } + Value& operator=(Value const& other); + Value& operator=(String const&); + Value& operator=(String&& string) + { + operator=(string); + return *this; + } + Value& operator=(int); + Value& operator=(u32); + Value& operator=(double); + Value& set_null(); + + 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]] bool can_cast(Value const&) const; + [[nodiscard]] u32 hash() const { return (is_null()) ? 0 : m_hash(); } + + 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; } + + void serialize(ByteBuffer& buffer) const + { + VERIFY(!is_null()); + m_serialize(buffer); + } + +private: + void setup(SQLType sql_type); + void setup_text(); + void setup_int(); + void setup_float(); + + 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 {}; +}; + +}