Skip to content

Task 4/read write bench#14

Merged
ik4rd merged 9 commits intomainfrom
task-4/read-write-bench
Apr 8, 2026
Merged

Task 4/read write bench#14
ik4rd merged 9 commits intomainfrom
task-4/read-write-bench

Conversation

@ik4rd
Copy link
Copy Markdown
Owner

@ik4rd ik4rd commented Apr 8, 2026

Summary by CodeRabbit

Примечания к выпуску

  • Новые возможности

    • Добавлена поддержка новых типов данных: bool, data, timestamp, char, 16-bit и 32-bit целые числа, 128-bit целые числа.
    • Внедрена структурированная система обработки ошибок.
    • Сборка теперь поддерживает опциональное включение тестов и эталонов.
  • Улучшения производительности

    • Оптимизирована работа со потоками данных.

Repository owner deleted a comment from coderabbitai Bot Apr 8, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 8, 2026

Caution

Failed to replace (edit) comment. This is likely due to insufficient permissions or the comment being deleted.

Error details
{"name":"HttpError","status":404,"request":{"method":"PATCH","url":"https://api.github.com/repos/ik4rd/columnar-engine/issues/comments/4207882799","headers":{"accept":"application/vnd.github.v3+json","user-agent":"octokit.js/0.0.0-development octokit-core.js/7.0.6 Node.js/24","authorization":"token [REDACTED]","content-type":"application/json; charset=utf-8"},"body":{"body":"<!-- This is an auto-generated comment: summarize by coderabbit.ai -->\n<!-- walkthrough_start -->\n\n<details>\n<summary>📝 Walkthrough</summary>\n\n## Обзор\n\nPR вводит поддержку дополнительных типов столбцов (Boolean, Int16, Int32, Int128, Date, Timestamp, Character), реструктурирует обработку ошибок путём замены простых исключений на структурированный класс Error с кодами и типами, добавляет явные объявления специальных функций-членов для управления семантикой копирования/перемещения, оптимизирует потоки с учётом endianness и расширяет утилиты разбора.\n\n## Изменения\n\n|Группа / Файлы|Краткое описание|\n|---|---|\n|**Конфигурация сборки** <br> `CMakeLists.txt`, `cmake/dependencies_package.cmake`, `src/CMakeLists.txt`|Включена организация папок IDE, добавлены опции `ENABLE_BENCHMARKS` и `ENABLE_TESTS` с условным включением, удалена подпапка `examples` из сборки по умолчанию, добавлены источники новых типов столбцов.|\n|**Новые типы столбцов — заголовки** <br> `include/model/column_boolean.h`, `include/model/column_character.h`, `include/model/column_date.h`, `include/model/column_int16.h`, `include/model/column_int32.h`, `include/model/column_int128.h`, `include/model/column_timestamp.h`|Добавлены новые классы столбцов, наследующие `FixedColumn`; каждый реализует `AppendFromString()` и `ValueAsString()` для своего типа данных.|\n|**Базовые классы для столбцов** <br> `include/model/fixed_column.h`, `include/model/variant_column.h`|Введены шаблонные классы `FixedColumn` (для фиксированного размера) и `VariantColumn` (для переменной длины); реализуют сериализацию, десериализацию, клонирование и управление хранилищем.|\n|**Рефакторинг существующих типов столбцов** <br> `include/model/column_int64.h`, `include/model/column_string.h`, `src/model/column_int64.cpp`, `src/model/column_string.cpp`|Переведены на наследование от `FixedColumn`/`VariantColumn` соответственно; удалены дублирующиеся реализации сериализации и управления хранилищем.|\n|**Реализация новых типов столбцов** <br> `src/model/column_boolean.cpp`, `src/model/column_character.cpp`, `src/model/column_date.cpp`, `src/model/column_int16.cpp`, `src/model/column_int32.cpp`, `src/model/column_int128.cpp`, `src/model/column_timestamp.cpp`|Реализованы методы разбора и преобразования в строку для каждого нового типа столбца.|\n|**Семантика специальных функций-членов (IO)** <br> `include/io/batch_io.h`, `include/io/csv.h`, `include/io/csv_batch_io.h`, `include/io/columnar_batch_io.h`|Добавлены явные конструкторы по умолчанию, конструкторы копирования/перемещения и операторы присваивания с удалением копирования/разрешением перемещения для управления жизненным циклом.|\n|**Семантика специальных функций-членов (модель)** <br> `include/model/batch.h`, `include/model/column.h`, `include/testing/temp_file.h`|Добавлены копирование/перемещение с поддержкой полного управления жизненным циклом; `Column` добавляет чистый виртуальный метод `Clone()`.|\n|**Оптимизация потоков** <br> `include/io/stream.h`|Добавлена быстрая дорожка для little-endian систем с прямым чтением/записью без обратного порядка байтов.|\n|**Переструктурирование обработки ошибок — заголовок** <br> `include/support/error.h`|Введён новый класс `Error` с перечислениями `Code` и `Type`, заводскими методами для создания специфичных ошибок (`InvalidArgument`, `InvalidData`, `Mismatch`, `Overflow` и т. д.) и методами доступа к метаданным ошибки.|\n|**Переструктурирование обработки ошибок — реализация** <br> `src/support/error.cpp`|Реализованы все вспомогательные методы, конструкторы и преобразователи строк для нового класса `Error`.|\n|**Обновление всех точек использования ошибок** <br> `src/io/batch_io.cpp`, `src/io/csv.cpp`, `src/io/fileio.cpp`, `src/io/stream.cpp`, `src/convert/csv_columnar.cpp`, `src/model/batch.cpp`, `src/model/column.cpp`, `src/model/metadata.cpp`, `src/model/schema.cpp`|Заменены все вызовы `error::MakeError(...)` и `error::MakePathError(...)` на соответствующие заводские методы `Error::*(...)`.|\n|**Расширение утилит разбора** <br> `include/support/parsing.h`, `src/support/parsing.cpp`, `include/support/int128.h`|Добавлены функции разбора и преобразования в строку для новых типов (boolean, int16, int32, int128, date, timestamp, character); введены типовые псевдонимы для 128-битных целых чисел.|\n|**Вспомогательные файлы** <br> `src/app/main.cpp`|Добавлена завершающая новая строка.|\n\n## Оценка трудозатрат на рецензирование\n\n🎯 4 (Сложный) | ⏱️ ~50 минут\n\n## Стихотворение\n\n> 🐰 Новых типов встанут ряды,  \n> Столбцы Boolean, Date, Timestamp спешат,  \n> Ошибки структурой теперь горят,  \n> А потоки быстро бегут с концов,  \n> Семантика кода — источник основ! 🌟\n\n</details>\n\n<!-- walkthrough_end -->\n\n\n<!-- pre_merge_checks_walkthrough_start -->\n\n<details>\n<summary>🚥 Pre-merge checks | ✅ 1 | ❌ 2</summary>\n\n### ❌ Failed checks (1 warning, 1 inconclusive)\n\n|     Check name     | Status         | Explanation                                                                                                                                                                                                                                                                                                | Resolution                                                                                                                                                                                                                                   |\n| :----------------: | :------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| Docstring Coverage | ⚠️ Warning     | Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%.                                                                                                                                                                                                                       | Write docstrings for the functions missing them to satisfy the coverage threshold.                                                                                                                                                           |\n|     Title check    | ❓ Inconclusive | The title 'Task 4/read write bench' is vague and does not clearly convey the actual scope of changes. The PR encompasses extensive refactoring including new column types, error handling redesign, special member function declarations, and build configuration changes—far more than just benchmarking. | Consider a more descriptive title such as 'Add support for additional column types and refactor error handling' or 'Implement column type system and special member functions' that better reflects the comprehensive nature of the changes. |\n\n<details>\n<summary>✅ Passed checks (1 passed)</summary>\n\n|     Check name    | Status   | Explanation                                                 |\n| :---------------: | :------- | :---------------------------------------------------------- |\n| Description Check | ✅ Passed | Check skipped - CodeRabbit’s high-level summary is enabled. |\n\n</details>\n\n<sub>✏️ Tip: You can configure your own custom pre-merge checks in the settings.</sub>\n\n</details>\n\n<!-- pre_merge_checks_walkthrough_end -->\n\n<!-- finishing_touch_checkbox_start -->\n\n<details>\n<summary>✨ Finishing Touches</summary>\n\n<details>\n<summary>📝 Generate docstrings</summary>\n\n- [ ] <!-- {\"checkboxId\": \"7962f53c-55bc-4827-bfbf-6a18da830691\"} --> Create stacked PR\n- [ ] <!-- {\"checkboxId\": \"3e1879ae-f29b-4d0d-8e06-d12b7ba33d98\"} --> Commit on current branch\n\n</details>\n<details>\n<summary>🧪 Generate unit tests (beta)</summary>\n\n- [ ] <!-- {\"checkboxId\": \"f47ac10b-58cc-4372-a567-0e02b2c3d479\", \"radioGroupId\": \"utg-output-choice-group-unknown_comment_id\"} -->   Create PR with unit tests\n- [ ] <!-- {\"checkboxId\": \"6ba7b810-9dad-11d1-80b4-00c04fd430c8\", \"radioGroupId\": \"utg-output-choice-group-unknown_comment_id\"} -->   Commit unit tests in branch `task-4/read-write-bench`\n\n</details>\n\n</details>\n\n<!-- finishing_touch_checkbox_end -->\n\n<!-- tips_start -->\n\n---\n\nThanks for using [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=ik4rd/columnar-engine&utm_content=14)! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.\n\n<details>\n<summary>❤️ Share</summary>\n\n- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)\n- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)\n- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)\n- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)\n\n</details>\n\n<sub>Comment `@coderabbitai help` to get the list of available commands and usage tips.</sub>\n\n<!-- tips_end -->\n\n<!-- internal state start -->\n\n\n<!-- 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 -->\n\n<!-- internal state end -->"},"request":{"signal":{}}},"response":{"url":"https://api.github.com/repos/ik4rd/columnar-engine/issues/comments/4207882799","status":404,"headers":{"access-control-allow-origin":"*","access-control-expose-headers":"ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset","content-encoding":"gzip","content-security-policy":"default-src 'none'","content-type":"application/json; charset=utf-8","date":"Wed, 08 Apr 2026 16:54:21 GMT","referrer-policy":"origin-when-cross-origin, strict-origin-when-cross-origin","server":"github.com","strict-transport-security":"max-age=31536000; includeSubdomains; preload","transfer-encoding":"chunked","vary":"Accept-Encoding, Accept, X-Requested-With","x-accepted-github-permissions":"issues=write; pull_requests=write","x-content-type-options":"nosniff","x-frame-options":"deny","x-github-api-version-selected":"2022-11-28","x-github-media-type":"github.v3; format=json","x-github-request-id":"83D7:12B744:1A8E78:6A15FE:69D6883D","x-ratelimit-limit":"5000","x-ratelimit-remaining":"4971","x-ratelimit-reset":"1775669976","x-ratelimit-resource":"core","x-ratelimit-used":"29","x-xss-protection":"0"},"data":{"message":"Not Found","documentation_url":"https://docs.github.com/rest/issues/comments#update-an-issue-comment","status":"404"}}}

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

🧹 Nitpick comments (8)
include/testing/temp_file.h (2)

38-39: Избыточная метка public:.

Метка public: на строке 38 избыточна — секция уже открыта на строке 23.

♻️ Предлагаемое исправление
     ~TempFile() { Remove(); }

-   public:
     const std::filesystem::path& Path() const { return path_; }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@include/testing/temp_file.h` around lines 38 - 39, В коде присутствует лишняя
метка доступа `public:` перед метода `Path()`; удалите этот лишний `public:`
(оставьте только одну открывающую секцию `public:` уже объявленную ранее) чтобы
избежать дублирования и сохранить метод `const std::filesystem::path& Path()
const` и поле `path_` в существующей публичной секции.

41-48: Remove() помечен как const — допустимо, но семантически спорно.

Метод не изменяет path_, поэтому const технически корректен. Однако он имеет побочный эффект (удаление файла), что может ввести в заблуждение при чтении кода. Рекомендую убрать const для ясности намерения.

Также: ошибка удаления игнорируется — для RAII-деструктора это допустимо (исключения в деструкторе нежелательны), но для тестового утилитарного класса может быть полезно хотя бы логировать неудачи.

♻️ Предлагаемое исправление
    private:
-    void Remove() const {
+    void Remove() {
         if (path_.empty()) {
             return;
         }
         std::error_code ec;
         std::filesystem::remove(path_, ec);
+        // Опционально: логирование для отладки
+        // if (ec) { std::cerr << "Failed to remove temp file: " << path_ << '\n'; }
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@include/testing/temp_file.h` around lines 41 - 48, The Remove() method is
marked const but performs filesystem side-effects and currently swallows errors;
change Remove() to be non-const (remove the const qualifier from the method
declaration/definition for Remove()) to reflect its mutating/side-effecting
behavior, and enhance error handling by checking the std::error_code from
std::filesystem::remove(path_, ec) and logging or reporting failures (use the
class's logging facility or std::cerr) including path_ and ec.message() so
failed deletions are visible during tests.
CMakeLists.txt (1)

26-26: Сделайте -O2 конфигурационно-зависимым, а не глобальным.
Сейчас оптимизация включается даже в Debug и в санитайзерных целях, что ухудшает отладочность и качество диагностики.

Предлагаемое исправление
-add_compile_options(-Wall -Wextra -Werror -Wpedantic -O2)
+add_compile_options(
+    -Wall -Wextra -Werror -Wpedantic
+    $<$<OR:$<CONFIG:Release>,$<CONFIG:RelWithDebInfo>,$<CONFIG:MinSizeRel>>:-O2>
+)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@CMakeLists.txt` at line 26, The global add_compile_options line currently
forces -O2 for all builds; remove -O2 from add_compile_options(-Wall -Wextra
-Werror -Wpedantic -O2) and instead apply optimization only for Release builds:
either set CMAKE_C_FLAGS_RELEASE/CMAKE_CXX_FLAGS_RELEASE to include -O2 or add
target_compile_options(...) with a generator expression $<CONFIG:Release> or
check CMAKE_BUILD_TYPE and append -O2 for Release; update references to
add_compile_options and ensure Debug/Sanitizer configs do not receive -O2.
include/model/batch.h (1)

16-22: Правило пяти реализовано корректно.

Move-операции помечены noexcept — это правильно для работы с контейнерами STL. Copy-семантика через Clone() обеспечит глубокое копирование.

Небольшое замечание: на строке 22 дублируется спецификатор public:, который уже был объявлен на строке 12.

♻️ Удалить дублирующийся спецификатор доступа
     ~Batch() = default;

-   public:
    const Schema& GetSchema() const { return schema_; }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@include/model/batch.h` around lines 16 - 22, В классе Batch удалите
дублирующийся спецификатор доступа public: (второй экземпляр после объявлений
конструктора/операторов/деструктора), оставив единственный public: ранее в
объявлении; отредактируйте декларацию конструкций Batch(const Batch&), operator=
и т.д. так, чтобы сохранить текущую семантику, но убрать лишний public: маркер.
src/support/parsing.cpp (1)

38-49: Отсутствует проверка границ в ParseUnsignedPart.

Функция не проверяет, что offset + length <= input.size(). Хотя вызывающий код (ParseDatePoint, ParseTimestamp) проверяет общую длину строки, функция сама по себе небезопасна.

🛡️ Предлагаемое исправление
 static unsigned ParseUnsignedPart(const std::string_view input, const size_t offset, const size_t length,
                                   const std::string_view type_name) {
+    if (offset + length > input.size()) {
+        throw Error::InvalidData("parsing", "invalid " + std::string(type_name) + " value");
+    }
     unsigned value = 0;
     const char* begin = input.data() + offset;
     const char* end = begin + length;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/support/parsing.cpp` around lines 38 - 49, ParseUnsignedPart lacks bounds
checking for offset+length against input.size(); add an explicit check at the
start of ParseUnsignedPart (using parameters input, offset, length) that
validates offset <= input.size() and offset + length <= input.size(), and if it
fails throw Error::InvalidData("parsing", "invalid " + std::string(type_name) +
" value") (same error used for parse failures) before computing begin/end or
calling std::from_chars so the function is safe to call independently from
ParseDatePoint/ParseTimestamp.
src/io/batch_io.cpp (1)

43-50: Потенциальный выход за границы массива в EstimateRowBytes.

Функция предполагает, что columns.size() == row.size(), но это не проверяется. Несмотря на то, что вызывающий код в ReadNext() проверяет row.size() != column_count перед вызовом, функция EstimateRowBytes сама по себе небезопасна при независимом использовании.

🛡️ Рекомендуемое исправление
 static uint64_t EstimateRowBytes(const std::vector<std::unique_ptr<Column>>& columns,
                                  const std::vector<std::string>& row) {
+    assert(columns.size() == row.size());
     uint64_t bytes = 0;
     for (size_t i = 0; i < columns.size(); ++i) {
         bytes = AddChecked(bytes, columns[i]->EstimateSizeFromString(row[i]));
     }
     return bytes;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/io/batch_io.cpp` around lines 43 - 50, EstimateRowBytes currently assumes
columns.size() == row.size() and can read out-of-bounds; add an explicit guard
at the start of EstimateRowBytes that compares columns.size() and row.size() and
handles mismatches (e.g., throw a std::invalid_argument or log+return) instead
of indexing past the end. Keep the rest of the loop using AddChecked and
Column::EstimateSizeFromString unchanged; update callers such as ReadNext only
if you choose a non-exception error path so they propagate or handle the new
error behavior.
include/model/variant_column.h (1)

36-61: Потенциальное переполнение total_size при записи.

В цикле на строках 37-42 переменная total_size может переполниться при большом количестве строк. Хотя на практике это маловероятно, для согласованности с другими проверками на переполнение (строка 38) рекомендуется добавить проверку.

🛡️ Предлагаемое исправление
         if constexpr (std::endian::native == std::endian::little) {
             size_t total_size = 0;
             for (const auto& value : values_) {
                 if (value.size() > std::numeric_limits<uint32_t>::max()) {
                     throw Error::Overflow(Derived::ModuleName(), "value exceeds supported size");
                 }
+                const size_t entry_size = sizeof(uint32_t) + value.size();
+                if (entry_size > std::numeric_limits<size_t>::max() - total_size) {
+                    throw Error::Overflow(Derived::ModuleName(), "total column size overflow");
+                }
-                total_size += sizeof(uint32_t) + value.size();
+                total_size += entry_size;
             }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@include/model/variant_column.h` around lines 36 - 61, total_size accumulation
can overflow when summing sizeof(uint32_t) + value.size() across many entries;
in the loop that iterates values_ (the block computing total_size before
creating buffer), add an explicit overflow check before doing total_size +=
sizeof(uint32_t) + value.size(): ensure total_size + sizeof(uint32_t) +
value.size() does not exceed std::numeric_limits<size_t>::max() (or at least
std::numeric_limits<uint32_t>::max() if you want symmetric checks), and if it
would overflow, throw Error::Overflow(Derived::ModuleName(), "total size exceeds
supported size"); keep the rest of the logic (buffer construction, memcpy,
WriteBytes) unchanged.
include/model/fixed_column.h (1)

46-61: Потенциальная проблема при ошибке чтения.

После values_.resize(row_count) на строке 52, если ReadBytes бросит исключение, вектор останется с неинициализированными/мусорными значениями. Рекомендуется использовать временный буфер или очистить вектор при ошибке.

♻️ Вариант с временным буфером
     void ReadFrom(std::istream& in, const uint32_t row_count, const uint64_t size) override {
         const uint64_t expected = static_cast<uint64_t>(row_count) * sizeof(T);
         if (size != expected) {
             throw Error::Mismatch(Derived::ModuleName(), "column chunk size mismatch");
         }

-        values_.resize(row_count);
         if constexpr (std::endian::native == std::endian::little) {
+            std::vector<T> temp(row_count);
-            ReadBytes(in, reinterpret_cast<char*>(values_.data()), values_.size() * sizeof(T));
+            ReadBytes(in, reinterpret_cast<char*>(temp.data()), temp.size() * sizeof(T));
+            values_ = std::move(temp);
             return;
         }

+        std::vector<T> temp;
+        temp.reserve(row_count);
         for (uint32_t row_index = 0; row_index < row_count; ++row_index) {
-            values_[row_index] = ReadStream<T>(in);
+            temp.push_back(ReadStream<T>(in));
         }
+        values_ = std::move(temp);
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@include/model/fixed_column.h` around lines 46 - 61, В ReadFrom проверка
размера корректна, но после values_.resize(row_count) возможное исключение из
ReadBytes оставит values_ в частично инициализированном состоянии; исправьте
это, читая данные во временный буфер и только после успешного чтения
присваивая/меняя его с values_. Для little-endian ветви создайте временный
std::vector<T> tmp(row_count), вызовите
ReadBytes(reinterpret_cast<char*>(tmp.data()), tmp.size()*sizeof(T)) и затем
values_.swap(tmp); для big-endian ветви аналогично заполняйте tmp с помощью
ReadStream<T>(in) в цикле и в конце swap с values_; это гарантирует, что при
броске исключения values_ останется нетронутым.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@cmake/dependencies_package.cmake`:
- Around line 3-6: The code currently forces ENABLE_TESTS off into the CMake
cache when GTest_FOUND is false (using set(ENABLE_TESTS OFF CACHE BOOL "Build
tests" FORCE)), which persists across runs; change this so the temporary disable
only affects the current configure: remove the CACHE ... FORCE usage and set
ENABLE_TESTS with a non-cached value (e.g., set(ENABLE_TESTS OFF)) or otherwise
only modify the variable for this scope when GTest_FOUND is false; ensure the
check still uses GTest_FOUND and that any user-visible option for ENABLE_TESTS
remains controllable by the cache/options mechanism.

In `@include/support/int128.h`:
- Around line 17-18: Заменить устаревший typedef-вариант на каноническое
написание: в объявлении типа Int128 (символ Int128) использовать __int128 вместо
__int128_t, чтобы согласовать его с существующим помощью UInt128 (символ
UInt128) и обеспечить совместимость с GCC/Clang; просто поправить определение
типа Int128 на использование __int128.

In `@src/convert/csv_columnar.cpp`:
- Around line 10-12: Add regression unit tests for the changed logic in
ConvertColumnarToCsv: (1) a test that calls ConvertColumnarToCsv (or the public
wrapper that validates max_rows_per_group) with max_rows_per_group == 0 and
asserts that Error::InvalidArgument is thrown and that its message contains "row
group size must be > 0" (match the new contract), and (2) a test that invokes
ConvertColumnarToCsv on a small columnar input that does not fill a full group
and asserts that the internal buffer is flushed at the end (e.g., by verifying
the output contains the remaining rows or by mocking/inspecting the Flush()
side-effect). Ensure tests reference the ConvertColumnarToCsv entry point and
validate both exception type/message and final Flush behavior.

In `@src/io/csv.cpp`:
- Around line 16-37: Add regression tests covering the CSV parser and post-move
behavior: create unit tests that exercise CsvReader::CsvReader(CsvReader&&),
CsvReader::operator=(CsvReader&&) and RebindAfterMove by moving readers and
verifying they continue to parse correctly; include parsing cases for escaped
quotes (e.g. fields with doubled quotes), CRLF line endings, a final line
without a trailing '\n', empty fields, and combinations thereof to ensure the
character-by-character parser and moved-from/moved-to streams behave correctly
after the refactor.
- Around line 42-57: The parser currently calls streambuf methods
(buffer->sbumpc()/sgetc()), which bypass istream error and exception handling;
replace those direct buffer calls with istream methods: use in_->get() instead
of sbumpc() and in_->peek() instead of sgetc() at each of the four sites
referenced, capture the result into Traits::int_type (e.g. Traits::int_type next
= in_->get()), and then check for EOF/error either via Traits::eq_int_type(next,
Traits::eof()) or by testing in_->eof()/in_->fail() so the stream's
failbit/badbit/exception mask are honored (update code around variables buffer,
sbumpc, sgetc, and uses of Traits accordingly).
- Around line 16-18: The move constructor currently nulls other.in_/other.out_
causing UB when methods like ReadRow(), WriteRow(), Flush() dereference them;
instead, after moving owned_in_/owned_out_ keep other.in_/other.out_ bound to
&other.owned_in_ and &other.owned_out_ (i.e. call RebindAfterMove to point the
moved-from object's in_/out_ to its owned_* members rather than setting them to
nullptr) so moved-from CsvReader objects remain in a valid state; update
CsvReader::CsvReader(CsvReader&&), any move-assignment logic and RebindAfterMove
usage to rebind to &other.owned_in_/&other.owned_out_ rather than nulling
pointers and ensure ReadRow, WriteRow, Flush keep assuming non-null in_/out_.

In `@src/model/column_int32.cpp`:
- Around line 5-7: Add unit tests that exercise Int32Column::AppendFromString
and Int32Column::ValueAsString for both successful round-trips and parsing
failures: cover valid decimal strings, negative numbers, INT32_MIN and INT32_MAX
boundary values, and invalid inputs (empty string, non-numeric, out-of-range) to
assert that ParseInt32 produces the expected values or errors; mirror the same
pattern for all other new column_* implementations in this PR so each
AppendFromString/ValueAsString pair has tests for round-trip correctness and
error handling. Ensure tests call AppendFromString, then verify ValueAt(row) or
ValueAsString(row) as appropriate and assert that parsing errors are
raised/handled consistently.

In `@src/support/parsing.cpp`:
- Around line 246-268: TimestampToString can receive negative subseconds for
pre-1970 timestamps (from ParseTimestamp), causing a "-" to appear in the
fractional part; fix by normalizing subseconds before formatting: compute
int64_t fractional = tod.subseconds().count(); if (fractional < 0) fractional =
-fractional; then zero-pad and trim trailing zeros as currently done (use the
absolute value when calling std::to_string/frac.insert/frac.pop_back), and add
unit tests for pre-epoch dates (e.g., "1969-12-31 23:59:59.*") to cover
ParseTimestamp -> TimestampToString roundtrip; alternatively, if you prefer to
reject unsupported ranges, add range validation at the start of
TimestampToString and document the limitation instead of normalizing.

---

Nitpick comments:
In `@CMakeLists.txt`:
- Line 26: The global add_compile_options line currently forces -O2 for all
builds; remove -O2 from add_compile_options(-Wall -Wextra -Werror -Wpedantic
-O2) and instead apply optimization only for Release builds: either set
CMAKE_C_FLAGS_RELEASE/CMAKE_CXX_FLAGS_RELEASE to include -O2 or add
target_compile_options(...) with a generator expression $<CONFIG:Release> or
check CMAKE_BUILD_TYPE and append -O2 for Release; update references to
add_compile_options and ensure Debug/Sanitizer configs do not receive -O2.

In `@include/model/batch.h`:
- Around line 16-22: В классе Batch удалите дублирующийся спецификатор доступа
public: (второй экземпляр после объявлений конструктора/операторов/деструктора),
оставив единственный public: ранее в объявлении; отредактируйте декларацию
конструкций Batch(const Batch&), operator= и т.д. так, чтобы сохранить текущую
семантику, но убрать лишний public: маркер.

In `@include/model/fixed_column.h`:
- Around line 46-61: В ReadFrom проверка размера корректна, но после
values_.resize(row_count) возможное исключение из ReadBytes оставит values_ в
частично инициализированном состоянии; исправьте это, читая данные во временный
буфер и только после успешного чтения присваивая/меняя его с values_. Для
little-endian ветви создайте временный std::vector<T> tmp(row_count), вызовите
ReadBytes(reinterpret_cast<char*>(tmp.data()), tmp.size()*sizeof(T)) и затем
values_.swap(tmp); для big-endian ветви аналогично заполняйте tmp с помощью
ReadStream<T>(in) в цикле и в конце swap с values_; это гарантирует, что при
броске исключения values_ останется нетронутым.

In `@include/model/variant_column.h`:
- Around line 36-61: total_size accumulation can overflow when summing
sizeof(uint32_t) + value.size() across many entries; in the loop that iterates
values_ (the block computing total_size before creating buffer), add an explicit
overflow check before doing total_size += sizeof(uint32_t) + value.size():
ensure total_size + sizeof(uint32_t) + value.size() does not exceed
std::numeric_limits<size_t>::max() (or at least
std::numeric_limits<uint32_t>::max() if you want symmetric checks), and if it
would overflow, throw Error::Overflow(Derived::ModuleName(), "total size exceeds
supported size"); keep the rest of the logic (buffer construction, memcpy,
WriteBytes) unchanged.

In `@include/testing/temp_file.h`:
- Around line 38-39: В коде присутствует лишняя метка доступа `public:` перед
метода `Path()`; удалите этот лишний `public:` (оставьте только одну открывающую
секцию `public:` уже объявленную ранее) чтобы избежать дублирования и сохранить
метод `const std::filesystem::path& Path() const` и поле `path_` в существующей
публичной секции.
- Around line 41-48: The Remove() method is marked const but performs filesystem
side-effects and currently swallows errors; change Remove() to be non-const
(remove the const qualifier from the method declaration/definition for Remove())
to reflect its mutating/side-effecting behavior, and enhance error handling by
checking the std::error_code from std::filesystem::remove(path_, ec) and logging
or reporting failures (use the class's logging facility or std::cerr) including
path_ and ec.message() so failed deletions are visible during tests.

In `@src/io/batch_io.cpp`:
- Around line 43-50: EstimateRowBytes currently assumes columns.size() ==
row.size() and can read out-of-bounds; add an explicit guard at the start of
EstimateRowBytes that compares columns.size() and row.size() and handles
mismatches (e.g., throw a std::invalid_argument or log+return) instead of
indexing past the end. Keep the rest of the loop using AddChecked and
Column::EstimateSizeFromString unchanged; update callers such as ReadNext only
if you choose a non-exception error path so they propagate or handle the new
error behavior.

In `@src/support/parsing.cpp`:
- Around line 38-49: ParseUnsignedPart lacks bounds checking for offset+length
against input.size(); add an explicit check at the start of ParseUnsignedPart
(using parameters input, offset, length) that validates offset <= input.size()
and offset + length <= input.size(), and if it fails throw
Error::InvalidData("parsing", "invalid " + std::string(type_name) + " value")
(same error used for parse failures) before computing begin/end or calling
std::from_chars so the function is safe to call independently from
ParseDatePoint/ParseTimestamp.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 7f870d37-099e-4342-8e13-eaa1f94d99bd

📥 Commits

Reviewing files that changed from the base of the PR and between 57fbfcb and 8bd6cac.

⛔ Files ignored due to path filters (16)
  • .gitignore is excluded by none and included by none
  • benchmarks/CMakeLists.txt is excluded by none and included by none
  • benchmarks/benchmark_csv_to_columnar.cpp is excluded by none and included by none
  • examples/CMakeLists.txt is excluded by none and included by none
  • examples/data.csv is excluded by !**/*.csv and included by none
  • examples/example_batch.cpp is excluded by none and included by none
  • examples/example_columnar.cpp is excluded by none and included by none
  • examples/example_columns.cpp is excluded by none and included by none
  • examples/example_csv.cpp is excluded by none and included by none
  • examples/example_fileio.cpp is excluded by none and included by none
  • examples/schema.csv is excluded by !**/*.csv and included by none
  • tests/test_batch.cpp is excluded by none and included by none
  • tests/test_columnar.cpp is excluded by none and included by none
  • tests/test_columns.cpp is excluded by none and included by none
  • tests/test_csv.cpp is excluded by none and included by none
  • tests/test_fileio.cpp is excluded by none and included by none
📒 Files selected for processing (47)
  • CMakeLists.txt
  • cmake/dependencies_package.cmake
  • include/io/batch_io.h
  • include/io/columnar_batch_io.h
  • include/io/csv.h
  • include/io/csv_batch_io.h
  • include/io/stream.h
  • include/model/batch.h
  • include/model/column.h
  • include/model/column_boolean.h
  • include/model/column_character.h
  • include/model/column_date.h
  • include/model/column_int128.h
  • include/model/column_int16.h
  • include/model/column_int32.h
  • include/model/column_int64.h
  • include/model/column_string.h
  • include/model/column_timestamp.h
  • include/model/fixed_column.h
  • include/model/schema.h
  • include/model/variant_column.h
  • include/support/error.h
  • include/support/int128.h
  • include/support/parsing.h
  • include/testing/temp_file.h
  • src/CMakeLists.txt
  • src/app/main.cpp
  • src/convert/csv_columnar.cpp
  • src/io/batch_io.cpp
  • src/io/csv.cpp
  • src/io/fileio.cpp
  • src/io/stream.cpp
  • src/model/batch.cpp
  • src/model/column.cpp
  • src/model/column_boolean.cpp
  • src/model/column_character.cpp
  • src/model/column_date.cpp
  • src/model/column_int128.cpp
  • src/model/column_int16.cpp
  • src/model/column_int32.cpp
  • src/model/column_int64.cpp
  • src/model/column_string.cpp
  • src/model/column_timestamp.cpp
  • src/model/metadata.cpp
  • src/model/schema.cpp
  • src/support/error.cpp
  • src/support/parsing.cpp

Comment on lines +3 to +6
if (NOT GTest_FOUND)
message(WARNING "GTest not found; tests will be disabled for this configure run.")
set(ENABLE_TESTS OFF CACHE BOOL "Build tests" FORCE)
endif ()
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's look at the file context
cat -n cmake/dependencies_package.cmake | head -20

Repository: ik4rd/columnar-engine

Length of output: 360


🏁 Script executed:

# Search for ENABLE_TESTS usage in the codebase
rg "ENABLE_TESTS" -n

Repository: ik4rd/columnar-engine

Length of output: 349


🏁 Script executed:

# Look for any CMakeLists.txt or other cmake files that might be affected
git ls-files | grep -i cmake | head -20

Repository: ik4rd/columnar-engine

Length of output: 245


🏁 Script executed:

# Check if ENABLE_TESTS is used elsewhere that might depend on cache behavior
rg -B 3 -A 3 "ENABLE_TESTS" CMakeLists.txt | head -40

Repository: ik4rd/columnar-engine

Length of output: 707


🏁 Script executed:

# Check if there's any code that modifies or checks CMakeCache.txt
rg "CMakeCache" -i

Repository: ik4rd/columnar-engine

Length of output: 47


🏁 Script executed:

# Look at the workflow file to understand how configure is run
cat -n .github/workflows/cmake-single-platform.yml

Repository: ik4rd/columnar-engine

Length of output: 2143


Не используйте CACHE ... FORCE для временного отключения ENABLE_TESTS.
Сообщение утверждает отключение только для текущей конфигурации, но CACHE ... FORCE сохраняет значение OFF в CMakeCache.txt, которое будет действовать при последующих запусках cmake. При локальной разработке, если один раз запустить cmake без GTest, отключение тестов останется в кэше, даже если GTest будет позже установлен.

Исправление
-        set(ENABLE_TESTS OFF CACHE BOOL "Build tests" FORCE)
+        set(ENABLE_TESTS OFF)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (NOT GTest_FOUND)
message(WARNING "GTest not found; tests will be disabled for this configure run.")
set(ENABLE_TESTS OFF CACHE BOOL "Build tests" FORCE)
endif ()
if (NOT GTest_FOUND)
message(WARNING "GTest not found; tests will be disabled for this configure run.")
set(ENABLE_TESTS OFF)
endif ()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmake/dependencies_package.cmake` around lines 3 - 6, The code currently
forces ENABLE_TESTS off into the CMake cache when GTest_FOUND is false (using
set(ENABLE_TESTS OFF CACHE BOOL "Build tests" FORCE)), which persists across
runs; change this so the temporary disable only affects the current configure:
remove the CACHE ... FORCE usage and set ENABLE_TESTS with a non-cached value
(e.g., set(ENABLE_TESTS OFF)) or otherwise only modify the variable for this
scope when GTest_FOUND is false; ensure the check still uses GTest_FOUND and
that any user-visible option for ENABLE_TESTS remains controllable by the
cache/options mechanism.

Comment thread include/support/int128.h
Comment on lines +17 to +18
using Int128 = __int128_t;
using UInt128 = unsigned __int128;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Check the current state of the file
cat -n include/support/int128.h | head -30

Repository: ik4rd/columnar-engine

Length of output: 784


🏁 Script executed:

# Search for usages of Int128 and UInt128 in the codebase
rg "Int128|UInt128" --type cpp --type h -n

Repository: ik4rd/columnar-engine

Length of output: 2264


🌐 Web query:

__int128 vs __int128_t portability toolchain compatibility

💡 Result:

What they are

  • __int128 / unsigned __int128 are GCC/Clang language extensions (a builtin 128-bit integer type) available only on targets where the compiler has a 128-bit integer mode. (gcc.gnu.org)
  • __int128_t / __uint128_t are non-standard typedef names used by some toolchains/headers; they’re not the “canonical” spelling in GCC docs, and historically were considered “old typedefs” compared to the __int128 keyword form. (gcc.gnu.org)

Portability / toolchain compatibility (practical guidance)

  • GCC + Clang (Linux/macOS, many embedded targets): __int128 is commonly supported; library support is inconsistent (e.g., printing needs custom code; std::numeric_limits / traits support has had historical caveats). (gcc.gnu.org)
  • MSVC (Visual C++): no native __int128 (so code using it won’t be portable to MSVC without a fallback type). (lists.llvm.org)
  • C23 / “real” int128_t: C23 ties int128_t/uint128_t in <stdint.h> to the existence of a suitable 128-bit type and also implies a bunch of library surface area (macros, format macros in <inttypes.h>, etc.), so availability depends on both compiler + C library support, not just the frontend. (gcc.gnu.org)

Recommendation for maximum portability

  1. Prefer the canonical extension spelling when available:
    • __int128 and unsigned __int128 (not __int128_t).
  2. Gate it with a feature check and provide a fallback:
    • Common practice is to test __SIZEOF_INT128__ (widely used, though not perfectly standardized/documented across all historical versions). (stackoverflow.com)
  3. For MSVC builds, use a fallback such as:
    • a library type (boost::multiprecision::int128_t / cpp_int), or
    • a custom 128-bit struct implementation.

Minimal pattern

`#if` defined(__SIZEOF_INT128__)
  typedef __int128 i128;
  typedef unsigned __int128 u128;
`#else`
  /* fallback: boost::multiprecision or custom implementation */
`#endif`

Sources (index)

  1. GCC docs: __int128 extension (gcc.gnu.org)
  2. GCC mailing list: prefers __int128 keyword; calls __int128_t/__uint128_t “old typedefs” (gcc.gnu.org)
  3. LLVM-dev: MSVC doesn’t support __int128 (lists.llvm.org)
  4. GCC libstdc++ notes: evolving “integral type” handling for __int128 (gcc.gnu.org)
  5. C23/C++ papers/notes on int128_t requirements & library implications (gcc.gnu.org)
  6. Usage note mentioning __SIZEOF_INT128__ detection (stackoverflow.com)

Citations:


Использовать каноническую форму __int128 вместо __int128_t для лучшей совместимости.

На строке 17 используется __int128_t, а на строке 18 уже правильно применяется unsigned __int128. По рекомендациям GCC/Clang, __int128 — это канонический спеллинг, а __int128_t считается устаревшим typedef'ом. Кроме того, это устраняет несогласованность в коде.

Предлагаемое исправление
-using Int128 = __int128_t;
+using Int128 = __int128;
 using UInt128 = unsigned __int128;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
using Int128 = __int128_t;
using UInt128 = unsigned __int128;
using Int128 = __int128;
using UInt128 = unsigned __int128;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@include/support/int128.h` around lines 17 - 18, Заменить устаревший
typedef-вариант на каноническое написание: в объявлении типа Int128 (символ
Int128) использовать __int128 вместо __int128_t, чтобы согласовать его с
существующим помощью UInt128 (символ UInt128) и обеспечить совместимость с
GCC/Clang; просто поправить определение типа Int128 на использование __int128.

Comment on lines 10 to 12
if (max_rows_per_group == 0) {
throw error::MakeError("columnar", "row group size must be > 0");
throw Error::InvalidArgument("columnar", "row group size must be > 0");
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Добавьте регрессионные тесты для изменённой логики.

На Line 11 изменился контракт ошибки, а на Line 41 добавлен явный Flush(). Нужны тесты минимум на:

  1. max_rows_per_group == 0 (ожидаемый тип/сообщение исключения),
  2. финальный сброс буфера в ConvertColumnarToCsv.

As per coding guidelines "Если меняется логика — попроси тесты."

Also applies to: 37-41

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/convert/csv_columnar.cpp` around lines 10 - 12, Add regression unit tests
for the changed logic in ConvertColumnarToCsv: (1) a test that calls
ConvertColumnarToCsv (or the public wrapper that validates max_rows_per_group)
with max_rows_per_group == 0 and asserts that Error::InvalidArgument is thrown
and that its message contains "row group size must be > 0" (match the new
contract), and (2) a test that invokes ConvertColumnarToCsv on a small columnar
input that does not fill a full group and asserts that the internal buffer is
flushed at the end (e.g., by verifying the output contains the remaining rows or
by mocking/inspecting the Flush() side-effect). Ensure tests reference the
ConvertColumnarToCsv entry point and validate both exception type/message and
final Flush behavior.

Comment thread src/io/csv.cpp
Comment on lines +16 to +37
CsvReader::CsvReader(CsvReader&& other) noexcept : owned_in_(std::move(other.owned_in_)) {
RebindAfterMove(other.in_ == &other.owned_in_, other.in_);
other.in_ = nullptr;
}

CsvReader& CsvReader::operator=(CsvReader&& other) noexcept {
if (this != &other) {
owned_in_ = std::move(other.owned_in_);
RebindAfterMove(other.in_ == &other.owned_in_, other.in_);
other.in_ = nullptr;
}
return *this;
}

void CsvReader::RebindAfterMove(const bool uses_owned_stream, std::istream* source_stream) noexcept {
if (uses_owned_stream) {
in_ = &owned_in_;
return;
}
in_ = source_stream;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Нужны регрессионные тесты на новый автомат парсинга CSV и move-сценарии.

Здесь меняется и посимвольный парсер, и post-move поведение reader/writer. Без кейсов на escaped quotes, CRLF, последнюю строку без \n, пустые поля и move ctor/assignment такой рефакторинг тяжело удерживать.

As per coding guidelines, **/*.{h,hpp,cpp,cc,cxx}: Если меняется логика — попроси тесты.

Also applies to: 38-112, 118-173

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/io/csv.cpp` around lines 16 - 37, Add regression tests covering the CSV
parser and post-move behavior: create unit tests that exercise
CsvReader::CsvReader(CsvReader&&), CsvReader::operator=(CsvReader&&) and
RebindAfterMove by moving readers and verifying they continue to parse
correctly; include parsing cases for escaped quotes (e.g. fields with doubled
quotes), CRLF line endings, a final line without a trailing '\n', empty fields,
and combinations thereof to ensure the character-by-character parser and
moved-from/moved-to streams behave correctly after the refactor.

Comment thread src/io/csv.cpp
Comment on lines +16 to +18
CsvReader::CsvReader(CsvReader&& other) noexcept : owned_in_(std::move(other.owned_in_)) {
RebindAfterMove(other.in_ == &other.owned_in_, other.in_);
other.in_ = nullptr;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Проверка: показать, что moved-from указатели зануляются, а затем
# используются без null-guard в публичных методах.
rg -n -C2 'other\.(in_|out_)\s*=\s*nullptr|in_->|out_->' src/io/csv.cpp

Repository: ik4rd/columnar-engine

Length of output: 1192


🏁 Script executed:

# Retrieve the full RebindAfterMove implementation
rg -n -A 10 'void RebindAfterMove' src/io/csv.cpp

Repository: ik4rd/columnar-engine

Length of output: 47


🏁 Script executed:

# Retrieve the full ReadRow implementation
rg -n -A 30 'bool CsvReader::ReadRow' src/io/csv.cpp

Repository: ik4rd/columnar-engine

Length of output: 1058


🏁 Script executed:

# Retrieve the full WriteRow and Flush implementations
rg -n -A 15 'void CsvWriter::(WriteRow|Flush)' src/io/csv.cpp

Repository: ik4rd/columnar-engine

Length of output: 1037


🏁 Script executed:

# Check the class definition for structure
fd -e h csv | xargs cat -n | head -100

Repository: ik4rd/columnar-engine

Length of output: 3709


🏁 Script executed:

# Search for RebindAfterMove implementation more broadly
rg -n 'RebindAfterMove' src/io/csv.cpp

Repository: ik4rd/columnar-engine

Length of output: 555


🏁 Script executed:

# Get line count and structure of csv.cpp
wc -l src/io/csv.cpp && echo "---" && head -50 src/io/csv.cpp

Repository: ik4rd/columnar-engine

Length of output: 1505


🏁 Script executed:

# Get the full RebindAfterMove implementations for both classes
sed -n '30,35p;132,137p' src/io/csv.cpp

Repository: ik4rd/columnar-engine

Length of output: 475


🏁 Script executed:

# Verify no null-checks exist in the methods that dereference in_ and out_
rg -n 'in_\s*!=\s*nullptr|out_\s*!=\s*nullptr|if.*in_|if.*out_' src/io/csv.cpp | head -20

Repository: ik4rd/columnar-engine

Length of output: 168


Moved-from объекты остаются в небезопасном состоянии с dereferencing нулевых указателей.

После move операции other.in_ и other.out_ зануляются, но ReadRow() (линия 42: in_->rdbuf()), WriteRow() (линии 143, 148, 152, 155, 162: *out_ << ...) и Flush() (линии 169–170: out_->flush(), *out_) разыменовывают эти указатели без проверок на null. Это приводит к UB при любом вызове метода на moved-from объекте.

Вместо обнуления указателя привяжите его к &other.owned_*_. Хотя other.owned_*_ является moved-from объектом, он всё ещё валиден и безопасен для использования (например, можно вызвать rdbuf() на moved-from std::ifstream).

Рекомендуемое исправление
 CsvReader::CsvReader(CsvReader&& other) noexcept : owned_in_(std::move(other.owned_in_)) {
     RebindAfterMove(other.in_ == &other.owned_in_, other.in_);
-    other.in_ = nullptr;
+    other.in_ = &other.owned_in_;
 }
 
 CsvReader& CsvReader::operator=(CsvReader&& other) noexcept {
     if (this != &other) {
         owned_in_ = std::move(other.owned_in_);
         RebindAfterMove(other.in_ == &other.owned_in_, other.in_);
-        other.in_ = nullptr;
+        other.in_ = &other.owned_in_;
     }
     return *this;
 }
 
 CsvWriter::CsvWriter(CsvWriter&& other) noexcept : owned_out_(std::move(other.owned_out_)) {
     RebindAfterMove(other.out_ == &other.owned_out_, other.out_);
-    other.out_ = nullptr;
+    other.out_ = &other.owned_out_;
 }
 
 CsvWriter& CsvWriter::operator=(CsvWriter&& other) noexcept {
     if (this != &other) {
         owned_out_ = std::move(other.owned_out_);
         RebindAfterMove(other.out_ == &other.owned_out_, other.out_);
-        other.out_ = nullptr;
+        other.out_ = &other.owned_out_;
     }
     return *this;
 }

Также затрагивает: линии 23–25, 118–120, 125–127.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/io/csv.cpp` around lines 16 - 18, The move constructor currently nulls
other.in_/other.out_ causing UB when methods like ReadRow(), WriteRow(), Flush()
dereference them; instead, after moving owned_in_/owned_out_ keep
other.in_/other.out_ bound to &other.owned_in_ and &other.owned_out_ (i.e. call
RebindAfterMove to point the moved-from object's in_/out_ to its owned_* members
rather than setting them to nullptr) so moved-from CsvReader objects remain in a
valid state; update CsvReader::CsvReader(CsvReader&&), any move-assignment logic
and RebindAfterMove usage to rebind to &other.owned_in_/&other.owned_out_ rather
than nulling pointers and ensure ReadRow, WriteRow, Flush keep assuming non-null
in_/out_.

Comment thread src/io/csv.cpp
Comment on lines +42 to +57
auto* buffer = in_->rdbuf();
using Traits = std::streambuf::traits_type;
/* До этого чтение происходило через istream::get() и peek()
* Traits требуется только как вспомогательный тип для:
* - eof()
* - to_chat_type()
* - eq_int_type()
*/

std::string field;
bool in_quotes = false;
bool saw_data = false;

while (true) {
const int next = in_->get();
if (next == EOF) {
const Traits::int_type next = buffer->sbumpc();

if (Traits::eq_int_type(next, Traits::eof())) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

Do direct calls to std::streambuf::sbumpc/sgetc update the owning std::istream's failbit/badbit and honor the stream exception mask, or is that state management only done by std::istream operations such as get()/peek()?

💡 Result:

Direct calls on the buffer (e.g. is.rdbuf()->sbumpc() / sgetc()) do not update the owning std::istream’s rdstate() (so they won’t set failbit/badbit/eofbit for you), and they do not “honor” the stream’s exception mask (they won’t automatically translate conditions into setstate(...) and then throw ios_base::failure).

That state/exception-mask management is specified as behavior of std::basic_istream input functions: when an input function calls rdbuf()->sbumpc()/sgetc() and gets traits::eof(), the input function does setstate(eofbit) (and other bits as required) and that setstate “may throw” according to the exception mask; similarly, if a called buffer operation throws, the input function sets badbit and rethrows only if exceptions() & badbit is set. [1]

This is also reflected in the description of basic_istream::sentry: during whitespace skipping it may call sbumpc/sgetc(), and then the sentry/istream logic calls setstate(failbit|eofbit) (which may throw). [2]

So: istream operations like get()/peek() (and generally anything specified as an istream “input function”) manage the stream state and exception mask; raw streambuf calls don’t. [1][2]

Sources: [1] [2]


🏁 Script executed:

# Examine the file and specific line ranges
wc -l src/io/csv.cpp

Repository: ik4rd/columnar-engine

Length of output: 83


🏁 Script executed:

# View the code around lines 42-57
sed -n '42,57p' src/io/csv.cpp

Repository: ik4rd/columnar-engine

Length of output: 544


🏁 Script executed:

# View the code around lines 72-75
sed -n '70,76p' src/io/csv.cpp

Repository: ik4rd/columnar-engine

Length of output: 348


🏁 Script executed:

# View the code around lines 103-105
sed -n '101,107p' src/io/csv.cpp

Repository: ik4rd/columnar-engine

Length of output: 239


🏁 Script executed:

# Search for all sbumpc and sgetc calls in the file
rg -n 'sbumpc|sgetc' src/io/csv.cpp

Repository: ik4rd/columnar-engine

Length of output: 337


Прямые вызовы rdbuf() приводят к потере сигнализации I/O-ошибок.

sbumpc() и sgetc() обходят механизм управления состоянием istream: они не устанавливают failbit/badbit/eofbit и не учитывают exception mask. В результате реальные ошибки чтения могут быть ошибочно интерпретированы как EOF и привести к молчаливому обрезанию входа. Для CSV-парсера следует использовать get()/peek(), которые правильно управляют состоянием потока и исключениями.

Требуется замена на всех четырёх местах: строки 55, 72, 74, 104–105.

Вариант исправления
-    auto* buffer = in_->rdbuf();
-    using Traits = std::streambuf::traits_type;
+    using Traits = std::char_traits<char>;
 ...
-        const Traits::int_type next = buffer->sbumpc();
+        const Traits::int_type next = in_->get();
 ...
-                const Traits::int_type peek = buffer->sgetc();
-                if (Traits::to_char_type(peek) == '"') {
-                    buffer->sbumpc();
+                const Traits::int_type peek = in_->peek();
+                if (Traits::eq_int_type(peek, Traits::to_int_type('"'))) {
+                    in_->get();
                     row.back().push_back('"');
                 } else {
                     in_quotes = false;
                 }
 ...
-            if (Traits::to_char_type(buffer->sgetc()) == '\n') {
-                buffer->sbumpc();
+            if (const Traits::int_type peek = in_->peek();
+                Traits::eq_int_type(peek, Traits::to_int_type('\n'))) {
+                in_->get();
             }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/io/csv.cpp` around lines 42 - 57, The parser currently calls streambuf
methods (buffer->sbumpc()/sgetc()), which bypass istream error and exception
handling; replace those direct buffer calls with istream methods: use in_->get()
instead of sbumpc() and in_->peek() instead of sgetc() at each of the four sites
referenced, capture the result into Traits::int_type (e.g. Traits::int_type next
= in_->get()), and then check for EOF/error either via Traits::eq_int_type(next,
Traits::eof()) or by testing in_->eof()/in_->fail() so the stream's
failbit/badbit/exception mask are honored (update code around variables buffer,
sbumpc, sgetc, and uses of Traits accordingly).

Comment on lines +5 to +7
void Int32Column::AppendFromString(const std::string& value) { AppendValue(ParseInt32(value)); }

std::string Int32Column::ValueAsString(const size_t row) const { return std::to_string(ValueAt(row)); }
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Нужны тесты на round-trip и ошибки парсинга для новых fixed-column реализаций.

Добавьте тесты для Int32Column (валидные/невалидные строки, граничные значения), и аналогично для остальных новых column_* в этом PR, чтобы зафиксировать поведение AppendFromString/ValueAsString.

As per coding guidelines "Если меняется логика — попроси тесты."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/model/column_int32.cpp` around lines 5 - 7, Add unit tests that exercise
Int32Column::AppendFromString and Int32Column::ValueAsString for both successful
round-trips and parsing failures: cover valid decimal strings, negative numbers,
INT32_MIN and INT32_MAX boundary values, and invalid inputs (empty string,
non-numeric, out-of-range) to assert that ParseInt32 produces the expected
values or errors; mirror the same pattern for all other new column_*
implementations in this PR so each AppendFromString/ValueAsString pair has tests
for round-trip correctness and error handling. Ensure tests call
AppendFromString, then verify ValueAt(row) or ValueAsString(row) as appropriate
and assert that parsing errors are raised/handled consistently.

Comment thread src/support/parsing.cpp
Comment on lines +246 to +268
std::string TimestampToString(const int64_t value) {
const std::chrono::sys_time<std::chrono::microseconds> timestamp{std::chrono::microseconds{value}};
const auto day_point = std::chrono::floor<std::chrono::days>(timestamp);
const std::chrono::hh_mm_ss<std::chrono::microseconds> tod{timestamp - day_point};

std::ostringstream out;
out << FormatDateParts(std::chrono::year_month_day{day_point}) << ' ' << std::setfill('0') << std::setw(2)
<< tod.hours().count() << ':' << std::setw(2) << tod.minutes().count() << ':' << std::setw(2)
<< tod.seconds().count();

const int64_t fractional = tod.subseconds().count();

if (fractional != 0) {
std::string frac = std::to_string(fractional);
frac.insert(frac.begin(), 6 - static_cast<std::ptrdiff_t>(frac.size()), '0');
while (!frac.empty() && frac.back() == '0') {
frac.pop_back();
}
out << '.' << frac;
}

return out.str();
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Поиск тестов для TimestampToString с отрицательными значениями
rg -n "TimestampToString" --type cpp -C 3

Repository: ik4rd/columnar-engine

Length of output: 1483


🏁 Script executed:

# Поиск файлов с тестами
find . -type f -name "*test*" | grep -E "\.(cpp|cc|cxx|h)$" | head -20

Repository: ik4rd/columnar-engine

Length of output: 207


🏁 Script executed:

# Поиск всех использований TimestampToString в тестах
rg -l "TimestampToString" --type cpp

Repository: ik4rd/columnar-engine

Length of output: 145


🏁 Script executed:

# Поиск любых тестов, связанных с parsing или timestamp
find . -type f \( -name "*test*.cpp" -o -name "*test*.cc" \) | head -10

Repository: ik4rd/columnar-engine

Length of output: 207


🏁 Script executed:

# Поиск TimestampToString в тестовых файлах
rg "TimestampToString" tests/ --type cpp -A 5 -B 5

Repository: ik4rd/columnar-engine

Length of output: 47


🏁 Script executed:

# Поиск negative timestamp или related tests
rg -i "negative|epoch|timestamp" tests/ --type cpp -l

Repository: ik4rd/columnar-engine

Length of output: 111


🏁 Script executed:

# Посмотрим содержимое test_columns.cpp, где вероятнее всего тесты для TimestampColumn
wc -l tests/test_columns.cpp

Repository: ik4rd/columnar-engine

Length of output: 91


🏁 Script executed:

# Посмотрим содержимое test_columns.cpp
cat -n tests/test_columns.cpp

Repository: ik4rd/columnar-engine

Length of output: 5650


🏁 Script executed:

# Посмотрим test_columnar.cpp
cat -n tests/test_columnar.cpp | head -100

Repository: ik4rd/columnar-engine

Length of output: 4064


🏁 Script executed:

# Поиск в test_columnar более специфично для timestamp
rg -n "timestamp|Timestamp" tests/test_columnar.cpp -i -A 3 -B 1

Repository: ik4rd/columnar-engine

Length of output: 188


🏁 Script executed:

# Проверим, есть ли какие-то комментарии о поддержке отрицательных значений
rg -n "negative|before.*epoch|Unix epoch" src/support/parsing.cpp -A 2 -B 2

Repository: ik4rd/columnar-engine

Length of output: 1018


🏁 Script executed:

# Посмотрим полный код функции TimestampToString еще раз для детального анализа
sed -n '246,268p' src/support/parsing.cpp

Repository: ik4rd/columnar-engine

Length of output: 1040


🏁 Script executed:

# Поищем где определены или используются отрицательные timestamp значения
rg -rn "< 0|negative.*timestamp|timestamp.*negative" src/ tests/ --type cpp

Repository: ik4rd/columnar-engine

Length of output: 162


🏁 Script executed:

# Посмотрим функцию ParseTimestamp, которая создает timestamp values
rg -n "ParseTimestamp" src/support/parsing.cpp -A 30 | head -60

Repository: ik4rd/columnar-engine

Length of output: 1316


🏁 Script executed:

# Поищем информацию о том, какие значения timestamp могут быть
rg -n "int64_t.*timestamp|timestamp.*int64" src/support/parsing.cpp -B 2 -A 2

Repository: ik4rd/columnar-engine

Length of output: 47


🏁 Script executed:

# Проверим, есть ли валидация на отрицательные значения при создании timestamp
cat -n src/support/parsing.cpp | sed -n '1,250p' | tail -50

Repository: ik4rd/columnar-engine

Length of output: 1784


🏁 Script executed:

# Посмотрим ParseDatePoint функцию
rg -n "ParseDatePoint" src/support/parsing.cpp -A 20

Repository: ik4rd/columnar-engine

Length of output: 2422


🏁 Script executed:

# Проверим, упоминается ли диапазон значений или ограничения timestamp
rg -n "Timestamp" include/support/parsing.h -B 2 -A 2

Repository: ik4rd/columnar-engine

Length of output: 468


🏁 Script executed:

# Создадим простой тест для проверки behavior std::chrono с отрицательными значениями
cat > /tmp/test_chrono.cpp << 'EOF'
`#include` <chrono>
`#include` <iostream>

int main() {
    // Negative timestamp: -1000000 microseconds (1 second before epoch)
    const int64_t negative_value = -1000000;
    const std::chrono::sys_time<std::chrono::microseconds> timestamp{
        std::chrono::microseconds{negative_value}
    };
    const auto day_point = std::chrono::floor<std::chrono::days>(timestamp);
    const std::chrono::hh_mm_ss<std::chrono::microseconds> tod{timestamp - day_point};
    
    std::cout << "Negative timestamp: " << negative_value << std::endl;
    std::cout << "Day point (days since epoch): " << day_point.time_since_epoch().count() << std::endl;
    std::cout << "Hours: " << tod.hours().count() << std::endl;
    std::cout << "Minutes: " << tod.minutes().count() << std::endl;
    std::cout << "Seconds: " << tod.seconds().count() << std::endl;
    std::cout << "Subseconds: " << tod.subseconds().count() << std::endl;
    
    return 0;
}
EOF
# Try to compile and understand the behavior
cat /tmp/test_chrono.cpp

Repository: ik4rd/columnar-engine

Length of output: 1001


🏁 Script executed:

# Проверим все места где вызывается TimestampToString
rg -n "TimestampToString\(" src/ tests/ --type cpp -B 3 -A 1

Repository: ik4rd/columnar-engine

Length of output: 785


🏁 Script executed:

# Поищем комментарии или документацию о диапазоне timestamp значений
rg -n "range|limit|support" include/support/parsing.h -i

Repository: ik4rd/columnar-engine

Length of output: 47


🏁 Script executed:

# Проверим, есть ли где-то документация о том, какие значения поддерживаются
head -50 include/support/parsing.h

Repository: ik4rd/columnar-engine

Length of output: 857


🏁 Script executed:

# Проверим DateToString, похожа ли она на TimestampToString
sed -n '241,244p' src/support/parsing.cpp

Repository: ik4rd/columnar-engine

Length of output: 261


🏁 Script executed:

# Посмотрим на ValueAt в TimestampColumn чтобы подтвердить откуда берутся значения
rg -n "class TimestampColumn" src/model/ --type cpp -A 20

Repository: ik4rd/columnar-engine

Length of output: 47


🏁 Script executed:

# Поищем определение TimestampColumn
rg -n "TimestampColumn" include/ --type cpp -B 2 -A 10

Repository: ik4rd/columnar-engine

Length of output: 821


🏁 Script executed:

# Посмотрим на column_timestamp.h
find . -name "*column_timestamp*" -type f

Repository: ik4rd/columnar-engine

Length of output: 132


🏁 Script executed:

# Поищем определение класса Column и его наследников
rg -n "class.*Column" include/ --type hpp -A 5 | head -60

Repository: ik4rd/columnar-engine

Length of output: 92


🏁 Script executed:

# Посмотрим на FixedColumn для понимания ValueAt
rg -n "class FixedColumn|ValueAt" include/model/ --type cpp -A 3 | head -40

Repository: ik4rd/columnar-engine

Length of output: 898


🏁 Script executed:

# Посмотрим на полный файл column_timestamp.cpp
cat -n src/model/column_timestamp.cpp

Repository: ik4rd/columnar-engine

Length of output: 383


🏁 Script executed:

# Проверим, есть ли где-то валидация на отрицательные значения при сохранении
rg -n "AppendValue|negative|< 0" src/model/ --type cpp -B 2 -A 2 | head -50

Repository: ik4rd/columnar-engine

Length of output: 3174


🏁 Script executed:

# Финальная проверка: есть ли прямые вызовы TimestampToString с переменными
rg -n "TimestampToString\(" . --type cpp -B 5 | grep -E "(TimestampToString|int64|value|ValueAt)"

Repository: ik4rd/columnar-engine

Length of output: 992


🏁 Script executed:

# Проверим, есть ли какая-то документация о поддержке отрицательных значений
rg -n "before.*epoch|pre-epoch|negative.*timestamp" . --type cpp --type h

Repository: ik4rd/columnar-engine

Length of output: 47


🏁 Script executed:

# Финальная проверка: как ParseTimestamp валидирует даты (может ли быть дата до 1970)
sed -n '51,71p' src/support/parsing.cpp

Repository: ik4rd/columnar-engine

Length of output: 875


🏁 Script executed:

# Финальная проверка: насколько ParseUnsignedPart строгая для года
rg -n "ParseUnsignedPart" src/support/parsing.cpp -B 5 -A 10 | head -40

Repository: ik4rd/columnar-engine

Length of output: 1624


🏁 Script executed:

# Проверим конкретный пример: что будет для даты 1969
python3 << 'EOF'
import datetime

# Unix epoch
epoch = datetime.datetime(1970, 1, 1)

# Before epoch
before_epoch = datetime.datetime(1969, 12, 31, 23, 59, 59)

# Microseconds since epoch
td = before_epoch - epoch
microseconds = int(td.total_seconds() * 1_000_000)
print(f"1969-12-31 23:59:59 -> {microseconds} microseconds (negative: {microseconds < 0})")

# 1 second before epoch
one_sec_before = datetime.datetime(1970, 1, 1, 0, 0, -1)
EOF

Repository: ik4rd/columnar-engine

Length of output: 253


Проблема с отрицательными timestamp значениями требует исправления.

Функция ParseTimestamp может создавать отрицательные значения для дат до 1970-01-01 (например, "1969-12-31 23:59:59"). При передаче такого отрицательного значения в TimestampToString, вызов tod.subseconds().count() вернёт отрицательное число, которое std::to_string() преобразует в строку вида "-1000000". Это приведёт к некорректному форматированию дробной части (отрицательный знак в fractional).

Необходимо либо:

  1. Добавить валидацию на неподдерживаемый диапазон в TimestampToString и документировать ограничение
  2. Реализовать корректную обработку отрицательных timestamp значений
  3. Добавить тесты для pre-epoch дат, чтобы выявить и предотвратить регрессию
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/support/parsing.cpp` around lines 246 - 268, TimestampToString can
receive negative subseconds for pre-1970 timestamps (from ParseTimestamp),
causing a "-" to appear in the fractional part; fix by normalizing subseconds
before formatting: compute int64_t fractional = tod.subseconds().count(); if
(fractional < 0) fractional = -fractional; then zero-pad and trim trailing zeros
as currently done (use the absolute value when calling
std::to_string/frac.insert/frac.pop_back), and add unit tests for pre-epoch
dates (e.g., "1969-12-31 23:59:59.*") to cover ParseTimestamp ->
TimestampToString roundtrip; alternatively, if you prefer to reject unsupported
ranges, add range validation at the start of TimestampToString and document the
limitation instead of normalizing.

@ik4rd ik4rd merged commit 3a81681 into main Apr 8, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants