* feat(mito2): support exact sequence range reads Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * test(mito2): cover preserve row sequence table alter Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * fix(mito2): clear preserve_row_sequence marker on copy_region_from copy_region_from copies source FileMeta into the target region, which has an independent sequence domain. The physical per-row sequences in the copied file belong to the source region only; trusting them in the target would let an exact sequence-range request replay source-domain rows as if they were target sequences. Clear the preserve_row_sequence marker on copied files so the target fails closed with SequenceRangeUnsupported until the scan provably cannot intersect the copied rows. Add a regression test: copying from a preserve-enabled source into a preserve-enabled target clears the marker, and an exact (2, 7] request on the target returns SequenceRangeUnsupported instead of replaying source rows. Fixes #8865 Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * style: remove redundant doc comments for exact sequence range options Approved comment-cleanup-only changes for #8865: drop outdated doc summaries duplicated on the exact_sequence_range wrapper and the preserve_row_sequence field, drop pure-restatement doc comments on the SetRegionOption/UnsetRegionOption PreserveRowSequence variants, and remove the four structural SQL comments from the alter_preserve_row_sequence case. No behavior changes; .result regenerated by the sqlness runner. Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * fix(mito2): fail closed exact reads on copied files and extension ranges Address review feedback on #8865: - copy_region_from: clear the source-domain FileMeta::sequence along with the preserve_row_sequence marker. An unmarked file retaining a stale source-domain max sequence could be silently skipped by files_allow_exact_sequence_range() as 'proven disjoint' in the target's independent sequence domain, dropping rows on exact (C, H] reads. With sequence=None the capability check fails closed (SequenceRangeUnsupported) until the copied rows are provably disjoint. - Engine/reader: reject exact sequence-range reads whenever a follower region has an extension range provider attached. Extension streams are returned without a row-level sequence filter, so exactness cannot be proven; treat the capability as missing (fail closed) instead of emitting out-of-range rows. The reader also fails closed as defense in depth. Tests: extend copy_region_from regression to assert the copied file's sequence hint is cleared; mito2 suite 1148/1148 passing. Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * style(mito2): use doc comments for test function descriptions Elevate the block comments describing test functions (in scan_test and copy_region_from_test) to /// doc comments, matching the convention used elsewhere in the exact sequence range change. No logic change. Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * refactor(mito2): extract helpers and trim comment noise in exact sequence reads PR finalization for #8865 (zero behavior change, full mito2 suite green): - engine: extract validate_sequence_fences and sequence_range_unsupported_reason, keeping error variants, check order and reason strings identical; OSS binds the extension blocker to false. - handle_copy_region: extract remap_copied_file_meta and file_descriptors_for_meta; rename file_ids -> source_file_ids and files_to_copy -> new_file_metas. - compactor: rename max_input_sequence -> known_max_input_sequence, document the None semantics (empty input vs unknown sequence). - Remove restating/outdated comments (ScanInput::sequence_range doc first line, outdated file-pruning note, options test restatements), compress verbatim comments while keeping why/invariants/contracts. Verified: cargo check -p mito2 (+ --features enterprise), cargo fmt, git diff --check, mito2 suite 1148/1148 passing. Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * fix(mito2): reject foreign-region SSTs in exact sequence reads Reading an SST whose FileMeta.region_id differs from the scanned region means the region's sequence domain is broken (manifest corruption or a repartition/copy path that leaked a source-domain file). Treat this as an explicit RegionSequenceDomainBroken error instead of silently ignoring the file's sequence or falling back to a full scan: the region is unusable for exact sequence-range reads until the foreign lineage is compacted away or repaired. - files_allow_exact_sequence_range / exact_sequence_range now return Result and propagate the error through engine fence validation and scan construction (StatusCode::Internal, distinct from the fallback-capable SequenceRangeUnsupported). - Row-level flat-batch sequence filtering rejects foreign-region files as defense in depth. - Engine test asserts the broken-domain error rather than Unsupported/fallback. Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * fix(mito2): never trust unmarked SSTs for exact-range disjoint skipping An unmarked file's FileMeta.sequence may be synthesized by the region-edit or repartition paths (committed+1 import barrier), not a physical max of its rows. Treating it as a whole-file disjoint proof could permanently skip rows that were never incrementally consumed once the flow checkpoint passes that value. Exact sequence-range capability now requires every SST in the region to carry the preserve_row_sequence marker; any unmarked file disables exactness (fallback), and the (C, H] file-selection skip also only applies to marked files. Foreign-region files still raise RegionSequenceDomainBroken as before. Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * fix(mito2): scope exact sequence-range capability to the time-selected read set The exact capability check used to walk the entire SstVersion, so a single unmarked or foreign-region SST anywhere in the region disabled exact reads or raised RegionSequenceDomainBroken even when the request's time range could never touch that file. Both the engine fence and the scan builder now derive the read set with shared time-pruning + exact-min/sst-min selection and validate capability only over the files actually selected: a time-pruned file cannot contribute a row to (C, H], so it cannot affect exactness. The existing fail-loud semantics are unchanged for every selected file (foreign region id -> RegionSequenceDomainBroken; unmarked -> exact unavailable). Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * fix(mito2): wash untrusted sequences in compaction and restore barrier skipping Compaction with any non-preserved input now writes a sequence-less output: the physical __sequence column is zeroed (the flat format requires the internal columns) and FileMeta.sequence records the region-local admission barrier committed_sequence + 1 (falling back to the flushed frontier). preserve_row_sequence stays false. Exact sequence-range scans interpret an unmarked file's sequence as an admission barrier: barrier <= C means flow has already consumed the whole file, so it is skipped at file level; a missing or newer barrier fails closed. Foreign-region files stay in the selected read set so the capability fence still raises RegionSequenceDomainBroken. This closes the recovery loop: after a region repartition, one time-scoped fallback consumes the migrated rows, then compaction washes the untrusted per-row sequences away and exact incremental reads resume via file-level barrier skipping. Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * chore(mito2): drop restating comments in known_max_input_sequence tests Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * test(mito2): trim SQLness result EOF whitespace Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * refactor(mito2): reuse exact scan file selection Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * test(mito2): strengthen sequence scan coverage Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * style(mito2): trim ALTER option comments Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * test(mito2): remove no-op bulk compaction check Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * fix(mito2): preserve trusted row sequences when reading SSTs Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * fix(mito2): preserve target sequence domain for imported SSTs Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * test(mito2): add trailing blank line to SQLness result EOF Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * chore(mito2): trim exact sequence scan plumbing Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * refactor(mito2): fold exact SST selection checks Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * test(mito2): make legacy compaction rewrite deterministic Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * test(mito2): make PK compaction rewrite deterministic Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> --------- Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>
GreptimeDB Compatibility Test Framework
Compatibility tests verify that one GreptimeDB version can restart on state written by another version.
Tests are run via cargo sqlness compat and reuse the sqlness-runner infrastructure.
Quick Start
# Self-compat smoke test (current binary only):
cargo run -p sqlness-runner -- compat
# Test from a specific released version to current:
cargo run -p sqlness-runner -- compat --from-version v0.9.5
# Test between two local binary directories:
cargo run -p sqlness-runner -- compat --from-bins-dir ./bins/old --to-bins-dir ./bins/new
# Test a downgrade from the current build to a released binary:
cargo run -p sqlness-runner -- compat --from-bins-dir ./bins/current --to-version v1.1.4
# Run a compatibility case in standalone mode:
cargo run -p sqlness-runner -- compat --topology standalone --test-filter "downgrade_compatibility"
# Run a specific case:
cargo run -p sqlness-runner -- compat --test-filter "basic_table"
# Preview which cases would run (no services started):
cargo run -p sqlness-runner -- compat --dry-run --from-version v0.9.5
# See all options:
cargo run -p sqlness-runner -- compat --help
Prerequisites
- Docker (for etcd): PR1 always uses Docker etcd for distributed metadata. External metadata stores are future work.
- From binary: Either
--from-version <version>to auto-pull a release, or--from-bins-dir <path>to use a local build. The binarygreptimemust exist directly inside the given directory. - To binary: Defaults to the current debug build (
target/debug/greptime). Override with--to-bins-dir <path>or fetch a release with--to-version <version>. - Custom target-dir: If you use a non-default
CARGO_TARGET_DIR, the debug binary won't be attarget/debug/greptime. Pass--from-bins-dir/--to-bins-direxplicitly pointing to your custom target directory. Alternatively, runcargo build -p greptimewithout a custom target-dir.
Case Format
Each compat case is a directory under tests/compatibility/cases/ containing three required files plus an expected output file:
my_case/
case.toml # Metadata (required)
setup.sql # SQL to run on the from version (required)
verify.sql # SQL to run on the to version (required)
verify.result # Expected output from verify.sql
case.toml — Required Metadata
name = "my_case"
reason = "Why this compatibility case exists"
introduced_by = "PR #1234 or feature name"
topologies = ["distributed", "standalone"]
from_range = ["*"]
to_range = ["*"]
features = ["table"]
owner = "team-name"
# optional:
namespace = "my_explicit_namespace" # defaults to sanitized directory name
Required fields: name, reason, introduced_by, topologies, from_range, to_range, features, owner.
Old-Stage Datanode Configuration Overlay
To apply a datanode configuration overlay while running the old stage, add this
strict optional table to case.toml:
[old_config]
datanode = "old-datanode.overlay.toml"
datanode is required whenever [old_config] is present; empty tables and
unknown keys are rejected. The reference is relative to the case directory and
must remain confined to that directory. The sidecar is native datanode TOML,
which the runner loads and preflights before starting services or creating
state.
The runner first applies the datanode baseline, then merges the sidecar. Tables
merge recursively only when both values are tables. Scalars, type mismatches,
arrays, and arrays of tables replace the baseline value atomically. In
particular, region_engine has no special merge behavior.
Runner-owned fields cannot be changed by an overlay: mode, node_id,
storage.data_home, meta_client_options.metasrv_addrs, and wal.provider,
plus wal.dir for Raft WAL or wal.broker_endpoints for Kafka WAL. The runner
restores these fields to its baseline values, or deletes them when the baseline
has no value. It warns about protected-field overrides without printing their
values.
Version-Range Filtering
from_range and to_range control which binary versions a case applies to:
| Entry | Meaning |
|---|---|
"*" |
Matches any version (including unknown). |
"vX.Y.Z" or "=vX.Y.Z" |
Matches exactly version X.Y.Z. |
">=vX.Y.Z" |
Matches X.Y.Z or later. |
">vX.Y.Z" |
Matches versions strictly later than X.Y.Z. |
"<=vX.Y.Z" |
Matches X.Y.Z or earlier. |
"<vX.Y.Z" |
Matches versions strictly earlier than X.Y.Z. |
The range list is OR: a case matches if any entry matches.
Best-effort enforcement: The runner tries to determine the effective version:
--from-versionis used directly.--from-bins-dir/--to-bins-dir(or the default debug build) runs<binary> --versionto infer the version.- When the version cannot be determined (e.g. binary missing or
--versionfails), non-wildcard ranges are skipped with a message; wildcard (*) ranges still match.
Example (legacy_jsonb):
from_range = ["<=v1.1.0"]
to_range = [">=v1.1.1"]
This case only runs when the old binary is <= v1.1.0 and the new binary is >= v1.1.1.
CI Version Window
The CI job uses tests/compatibility/ci.toml to choose the small sliding
window of recent released from versions to test against the PR-built to
binary:
from_versions = ["v1.0.0", "v1.1.0"]
Keep this window small for PR and merge-queue latency: the goal is to catch
upgrade compatibility issues from recent releases to the latest build, not to
retest every historical version on every PR. Case-level from_range/to_range
still decides which cases run for each version pair; the CI window only decides
which old binaries are sampled. Broader historical windows belong in nightly or
release-validation workflows.
The GitHub Actions workflow delegates the window loading and compat invocation
to .github/scripts/run-compat.py; the workflow YAML should stay as a thin
wrapper around artifact download/extraction and this script.
downgrade_to_versions optionally lists releases that CI restarts after the
PR-built cluster. Those runs select only the downgrade_compatibility case in
both distributed and standalone topologies.
setup.sql — Setup Phase (From Version)
SQL statements executed on the from version cluster. These must succeed (any error fails the case). Setup output is NOT compared against any result file.
Rules:
- Statements are semicolon-terminated
--prefix for ordinary comments-- SQLNESS ...interceptor comments follow ordinary sqlness semantics
verify.sql — Verify Phase (To Version)
SQL statements executed on the to version cluster. Output is compared against verify.result in sqlness snapshot style.
verify.result — Expected Output
Expected output in sqlness format. If this file is missing, the runner generates it from actual output and fails — the author must review, commit the generated file, and rerun.
<statement>;
<output>
<next statement>;
<output>
If output differs from expected, the run fails and verify.result is updated with actual output.
PR1 Limitations
- Sqlness interceptors:
-- SQLNESS ...comments are applied per statement using the same interceptor registry as the ordinary sqlness runner, including the GreptimeDBPROTOCOLinterceptor. ForPROTOCOL POSTGRES, the namespace prelude usesSET search_pathinstead ofUSE. Avoid unqualified PostgreSQL-protocol table names starting withpg_: GreptimeDB's current PostgreSQL compatibility parser rewrites them topg_catalog.<table>. - Distributed topology: The compat runner starts 1 metasrv + 3 datanodes + 1 frontend + 1 flownode. Standalone compatibility runs need no external metadata store.
- No comment-based compat config: The compat runner does not define extra compatibility configuration in SQL comments; sqlness comments keep their normal sqlness meaning.
Namespace Isolation
Each case runs in its own database namespace to prevent cross-case interference:
- Default namespace is derived from the case directory name (sanitized to
[a-z][a-z0-9_]*) - Override with
namespaceincase.toml - Duplicate namespaces are rejected at discovery time (before version filtering)
- Before each statement, the runner executes a namespace prelude (not written to verify.result):
CREATE DATABASE IF NOT EXISTS <ns>via gRPC; thenUSE <ns>for gRPC/MySQL statements orSET search_path TO '<ns>'for PostgreSQL statements.
Batch Behavior
- The baseline (no-overlay) profile runs first. Cases whose old datanode TOML is semantically equivalent share one profile; profiles run serially and in isolation.
- Each profile has its own state and etcd lifecycle. Its overlay is applied only to old-stage datanodes and remains in effect through old-stage setup restarts. The current stage always uses a clean configuration.
- Cases run serially (no parallelism in PR1). Namespace state is session/protocol state and cannot be shared concurrently.
- Same namespace across cases is rejected.
- Without fail-fast, the runner verifies only cases whose setup succeeded. With fail-fast, it cleans up the active profile before stopping.
--dry-rundisplays the selected profiles, cases, and sidecar paths without printing configuration values; it starts no services.
xfail Policy (Future)
For PR1, all cases are expected to pass. Future PRs will add xfail support with required issue and expiry fields.
Cross-Job Distributed State
PR1 runs setup and verify in the same job (same process). Cross-job artifact restore for distributed state is not supported in PR1 due to port randomization and etcd lease expiration.