Skip to content

fix(cpp): propagate table read failures without silent data loss - #970

Open
ColinLeeo wants to merge 9 commits into
apache:developfrom
ColinLeeo:colin/fix-table-read-error-propagation
Open

ColinLeeo wants to merge 9 commits into
apache:developfrom
ColinLeeo:colin/fix-table-read-error-propagation

Conversation

@ColinLeeo

Copy link
Copy Markdown
Contributor

Propagate metadata and device-index read errors through table queries and tag filters instead of returning empty or partial results or misleading errors. Preserve terminal result-set failures and reject unexpected short reads while allowing EOF-limited prefetches.

Use checked schema lookups in the C/Python bindings and release tag filters when query creation fails. Add fault-injection regression coverage for row and batch queries, tag filtering, and leaf/internal device indexes.

Validation: C++ 953 passed, 3 skipped; Python 356 passed, 1 skipped. Spotless, Black, and whitespace checks passed.

Propagate metadata and device-index read errors through table queries and tag filters instead of returning empty or partial results or misleading errors. Preserve terminal result-set failures and reject unexpected short reads while allowing EOF-limited prefetches.

Use checked schema lookups in the C/Python bindings and release tag filters when query creation fails. Add fault-injection regression coverage for row and batch queries, tag filtering, and leaf/internal device indexes.

Validation: C++ 953 passed, 3 skipped; Python 356 passed, 1 skipped. Spotless, Black, and whitespace checks passed.
Comment on lines 51 to 198
@@ -177,22 +192,25 @@ std::shared_ptr<ResultSetMetadata> TableResultSet::get_metadata() {
int TableResultSet::get_next_tsblock(common::TsBlock*& block) {
int ret = common::E_OK;
block = nullptr;
if (read_error_ != common::E_OK) {
return read_error_;
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

There are quite a few state transitions here, so I'd prefer to refactor this logic. For example, I'd introduce an explicit state machine and model the read-state transitions separately for row-wise and batch reads.

Remove the legacy no-error table schema and tag factory entry points. Make table and timeseries schema lookups return allocated objects with ERRNO output parameters, including read failures instead of silently returning empty or partial schemas.

Update the C++ reader, CLI, examples, Python bindings, Go cgo bridge, and tests to the unified API. Add fault-injection coverage for all-timeseries schema reads.

Validation: C++ 953 passed, 3 skipped; Python 357 passed, 1 skipped; Go tests passed. Formatting and whitespace checks passed.
@ColinLeeo
ColinLeeo requested review from hongzhi-gao and jt2594838 and a lite review from Copilot and removed request for Copilot September 28, 2026 03:23

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot review overview

🟡 Changes recommended

Critical C ABI compatibility and exception-safety issues, plus unresolved CLI and native-memory leaks, block approval.

Review effort: Lite
Findings: 3 High severity · 2 Medium severity · 1 Low severity

Open (6)
What changed in this PR

This PR propagates table metadata, device-index, and result-set read failures across C++, Python, Go, and CLI interfaces.

Changes:

  • Adds checked schema and metadata APIs.
  • Preserves query failures and rejects unexpected short reads.
  • Adds fault-injection and binding regression coverage.
File Summary
python/​tsfile/​tsfile_reader.pyx Manages tag-filter lifetime.
python/​tsfile/​tsfile_py_cpp.pyx Uses checked schema APIs.
python/​tsfile/​tsfile_cpp.pxd Updates C API declarations.
python/​tests/​test_reader_sources.py Adds read-failure coverage.
go/​tsfile/​cgo_bridge.go Propagates schema errors.
cpp/​tools/​commands/​row_query.cc Handles schema and filter errors.
cpp/​tools/​commands/​cmd_stats.cc Handles schema lookup errors.
cpp/​tools/​commands/​cmd_count.cc Handles schema lookup errors.
cpp/​test/​reader/​table_view/​tsfile_reader_table_test.cc Updates schema tests.
cpp/​test/​reader/​table_read_failure_test.cc Adds failure regression tests.
cpp/​test/​cwrapper/​cwrapper_test.cc Updates wrapper tests.
cpp/​test/​cwrapper/​c_release_test.cc Updates C API cleanup tests.
cpp/​src/​reader/​tsfile_reader.h Defines checked schema lookup.
cpp/​src/​reader/​tsfile_reader.cc Implements checked lookup.
cpp/​src/​reader/​task/​device_task_iterator.h Propagates iterator errors.
cpp/​src/​reader/​task/​device_task_iterator.cc Propagates iterator errors.
cpp/​src/​reader/​table_result_set.h Tracks terminal read failures.
cpp/​src/​reader/​table_result_set.cc Preserves result-set failures.
cpp/​src/​reader/​table_query_executor.cc Propagates metadata failures.
cpp/​src/​reader/​meta_data_querier.h Removes cached metadata state.
cpp/​src/​reader/​meta_data_querier.cc Uses checked metadata access.
cpp/​src/​reader/​device_meta_iterator.h Tracks index ownership and errors.
cpp/​src/​reader/​device_meta_iterator.cc Propagates index failures.
cpp/​src/​reader/​chunk_reader.cc Rejects unexpected short reads.
cpp/​src/​reader/​block/​device_ordered_tsblock_reader.cc Propagates iterator errors.
cpp/​src/​reader/​aligned_chunk_reader.cc Validates aligned reads.
cpp/​src/​cwrapper/​tsfile_cwrapper.h Revises checked C APIs.
cpp/​src/​cwrapper/​tsfile_cwrapper.cc Implements checked wrappers.
cpp/​examples/​cpp_examples/​demo_read.cpp Updates schema API usage.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread cpp/src/cwrapper/tsfile_cwrapper.cc Outdated
Comment on lines 1140 to 1144
uint32_t* size,
ERRNO* error_code) {
if (size != nullptr) {
*size = 0;
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in commit 764d8ba8a. The C wrapper now checks every calloc/strdup result, validates schema vectors, catches std::bad_alloc and other exceptions, and frees partially initialized schema arrays before returning E_OOM or E_FILE_READ_ERR. The read-failure regression tests cover the metadata APIs.

Comment thread cpp/src/cwrapper/tsfile_cwrapper.h Outdated
Comment on lines +1025 to +1027
TableSchema* tsfile_reader_get_table_schema(TsFileReader reader,
const char* table_name,
ERRNO* error_code);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks for pointing this out. For this PR we intentionally accept the breaking API change: backward compatibility is out of scope, and the old signatures could not report metadata read failures reliably. The original exported names now use the error-reporting signatures directly, so the C, Go, and Python bindings share one consistent API without _with_error compatibility shims.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Follow-up: the latest implementation preserves the original exported C APIs and adds checked variants for error propagation. The legacy wrappers delegate to the checked implementations and handle returned errors, so existing source and ABI compatibility are retained. My previous reply describing an intentional breaking change referred to an earlier revision and is no longer accurate.

Comment on lines +155 to +158
int build_table_tag_filter(const ParsedArgs& args,
storage::TsFileReader& reader,
const std::string& table_name, std::ostream& err,
std::unique_ptr<storage::Filter>& ret_filter) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed. cpp/tools/commands/commands.h now declares the status-code/out-parameter signature used by row_query.cc, and the CLI builds successfully.

Comment on lines +433 to +436
if (schemas.empty() || !schemas[0]) {
err << "Error: table '" << args.table << "' does not exist\n";
return kExitUsage;
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed. The no-table path now calls the status-returning reader.get_all_table_schemas(schemas) overload, reports metadata read failures, and returns kExitFile instead of treating a read failure as “no table found”.

Comment on lines +307 to +310
std::shared_ptr<storage::TableSchema> table_schema;
const int schema_ret =
reader.get_table_schema(table_name, table_schema);
if (schema_ret != common::E_OK) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed. cmd_table_stats now uses the status-returning schema enumeration overload and propagates metadata read errors before applying the missing-table usage diagnostic.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Correction to my previous reply: row_query now uses the status-returning schema enumeration overload in the no-table path and propagates metadata read errors before reporting that no table was found.

Comment thread cpp/test/reader/table_read_failure_test.cc Outdated
Comment on lines -80 to +81
std::queue<MetaIndexNode*> meta_index_nodes_;
// Roots are borrowed from file metadata; descendant nodes belong to pa_.
std::queue<std::pair<MetaIndexNode*, bool>> meta_index_nodes_;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

What does the bool represent? Add to the comment.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The flag tracks ownership: roots borrowed from file metadata are false, while descendants allocated in pa_ are true and must be explicitly destroyed by the iterator. I added this comment next to the member declaration.

Comment on lines +1205 to +1208
if (table_num > std::numeric_limits<uint32_t>::max()) {
*error_code = common::E_OVERFLOW;
return nullptr;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Why use uint32?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The public C API exposes schema counts as uint32_t, so this implementation keeps the existing ABI and checks the internal size_t count before narrowing. If the count exceeds UINT32_MAX, it returns E_OVERFLOW instead of truncating it.

@ColinLeeo
ColinLeeo force-pushed the colin/fix-table-read-error-propagation branch from c586f10 to 764d8ba Compare September 30, 2026 02:54
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.

4 participants