Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
1ad4250
Add exact-output test harness, golden oracle, and defect red list
jgarzik Aug 11, 2026
2b10512
Delete dead join.rs; make JoinOperator matching exhaustive
jgarzik Aug 11, 2026
2db8593
Upgrade sqlparser 0.36 -> 0.62
jgarzik Aug 11, 2026
db89948
Size the VM register file from the compiler's allocation count
jgarzik Aug 11, 2026
a8db112
Add symbolic jump labels; fix two silent wrong-answer bugs they exposed
jgarzik Aug 11, 2026
d05ed67
Add Not, Compare and Jump opcodes
jgarzik Aug 11, 2026
9282a86
Add the unified expression compiler; fix six defects
jgarzik Aug 11, 2026
b63d1fd
Support column references on the right-hand side of UPDATE SET
jgarzik Aug 11, 2026
4498529
Support DISTINCT in aggregates
jgarzik Aug 11, 2026
dc23731
Route all projection expressions through the unified compiler
jgarzik Aug 11, 2026
794f560
Apply WHERE and full key comparison to GROUP BY
jgarzik Aug 11, 2026
c124023
Bind HAVING to the aggregate and group key it names
jgarzik Aug 11, 2026
f3c2f7a
Apply ORDER BY and LIMIT to GROUP BY, aggregate and join results
jgarzik Aug 11, 2026
57b3c57
Implement three-valued logic and type coercion for comparisons
jgarzik Aug 11, 2026
8a8bc5a
Emit grouped results in projection order
jgarzik Aug 11, 2026
7443a32
Emit join results in projection order
jgarzik Aug 11, 2026
d008a48
Decouple ORDER BY keys from the projection
jgarzik Aug 11, 2026
0f98055
Compose aggregates with expressions in both directions
jgarzik Aug 11, 2026
869209f
Execute multi-statement scripts one statement at a time
jgarzik Aug 11, 2026
40bcd35
Replace rows in place on UPDATE
jgarzik Aug 11, 2026
2a5bb61
Give unordered window aggregates the partition total
jgarzik Aug 11, 2026
bddf57c
Fix MSRV, trim the published crate, document tsq, and cover it with t…
jgarzik Aug 11, 2026
96f12a4
Document subqueries, window functions, NULL semantics and coercion
jgarzik Aug 11, 2026
5ce13c7
Add fmt, clippy and MSRV gates to CI; serialize cross-platform tests
jgarzik Aug 11, 2026
40c7a5b
Support aggregates and GROUP BY over explicit inner joins
jgarzik Aug 11, 2026
d948975
Support derived tables (subqueries in FROM)
jgarzik Aug 11, 2026
8c6cdd0
Route join and multi-table conditions through the shared expression c…
jgarzik Aug 11, 2026
4f3c6da
Unify the register-addressed join path with the cursor-addressed ones
jgarzik Aug 11, 2026
c7474b2
Give function arguments the caller's name-resolution scope
jgarzik Aug 11, 2026
60505f6
Support table aliases in explicit joins
jgarzik Aug 11, 2026
b04907f
Remove the dead no-alias join condition wrapper
jgarzik Aug 11, 2026
f4559e7
Reject unimplemented window frames instead of ignoring them
jgarzik Aug 11, 2026
dd75872
Correct the REPL documentation and stop the version string drifting
jgarzik Aug 11, 2026
2374f85
Apply ORDER BY and LIMIT to multi-table queries
jgarzik Aug 11, 2026
509236c
Address Copilot review: stale row count, weak assertion, wrong opcode…
jgarzik Aug 11, 2026
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
55 changes: 54 additions & 1 deletion .github/workflows/TestingCI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -74,5 +74,58 @@ jobs:
restore-keys: ${{ runner.os }}-cargo-
- name: Build
run: cargo build --verbose
# Serialized like the Linux jobs. The REPL tests spawn the binary as a
# subprocess and several tests share fixtures under tests/data/, so
# running them in parallel here made macOS and Windows flaky in a way
# Linux never showed.
- name: Run tests
run: cargo test --verbose
run: cargo test --verbose
env:
RUST_TEST_THREADS: 1

lint:
name: Format and Clippy
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt, clippy
- name: Cache dependencies
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: ${{ runner.os }}-cargo-lint-${{ hashFiles('**/Cargo.lock') }}
restore-keys: ${{ runner.os }}-cargo-lint-
- name: Check formatting
run: cargo fmt --all -- --check
- name: Clippy
run: cargo clippy --all-targets -- -D warnings

msrv:
name: Minimum Supported Rust Version
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# Pinned to the rust-version declared in Cargo.toml. That field claimed
# 1.70 for a long time while the dependency tree had moved well past it,
# because nothing ever checked.
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@1.88
- name: Cache dependencies
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: ${{ runner.os }}-cargo-msrv-${{ hashFiles('**/Cargo.lock') }}
restore-keys: ${{ runner.os }}-cargo-msrv-
# Library and binaries only: rust-version is a promise to consumers, and
# dev-dependencies are not part of it.
- name: Check build at MSRV
run: cargo check --verbose
34 changes: 29 additions & 5 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,22 @@ SQL execution uses a bytecode VM inspired by SQLite's architecture:

### VM Module (`src/vm/`)

- `bytecode.rs`: Defines bytecode instruction set
- `compiler.rs`: Compiles SQL AST to bytecode
- `bytecode.rs`: Bytecode instruction set and register/program types
- `compiler.rs`: SQL AST to bytecode. Holds `code_expr`, the single expression
compiler (the analogue of SQLite's `sqlite3ExprCode`) used from the SELECT
list, WHERE, HAVING, ORDER BY and SET clauses alike, plus `NameCtx`, which
carries the FROM sources a name can resolve against
- `compiler_aggregate.rs`: aggregates, GROUP BY, HAVING
- `compiler_join.rs`: joins
- `compiler_dml.rs`: INSERT / UPDATE / DELETE
- `compiler_ddl.rs`: CREATE / DROP / ALTER / TRUNCATE
- `compiler_window.rs`: window functions
- `ast_compat.rs`: thin accessors normalizing sqlparser AST shapes
- `engine.rs`: Executes bytecode instructions

Each statement is compiled and executed separately, so a statement observes the
effects of the ones before it.

### File Handling

- **`file_handler.rs`**: Manages loading files into database tables
Expand All @@ -62,9 +74,10 @@ SQL execution uses a bytecode VM inspired by SQLite's architecture:

### SQL Features

- `aggregate.rs`: COUNT, SUM, AVG, MIN, MAX functions
- `join.rs`: Cross join and INNER JOIN implementations
- `string_functions.rs`: UPPER, LOWER, TRIM, SUBSTR, REPLACE
Expression compilation, including all scalar and string functions, lives in
`src/vm/compiler.rs` behind `code_expr`. Aggregates are compiled in
`src/vm/compiler_aggregate.rs` and executed by the `AggStep`/`AggFinal`
opcodes; `src/aggregate.rs` retains only the function-name lookup.

### Safe Writeback Model

Expand All @@ -79,4 +92,15 @@ Tests are in `tests/` organized by functionality:
- `helpers/`: Test utilities
- `common.rs`: Shared test helpers including REPL script runner

Two suites carry the correctness contract:

- `tests/golden/`: exact-output characterization tests. They assert the
COMPLETE stdout -- every row, in order -- via `assert_query`. The rest of the
suite uses `predicate::str::contains`, which is substring matching: a test
expecting `"Engineering,3"` passes even when extra wrong rows are emitted and
regardless of row order. Prefer `assert_query` for new tests.
- `tests/defects/`: `#[ignore]`d tests encoding CORRECT behaviour for known
defects. Run with `cargo test --test mod defects -- --ignored`. Remove the
`#[ignore]` when a defect is fixed; the list only shrinks.

Tests use `assert_cmd` for CLI testing and `tempfile` for temporary test data.
88 changes: 86 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 15 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,24 @@ homepage = "https://github.com/jgarzik/sqawk"
readme = "README.md"
keywords = ["csv", "tsv", "sql", "delimited", "awk"]
categories = ["command-line-utilities", "text-processing", "parser-implementations"]
# Minimum version of Rust required
rust-version = "1.70.0"
exclude = ["tests/", "doc/", ".github/", "CLAUDE.md"]
# Minimum supported Rust version.
#
# Verified by building with that toolchain, not inferred. The binding
# constraints are transitive: `home` (via rustyline) and `psm` (via
# sqlparser -> recursive -> stacker) both require 1.88.
rust-version = "1.88"
exclude = [
"tests/",
"doc/",
".github/",
"CLAUDE.md",
"generated-icon.png",
".replit",
]

[dependencies]
clap = { version = "4", features = ["derive"] }
sqlparser = "0.36"
sqlparser = "0.62"
csv = "1"
anyhow = "1"
regex = "1"
Expand Down
28 changes: 28 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ Sqawk is an SQL-based command-line tool for processing delimiter-separated files
- **Joins** - INNER, LEFT, RIGHT, FULL OUTER, and CROSS joins with ON conditions
- **Aggregates** - COUNT, SUM, AVG, MIN, MAX with GROUP BY support
- **Functions** - String (UPPER, LOWER, SUBSTR, REPLACE, etc.), math (ABS, ROUND, etc.), date/time
- **Subqueries** - Scalar, `IN (SELECT ...)`, and `EXISTS`, including correlated
- **Set Operations** - UNION, UNION ALL, INTERSECT, EXCEPT
- **Window Functions** - ROW_NUMBER, RANK, DENSE_RANK, LAG, LEAD, and aggregates with `OVER (PARTITION BY ... ORDER BY ...)`
- **DDL** - CREATE TABLE, CREATE TABLE AS SELECT, DROP, ALTER TABLE ADD COLUMN, TRUNCATE
- **Expressions** - CASE, CAST, COALESCE, NULLIF, BETWEEN, IN, LIKE/ILIKE, `||`, arithmetic
- **File Formats** - CSV, TSV, and custom delimiters; headerless files via `--tabledef`
- **Safe by Default** - Files unchanged unless `--write` flag is specified
- **Interactive REPL** - Explore data interactively with `-i` flag
Expand All @@ -38,6 +43,29 @@ sqawk -s "SELECT department, AVG(salary) FROM employees GROUP BY department" emp
sqawk -s "UPDATE data SET status = 'archived' WHERE year < 2020" data.csv --write
```

## `tsq` - test data generator

`cargo install sqawk` also installs `tsq`, which generates deterministic
multi-table CSV data plus a corpus of SQL queries for exercising sqawk.

```sh
tsq --seed 42 --rows 1000 --output-dir /tmp/sqawk-test
sqawk -s "SELECT * FROM customers LIMIT 10" /tmp/sqawk-test/data/customers.csv
```

It writes `data/` (customers, products, orders, order_items, reviews, with
realistic foreign-key relationships), `queries/` (numbered `.sql` files
covering selects, joins, aggregates, subqueries and window functions),
`verify/run_verification.sh`, and a `metadata.json` recording the seed and row
counts. The same seed always produces the same data.

| Option | Meaning |
| --- | --- |
| `-s`, `--seed` | Seed for reproducible generation; a random one is printed if omitted |
| `-r`, `--rows` | Base customer row count; other tables scale proportionally (default 100000) |
| `-o`, `--output-dir` | Where to write the generated tree (required) |
| `-v`, `--verbose` | Show generation progress |

## Documentation

- [User Guide](doc/user_guide.md) - Installation, CLI options, and examples
Expand Down
Loading
Loading