diff options
author | Jan de Visser <jan@de-visser.net> | 2021-06-17 13:23:52 -0400 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2021-06-19 22:06:45 +0200 |
commit | 2a465291703f553b281639cc13f4a834ff4125ab (patch) | |
tree | bf4a44dbcad2282513b5357c47f7b4d8c444f338 /Userland | |
parent | a6ba05b02b7e89102b3e724d48ae88fa38698334 (diff) | |
download | serenity-2a465291703f553b281639cc13f4a834ff4125ab.zip |
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.
Diffstat (limited to 'Userland')
-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 |
9 files changed, 971 insertions, 5 deletions
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 {}; +}; + +} |