Общее описание задачи на C++

от вас требуется реализовать собственную in-memory реляционную базу данных.
Чтобы не беспокоиться о сетевых взаимодействиях, правах доступа, пользовательском интерфейсе и прочих подобных вещах, оформить ее предлагается в виде библиотеки, которую можно подключить к произвольному проекту и использовать либо напрямую из пользовательского кода, либо через сторонние интерфейсы (по сети, из консоли или как-нибудь еще).
Что представляет из себя база данных? На самом деле, просто набор таблиц, состоящих из именованных колонок (как правило, их число фиксировано, либо меняется очень редко) и произвольного количества строк (вот они могут добавляться и удаляться часто). Можно думать об этом, как о множестве неких структур.
Естественно, просто хранить таблицы недостатчоно, требуется так же выполнять запросы к ним. Запросы как правило заключаются в добавлении /удалении строк, изменениии существующих и выборке по произвольным условиям. Естественно, критична производительность всего этого.
Требуются следующие возможности:
1. Создание объекта базы. Независимых объектов может быть много и с точки зрения пользователя они ничего не знают друг о друге.
2. Выполнение запросов (иначе какая это база данных). Поскольку предполагается, что запросы могут поступать динамически, требуется единый интерфейс, принимающий их в текстовом виде. Условный db.execute(user_query). О языке запросов - в секции 3.
3. Получение результатов запросов. Если точнее, хочется иметь возможность проитерироваться по строкам результата и получить из них значения конкретных колонок по их именам.
4. Сохранение состояний базы в файл и последующая его загрузка.
5. Естественно, критичны скорость работы и потребление памяти.

Ожидаемый интерфейс выглядит примерно так (но вы можете придумать лучше):
```
memdb::Database db;
db.load_from_file(std::ifstream("db.bin", ios::bin));
auto result = db.execute(query);
if (result.is_ok()) {
for (auto &row : result) {
int i = resp.get<int>("column1");
std::string_view s = resp.get<std::string_view>("column2");
do_something(i, s);
}
} else {
std::cerr << "Error: " << result.get_error() << "\n";
}
...
db.save_to_file(std::ofstream("db.bin", ios::bin));
```

Способ сдачи и требования к решению

Проверка проходит вручную. На проверку вы сдаете код с описанием его работы и инструкцией по компиляции и использованию. 
Запрещено использование библиотек, которые уже реализуют требуемую функциональность или ее часть (например LLVM или sqlite).
Служебные библиотеки (например ranges) или библиотеки для тестирования (boost::ut, gtest) — разрешены.
К реализации должны быть написаны тесты.
Проверяющий в праве задать любые уточняющие вопросы.
Не забывайте про хорошую декомпозицию кода.

Язык запросов

Можно было бы реализовать полноценный SQL, но мы возьмем собственный язык, похожий на него. Это во-первых, позволит проигнорировать несущественные вещи из SQL, а во-вторых даст возможность для расширений языка.

Общие правила:

- Ключевые слова (select, create и т.п.) - нечувствительны к регистру.
- Названия пользовательских сущностей (таблиц и колонок) — чувствительны.
- Требования к названиям — такие же, как в С++. Для простоты считаем, что все символы — ASCI.
- Пробельные символы (включая перевод строки) служат для разделения ключевых слов или параметров. Разделитель может содержать произвольное их количество (но, конечно, больше нуля).

Типы данных

Для простоты введем всего 4 типа данных:

1. int32 — знаковое 32-битное целое число. В запросах записывается, как последовательность цифр, возможно начинающаяся со знака + или -.
2. bool — логическое значение (true или false). В запросах записывается одноименными ключевыми словами.
3. string[X] - строка длины не больше X. В запросах записывается внутри двойных ковычек ("). Может содержать экранированные символы, аналогично строковым литералам в С++ (например "\x00\n\t").
4. bytes[X] - байтовая последовательность длины ровно X. В запросах описывается либо аналогично строке, либо в шестнадцатиричном виде: 0x000abeef

Выражения

Чтобы делать любые хоть немного сложные запросы надо уметь выполнять операции с данными. Мы ограничимся следующими:

- Арифметика (+, -, *, /, %) - выполняется над числами, семантика аналогична С++.
- Сравнения (<, =, >, <=, >=, !=) - выполняется над двумя значениями одинакового типа, семантика аналогична С++, кроме операции =, которая аналогична ==. Числа сравниваются очевидным образом, строки и байтовые последовательности - лексикографически, логические значения - по правилу true > false.
- Логические операции &&, ||, !, ^^ - применяются к логическим значениям, аналогичны С++, кроме ^^, который аналогичен ^
- |s| — применяется к строкам и байтовым последовательностям означет длину строки/последовательности.
- + для строк — конкатенация
- Скобки — задают приоритет операций
Приоритеты операций аналогичны таковым в С++. Неявных преобразваний типов нет, т.е. 42 || 0 или true + false - считаются невалидными выражениями.

Create table

Основная сущность базы данных - это таблица. Но чтобы с ней работать, надо ее сначала создать: create table <имя таблицы> (описание
колонок)
Описание колонок - это последовательность разделенных запятой описаний следующего вида: [{attributes}] <name>: <type> [= <value>],
где
- name - имя колонки
- type - тип значений в колонке
- value - значение по-умалчанию для содержимого колонки, если оно есть
- attributes - последовательность аттрибутов колонки, перечисленных через запятую. Аттрибуты служат для более точного описания свойств колонки. Нужно реализовать хотя бы 3 аттрибута:
+ unique - означает, что значения в колонке должны быть уникальными
+ autoincrement - означет, что при добавлении новых строк значение в этой колонке в первый раз будет 0 и будет становиться на 1 больше в каждой новой строке. Применимо только к целочисленным колонкам.
+ key - то же, что unique, но автоматически построит индекс (о них ниже) по каждой колонке с этим аттрибутом.
Пример запроса: create table users ({key, autoincrement} id : int32, {unique} login: string[32], password_hash: bytes [8], is_admin: bool = false)

Insert

Служит для добавления новых строк в таблицу.
Синтаксис: insert (<values>) to <table>, где
- table - имя таблицы, куда надо вставить значения.
- values - одно из двух. Либо просто список значений через запятую, либо последовательность записей вида <name> = <value>, разделенных запятыми, где name - имя колонки в таблице, a value значение. В случае использования второго вида порядок колонок не важен. Колонки, которые имеют значение по умолчанию или атрибут autoincrement - можно опустить. В первой форме записи для этого просто оставляют пустое место между запятыми (см. примеры).
Примеры:
- insert (,"vasya", Oxdeadbeefdeadbeef) to users
- insert (login = "vasya", password_hash = Oxdeadbeefdeadbeef) to users (эквивалентно предыдущему)
- insert (, "admin", 0x0000000000000000, true) to users
- insert (
    is_admin = true,
    login = "admin",
    password_hash = 0x0000000000000000
) to users (эквивалентно предыдущему)

Select

Запрос выборки данных. Берет таблицу, выбирает из нее данные, соответствующие условия и возвращает новую таблицу.
Синтаксис: select <columns> from <table> where <condition>, где
- table - либо имя таблицы, из которой надо получить значения, либо выражение join (о них ниже)
- columns имена колонок через запятую в формате <table>.<column> (например users.id). Если в запросе используется только одна таблица то ее имя можно опустить и писать только имя колонки.
- condition - логическое выражение над значениями колонок, по которому надо отфильтровать результаты.
Пример: select id, login from users where is_admin || id < 10

Update
Запрос обновления данных.
Синтаксис: update <table> set <assignments> where <condition>,
где
- table - аналогично select
- condition аналогично select
- assignments набор имен колонок и их значений, синтаксис анналогичен второй форме insert за исключением того, что значения могут быть не константами, а выражениями от старых значений данной строки.
Примеры:
- update users set is_admin = true where login = "vasya"
- update users set login = login + "_deleted", is_admin = false where password_hash < 0x00000000ffffffff

Delete
Запрос удаления данных.
Синтаксис: delete <table> where <condition>, где
- table - имя таблицы, из которой надо удалить строки
- condition - аналогично select
Пример:
delete users where |login| % 2 = 0

Join
Естественно, делать запросы к отдельным таблицам - не особо полезно.
Чтобы собирать данные из нескольких таблиц служит join.
Это не отдельная команда, а конструкция, которая может использоваться в запроcax select и update вместо имени таблицы.
join делает очень простую вещь - он выбирает из первой и второй таблицы пары строк, подходящие под условие и составляет новую таблицу, имеющую колонки из обеих предыдущих и содержащую в соответствующих колонках данные из них.
Синтаксис: <table1> join <table2> on <condition>, где
- table1 и table2 - имена таблиц для объединения.
- condition - выражение, определяющее, какие пары записей брать.
Пример: пусть есть 2 таблицы users и posts:
id | login   | is_admin
1  | "vasya" | false
2  | "petya" | false
3  | "admin" | true
и
id | user_id | text
1  | 1       | "А"
2  | 1       | "B"
3  | 3       | "С"
Тoгдa select posts.id, users.login, posts.text from users join posts on users.id = posts.user_id where true
Должен вернуть
id | login   | text
1  | "vasya" | "A"
2  | "vasya" | "B"
3  | "admin" | "C"

Create index
Запрос создания индекса. Индексы никак не влияют на результат запросов, но могут менять время исполнения. Индекс создается по нескольким колонкам и позволяет базе в дальнейшем быстрее выполнять сравнения этих колонок. В нашей базе будет 2 вида индексов:
1. ordered - если такой был создан по колонке х то он позволяет быстро делать выборку, сравнивая значения этой колонки с чем-то. Например select … where x > 10. Естественно, ожидается, что это повлияет и на более сложные запросы с дополнительными условиями, например select … where x > 100 && x < 200 && y > x.
2. unordered создается по набору колонок и позволяет быстро отбирать строки, где весь этот набор равен чему-нибудь. Например, если создать такой индекс по колонкам х и у то ожидается, что запросы типа select … where x = 10 && y = 20 будут выполняться быстро. Аналогично предыдущему, условия могут быть и более сложными.
Мы будем считать, что индексы работают достаточно хорошо, если:

- для ordered - при наличии индекса по колонке х будет наблюдаться заметное ускорение при запросах с условиями вида (…) && (х < 1000) && (x > 500) && (…) (Естественно, порядок выражений может быть произвольным, вместо < и > могут идти другие сравнения, а вместо 1000 и 500 - другие значения, не обязательно числовые).
- для unordered - при наличии индекса по колонкам х, y, z будет наблюдаться заметное ускорение при запросах с условиями вида (…) && (x = 1) && (у = 2) && (z = 3) && (…). (Порядок выражений произвольный, значения могут быть другими).

Если в запросе можно применить несколько индексов то достаточно, чтобы был применен хотя бы один.
Hint: Возможно, тут пригодится STL…
Синтаксис: create <index_type> index on <table> by <columns>, где
- index_type - тип индекса
- table - имя таблицы
- columns - список колонок через запятую.
Примеры:
create ordered index on users by login
create unordered index on users by is_admin

Для всего этого задания можно добавить любые улучшения сверх описанного. Подойдут подзапросы, nullable колонки и 3 вида outer join, constexpr-запросы, ORM, structured binding на результат выборки, новые хитрые виды индексов и атрибутов, особо хорошая оптимизация и все, что может впечатлить проверяющего. Если ты что-то добавляешь, чего не было указано в задании - напиши об этом подробно, в коде сделай дополнительные пометки.

Весь функционал должен быть протестирован. Полно и чётко. За отсутствие тестов или неполное тестирование штрафы.

в настоящий момент структура проекта выглядит так:
memdb_project/
├── include/
│   └── memdb/
│       ├── core/
│       │   ├── Column.h
│       │   ├── Database.h
│       │   ├── DataType.h
│       │   ├── QueryExecutor.h
│       │   ├── QueryParser.h
│       │   ├── QueryResult.h
│       │   ├── Row.h
│       │   ├── Table.h
│       │   ├── Value.h
│       │   ├── exceptions/
│       │   │	├── DatabaseException.h
│       │   │	└── TypeMismatchException.h
│       │   ├── structs/
│       │   │	└── ParsedQuery.h
│       │   └── enums/
│       │   │	├── ColumnAttributes.h
│       │   	└── Type.h
│       ├── dependencies/
│       │   └── json.hpp # nlohmann/json
│       └── api/
├── src/
│   └── core/
│       ├── Column.cpp
│       ├── Database.cpp
│       ├── DataType.cpp
│       ├── QueryExecutor.cpp
│       ├── QueryParser.cpp
│       ├── QueryResult.cpp
│       ├── Row.cpp
│       ├── Table.cpp
│       └── Value.cpp
├── examples/
│   └── example_basic_usage.cpp
├── tests/
├── CMakeLists.txt
├── README.md
└── .gitignore

enums/ColumnAttribute.h
```
#ifndef MEMDB_CORE_ENUMS_COLUMNATTRIBUTE_H
#define MEMDB_CORE_ENUMS_COLUMNATTRIBUTE_H

namespace memdb {
namespace core {

enum class ColumnAttribute {
    Unique,
    AutoIncrement,
    Key
};

} // namespace core
} // namespace memdb

#endif // MEMDB_CORE_ENUMS_COLUMNATTRIBUTE_H
```
enums/Type.h
```
#ifndef MEMDB_CORE_ENUMS_TYPE_H
#define MEMDB_CORE_ENUMS_TYPE_H

namespace memdb {
namespace core {

enum class Type {
    Int32,
    Int64,
    Bool,
    String,
    Bytes
};

} // namespace core
} // namespace memdb

#endif // MEMDB_CORE_ENUMS_TYPE_H
```
exceptions/DatabaseException.h
```
#ifndef MEMDB_CORE_EXCEPTIONS_DATABASEEXCEPTION_H
#define MEMDB_CORE_EXCEPTIONS_DATABASEEXCEPTION_H

#include <stdexcept>
#include <string>

namespace memdb {
namespace core {
namespace exceptions {

class DatabaseException : public std::runtime_error {
public:
    explicit DatabaseException(const std::string& message)
        : std::runtime_error(message) {}
};

class TableNotFoundException : public DatabaseException {
public:
    explicit TableNotFoundException(const std::string& table_name)
        : DatabaseException("Table not found: " + table_name) {}
};

class TableAlreadyExistsException : public DatabaseException {
public:
    explicit TableAlreadyExistsException(const std::string& table_name)
        : DatabaseException("Table already exists: " + table_name) {}
};

class RowNotFoundException : public DatabaseException {
public:
    explicit RowNotFoundException(core::RowID row_id)
        : DatabaseException("Row ID not found: " + std::to_string(row_id)) {}
};

class SerializationException : public DatabaseException {
public:
    explicit SerializationException(const std::string& message)
        : DatabaseException("Serialization Error: " + message) {}
};

} // namespace exceptions
} // namespace core
} // namespace memdb

#endif // MEMDB_CORE_EXCEPTIONS_DATABASEEXCEPTION_H
```
exceptions/TypeMismatchException.h
```
#ifndef MEMDB_CORE_EXCEPTIONS_TYPEMISMATCHEXCEPTION_H
#define MEMDB_CORE_EXCEPTIONS_TYPEMISMATCHEXCEPTION_H

#include <stdexcept>
#include <string>

namespace memdb {
namespace core {
namespace exceptions {

class TypeMismatchException : public std::runtime_error {
public:
    explicit TypeMismatchException(const std::string& message)
        : std::runtime_error(message) {}
};

} // namespace exceptions
} // namespace core
} // namespace memdb

#endif // MEMDB_CORE_EXCEPTIONS_TYPEMISMATCHEXCEPTION_H
```
structs/ParsedQuery.h
```
#ifndef MEMDB_CORE_STRUCTS_PARSEDQUERY_H
#define MEMDB_CORE_STRUCTS_PARSEDQUERY_H

#include <string>
#include <vector>
#include <optional>
#include <unordered_map>
#include "memdb/core/Column.h"
#include "memdb/core/Value.h"

namespace memdb {
namespace core {

struct ParsedQuery {
    enum class QueryType {
        CreateTable,
        Insert,
        Select,
        Update,
        Delete,
        CreateIndex,
    } type;

    std::string table_name;
    std::vector<Column> columns;

    std::optional<std::vector<std::optional<Value>>> insert_values;
    std::optional<std::unordered_map<std::string, Value>> insert_named_values;

    // TODO: other fields
};

} // namespace core
} // namespace memdb

#endif // MEMDB_CORE_STRUCTS_PARSEDQUERY_H
```
Column.h
```
#ifndef MEMDB_CORE_COLUMN_H
#define MEMDB_CORE_COLUMN_H

#include "memdb/core/DataType.h"
#include "memdb/core/Value.h"

#include "memdb/core/enums/ColumnAttribute.h"

#include <string>
#include <optional>
#include <vector>
#include <stdexcept>

namespace memdb {
namespace core {

class Column {
public:
    Column(const std::string& name, const DataType& type,
           const std::vector<ColumnAttribute>& attributes = {},
           const std::optional<Value>& default_value = std::nullopt);

    const std::string& get_name() const;
    const DataType& get_type() const;
    const std::vector<ColumnAttribute>& get_attributes() const;
    const std::optional<Value>& get_default_value() const;

    bool has_attribute(ColumnAttribute attribute) const;

    std::string to_string() const;

private:
    std::string name_;
    DataType type_;
    std::vector<ColumnAttribute> attributes_;
    std::optional<Value> default_value_;
};

} // namespace core
} // namespace memdb

#endif // MEMDB_CORE_COLUMN_H
```
Database.h
```
#ifndef MEMDB_CORE_DATABASE_H
#define MEMDB_CORE_DATABASE_H

#include "memdb/core/Table.h"
#include "memdb/core/Value.h"
#include "memdb/core/Column.h"
#include "memdb/core/Row.h"
#include "memdb/core/QueryParser.h"
#include "memdb/core/QueryExecutor.h"

#include "memdb/core/structs/ParsedQuery.h"

#include <string>
#include <unordered_map>
#include <memory>
#include <optional>
#include <vector>
#include <fstream>
#include <stdexcept>

namespace memdb {
namespace core {

class Database {
public:
    Database();

    void create_table(const std::string& table_name, const std::vector<Column>& columns);
    void drop_table(const std::string& table_name);
    std::shared_ptr<Table> get_table(const std::string& table_name) const;
    bool has_table(const std::string& table_name) const;

    RowID insert_row(const std::string& table_name, const std::vector<std::optional<Value>>& values);
    void delete_row(const std::string& table_name, core::RowID row_id);
    Row& get_row(const std::string& table_name, core::RowID row_id);
    const Row& get_row(const std::string& table_name, core::RowID row_id) const;

    const std::unordered_map<std::string, std::shared_ptr<Table>>& get_all_tables() const;
    void save_to_file(const std::string& filename) const;
    void load_from_file(const std::string& filename);

    QueryResult execute(const std::string& query);
    std::string to_string() const;

private:
    std::unordered_map<std::string, std::shared_ptr<Table>> tables_;

    QueryParser parser_;
    QueryExecutor executor_;
};

} // namespace core
} // namespace memdb

#endif // MEMDB_CORE_DATABASE_H
```
DataType.h
```
#ifndef MEMDB_CORE_DATATYPE_H
#define MEMDB_CORE_DATATYPE_H

#include "memdb/core/enums/Type.h"

#include <string>
#include <variant>
#include <vector>
#include <cstdint>
#include <stdexcept>
#include <iostream>

namespace memdb {
namespace core {

class DataType {
public:
    DataType(Type type);
    DataType(Type type, size_t size); // for String[X] and Bytes[X]

    Type get_type() const;
    size_t get_size() const; // for String[X] adn Bytes[X]

    bool is_string() const;
    bool is_bytes() const;
    bool is_int32() const;
    bool is_int64() const;
    bool is_bool() const;

    std::string to_string() const;

private:
    Type type_;
    size_t size_; // for String[X] adn Bytes[X]
};

} // namespace core
} // namespace memdb

#endif // MEMDB_CORE_DATATYPE_H
```
QueryExecutor.h
```
#ifndef MEMDB_CORE_QUERYEXECUTOR_H
#define MEMDB_CORE_QUERYEXECUTOR_H

#include "memdb/core/QueryResult.h"

#include "memdb/core/structs/ParsedQuery.h"

namespace memdb {
namespace core {

class Database;

class QueryExecutor {
public:
    QueryResult execute(const ParsedQuery& parsed_query, Database& db);
};

} // namespace core
} // namespace memdb

#endif // MEMDB_CORE_QUERYEXECUTOR_H
```
QueryParser.h
```
#ifndef MEMDB_CORE_QUERYPARSER_H
#define MEMDB_CORE_QUERYPARSER_H

#include "memdb/core/Column.h"

#include "memdb/core/structs/ParsedQuery.h"

#include <string>
#include <memory>

namespace memdb {
namespace core {

class QueryParser {
public:
    ParsedQuery parse(const std::string& query);
    Value parse_value(const std::string& value_str);

private:
    Column parse_column_definition(const std::string& col_def);
    std::vector<std::string> split_columns(const std::string& columns_def);
    std::string trim(const std::string& s);
};

} // namespace core
} // namespace memdb

#endif // MEMDB_CORE_QUERYPARSER_H
```
QueryResult.h
```
#ifndef MEMDB_CORE_QUERYRESULT_H
#define MEMDB_CORE_QUERYRESULT_H

#include "memdb/core/Value.h"

#include <vector>
#include <string>
#include <optional>

namespace memdb {
namespace core {

class QueryResult {
public:
    QueryResult() : success_(true) {}
    QueryResult(const std::vector<std::vector<std::optional<Value>>>& data) : success_(true), data_(data) {}
    QueryResult(const std::string& error_message) : success_(false), error_message_(error_message) {}

    bool is_ok() const { return success_; }
    std::string get_error() const { return error_message_; }
    const std::vector<std::vector<std::optional<Value>>>& get_data() const { return data_; }

private:
    bool success_;
    std::string error_message_;
    std::vector<std::vector<std::optional<Value>>> data_;
};

} // namespace core
} // namespace memdb

#endif // MEMDB_CORE_QUERYRESULT_H
```
Row.h
```
#ifndef MEMDB_CORE_ROW_H
#define MEMDB_CORE_ROW_H

#include "memdb/core/Value.h"

#include <vector>
#include <optional>
#include <cstdint>
#include <stdexcept>

namespace memdb {
namespace core {

using RowID = uint64_t;

class Row {
public:
    Row(RowID id, const std::vector<std::optional<Value>>& values);

    RowID get_id() const;

    std::vector<std::optional<Value>>& get_values();
    const std::vector<std::optional<Value>>& get_values() const;

    std::optional<Value>& get_value(size_t index);
    const std::optional<Value>& get_value(size_t index) const;

    void set_value(size_t index, const Value& value);

private:
    RowID id_;
    std::vector<std::optional<Value>> values_;
};

} // namespace core
} // namespace memdb

#endif // MEMDB_CORE_ROW_H
```
Table.h
```
#ifndef MEMDB_CORE_TABLE_H
#define MEMDB_CORE_TABLE_H

#include "memdb/core/Column.h"
#include "memdb/core/DataType.h"
#include "memdb/core/Row.h"
#include "memdb/core/Value.h"

#include <string>
#include <vector>
#include <unordered_map>
#include <optional>
#include <memory>
#include <stdexcept>
#include <cstdint>
#include <json.hpp>


namespace memdb {
namespace core {

class Table {
public:
    Table(const std::string& name, const std::vector<Column>& columns);

    const std::string& get_name() const;
    const std::vector<Column>& get_columns() const;
    size_t get_column_index(const std::string& column_name) const;

    void add_column(const Column& column);
    bool has_column(const std::string& column_name) const;

    RowID insert_row(const std::vector<std::optional<Value>>& values);
    RowID insert_row(const std::vector<std::optional<Value>>& values, RowID id);
    void delete_row(RowID id);
    Row& get_row(RowID id);
    const Row& get_row(RowID id) const;
    const std::unordered_map<RowID, Row>& get_all_rows() const;

    void validate_row(const std::vector<std::optional<Value>>& values) const;

    nlohmann::json to_json() const;
    static Table from_json(const nlohmann::json& j);

    std::string to_string() const;

private:
    std::string name_;
    std::vector<Column> columns_;
    std::unordered_map<RowID, Row> rows_;
    RowID next_row_id_;
};

} // namespace core
} // namespace memdb

#endif // MEMDB_CORE_TABLE_H
```
Value.h
```
#ifndef MEMDB_CORE_VALUE_H
#define MEMDB_CORE_VALUE_H

#include "memdb/core/enums/Type.h"

#include <string>
#include <variant>
#include <vector>
#include <cstdint>
#include <optional>
#include <stdexcept>
#include <iostream>

namespace memdb {
namespace core {

class Value {
public:
    Value() = default;
    Value(int32_t int_val);
    Value(int64_t int_val);
    Value(bool bool_val);
    Value(const std::string& str_val);
    Value(const std::vector<uint8_t>& bytes_val);

    Type get_type() const;

    int32_t get_int() const;
    int64_t get_int64() const;
    bool get_bool() const;
    const std::string& get_string() const;
    const std::vector<uint8_t>& get_bytes() const;

    void set_int(int32_t int_val);
    void set_int64(int64_t int_val);
    void set_bool(bool bool_val);
    void set_string(const std::string& str_val);
    void set_bytes(const std::vector<uint8_t>& bytes_val);

    std::string to_string() const;

private:
    std::optional<std::variant<int32_t, int64_t, bool, std::string, std::vector<uint8_t>>> data_;
};

} // namespace core
} // namespace memdb

#endif // MEMDB_CORE_VALUE_H
```
Column.cpp
```
#include "memdb/core/Column.h"

namespace memdb {
namespace core {

// ======= Column Implementation =======

Column::Column(const std::string& name, const DataType& type,
               const std::vector<ColumnAttribute>& attributes,
               const std::optional<Value>& default_value)
    : name_(name), type_(type), attributes_(attributes), default_value_(default_value) {

    if (name.empty()) {
        throw std::invalid_argument("Column name cannot be empty.");
    }

    for (const auto& attr : attributes_) {
        if (attr == ColumnAttribute::AutoIncrement && !(type_.is_int32() || type_.is_int64())) {
            throw std::invalid_argument("AutoIncrement attribute can only be applied to int32 columns.");
        }
    }

    if (default_value_) {
        if (default_value_->get_type() != type_.get_type()) {
            throw std::invalid_argument("Default value type does not match column type.");
        }

        // for String and Bytes
        if (type_.is_string()) {
            if (default_value_->get_string().size() > type_.get_size()) {
                throw std::invalid_argument("Default string value exceeds defined size.");
            }
        } else if (type_.is_bytes()) {
            if (default_value_->get_bytes().size() > type_.get_size()) {
                throw std::invalid_argument("Default bytes value exceeds defined size.");
            }
        }
    }
}

const std::string& Column::get_name() const {
    return name_;
}

const DataType& Column::get_type() const {
    return type_;
}

const std::vector<ColumnAttribute>& Column::get_attributes() const {
    return attributes_;
}

const std::optional<Value>& Column::get_default_value() const {
    return default_value_;
}

bool Column::has_attribute(ColumnAttribute attribute) const {
    for (const auto& attr : attributes_) {
        if (attr == attribute) {
            return true;
        }
    }
    return false;
}

std::string Column::to_string() const {
    std::string attr_str;
    if (!attributes_.empty()) {
        attr_str += "{";
        for (size_t i = 0; i < attributes_.size(); ++i) {
            switch (attributes_[i]) {
                case ColumnAttribute::Unique:
                    attr_str += "unique";
                    break;
                case ColumnAttribute::AutoIncrement:
                    attr_str += "autoincrement";
                    break;
                case ColumnAttribute::Key:
                    attr_str += "key";
                    break;
                default:
                    attr_str += "unknown";
            }
            if (i < attributes_.size() - 1) {
                attr_str += ", ";
            }
        }
        attr_str += "} ";
    }

    std::string default_str = "";
    if (default_value_) {
        default_str = " = " + default_value_->to_string();
    }

    return attr_str + name_ + " : " + type_.to_string() + default_str;
}

} // namespace core
} // namespace memdb
```
Database.cpp
```
#include "memdb/core/Database.h"

#include "memdb/core/exceptions/DatabaseException.h"

#include <fstream>
#include <json.hpp>

namespace memdb {
namespace core {

using json = nlohmann::json;

// ======= Database Implementation =======

Database::Database() = default;

void Database::create_table(const std::string& table_name, const std::vector<Column>& columns) {
    if (has_table(table_name)) {
        throw exceptions::TableAlreadyExistsException(table_name);
    }
    auto table = std::make_shared<Table>(table_name, columns);
    tables_.emplace(table_name, table);
}

void Database::drop_table(const std::string& table_name) {
    auto it = tables_.find(table_name);
    if (it != tables_.end()) {
        tables_.erase(it);
    } else {
        throw exceptions::TableNotFoundException(table_name);
    }
}

std::shared_ptr<Table> Database::get_table(const std::string& table_name) const {
    auto it = tables_.find(table_name);
    if (it != tables_.end()) {
        return it->second;
    }
    throw exceptions::TableNotFoundException(table_name);
}

bool Database::has_table(const std::string& table_name) const {
    return tables_.find(table_name) != tables_.end();
}

core::RowID Database::insert_row(const std::string& table_name, const std::vector<std::optional<Value>>& values) {
    auto table = get_table(table_name);
    return table->insert_row(values);
}

void Database::delete_row(const std::string& table_name, core::RowID row_id) {
    auto table = get_table(table_name);
    table->delete_row(row_id);
}

Row& Database::get_row(const std::string& table_name, core::RowID row_id) {
    auto table = get_table(table_name);
    return table->get_row(row_id);
}

const Row& Database::get_row(const std::string& table_name, core::RowID row_id) const {
    auto table = get_table(table_name);
    return table->get_row(row_id);
}

const std::unordered_map<std::string, std::shared_ptr<Table>>& Database::get_all_tables() const {
    return tables_;
}

void Database::save_to_file(const std::string& filename) const {
    json j;
    j["tables"] = json::array();
    for (const auto& [name, table] : tables_) {
        j["tables"].push_back(table->to_json());
    }
    std::ofstream ofs(filename, std::ios::binary);
    if (!ofs) {
        throw exceptions::SerializationException("Failed to open file for saving: " + filename);
    }
    ofs << j.dump(4);
}

void Database::load_from_file(const std::string& filename) {
    std::ifstream ifs(filename, std::ios::binary);
    if (!ifs) {
        throw exceptions::SerializationException("Failed to open file for loading: " + filename);
    }
    json j;
    ifs >> j;
    if (!j.contains("tables")) {
        throw exceptions::SerializationException("Invalid database file format.");
    }
    for (const auto& table_json : j["tables"]) {
        Table table = Table::from_json(table_json);
        auto table_ptr = std::make_shared<Table>(table);
        tables_.emplace(table.get_name(), table_ptr);
    }
}

std::string Database::to_string() const {
    std::string db_str = "Database:\n";
    for (const auto& [name, table] : tables_) {
        db_str += table->to_string() + "\n";
    }
    return db_str;
}

QueryResult Database::execute(const std::string& query) {
    try {
        auto parsed_query = parser_.parse(query);
        return executor_.execute(parsed_query, *this);
    }
    catch (const std::invalid_argument& e) {
        return QueryResult(e.what());
    }
    catch (const exceptions::DatabaseException& e) {
        return QueryResult(e.what());
    }
    catch (...) {
        return QueryResult("Unknown error during query execution.");
    }
}

} // namespace core
} // namespace memdb
```
DataType.cpp
```
#include "memdb/core/DataType.h"

namespace memdb {
namespace core {

// ======= DataType Implementation =======

DataType::DataType(Type type) : type_(type), size_(0) {
    if ((type_ != Type::String && type_ != Type::Bytes) && size_ != 0) {
        throw std::invalid_argument("Size can only be set for String and Bytes types.");
    }
}

DataType::DataType(Type type, size_t size) : type_(type), size_(size) {
    if ((type_ != Type::String && type_ != Type::Bytes) || size_ == 0) {
        throw std::invalid_argument("Size can only be set for String and Bytes types with size > 0.");
    }
}

Type DataType::get_type() const {
    return type_;
}

size_t DataType::get_size() const {
    if (!is_string() && !is_bytes()) {
        throw std::logic_error("Only String and Bytes types have size.");
    }
    return size_;
}

bool DataType::is_string() const {
    return type_ == Type::String;
}

bool DataType::is_bytes() const {
    return type_ == Type::Bytes;
}

bool DataType::is_int32() const {
    return type_ == Type::Int32;
}

bool DataType::is_int64() const {
    return type_ == Type::Int64;
}

bool DataType::is_bool() const {
    return type_ == Type::Bool;
}

std::string DataType::to_string() const {
    switch (type_) {
        case Type::Int32:
            return "int32";
        case Type::Int64:
            return "int64";
        case Type::Bool:
            return "bool";
        case Type::String:
            return "string[" + std::to_string(size_) + "]";
        case Type::Bytes:
            return "bytes[" + std::to_string(size_) + "]";
        default:
            return "unknown";
    }
}

} // namespace core
} // namespace memdb
```
QueryExecutor.cpp
```
#include "memdb/core/Database.h"
#include "memdb/core/QueryExecutor.h"

#include "memdb/core/exceptions/DatabaseException.h"

#include <algorithm>
#include <iostream>

namespace memdb {
namespace core {

QueryResult QueryExecutor::execute(const ParsedQuery& parsed_query, Database& db) {
    switch (parsed_query.type) {
        case ParsedQuery::QueryType::CreateTable:
            try {
                db.create_table(parsed_query.table_name, parsed_query.columns);
                return QueryResult();
            }
            catch (const exceptions::DatabaseException& e) {
                return QueryResult(e.what());
            }
            break;
        
        case ParsedQuery::QueryType::Insert:
            try {
                std::shared_ptr<Table> table = db.get_table(parsed_query.table_name);
                core::RowID new_row_id = table->insert_row(*(parsed_query.insert_values));
                std::vector<std::vector<std::optional<Value>>> data = { { Value(static_cast<int64_t>(new_row_id)) } };
                return QueryResult(data);
            }
            catch (const exceptions::DatabaseException& e) {
                return QueryResult(e.what());
            }
            break;
        
        case ParsedQuery::QueryType::Select:
            return QueryResult("SELECT not yet implemented.");
            break;
        
        case ParsedQuery::QueryType::Update:
            return QueryResult("UPDATE not yet implemented.");
            break;
        
        case ParsedQuery::QueryType::Delete:
            return QueryResult("DELETE not yet implemented.");
            break;
        
        case ParsedQuery::QueryType::CreateIndex:
            return QueryResult("CREATE INDEX not yet implemented.");
            break;
        
        default:
            return QueryResult("Unsupported query type.");
    }
}

} // namespace core
} // namespace memdb
```
QueryParser.cpp
```
#include "memdb/core/QueryParser.h"

#include <sstream>
#include <algorithm>
#include <cctype>
#include <limits>
#include <stdexcept>
#include <regex>

namespace memdb {
namespace core {

ParsedQuery QueryParser::parse(const std::string& query) {
    std::string trimmed_query = trim(query);

    ParsedQuery pq;
    std::istringstream iss(trimmed_query);
    std::string command;
    iss >> command;

    std::transform(command.begin(), command.end(), command.begin(), ::tolower);

    if (command == "create") {
        std::string subcommand;
        iss >> subcommand;
        std::transform(subcommand.begin(), subcommand.end(), subcommand.begin(), ::tolower);
        if (subcommand == "table") {
            pq.type = ParsedQuery::QueryType::CreateTable;
            iss >> pq.table_name;
            char c;
            iss >> c;
            if (c != '(') {
                throw std::invalid_argument("Expected '(' after table name.");
            }

            std::string columns_def;
            std::getline(iss, columns_def, ')');

            std::vector<std::string> column_defs = split_columns(columns_def);

            for (const auto& col_def : column_defs) {
                std::cout << "Parsing Column Definition: " << col_def << std::endl;
                Column column = parse_column_definition(col_def);
                pq.columns.push_back(column);
            }
        }
        else if (subcommand == "index") {
            // TODO: do...
            throw std::invalid_argument("CREATE INDEX not yet implemented.");
        }
        else {
            throw std::invalid_argument("Unknown CREATE subcommand: " + subcommand);
        }
    }
    else if (command == "insert") {
        // insert (,"vasya", Oxdeadbeefdeadbeef) to users
        // insert (login = "vasya", password_hash = Oxdeadbeefdeadbeef) to users
        pq.type = ParsedQuery::QueryType::Insert;
        pq.insert_values = std::vector<std::optional<Value>>();

        std::regex insert_regex(R"(^insert\s*\((.*)\)\s*to\s+(\w+)$)", std::regex::icase);
        std::smatch matches;
        if (std::regex_match(trimmed_query, matches, insert_regex)) {
            std::string values_str = matches[1];
            std::string table_name = matches[2];
            pq.table_name = table_name;

            std::vector<std::string> value_defs;
            std::stringstream values_stream(values_str);
            std::string val_def;
            while (std::getline(values_stream, val_def, ',')) {
                val_def.erase(val_def.begin(), std::find_if(val_def.begin(), val_def.end(), 
                                [](unsigned char ch) { return !std::isspace(ch); }));
                val_def.erase(std::find_if(val_def.rbegin(), val_def.rend(),
                                [](unsigned char ch) { return !std::isspace(ch); }).base(), val_def.end());

                value_defs.push_back(val_def);
            }

            for (const auto& val_def : value_defs) {
                if (val_def.empty()) {
                    pq.insert_values->emplace_back(std::nullopt);
                }
                else {
                    size_t equal_pos = val_def.find('=');
                    if (equal_pos != std::string::npos) {
                        std::string col_name = val_def.substr(0, equal_pos);
                        std::string value_str = val_def.substr(equal_pos + 1);

                        col_name.erase(col_name.begin(), std::find_if(col_name.begin(), col_name.end(),
                                        [](unsigned char ch) { return !std::isspace(ch); }));
                        col_name.erase(std::find_if(col_name.rbegin(), col_name.rend(), 
                                        [](unsigned char ch) { return !std::isspace(ch); }).base(), col_name.end());

                        value_str.erase(value_str.begin(), std::find_if(value_str.begin(), value_str.end(),
                                        [](unsigned char ch) { return !std::isspace(ch); }));
                        value_str.erase(std::find_if(value_str.rbegin(), value_str.rend(),
                                        [](unsigned char ch) { return !std::isspace(ch); }).base(), value_str.end());

                        Value value = parse_value(value_str);
                        pq.insert_values->push_back(value);
                    }
                    else {
                        Value value = parse_value(val_def);
                        pq.insert_values->push_back(value);
                    }
                }
            }
        }
        else {
            throw std::invalid_argument("Invalid INSERT syntax.");
        }
    }
    else if (command == "select") {
        throw std::invalid_argument("SELECT not yet implemented.");
    }
    else if (command == "update") {
        throw std::invalid_argument("UPDATE not yet implemented.");
    }
    else if (command == "delete") {
        throw std::invalid_argument("DELETE not yet implemented.");
    }
    else {
        throw std::invalid_argument("Unknown command: " + command);
    }

    std::cout << "Parsed Columns for Table " << pq.table_name << ":" << std::endl;
    for (const auto& column : pq.columns) {
        std::cout << "  " << column.to_string() << std::endl;
    }

    return pq;
}

Column QueryParser::parse_column_definition(const std::string& col_def) {
    std::vector<ColumnAttribute> attributes;
    std::optional<Value> default_value = std::nullopt;
    std::string col_name;
    DataType data_type(Type::Int64);

    size_t start = 0;
    if (col_def[start] == '{') {
        size_t end = col_def.find('}', start);
        if (end == std::string::npos) {
            throw std::invalid_argument("Expected '}' for column attributes.");
        }
        std::string attrs_str = col_def.substr(start + 1, end - start - 1);
        std::stringstream attrs_stream(attrs_str);
        std::string attr;
        while (std::getline(attrs_stream, attr, ',')) {
            attr.erase(attr.begin(), std::find_if(attr.begin(), attr.end(),
                        [](unsigned char ch) { return !std::isspace(ch); }));
            attr.erase(std::find_if(attr.rbegin(), attr.rend(),
                        [](unsigned char ch) { return !std::isspace(ch); }).base(), attr.end());

            if (attr == "unique") {
                attributes.emplace_back(ColumnAttribute::Unique);
            }
            else if (attr == "autoincrement") {
                attributes.emplace_back(ColumnAttribute::AutoIncrement);
            }
            else if (attr == "key") {
                attributes.emplace_back(ColumnAttribute::Key);
            }
            else {
                throw std::invalid_argument("Unknown column attribute: " + attr);
            }
        }
        start = end + 1;
    }

    size_t colon_pos = col_def.find(':', start);
    if (colon_pos == std::string::npos) {
        throw std::invalid_argument("Expected ':' in column definition.");
    }

    std::string name_part = col_def.substr(start, colon_pos - start);
    name_part.erase(name_part.begin(), std::find_if(name_part.begin(), name_part.end(),
                    [](unsigned char ch) { return !std::isspace(ch); }));
    name_part.erase(std::find_if(name_part.rbegin(), name_part.rend(),
                    [](unsigned char ch) { return !std::isspace(ch); }).base(), name_part.end());

    if (name_part.empty()) {
        throw std::invalid_argument("Column name is empty.");
    }
    col_name = name_part;

    size_t eq_pos = col_def.find('=', colon_pos + 1);
    std::string type_str;
    if (eq_pos != std::string::npos) {
        type_str = col_def.substr(colon_pos + 1, eq_pos - colon_pos - 1);
        type_str.erase(type_str.begin(), std::find_if(type_str.begin(), type_str.end(),
                        [](unsigned char ch) { return !std::isspace(ch); }));
        type_str.erase(std::find_if(type_str.rbegin(), type_str.rend(),
                        [](unsigned char ch) { return !std::isspace(ch); }).base(), type_str.end());

        std::string default_val_str = col_def.substr(eq_pos + 1);
        default_val_str.erase(default_val_str.begin(), std::find_if(default_val_str.begin(), default_val_str.end(),
                                [](unsigned char ch) { return !std::isspace(ch); }));
        default_val_str.erase(std::find_if(default_val_str.rbegin(), default_val_str.rend(),
                                [](unsigned char ch) { return !std::isspace(ch); }).base(), default_val_str.end());

        default_value = parse_value(default_val_str);
    }
    else {
        type_str = col_def.substr(colon_pos + 1);
        type_str.erase(type_str.begin(), std::find_if(type_str.begin(), type_str.end(),
                        [](unsigned char ch) { return !std::isspace(ch); }));
        type_str.erase(std::find_if(type_str.rbegin(), type_str.rend(),
                        [](unsigned char ch) { return !std::isspace(ch); }).base(), type_str.end());
    }

    Type type;
    size_t size = 0;
    if (type_str.find("int32") != std::string::npos) {
        type = Type::Int32;
    }
    else if (type_str.find("int64") != std::string::npos) {
        type = Type::Int64;
    }
    else if (type_str.find("string") != std::string::npos) {
        type = Type::String;
        size = std::stoul(type_str.substr(type_str.find('[') + 1,
                                           type_str.find(']') - type_str.find('[') - 1));
    }
    else if (type_str.find("bytes") != std::string::npos) {
        type = Type::Bytes;
        size = std::stoul(type_str.substr(type_str.find('[') + 1,
                                           type_str.find(']') - type_str.find('[') - 1));
    }
    else if (type_str.find("bool") != std::string::npos) {
        type = Type::Bool;
    }
    else {
        throw std::invalid_argument("Unknown column type: " + type_str);
    }

    if (type == Type::String || type == Type::Bytes) {
        data_type = DataType(type, size);
    }
    else {
        data_type = DataType(type);
    }

    for (const auto& attr : attributes) {
        switch (attr) {
            case ColumnAttribute::Unique:
                std::cout << "unique ";
                break;
            case ColumnAttribute::AutoIncrement:
                std::cout << "autoincrement ";
                break;
            case ColumnAttribute::Key:
                std::cout << "key ";
                break;
            default:
                std::cout << "unknown ";
        }
    }
    if (default_value) {
        std::cout << ", Default: " << default_value->to_string();
    }
    std::cout << std::endl;

    return Column(col_name, data_type, attributes, default_value);
}

Value QueryParser::parse_value(const std::string& value_str) {
    if (value_str.empty()) {
        throw std::invalid_argument("Empty value.");
    }

    if (value_str[0] == '"' && value_str.back() == '"') {
        std::string str_val = value_str.substr(1, value_str.size() - 2);
        return Value(str_val);
    }
    else if (value_str.find("0x") == 0 || value_str.find("0X") == 0) {
        std::string hex_str = value_str.substr(2);
        if (hex_str.size() % 2 != 0) {
            throw std::invalid_argument("Invalid hex string for bytes.");
        }
        std::vector<uint8_t> bytes;
        bytes.reserve(hex_str.size() / 2);
        for (size_t i = 0; i < hex_str.size(); i += 2) {
            std::string byte_str = hex_str.substr(i, 2);
            uint8_t byte = static_cast<uint8_t>(std::stoul(byte_str, nullptr, 16));
            bytes.push_back(byte);
        }
        return Value(bytes);
    }
    else if (value_str == "true" || value_str == "false") {
        bool bool_val = (value_str == "true");
        return Value(bool_val);
    }
    else {
        try {
            long long_val = std::stoll(value_str);
            if (long_val >= std::numeric_limits<int32_t>::min() && long_val <= std::numeric_limits<int32_t>::max()) {
                return Value(static_cast<int32_t>(long_val));
            }
            else {
                return Value(static_cast<int64_t>(long_val));
            }
        }
        catch (const std::exception& e) {
            throw std::invalid_argument("Invalid value: " + value_str);
        }
    }
}

std::vector<std::string> QueryParser::split_columns(const std::string& columns_def) {
    std::vector<std::string> columns;
    int brace_level = 0;
    std::string current;
    
    for (char ch : columns_def) {
        if (ch == '{') {
            brace_level++;
            current += ch;
        }
        else if (ch == '}') {
            brace_level--;
            current += ch;
        }
        else if (ch == ',' && brace_level == 0) {
            size_t first = current.find_first_not_of(" \t\r\n");
            size_t last = current.find_last_not_of(" \t\r\n");
            if (first != std::string::npos && last != std::string::npos) {
                columns.emplace_back(current.substr(first, last - first + 1));
            }
            current.clear();
        }
        else {
            current += ch;
        }
    }
    
    size_t first = current.find_first_not_of(" \t\r\n");
    size_t last = current.find_last_not_of(" \t\r\n");
    if (first != std::string::npos && last != std::string::npos) {
        columns.emplace_back(current.substr(first, last - first + 1));
    }
    
    return columns;
}

std::string QueryParser::trim(const std::string& s) {
    auto start = std::find_if_not(s.begin(), s.end(), [](unsigned char ch) {
        return std::isspace(ch);
    });
    auto end = std::find_if_not(s.rbegin(), s.rend(), [](unsigned char ch) {
        return std::isspace(ch);
    }).base();
    
    if (start >= end) {
        return "";
    }
    return std::string(start, end);
}

} // namespace core
} // namespace memdb
```
QueryResult.cpp
```
#include "memdb/core/QueryResult.h"
```
Row.cpp
```
#include "memdb/core/Row.h"

namespace memdb {
namespace core {

// ======= Row Implementation =======

Row::Row(RowID id, const std::vector<std::optional<Value>>& values) : id_(id), values_(values) {}

RowID Row::get_id() const {
    return id_;
}

std::vector<std::optional<Value>>& Row::get_values() {
    return values_;
}

const std::vector<std::optional<Value>>& Row::get_values() const {
    return values_;
}

std::optional<Value>& Row::get_value(size_t index) {
    if (index >= values_.size()) {
        throw std::out_of_range("Column index out of range.");
    }
    return values_[index];
}

const std::optional<Value>& Row::get_value(size_t index) const {
    if (index >= values_.size()) {
        throw std::out_of_range("Column index out of range.");
    }
    return values_[index];
}

void Row::set_value(size_t index, const Value& value) {
    if (index >= values_.size()) {
        throw std::out_of_range("Column index out of range.");
    }
    values_[index] = value;
}

} // namespace core
} // namespace memdb
```
Table.cpp
```
#include "memdb/core/Row.h"
#include "memdb/core/Table.h"
#include "memdb/core/QueryParser.h"

#include "memdb/core/exceptions/DatabaseException.h"

namespace memdb {
namespace core {

using json = nlohmann::json;

// ======= Table Implementation =======

Table::Table(const std::string& name, const std::vector<Column>& columns) : name_(name), columns_(columns), next_row_id_(1) {
    if (name_.empty()) {
        throw std::invalid_argument("Table name cannot be empty.");
    }

    std::unordered_map<std::string, bool> column_names;
    for (const auto& column : columns_) {
        if (column_names.find(column.get_name()) != column_names.end()) {
            throw std::invalid_argument("Duplicate column name: " + column.get_name());
        }
        column_names[column.get_name()] = true;
    }
}

const std::string& Table::get_name() const {
    return name_;
}

const std::vector<Column>& Table::get_columns() const {
    return columns_;
}

size_t Table::get_column_index(const std::string& column_name) const {
    for (size_t i = 0; i < columns_.size(); ++i) {
        if (columns_[i].get_name() == column_name) {
            return i;
        }
    }
    throw std::invalid_argument("Column not found: " + column_name);
}

void Table::add_column(const Column& column) {
    if (has_column(column.get_name())) {
        throw std::invalid_argument("Column already exists: " + column.get_name());
    }

    columns_.push_back(column);

    for (auto& [id, row] : rows_) {
        if (column.get_default_value()) {
            row.get_values().emplace_back(*(column.get_default_value()));
        }
        else if (column.get_type().is_string()) {
            row.get_values().emplace_back(std::string());
        }
        else if (column.get_type().is_bytes()) {
            row.get_values().emplace_back(std::vector<uint8_t>());
        }
        else if (column.get_type().is_int32()) {
            row.get_values().emplace_back(int32_t(0));
        }
        else if (column.get_type().is_int64()) {
            row.get_values().emplace_back(int64_t(0));
        }
        else if (column.get_type().is_bool()) {
            row.get_values().emplace_back(bool(false));
        }
        else {
            row.get_values().emplace_back(std::nullopt);
        }
    }
}

bool Table::has_column(const std::string& column_name) const {
    for (const auto& column : columns_) {
        if (column.get_name() == column_name) {
            return true;
        }
    }
    return false;
}

RowID Table::insert_row(const std::vector<std::optional<Value>>& values) {
    validate_row(values);

    std::vector<std::optional<Value>> complete_values;
    complete_values.reserve(columns_.size());

    for (size_t i = 0; i < columns_.size(); ++i) {
        if (i < values.size() && values[i].has_value()) {
            complete_values.emplace_back(values[i]);
        }
        else {
            const auto& column = columns_[i];
            if (column.has_attribute(ColumnAttribute::AutoIncrement)) {
                complete_values.emplace_back(static_cast<int64_t>(next_row_id_));
            }
            else if (column.get_default_value()) {
                complete_values.emplace_back(*(column.get_default_value()));
            }
            else {
                complete_values.emplace_back(std::nullopt);
            }
        }
    }

    RowID new_id = next_row_id_++;
    rows_.emplace(new_id, Row(new_id, complete_values));
    return new_id;
}

RowID Table::insert_row(const std::vector<std::optional<Value>>& values, RowID id) {
    validate_row(values);

    std::vector<std::optional<Value>> complete_values = values;

    for (size_t i = 0; i < columns_.size(); ++i) {
        const auto& column = columns_[i];

        if (column.has_attribute(ColumnAttribute::AutoIncrement) && !complete_values[i].has_value()) {
            complete_values[i] = static_cast<int64_t>(next_row_id_);
        }
        else if (!complete_values[i].has_value() && column.get_default_value()) {
            complete_values[i] = *(column.get_default_value());
        }
    }

    RowID new_id;
    if (id != 0) {
        new_id = id;
        if (new_id >= next_row_id_) {
            next_row_id_ = new_id + 1;
        }
    }
    else {
        new_id = next_row_id_++;
    }

    for (size_t i = 0; i < columns_.size(); ++i) {
        const auto& column = columns_[i];
        if (column.has_attribute(ColumnAttribute::Unique) || column.has_attribute(ColumnAttribute::Key)) {
            if (complete_values[i].has_value()) {
                const Value& new_val = *(complete_values[i]);

                for (const auto& [existing_id, existing_row] : rows_) {
                    if (existing_id == new_id) {
                        continue;
                    }

                    const auto& existing_val_opt = existing_row.get_value(i);
                    if (existing_val_opt.has_value()) {
                        const Value& existing_val = *(existing_val_opt);

                        if (new_val.get_type() != existing_val.get_type()) {
                            continue;
                        }

                        bool equal = false;
                        switch (new_val.get_type()) {
                            case Type::Int32:
                                equal = (new_val.get_int() == existing_val.get_int());
                                break;
                            case Type::Int64:
                                equal = (new_val.get_int64() == existing_val.get_int64());
                                break;
                            case Type::Bool:
                                equal = (new_val.get_bool() == existing_val.get_bool());
                                break;
                            case Type::String:
                                equal = (new_val.get_string() == existing_val.get_string());
                                break;
                            case Type::Bytes:
                                equal = (new_val.get_bytes() == existing_val.get_bytes());
                                break;
                            default:
                                break;
                        }

                        if (equal) {
                            throw std::invalid_argument("Duplicate value for unique/key column '" + column.get_name() + "'.");
                        }
                    }
                }
            }
        }
    }

    rows_.emplace(new_id, Row(new_id, complete_values));
    return new_id;
}

void Table::delete_row(RowID id) {
    auto it = rows_.find(id);
    if (it != rows_.end()) {
        rows_.erase(it);
    }
    else {
        throw std::invalid_argument("Row ID not found: " + std::to_string(id));
    }
}

Row& Table::get_row(RowID id) {
    auto it = rows_.find(id);
    if (it != rows_.end()) {
        return it->second;
    }
    throw std::invalid_argument("Row ID not found: " + std::to_string(id));
}

const Row& Table::get_row(RowID id) const {
    auto it = rows_.find(id);
    if (it != rows_.end()) {
        return it->second;
    }
    throw std::invalid_argument("Row ID not found: " + std::to_string(id));
}

const std::unordered_map<RowID, Row>& Table::get_all_rows() const {
    return rows_;
}

void Table::validate_row(const std::vector<std::optional<Value>>& values) const {
    if (values.size() > columns_.size()) {
        throw std::invalid_argument("Too many values provided for insertion.");
    }

    for (size_t i = 0; i < values.size(); ++i) {
        if (!values[i].has_value()) {
            continue;
        }

        const auto& value = *(values[i]);
        const auto& column = columns_[i];

        if (value.get_type() != column.get_type().get_type()) {
            throw std::invalid_argument("Type mismatch for column '" + column.get_name() +
                                        "'. Expected: " + column.get_type().to_string() +
                                        ", Got: " + value.to_string());
        }

        // for String and Bytes
        if (column.get_type().is_string()) {
            if (value.get_string().size() > column.get_type().get_size()) {
                throw std::invalid_argument("Value for column '" + column.get_name() +
                                            "' exceeds maximum length.");
            }
        }
        else if (column.get_type().is_bytes()) {
            if (value.get_bytes().size() > column.get_type().get_size()) {
                throw std::invalid_argument("Value for column '" + column.get_name() +
                                            "' exceeds maximum byte size.");
            }
        }
    }

    for (size_t i = 0; i < values.size(); ++i) {
        if (!values[i].has_value()) {
            continue;
        }

        const auto& column = columns_[i];
        if (column.has_attribute(ColumnAttribute::Unique) || column.has_attribute(ColumnAttribute::Key)) {
            const Value& new_value = *(values[i]);

            for (const auto& [existing_id, existing_row] : rows_) {
                const auto& existing_value_opt = existing_row.get_value(i);
                if (existing_value_opt.has_value()) {
                    const Value& existing_value = *(existing_value_opt);
                    if (new_value.get_type() == existing_value.get_type()) {
                        bool equal = false;
                        switch (new_value.get_type()) {
                            case Type::Int32:
                                equal = (new_value.get_int() == existing_value.get_int());
                                break;
                            case Type::Int64:
                                equal = (new_value.get_int64() == existing_value.get_int64());
                                break;
                            case Type::Bool:
                                equal = (new_value.get_bool() == existing_value.get_bool());
                                break;
                            case Type::String:
                                equal = (new_value.get_string() == existing_value.get_string());
                                break;
                            case Type::Bytes:
                                equal = (new_value.get_bytes() == existing_value.get_bytes());
                                break;
                            default:
                                break;
                        }
                        if (equal) {
                            throw std::invalid_argument("Duplicate value for unique/key column '" +
                                                        column.get_name() + "'.");
                        }
                    }
                }
            }
        }
    }
}

nlohmann::json Table::to_json() const {
    nlohmann::json j;
    j["name"] = name_;

    j["columns"] = nlohmann::json::array();
    for (const auto& column : columns_) {
        nlohmann::json col_json;
        col_json["name"] = column.get_name();
        col_json["type"] = column.get_type().to_string();

        nlohmann::json attrs = nlohmann::json::array();
        for (const auto& attr : column.get_attributes()) {
            switch (attr) {
                case ColumnAttribute::Unique:
                    attrs.push_back("unique");
                    break;
                case ColumnAttribute::AutoIncrement:
                    attrs.push_back("autoincrement");
                    break;
                case ColumnAttribute::Key:
                    attrs.push_back("key");
                    break;
                default:
                    attrs.push_back("unknown");
            }
        }
        col_json["attributes"] = attrs;

        if (column.get_default_value()) {
            col_json["default"] = column.get_default_value()->to_string();
        }

        j["columns"].push_back(col_json);
    }

    j["rows"] = nlohmann::json::array();
    for (const auto& [id, row] : rows_) {
        nlohmann::json row_json;
        row_json["id"] = id;
        row_json["values"] = nlohmann::json::array();
        for (const auto& value : row.get_values()) {
            if (value.has_value()) {
                switch (value->get_type()) {
                    case Type::Int32:
                        row_json["values"].push_back(value->get_int());
                        break;
                    case Type::Int64:
                        row_json["values"].push_back(value->get_int64());
                        break;
                    case Type::Bool:
                        row_json["values"].push_back(value->get_bool());
                        break;
                    case Type::String:
                        row_json["values"].push_back(value->get_string());
                        break;
                    case Type::Bytes:
                        {
                            const auto& bytes = value->get_bytes();
                            std::string hex_str = "0x";
                            const char hex_chars[] = "0123456789ABCDEF";
                            for (uint8_t byte : bytes) {
                                hex_str += hex_chars[(byte >> 4) & 0xF];
                                hex_str += hex_chars[byte & 0xF];
                            }
                            row_json["values"].push_back(hex_str);
                        }
                        break;
                    default:
                        row_json["values"].push_back(nullptr);
                        break;
                }
            }
            else {
                row_json["values"].push_back(nullptr);
            }
        }
        j["rows"].push_back(row_json);
    }

    return j;
}

Table Table::from_json(const nlohmann::json& j) {
    std::string table_name = j.at("name").get<std::string>();
    std::vector<Column> columns;

    for (const auto& col_json : j.at("columns")) {
        std::string col_name = col_json.at("name").get<std::string>();
        std::string col_type_str = col_json.at("type").get<std::string>();

        Type type;
        size_t size = 0;
        if (col_type_str.find("int32") != std::string::npos) {
            type = Type::Int32;
        }
        else if (col_type_str.find("int64") != std::string::npos) {
            type = Type::Int64;
        }
        else if (col_type_str.find("bool") != std::string::npos) {
            type = Type::Bool;
        }
        else if (col_type_str.find("string") != std::string::npos) {
            type = Type::String;
            size = std::stoul(col_type_str.substr(col_type_str.find('[') + 1,
                        col_type_str.find_last_of(']') - col_type_str.find('[') - 1));
        }
        else if (col_type_str.find("bytes") != std::string::npos) {
            type = Type::Bytes;
            size = std::stoul(col_type_str.substr(col_type_str.find('[') + 1,
                        col_type_str.find_last_of(']') - col_type_str.find('[') - 1));
        }
        else {
            throw exceptions::SerializationException("Unknown column type: " + col_type_str);
        }

        std::vector<ColumnAttribute> attributes;
        for (const auto& attr : col_json.at("attributes")) {
            std::string attr_str = attr.get<std::string>();
            if (attr_str == "unique") {
                attributes.emplace_back(ColumnAttribute::Unique);
            }
            else if (attr_str == "autoincrement") {
                attributes.emplace_back(ColumnAttribute::AutoIncrement);
            }
            else if (attr_str == "key") {
                attributes.emplace_back(ColumnAttribute::Key);
            }
            else {
                // TODO: unknown attributes
            }
        }

        std::optional<Value> default_value = std::nullopt;
        if (col_json.contains("default")) {
            std::string default_val_str = col_json.at("default").get<std::string>();
            switch (type) {
                case Type::Int32:
                    default_value = Value(static_cast<int32_t>(std::stoi(default_val_str)));
                    break;
                case Type::Int64:
                    default_value = Value(static_cast<int64_t>(std::stoll(default_val_str)));
                    break;
                case Type::Bool:
                    default_value = Value((default_val_str == "true"));
                    break;
                case Type::String:
                    if (default_val_str.front() == '"' && default_val_str.back() == '"') {
                        default_val_str = default_val_str.substr(1, default_val_str.size() - 2);
                    }
                    default_value = Value(default_val_str);
                    break;
                case Type::Bytes:
                {
                    std::string hex_str = default_val_str;
                    if (hex_str.find("0x") != 0 && hex_str.find("0X") != 0) {
                        throw exceptions::SerializationException("Invalid bytes format.");
                    }
                    hex_str = hex_str.substr(2);
                    if (hex_str.size() % 2 != 0) {
                        throw exceptions::SerializationException("Invalid hex length for bytes.");
                    }
                    std::vector<uint8_t> bytes;
                    bytes.reserve(hex_str.size() / 2);
                    for (size_t i = 0; i < hex_str.size(); i += 2) {
                        uint8_t byte = static_cast<uint8_t>(std::stoi(hex_str.substr(i, 2), nullptr, 16));
                        bytes.push_back(byte);
                    }
                    default_value = Value(bytes);
                }
                break;
                default:
                    throw exceptions::SerializationException("Unhandled column type: " + col_type_str);
            }
        }

        DataType data_type = (type == Type::String || type == Type::Bytes)
                               ? DataType(type, size)
                               : DataType(type);

        columns.emplace_back(col_name, data_type, attributes, default_value);
    }

    Table table(table_name, columns);

    for (const auto& row_json : j.at("rows")) {
        core::RowID row_id = row_json.at("id").get<core::RowID>();
        std::vector<std::optional<Value>> values;
        const auto& cols = table.get_columns();
        const auto& val_array = row_json.at("values");

        for (size_t i = 0; i < cols.size(); ++i) {
            const auto& col = cols[i];
            const auto& val_json = val_array[i];

            if (val_json.is_null()) {
                values.emplace_back(std::nullopt);
            }
            else {
                switch (col.get_type().get_type()) {
                    case Type::Int32:
                        values.emplace_back(Value(static_cast<int32_t>(val_json.get<int32_t>())));
                        break;
                    case Type::Int64:
                        values.emplace_back(Value(static_cast<int64_t>(val_json.get<int64_t>())));
                        break;
                    case Type::Bool:
                        values.emplace_back(Value(val_json.get<bool>()));
                        break;
                    case Type::String:
                        values.emplace_back(Value(val_json.get<std::string>()));
                        break;
                    case Type::Bytes:
                        {
                            std::string hex_str = val_json.get<std::string>();
                            if (hex_str.find("0x") != 0 && hex_str.find("0X") != 0) {
                                throw exceptions::SerializationException("Invalid bytes format.");
                            }
                            hex_str = hex_str.substr(2);
                            if (hex_str.size() % 2 != 0) {
                                throw exceptions::SerializationException("Invalid hex length for bytes.");
                            }
                            std::vector<uint8_t> bytes;
                            bytes.reserve(hex_str.size() / 2);
                            for (size_t j = 0; j < hex_str.size(); j += 2) {
                                uint8_t byte = static_cast<uint8_t>(std::stoi(hex_str.substr(j, 2), nullptr, 16));
                                bytes.push_back(byte);
                            }
                            values.emplace_back(Value(bytes));
                        }
                        break;
                    default:
                        throw exceptions::SerializationException("Unknown column type: " + col.get_type().to_string());
                }
            }
        }

        table.insert_row(values, row_id);
    }

    return table;
}

std::string Table::to_string() const {
    std::ostringstream oss;
    oss << "Table: " << name_ << "\nColumns:\n";
    for (const auto& column : columns_) {
        oss << "  " << column.to_string() << "\n";
    }
    oss << "Rows:\n";
    for (const auto& [id, row] : rows_) {
        oss << "  RowID " << id << ": ";
        for (size_t i = 0; i < row.get_values().size(); ++i) {
            if (i > 0) oss << ", ";
            if (row.get_values()[i].has_value()) {
                oss << row.get_values()[i]->to_string();
            } else {
                oss << "NULL";
            }
        }
        oss << "\n";
    }
    return oss.str();
}

} // namespace core
} // namespace memdb
```
Table.cpp
```
#include "memdb/core/Value.h"

#include "memdb/core/exceptions/TypeMismatchException.h"

namespace memdb {
namespace core {

// ======= Value Implementation =======

Value::Value(int32_t int_val) 
    : data_(int_val) {}

Value::Value(int64_t int_val) 
    : data_(int_val) {}

Value::Value(bool bool_val) 
    : data_(bool_val) {}

Value::Value(const std::string& str_val) 
    : data_(str_val) {}

Value::Value(const std::vector<uint8_t>& bytes_val) 
    : data_(bytes_val) {}

Type Value::get_type() const {
    if (!data_) {
        throw exceptions::TypeMismatchException("Attempted to get type of NULL Value.");
    }
    return static_cast<Type>(data_->index());
}

int32_t Value::get_int() const {
    if (!data_ || !std::holds_alternative<int32_t>(*data_)) {
        throw exceptions::TypeMismatchException("Value is not of type Int32.");
    }
    return std::get<int32_t>(*data_);
}

int64_t Value::get_int64() const {
    if (!data_ || !std::holds_alternative<int64_t>(*data_)) {
        throw exceptions::TypeMismatchException("Value is not of type Int64.");
    }
    return std::get<int64_t>(*data_);
}

bool Value::get_bool() const {
    if (!data_ || !std::holds_alternative<bool>(*data_)) {
        throw exceptions::TypeMismatchException("Value is not of type Bool.");
    }
    return std::get<bool>(*data_);
}

const std::string& Value::get_string() const {
    if (!data_ || !std::holds_alternative<std::string>(*data_)) {
        throw exceptions::TypeMismatchException("Value is not of type String.");
    }
    return std::get<std::string>(*data_);
}

const std::vector<uint8_t>& Value::get_bytes() const {
    if (!data_ || !std::holds_alternative<std::vector<uint8_t>>(*data_)) {
        throw exceptions::TypeMismatchException("Value is not of type Bytes.");
    }
    return std::get<std::vector<uint8_t>>(*data_);
}

void Value::set_int(int32_t int_val) {
    if (data_ && !std::holds_alternative<int32_t>(*data_)) {
        throw exceptions::TypeMismatchException("Cannot set Int32 on Value of different type.");
    }
    data_ = int_val;
}

void Value::set_int64(int64_t int_val) {
    if (data_ && !std::holds_alternative<int64_t>(*data_)) {
        throw exceptions::TypeMismatchException("Cannot set Int64 on Value of different type.");
    }
    data_ = int_val;
}

void Value::set_bool(bool bool_val) {
    if (data_ && !std::holds_alternative<bool>(*data_)) {
        throw exceptions::TypeMismatchException("Cannot set Bool on Value of different type.");
    }
    data_ = bool_val;
}

void Value::set_string(const std::string& str_val) {
    if (data_ && !std::holds_alternative<std::string>(*data_)) {
        throw exceptions::TypeMismatchException("Cannot set String on Value of different type.");
    }
    data_ = str_val;
}

void Value::set_bytes(const std::vector<uint8_t>& bytes_val) {
    if (data_ && !std::holds_alternative<std::vector<uint8_t>>(*data_)) {
        throw exceptions::TypeMismatchException("Cannot set Bytes on Value of different type.");
    }
    data_ = bytes_val;
}

std::string Value::to_string() const {
    if (!data_) {
        return "NULL";
    }

    switch (data_->index()) {
        case 0: // int32_t
            return std::to_string(std::get<int32_t>(*data_));
        case 1: // int64_t
            return std::to_string(std::get<int64_t>(*data_));
        case 2: // bool
            return std::get<bool>(*data_) ? "true" : "false";
        case 3: // std::string
            return "\"" + std::get<std::string>(*data_) + "\"";
        case 4: // std::vector<uint8_t>
        {
            const auto& bytes = std::get<std::vector<uint8_t>>(*data_);
            std::string hex_str = "0x";
            const char hex_chars[] = "0123456789ABCDEF";
            for (uint8_t byte : bytes) {
                hex_str += hex_chars[(byte >> 4) & 0xF];
                hex_str += hex_chars[byte & 0xF];
            }
            return hex_str;
        }
        default:
            return "unknown";
    }
}

} // namespace core
} // namespace memdb
```
example_basic_usage.cpp
```
#include "memdb/core/Database.h"
#include "memdb/core/exceptions/DatabaseException.h"

#include "memdb/dependencies/json.hpp" 

#include <iostream>

int main() {
    memdb::core::Database db;

    try {
        std::string create_table_query = R"(
            create table users (
                {autoincrement, key} id : int64,
                name : string[50],
                active : bool = false
            )
        )";

        memdb::core::QueryResult result = db.execute(create_table_query);
        if (!result.is_ok()) {
            std::cerr << "Error creating table: " << result.get_error() << std::endl;
            return 1;
        }

        std::cout << "Table 'users' created successfully." << std::endl;

        std::string insert_query = R"(
            insert (, "Alice", true) to users
        )";

        result = db.execute(insert_query);
        if (!result.is_ok()) {
            std::cerr << "Error inserting row: " << result.get_error() << std::endl;
            return 1;
        }

        std::cout << "Inserted new row into 'users'. Row ID: ";
        try {
            const auto& data = result.get_data();
            if (!data.empty() && !data[0].empty() && data[0][0].has_value()) {
                int64_t row_id = data[0][0]->get_int64();
                std::cout << row_id << std::endl;
            }
            else {
                std::cout << "Unknown" << std::endl;
            }
        }
        catch (const std::exception& e) {
            std::cerr << "Error retrieving RowID: " << e.what() << std::endl;
        }

        std::cout << "SELECT operation is not yet implemented." << std::endl;

        std::string save_filename = "db.bin";
        db.save_to_file(save_filename);
        std::cout << "Database saved to file: " << save_filename << std::endl;

        memdb::core::Database loaded_db;
        loaded_db.load_from_file(save_filename);
        std::cout << "Database loaded from file: " << save_filename << std::endl;

        std::cout << "Loaded Database Structure:" << std::endl;
        std::cout << loaded_db.to_string() << std::endl;
    }
    catch (const memdb::core::exceptions::DatabaseException& e) {
        std::cerr << "Database Error: " << e.what() << std::endl;
        return 1;
    }
    catch (const std::exception& e) {
        std::cerr << "Standard Exception: " << e.what() << std::endl;
        return 1;
    }

    return 0;
}
```
пример работает, давай двигаться дальше по тз:
```
Выражения

Чтобы делать любые хоть немного сложные запросы надо уметь выполнять операции с данными. Мы ограничимся следующими:

- Арифметика (+, -, *, /, %) - выполняется над числами, семантика аналогична С++.
- Сравнения (<, =, >, <=, >=, !=) - выполняется над двумя значениями одинакового типа, семантика аналогична С++, кроме операции =, которая аналогична ==. Числа сравниваются очевидным образом, строки и байтовые последовательности - лексикографически, логические значения - по правилу true > false.
- Логические операции &&, ||, !, ^^ - применяются к логическим значениям, аналогичны С++, кроме ^^, который аналогичен ^
- |s| — применяется к строкам и байтовым последовательностям означет длину строки/последовательности.
- + для строк — конкатенация
- Скобки — задают приоритет операций
Приоритеты операций аналогичны таковым в С++. Неявных преобразваний типов нет, т.е. 42 || 0 или true + false - считаются невалидными выражениями.
```
и 
```
Select

Запрос выборки данных. Берет таблицу, выбирает из нее данные, соответствующие условия и возвращает новую таблицу.
Синтаксис: select <columns> from <table> where <condition>, где
- table - либо имя таблицы, из которой надо получить значения, либо выражение join (о них ниже)
- columns имена колонок через запятую в формате <table>.<column> (например users.id). Если в запросе используется только одна таблица то ее имя можно опустить и писать только имя колонки.
- condition - логическое выражение над значениями колонок, по которому надо отфильтровать результаты.
Пример: select id, login from users where is_admin || id < 10

Update
Запрос обновления данных.
Синтаксис: update <table> set <assignments> where <condition>,
где
- table - аналогично select
- condition аналогично select
- assignments набор имен колонок и их значений, синтаксис анналогичен второй форме insert за исключением того, что значения могут быть не константами, а выражениями от старых значений данной строки.
Примеры:
- update users set is_admin = true where login = "vasya"
- update users set login = login + "_deleted", is_admin = false where password_hash < 0x00000000ffffffff

Delete
Запрос удаления данных.
Синтаксис: delete <table> where <condition>, где
- table - имя таблицы, из которой надо удалить строки
- condition - аналогично select
Пример:
delete users where |login| % 2 = 0

Join
Естественно, делать запросы к отдельным таблицам - не особо полезно.
Чтобы собирать данные из нескольких таблиц служит join.
Это не отдельная команда, а конструкция, которая может использоваться в запроcax select и update вместо имени таблицы.
join делает очень простую вещь - он выбирает из первой и второй таблицы пары строк, подходящие под условие и составляет новую таблицу, имеющую колонки из обеих предыдущих и содержащую в соответствующих колонках данные из них.
Синтаксис: <table1> join <table2> on <condition>, где
- table1 и table2 - имена таблиц для объединения.
- condition - выражение, определяющее, какие пары записей брать.
Пример: пусть есть 2 таблицы users и posts:
id | login   | is_admin
1  | "vasya" | false
2  | "petya" | false
3  | "admin" | true
и
id | user_id | text
1  | 1       | "А"
2  | 1       | "B"
3  | 3       | "С"
Тoгдa select posts.id, users.login, posts.text from users join posts on users.id = posts.user_id where true
Должен вернуть
id | login   | text
1  | "vasya" | "A"
2  | "vasya" | "B"
3  | "admin" | "C"

Create index
Запрос создания индекса. Индексы никак не влияют на результат запросов, но могут менять время исполнения. Индекс создается по нескольким колонкам и позволяет базе в дальнейшем быстрее выполнять сравнения этих колонок. В нашей базе будет 2 вида индексов:
1. ordered - если такой был создан по колонке х то он позволяет быстро делать выборку, сравнивая значения этой колонки с чем-то. Например select … where x > 10. Естественно, ожидается, что это повлияет и на более сложные запросы с дополнительными условиями, например select … where x > 100 && x < 200 && y > x.
2. unordered создается по набору колонок и позволяет быстро отбирать строки, где весь этот набор равен чему-нибудь. Например, если создать такой индекс по колонкам х и у то ожидается, что запросы типа select … where x = 10 && y = 20 будут выполняться быстро. Аналогично предыдущему, условия могут быть и более сложными.
Мы будем считать, что индексы работают достаточно хорошо, если:

- для ordered - при наличии индекса по колонке х будет наблюдаться заметное ускорение при запросах с условиями вида (…) && (х < 1000) && (x > 500) && (…) (Естественно, порядок выражений может быть произвольным, вместо < и > могут идти другие сравнения, а вместо 1000 и 500 - другие значения, не обязательно числовые).
- для unordered - при наличии индекса по колонкам х, y, z будет наблюдаться заметное ускорение при запросах с условиями вида (…) && (x = 1) && (у = 2) && (z = 3) && (…). (Порядок выражений произвольный, значения могут быть другими).

Если в запросе можно применить несколько индексов то достаточно, чтобы был применен хотя бы один.
Hint: Возможно, тут пригодится STL…
Синтаксис: create <index_type> index on <table> by <columns>, где
- index_type - тип индекса
- table - имя таблицы
- columns - список колонок через запятую.
Примеры:
create ordered index on users by login
create unordered index on users by is_admin
```