ladybird/Userland/Libraries/LibSQL/Tuple.cpp
Jan de Visser 2a46529170 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.
2021-06-19 22:06:45 +02:00

245 lines
5.8 KiB
C++

/*
* 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;
}
}