diff options
-rw-r--r-- | Tests/LibSQL/TestSqlValueAndTuple.cpp | 215 | ||||
-rw-r--r-- | Userland/Libraries/LibSQL/CMakeLists.txt | 12 | ||||
-rw-r--r-- | Userland/Libraries/LibSQL/Forward.h | 4 | ||||
-rw-r--r-- | Userland/Libraries/LibSQL/Serialize.h | 28 | ||||
-rw-r--r-- | Userland/Libraries/LibSQL/Tuple.cpp | 245 | ||||
-rw-r--r-- | Userland/Libraries/LibSQL/Tuple.h | 87 | ||||
-rw-r--r-- | Userland/Libraries/LibSQL/TupleDescriptor.h | 39 | ||||
-rw-r--r-- | Userland/Libraries/LibSQL/Type.h | 40 | ||||
-rw-r--r-- | Userland/Libraries/LibSQL/Value.cpp | 413 | ||||
-rw-r--r-- | Userland/Libraries/LibSQL/Value.h | 108 |
10 files changed, 1186 insertions, 5 deletions
diff --git a/Tests/LibSQL/TestSqlValueAndTuple.cpp b/Tests/LibSQL/TestSqlValueAndTuple.cpp new file mode 100644 index 0000000000..7740e5ce48 --- /dev/null +++ b/Tests/LibSQL/TestSqlValueAndTuple.cpp @@ -0,0 +1,215 @@ +/* + * Copyright (c) 2021, Jan de Visser <jan@de-visser.net> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <unistd.h> + +#include <LibSQL/Meta.h> +#include <LibSQL/Row.h> +#include <LibSQL/Tuple.h> +#include <LibSQL/Value.h> +#include <LibTest/TestCase.h> + +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 faec52cb2a..f4d79cd413 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 14c08d0923..fffb12748f 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 0000000000..de9e027e7d --- /dev/null +++ b/Userland/Libraries/LibSQL/Serialize.h @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2021, Jan de Visser <jan@de-visser.net> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include <AK/ByteBuffer.h> +#include <string.h> + +namespace SQL { + +template<typename T> +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<typename T> +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 0000000000..406484e924 --- /dev/null +++ b/Userland/Libraries/LibSQL/Tuple.cpp @@ -0,0 +1,245 @@ +/* + * Copyright (c) 2021, Jan de Visser <jan@de-visser.net> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <cstring> + +#include <AK/String.h> +#include <AK/StringBuilder.h> +#include <LibSQL/Serialize.h> +#include <LibSQL/Tuple.h> +#include <LibSQL/TupleDescriptor.h> +#include <LibSQL/Value.h> + +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<u32>(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<u32>(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<size_t> 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 0000000000..19dc2d4dd9 --- /dev/null +++ b/Userland/Libraries/LibSQL/Tuple.h @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2021, Jan de Visser <jan@de-visser.net> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include <AK/Debug.h> +#include <LibSQL/Forward.h> +#include <LibSQL/TupleDescriptor.h> +#include <LibSQL/Value.h> + +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<size_t> index_of(String) const; + void copy_from(Tuple const&); + void deserialize(ByteBuffer&, size_t&); + +private: + TupleDescriptor m_descriptor; + Vector<Value> 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 0000000000..2fc34172f8 --- /dev/null +++ b/Userland/Libraries/LibSQL/TupleDescriptor.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2021, Jan de Visser <jan@de-visser.net> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include <AK/Vector.h> +#include <LibSQL/AST.h> +#include <LibSQL/Type.h> + +namespace SQL { + +struct TupleElement { + String name { "" }; + SQLType type { SQLType::Text }; + Order order { Order::Ascending }; + + bool operator==(TupleElement const&) const = default; +}; + +class TupleDescriptor : public Vector<TupleElement> { +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 0000000000..c9350d899e --- /dev/null +++ b/Userland/Libraries/LibSQL/Type.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2021, Jan de Visser <jan@de-visser.net> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include <AK/HashMap.h> +#include <AK/String.h> + +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 0000000000..06bf3b41cc --- /dev/null +++ b/Userland/Libraries/LibSQL/Value.cpp @@ -0,0 +1,413 @@ +/* + * Copyright (c) 2021, Jan de Visser <jan@de-visser.net> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <LibSQL/Value.h> +#include <cstring> + +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<int>(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<String> 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<int> 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<u32> Value::to_u32() const +{ + if (!m_is_null) { + auto ret = m_to_int(); + if (ret.has_value()) + return static_cast<u32>(ret.value()); + else + return {}; + } else { + return {}; + } +} + +Value::operator u32() const +{ + auto i = to_u32(); + VERIFY(i.has_value()); + return i.value(); +} + +Optional<double> 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<String>().length(), 63); + buffer.append(&len, sizeof(int)); + buffer.append(m_impl.get<String>().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<String> { + return m_impl.get<String>(); + }; + + m_to_int = [&]() -> Optional<int> { + return m_impl.get<String>().to_int(); + }; + + m_to_double = [&]() -> Optional<double> { + char* end_ptr; + double ret = strtod(m_impl.get<String>().characters(), &end_ptr); + if (end_ptr == m_impl.get<String>().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<String>().hash(); + }; +} + +void Value::setup_int() +{ + m_impl.set<int>(0); + m_type_name = []() { return "Integer"; }; + m_size = []() { return sizeof(int); }; + + m_deserialize = [&](ByteBuffer& buffer, size_t& at_offset) { + memcpy(m_impl.get_pointer<int>(), buffer.offset_pointer((int)at_offset), sizeof(int)); + at_offset += sizeof(int); + }; + + m_serialize = [&](ByteBuffer& buffer) { + buffer.append(m_impl.get_pointer<int>(), 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<int>(move(i)); + }; + + m_assign_double = [&](double d) { + m_impl.set<int>((int)d); + }; + + m_to_string = [&]() -> Optional<String> { + StringBuilder builder; + builder.appendff("{}", m_impl.get<int>()); + return builder.build(); + }; + + m_to_int = [&]() -> Optional<int> { + return m_impl.get<int>(); + }; + + m_to_double = [&]() -> Optional<double> { + return static_cast<double>(m_impl.get<int>()); + }; + + m_compare = [&](Value const& other) -> int { + auto casted = other.to_int(); + if (!casted.has_value()) { + return 1; + } + return m_impl.get<int>() - 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<int>()); + }; +} + +void Value::setup_float() +{ + m_impl.set<double>(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<double>(), buffer.offset_pointer((int)at_offset), sizeof(double)); + at_offset += sizeof(double); + }; + + m_serialize = [&](ByteBuffer& buffer) { + buffer.append(m_impl.get_pointer<double>(), sizeof(double)); + }; + + m_to_string = [&]() -> Optional<String> { + StringBuilder builder; + builder.appendff("{}", m_impl.get<double>()); + return builder.build(); + }; + + m_to_int = [&]() -> Optional<int> { + return (int)m_impl.get<double>(); + }; + + m_to_double = [&]() -> Optional<double> { + return m_impl.get<double>(); + }; + + m_assign_value = [&](Value const& other) { + auto dbl = other.to_double(); + VERIFY(dbl.has_value()); + m_impl.set<double>(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<double>(move(dbl)); + }; + + m_assign_int = [&](int i) { + m_impl.set<double>(static_cast<double>(i)); + }; + + m_assign_double = [&](double d) { + m_impl.set<double>(move(d)); + }; + + m_compare = [&](Value const& other) -> int { + auto casted = other.to_double(); + if (!casted.has_value()) { + return 1; + } + auto diff = m_impl.get<double>() - casted.value(); + return (diff < NumericLimits<double>::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 0000000000..74c90a8fea --- /dev/null +++ b/Userland/Libraries/LibSQL/Value.h @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2021, Jan de Visser <jan@de-visser.net> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include <AK/ByteBuffer.h> +#include <AK/Function.h> +#include <AK/String.h> +#include <AK/Variant.h> +#include <LibSQL/Type.h> + +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<String> to_string() const; + explicit operator String() const; + Optional<int> to_int() const; + explicit operator int() const; + Optional<double> to_double() const; + explicit operator double() const; + Optional<u32> 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<Optional<String>()> m_to_string; + Function<Optional<int>()> m_to_int; + Function<Optional<double>()> m_to_double; + Function<void(Value const&)> m_assign_value; + Function<void(String const&)> m_assign_string; + Function<void(int)> m_assign_int; + Function<void(double)> m_assign_double; + Function<int(Value const&)> m_compare; + Function<void(ByteBuffer&)> m_serialize; + Function<void(ByteBuffer&, size_t& offset)> m_deserialize; + Function<size_t()> m_size; + Function<const char*()> m_type_name; + Function<bool(Value const&)> m_can_cast; + Function<u32()> m_hash; + + SQLType m_type { SQLType::Text }; + bool m_is_null { true }; + + Variant<String, int, double> m_impl {}; +}; + +} |