Собственное хранилище данных (без внешних зависимостей)

стандартная_библиотека/хранилище.код — в отличие от база_данных.код (обёртка над 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-запросов, индексов, транзакций всё ещё нужен база_данных.код.