mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-12-04 05:20:30 +00:00
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.
This commit is contained in:
parent
a6ba05b02b
commit
2a46529170
Notes:
sideshowbarker
2024-07-18 12:00:45 +09:00
Author: https://github.com/JanDeVisser 🔰 Commit: https://github.com/SerenityOS/serenity/commit/2a465291703 Pull-request: https://github.com/SerenityOS/serenity/pull/7483 Reviewed-by: https://github.com/trflynn89 ✅
10 changed files with 1186 additions and 5 deletions
215
Tests/LibSQL/TestSqlValueAndTuple.cpp
Normal file
215
Tests/LibSQL/TestSqlValueAndTuple.cpp
Normal file
|
@ -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);
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
28
Userland/Libraries/LibSQL/Serialize.h
Normal file
28
Userland/Libraries/LibSQL/Serialize.h
Normal file
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
245
Userland/Libraries/LibSQL/Tuple.cpp
Normal file
245
Userland/Libraries/LibSQL/Tuple.cpp
Normal file
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
87
Userland/Libraries/LibSQL/Tuple.h
Normal file
87
Userland/Libraries/LibSQL/Tuple.h
Normal file
|
@ -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 };
|
||||
};
|
||||
|
||||
}
|
39
Userland/Libraries/LibSQL/TupleDescriptor.h
Normal file
39
Userland/Libraries/LibSQL/TupleDescriptor.h
Normal file
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
40
Userland/Libraries/LibSQL/Type.h
Normal file
40
Userland/Libraries/LibSQL/Type.h
Normal file
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
413
Userland/Libraries/LibSQL/Value.cpp
Normal file
413
Userland/Libraries/LibSQL/Value.cpp
Normal file
|
@ -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();
|
||||
};
|
||||
}
|
||||
|
||||
}
|
108
Userland/Libraries/LibSQL/Value.h
Normal file
108
Userland/Libraries/LibSQL/Value.h
Normal file
|
@ -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 {};
|
||||
};
|
||||
|
||||
}
|
Loading…
Reference in a new issue