Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions .claude/skills/krishiv-golden-category/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,11 @@ or by hand (ADR 0008). Three kinds exist; pick the one that fits.
`tests/fixtures/analysis/type-coercion/` (one file per coercion input file) and `registry-matrix/` (one file per
function) list the cases that fail
today, each with a reason; `crates/krishiv-testkit/tests/{type_coercion,registry_matrix}.rs` require the list to
match the actual failures exactly, so a fix shrinks it and a regression fails. `tests/spark-golden/known-failures.json`
holds the categories and rules while `tests/spark-golden/known-failures-cases/` holds one file per golden input file.
match the actual failures exactly, so a fix shrinks it and a regression fails. The golden baseline is split the same
way: `tests/spark-golden/known-failures-categories/` holds one file per category with the rules that assign it (each
rule has an `order`; rules are tried in ascending order across all files, so leave gaps) and
`tests/spark-golden/known-failures-cases/` one file per golden input file; `known-failures.json` itself holds only the
format version, Spark tag and freeze date.
- Regenerate with `KRISHIV_UPDATE_MANIFEST=1 cargo test -p krishiv-testkit --test <name>` (or `cargo xtask golden
--engine session --ratchet update` for the golden baseline), then read the diff:
entries removed are progress, entries added are regressions to explain or fix. Only the files whose content changed
Expand Down
2 changes: 1 addition & 1 deletion crates/krishiv-io/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,4 +76,4 @@ Paths are relative to this crate.

## Tests
`cargo nextest run -p krishiv-io --no-tests=pass` · `cargo clippy -p krishiv-io --all-targets -- -D warnings` ·
conformance: `cargo nextest run -p krishiv-testkit --test file_format_mock` · bench: `cargo bench -p krishiv-io --bench io`
conformance: `cargo nextest run -p krishiv-testkit --test file_format_mock` · bench: `cargo bench -p krishiv-io` (targets `io`, `coalesce` and `runtime_filter`)
4 changes: 4 additions & 0 deletions crates/krishiv-io/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ tokio = { workspace = true, features = ["macros", "rt", "rt-multi-thread", "sync
[target.'cfg(loom)'.dev-dependencies]
loom.workspace = true

[[bench]]
name = "coalesce"
harness = false

[[bench]]
name = "io"
harness = false
Expand Down
40 changes: 40 additions & 0 deletions crates/krishiv-io/benches/coalesce.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// SPDX-License-Identifier: Apache-2.0
//! Coalescing 10,000 column-chunk ranges of one file into requests, run
//! under CodSpeed in CI in its own binary (single-threaded, no I/O) so
//! allocator state from the Parquet encode/scan benchmarks cannot swing it.

#![expect(
missing_docs,
reason = "criterion_group! generates an undocumented function"
)]

use std::hint::black_box;
use std::ops::Range;

use criterion::{Criterion, criterion_group, criterion_main};
use krishiv_io::io::coalesce;

/// Column chunks of 500 row groups × 20 columns, a few KiB to a few MiB
/// apart, listed in column-major order (as a projection asks for them).
fn ranges() -> Vec<Range<u64>> {
let mut out = Vec::with_capacity(10_000);
for col in 0..20_u64 {
for rg in 0..500_u64 {
let start = rg * (8 << 20) + col * (300 << 10) + (rg * col) % 4096;
out.push(start..start + (200 << 10) + (col * 977) % 65_536);
}
}
out
}

fn bench(c: &mut Criterion) {
let ranges = ranges();
let mut group = c.benchmark_group("io");
group.bench_function("coalesce_10k_ranges", |b| {
b.iter(|| coalesce(black_box(&ranges), 1 << 20, 16 << 20));
});
group.finish();
}

criterion_group!(benches, bench);
criterion_main!(benches);
20 changes: 0 additions & 20 deletions crates/krishiv-io/benches/io.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
//! (single-threaded, no I/O):
//! - Spark split planning of 10,000 listed files (sizes up to 300 MiB, a
//! third not splittable);
//! - coalescing 10,000 column-chunk ranges of one file into requests;
//! - encoding 16,384 rows (an integer and a string column) as one Parquet
//! file with the default (snappy) settings, footer and page indexes
//! included: arrow-rs's encoder plus the writer's own per-batch work;
Expand All @@ -22,15 +21,13 @@
)]

use std::hint::black_box;
use std::ops::Range;

use std::sync::Arc;

use arrow_array::{Int64Array, RecordBatch, StringViewArray};
use arrow_schema::{DataType, Field, Schema};
use criterion::{Criterion, criterion_group, criterion_main};
use krishiv_common::config::Config;
use krishiv_io::io::coalesce;
use krishiv_io::parquet::ParquetFormat;
use krishiv_io::split::{SplitConfig, SplitFile, plan};
use krishiv_io::{
Expand All @@ -53,19 +50,6 @@ fn files() -> Vec<SplitFile> {
.collect()
}

/// Column chunks of 500 row groups × 20 columns, a few KiB to a few MiB
/// apart, listed in column-major order (as a projection asks for them).
fn ranges() -> Vec<Range<u64>> {
let mut out = Vec::with_capacity(10_000);
for col in 0..20_u64 {
for rg in 0..500_u64 {
let start = rg * (8 << 20) + col * (300 << 10) + (rg * col) % 4096;
out.push(start..start + (200 << 10) + (col * 977) % 65_536);
}
}
out
}

/// 16,384 rows in batches of 2,048: a key and a short, repetitive string.
fn rows() -> Vec<RecordBatch> {
let schema = Arc::new(Schema::new(vec![
Expand Down Expand Up @@ -181,14 +165,10 @@ fn scan(rt: &tokio::runtime::Runtime, io: &IoContext, file: &FileRef, req: &Scan
fn bench(c: &mut Criterion) {
let files = files();
let cfg = SplitConfig::from_config(&Config::new(), 16);
let ranges = ranges();
let mut group = c.benchmark_group("io");
group.bench_function("split_plan_10k_files", |b| {
b.iter(|| plan(black_box(&cfg), black_box(&files)));
});
group.bench_function("coalesce_10k_ranges", |b| {
b.iter(|| coalesce(black_box(&ranges), 1 << 20, 16 << 20));
});
let batches = rows();
let o = FormatOptions::defaults(ParquetFormat.options());
group.bench_function("parquet_encode_16k_rows", |b| {
Expand Down
10 changes: 6 additions & 4 deletions crates/krishiv-testkit/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@ the `SQLQueryTestSuite` golden runner over `tests/spark-golden/<tag>/` (`.sql.ou
three-number report, exclusion manifest `tests/spark-golden/exclusions.json`; `cargo xtask golden` writes
`target/golden-report.json`; `--engine sql-parse` measures the `krishiv-sql` parser on the DQL statements,
`golden::sql_parse`; `--engine session` runs every statement through `krishiv-session`, `golden::session_engine`),
the frozen statement-level baseline `tests/spark-golden/known-failures.json` (categories and classification rules) with
one file per golden input file under `tests/spark-golden/known-failures-cases/` (`golden::known_failures`: every failing
statement with a category — unsupported feature with its plan task, divergence with ADR 0023, or bug with its owner;
classification rules in the same file, `golden::classify`) and its CI ratchet (test `golden_ratchet`: a newly failing
the frozen statement-level baseline, one file per key (`golden::known_failures`): `tests/spark-golden/known-failures.json`
holds only the format version, Spark tag and freeze date; `known-failures-categories/<category>.json` one category
each (unsupported feature with its plan task, divergence with ADR 0023, or bug with its owner) with the classification
rules that assign it (`golden::classify`; each rule has an `order`, rules are tried in ascending order across all
files, so leave gaps); `known-failures-cases/<golden file>.json` one golden input file's failing statements by category.
Its CI ratchet (test `golden_ratchet`: a newly failing
statement fails, a listed statement that passes must be removed; refresh with
`cargo run -p krishiv-testkit --bin golden-report -- --engine session --ratchet update`), the batch == bounded-stream invariant
harness, a sqllogictest runner over `tests/sqllogictest/`, and the Spark function matrix (P2-T106a):
Expand Down
3 changes: 2 additions & 1 deletion crates/krishiv-testkit/src/bin/golden-report.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
//! `krishiv-session` ([`krishiv_testkit::golden::session_engine`]).
//! * `--cases FILE` writes every statement that did not pass, with why.
//! * `--ratchet check` compares the run with the frozen baseline
//! (`tests/spark-golden/known-failures.json`) and fails on any difference;
//! (`tests/spark-golden/known-failures.json` with its per-category and per-file directories)
//! and fails on any difference;
//! `--ratchet update` rewrites the baseline: passing statements leave it, new failures are
//! classified by its rules (the command fails, writing nothing, if a failure matches no rule).
#![forbid(unsafe_code)]
Expand Down
4 changes: 4 additions & 0 deletions crates/krishiv-testkit/src/golden/classify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ use super::parse::Statement;
pub struct Rule {
/// Category key the rule assigns.
pub category: String,
/// Position among all rules: lower matches first. Not part of the rule's
/// identity ([`PartialEq`] compares the rule as written).
pub order: u32,
command: Option<bool>,
depends: Option<bool>,
file: Option<Regex>,
Expand Down Expand Up @@ -108,6 +111,7 @@ impl Rule {
detail: re("detail")?,
family,
source: v.clone(),
order: 0,
category,
};
if rule.source.as_object().is_some_and(|o| o.len() == 1) {
Expand Down
Loading
Loading