summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Tests/LibSQL/TestSqlValueAndTuple.cpp215
-rw-r--r--Userland/Libraries/LibSQL/CMakeLists.txt12
-rw-r--r--Userland/Libraries/LibSQL/Forward.h4
-rw-r--r--Userland/Libraries/LibSQL/Serialize.h28
-rw-r--r--Userland/Libraries/LibSQL/Tuple.cpp245
-rw-r--r--Userland/Libraries/LibSQL/Tuple.h87
-rw-r--r--Userland/Libraries/LibSQL/TupleDescriptor.h39
-rw-r--r--Userland/Libraries/LibSQL/Type.h40
-rw-r--r--Userland/Libraries/LibSQL/Value.cpp413
-rw-r--r--Userland/Libraries/LibSQL/Value.h108
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 {};
+};
+
+}