Собственное хранилище данных (без внешних зависимостей)
стандартная_библиотека/хранилище.код — в отличие от база_данных.код
(обёртка над SQLite), это простое хранилище "ключ → значение", полностью
написанное на самой "Сфере", без единой внешней библиотеки. Формат —
append-only лог (как в Bitcask): каждое изменение дописывается в конец
файла, ничего не перезаписывается на месте; поиск сканирует файл целиком
(O(n) на запрос) — простое, честное первое решение, не самое быстрое.
подключить хранилище;
функция главная(): целое
сохранить("данные.db", "имя", "Никита");
результат из текста итог = получить("данные.db", "имя");
если (итог.успешно)
вывести(итог.значение);
освободить_память(итог.значение); // свежая копия, не ссылка
конец
удалить("данные.db", "имя");
вернуть 0;
конец
Полный, рабочий пример (сохранение, чтение, обновление значения,
удаление, несуществующий ключ) — examples/хранилище/хранилище_пример.код.
Проверено объективно: все операции дают правильный результат, valgrind
— 0 ошибок, 0 утечек (при аккуратном освобождении возвращённых значений
вызывающей стороной — как и для прочитать_файл).
⚠️ Два новых, отдельных встроенных инструмента для этого понадобились и
добавлены в сам компилятор: размер_файла(путь) (размер в байтах, без
чтения содержимого) и дописать_в_файл_бинарно(путь, буфер, размер)
(append с явным размером, не через strlen — безопасно для данных с
нулевыми байтами внутри, в отличие от уже существующей записать_файл).
Ускоренный доступ — структура Хранилище с индексом в памяти
Свободные функции выше сканируют файл целиком на каждый запрос — честно,
просто, но O(n). Структура Хранилище строит индекс "ключ → позиция в
файле" один раз при открытии, дальше .получить(ключ) находит запись
мгновенно (обращение к Словарь в памяти) и читает с диска РОВНО нужный
диапазон байт (через новую прочитать_часть_файла), не файл целиком.
подключить хранилище;
функция главная(): целое
результат из Хранилище хр_результат = открыть_хранилище("данные.db");
если ( не хр_результат.успешно) вернуть 1; конец
Хранилище хр = хр_результат.значение;
хр.сохранить("имя", "Никита");
результат из текста итог = хр.получить("имя");
если (итог.успешно)
вывести(итог.значение);
освободить_память(итог.значение);
конец
закрыть_хранилище(хр); // освобождает индекс в памяти
вернуть 0;
конец
Полный пример — examples/хранилище/хранилище_с_индексом_пример.код.
Проверено объективно: valgrind — 0 ошибок, 0 утечек.
⚠️ **Значимое, общее расширение самого компилятора понадобилось для
этого** — раньше Словарь из Ключ и Значение/Список из T нельзя было использовать как
поле пользовательской структуры (цепочки доступа вида это.поле.метод()
не поддерживались, только простые переменные) — это универсальное
ограничение убрано, теперь работает для любой структуры, не только
Хранилище.
⚠️ Новый метод Словарь из ....освободить() — Словарь никогда не
участвует в ARC автоматически (ни как переменная, ни как поле), поэтому
нужен явный способ вернуть память. Честно, узко: освобождает только то,
что сам Словарь гарантированно выделил (два внутренних массива,
структуру-заголовок) — НЕ освобождает отдельные текстовые ключи/значения
по одному (первая попытка сделать так крашилась: язык не различает
динамически выделенный текст и статический строковый литерал — оба
выглядят одинаково на уровне типов, и free() на литерале — падение).
Если ключи/значения сами были динамически выделены, их освобождение —
ответственность вызывающего кода, как и для текст вообще везде в языке.
Компактификация — сжать_хранилище
Append-only лог растёт со временем даже для небольшого числа ключей —
каждое обновление добавляет новую запись, не заменяет старую на месте.
сжать_хранилище переписывает файл, оставляя только самые свежие записи
(по одной на ключ), выбрасывая устаревшие значения и tombstone от
удалённых ключей.
результат из Хранилище хр_сжатое = сжать_хранилище(хр);
если (хр_сжатое.успешно)
Хранилище хр2 = хр_сжатое.значение;
// ... хр (старое значение) больше не используется
конец
Полный пример — examples/хранилище/хранилище_сжатие_пример.код.
Проверено объективно: файл сокращается с 196 до 58 байт при сохранении
корректности всех данных.
⚠️ Не требует перебора самого Словаря (язык этого пока не умеет) —
вместо этого пересканирует файл и для каждой записи проверяет, совпадает
ли её смещение с тем, что сейчас в индексе для этого ключа — совпадает
значит это самая свежая версия. Добавлена ещё одна новая встроенная
функция — переименовать_файл(старый, новый) (портативная замена через
C rename()) — заменяет исходный файл только что собранным.
⚠️ Та же узкая утечка ключей, что и у Словарь из ....освободить() выше,
применима и здесь — каждый ключ индекса, построенного заново при сжатии,
это свой malloc-буфер, который закрыть_хранилище не освобождает
(честное следствие уже описанного ограничения, не отдельная проблема).
⚠️ Честно: это простое хранилище "ключ → значение", не полноценная
реляционная база — для SQL-запросов, индексов, транзакций всё ещё нужен
база_данных.код.