AGENTS.md

June 29, 2026 · View on GitHub

This file captures the design and testing principles of the chdb-ds (DataStore) layer for AI coding agents working on this repository and follows the agents.md open standard.

For setup, build commands, the modify-then-test workflow, PR conventions, CI matrix, security, and other contributor mechanics, see CONTRIBUTING.md.


1. Fully lazy execution architecture

  • All methods returning DataFrame or Series should return Lazy objects (such as DataStore, LazySeries).
  • Defer execution until results are actually needed.
  • Preserve the ability to select the optimal execution engine (pandas vs chDB / SQL) at execution stage.
  • API style does not determine execution engine — pandas-style, Pythonic, SQL terminology should all compile to the same optimised backend execution.
  • Final execute stage selects pandas or chDB ExecutionEngine based on the config system.

2. Natural execution triggering (explicit calls prohibited)

  • Prohibit explicit calls to _execute().
  • Avoid explicit conversions like to_df(), to_list(), to_pandas() as much as possible.
  • Execution is triggered through natural means:
    • .columns — get column names
    • len() — get length
    • .index — get index
    • repr() / print() — display results
    • __iter__ — iteration
    • .equals() — comparison

3. Unified architecture, simplicity first

  • Do not consider backward compatibility — first priority is architectural simplicity and elegance.
  • Don't create split class hierarchies for different execution engines.
  • ColumnExpr uniformly wraps all expression types.
  • Handle lazy execution through unified LazySeries, LazyGroupBy, etc.
  • LazyOp uniformly manages all lazy operations.
  • Avoid duplicate definitions; keep code structure clear with single responsibility.

4. Testing principles

Philosophy:

  • Discovered problems are opportunities to improve the library.
  • Analyse problems from an architectural perspective — don't easily modify tests just to pass them.
  • Using reset_index() in tests to mask problems = DataStore bug, not correct test writing.
  • Don't obsess over container type differences between DataFrame and DataStore.

FORBIDDEN behaviours:

  • ❌ Using comments to describe expected behaviour without actual assertions
  • ❌ Using print() / logging without corresponding assertions
  • ❌ Only verifying len() without verifying actual values
  • ❌ Writing "verify X" comments but not actually verifying X
  • ❌ Using # TODO: verify later or similar postponement

REQUIRED:

  1. Mirror code pattern (DataStore ↔ pandas) — test code must mirror DataStore and pandas operations for easy comparison.

    # pandas operations
    pd_df = pd.DataFrame({'name': ['Alice', 'Bob'], 'age': [25, 30]})
    pd_result = pd_df[pd_df['age'] > 20].sort_values('name')
    
    # DataStore operations (mirror of pandas)
    ds_df = DataStore({'name': ['Alice', 'Bob'], 'age': [25, 30]})
    ds_result = ds_df[ds_df['age'] > 20].sort_values('name')
    
    assert_datastore_equals_pandas(ds_result, pd_result)
    
  2. Complete output comparison (columns + data + order) — comparison must be complete: column names, data values, row order (if the pandas operation preserves or defines order).

    from tests.test_utils import assert_datastore_equals_pandas
    
    # Full comparison (columns + values + order)
    assert_datastore_equals_pandas(ds_result, pd_result)
    
    # For unordered results (e.g. groupby without sort)
    assert_datastore_equals_pandas(ds_result, pd_result, check_row_order=False)
    
    # Column-level helpers
    # assert_column_values_equal(ds_result, pd_result, 'col_name')
    # assert_columns_match(ds_result, pd_result)
    # assert_row_count_match(ds_result, pd_result)
    
  3. SQL execution verification via logs:

    log_output = log_capture.getvalue()
    self.assertIn('WHERE "value" > 30', log_output)  # exact SQL clause
    self.assertIn('Segment 1/', log_output)           # segment execution
    self.assertIn('[Pandas]', log_output)             # Pandas ops logged
    
  4. Test name must reflect what is being verified.

    # BAD
    def test_complex_pipeline(self):
        ...
    # GOOD
    def test_sql_pandas_sql_segments_exact_values_and_structure(self):
        ...
    
  5. Multi-step mirror for bug-fix tests (>= 5 ops chain) — the Mark/Slack amazon_sample.parquet bug (PR #577) was a single-op (GROUP BY) dispatcher bug, but it only surfaced as part of a 5-step user chain (filter -> groupby -> agg -> sort -> head -> filter). Tests that only reproduced the smallest failing op pair would have left it hidden. When you fix a bug discovered by a multi-step chain, the regression test MUST mirror the user's full chain length (typically >= 5 ops).

    FORBIDDEN:

    • Adding a regression test that only reproduces the smallest failing op or op pair when the original user code was longer
    • Splitting one user chain into multiple shorter tests "for clarity"
    • Using synthetic data that doesn't exercise the column-name conflict / null pattern / size class the user hit

    REQUIRED:

    • Verbatim mirror of the user's chain (read top to bottom and it should look like what the user wrote)
    • Either include a structural assertion (SQL substring / plan.layers shape inspection) or add the matching shape to test_sql_snapshot_assertions.py / test_plan_invariants.py
    • Put the test under datastore/tests/journeys/ with a name that traces back to the user / issue (e.g. test_mark_slack_amazon_reviews.py)
  6. Property-based sweep for compositional bugs — for dispatcher / planner / SQL builder changes, run the test_property_based_chains.py hypothesis sweep locally before submitting. When hypothesis finds a falsifying example:

    • Do NOT add the example to the property test's skip filter as the first response — that hides the bug
    • DO copy the falsifying chain into a verbatim regression test under datastore/tests/journeys/ with @unittest.expectedFailure
    • DO add a skip filter to the property test referencing the journey test by name, so the property test stays green while the bug is open
    • When the bug is fixed, flip the expectedFailure and remove the skip filter in the same commit

Self-check before submitting a test:

  • Does every "expected behaviour" comment have a corresponding assertion?
  • Are actual data values verified, not just lengths?
  • Are DataStore and pandas code mirrored (same operations, same style)?
  • Is column order verified (not just column names as a set)?
  • Is row order verified for order-preserving operations?
  • Is segment structure (type, ops, is_first_segment) fully verified?
  • Are error messages descriptive for debugging?
  • Would this test catch a real bug, or just pass trivially?
  • If this is a bug-fix test, does the chain length match the user's original?
  • If this is a dispatcher/planner change, did the property-based sweep pass locally?

Core philosophy: users write familiar pandas-style code; the backend automatically selects the optimal execution engine.


Standard followed: agents.md.