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
22 changes: 11 additions & 11 deletions PARITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -399,12 +399,13 @@ to the Rust-core-public `functions::DEFAULT_FUNCTION_TIMEOUT` (30s, #820) —
config-schema surface shared by every install, with **no** Python/TypeScript
public-API binding, so no drift.

- **`on-file` (B2 #327).** A **required** `[[table]]` key naming a per-file
command whose JSON-array stdout becomes the table's rows (interpolation-only
placeholders `{path}` (the file's **absolute** path, #542) / `{root}` — a
template that omits one receives no value, no append-if-absent, #538/#539;
per-file error isolation; unbounded since #820 — bound a hook by wrapping
its command in `timeout(1)`). A `[[table]]` without
- **`on-file` (B2 #327).** A **required** `[[table]]` key naming a per-table
command whose JSON-array stdout becomes the table's rows (every matched
file's **absolute** path appended as a trailing argument; the
interpolation-only placeholder `{root}` — a template that omits it receives
no value, no append-if-absent, #538/#539; a failed command fails the build;
unbounded since #820 — bound a hook by wrapping its command in
`timeout(1)`). A `[[table]]` without
it is a load error since #634 (after fact-injection removal a hook-less table
would emit only all-NULL rows), so `config::TableConfig::on_file` is `String`,
not `Option<String>`. Parsed and executed in the shared Rust core
Expand All @@ -423,8 +424,8 @@ public-API binding, so no drift.

- **`--on-file` (path-table parser, #631).** A `dirsql query` flag naming a
command that supplies every path-table's rows and schema (a JSON array of row
objects; the `on-file` hook contract — argv splitting, `{path}`/`{root}`,
per-file failure isolation). The stat columns are not
objects; the `on-file` hook contract — argv splitting, `{root}`, trailing
paths, a failed command fails the query). The stat columns are not
reachable on a parsed path-table; parsed scans honor the same skip rules stat
scans do. Threaded through the shared Rust core (`db::Db::set_path_table_parser`
→ the `dirsql_parsed` module) via a **doc-hidden** `DirSQLBuilder::path_table_parser`
Expand All @@ -451,7 +452,7 @@ public-API binding, so no drift.
- `AsyncDirSQL` uses tokio and `OnceCell` internally.
- Watch returns `futures_channel::mpsc::UnboundedReceiver<RowEvent>` implementing `Stream`.
- All fallible operations return `Result<T, DirSqlError>`. Statements classified as writes by SQLite's `sqlite3_stmt_readonly` surface as the unit variant `DirSqlError::WriteForbidden`; in the Python/TS bindings the same condition is a `RuntimeError` / `Error` with a "read-only" message.
- **Parity restored (dirsql#715).** All three SDKs expose the files a scan could not index: Rust `DirSQL::scan_failures() -> &[OnFileFailure]`, Python `await db.scan_failures()` (a list of `ScanFailure` with `.path` / `.message`, importable as `from dirsql import ScanFailure`), TypeScript `await db.scanFailures()` (`ScanFailure[]` with `path` / `message`, exported from the package barrel). A hook that fails, or a row the table rejects, skips that file rather than the scan; the list is how a caller learns the index is incomplete and which files are missing. Empty after a clean scan. The CLI additionally reports skips on stderr (capped at ten, then `... and N more`) and exits `23` — CLI-only by nature, since a library has no exit code. `--allow-skipped` remains unbuilt and is tracked in #715.
- **Parity restored (dirsql#715).** All three SDKs expose the files a scan could not index: Rust `DirSQL::scan_failures() -> &[OnFileFailure]`, Python `await db.scan_failures()` (a list of `ScanFailure` with `.path` / `.message`, importable as `from dirsql import ScanFailure`), TypeScript `await db.scanFailures()` (`ScanFailure[]` with `path` / `message`, exported from the package barrel). A programmatic per-file hook that fails, or a row the table rejects, skips that file rather than the scan; the list is how a caller learns the index is incomplete and which files are missing. Empty after a clean scan. A config-declared `on-file` command runs once per table, so its failure is a build error, not a skip; the CLI prints it and exits `1`. `--allow-skipped` remains unbuilt and is tracked in #715.

### TypeScript
- Uses `camelCase` for method names.
Expand Down Expand Up @@ -486,7 +487,7 @@ Real-core file map:
| Async / ready | `async_dirsql_test.py` | `async_sdk.rs` | `index.test.ts` |
| Watch events | `async_dirsql_test.py`, `docs_gaps_test.py` | `sdk.rs`, `watcher.rs`, `watch_relative_root.rs` | `watch.test.ts`, `index.test.ts` |
| Config file | `from_config_test.py` | `from_config.rs`, `config.rs` | `from-config.test.ts` |
| Persistence | `persist_test.py` | `persist.rs`, `persist_parsed_path_table.rs` | `persist.test.ts` |
| Persistence | `persist_test.py` | `persist.rs` | `persist.test.ts` |
| Extensions | `extensions_test.py`, `extension_package_test.py`, `config_extension_package_test.py` | `extensions.rs` | `extensions.test.ts`, `extension-package.test.ts`, `config-extension-package.test.ts` |
| Quoted-identifier DDL (#204) | `table_name_resolution_test.py` | `table_name_resolution.rs` | (covered by `declared-table-name.test.ts`) |
| Declared table `name` (#962) | `declared_table_name_test.py` | `declared_table_name.rs`, `declared_table_name_e2e.rs` | `declared-table-name.test.ts` |
Expand Down Expand Up @@ -603,7 +604,6 @@ incl. #313).
| Persist: `.dirsql/` excluded from walk | Y | Y | Y |
| Persist: custom persist_path honored | Y | Y | Y |
| Persist: unchanged run leaves cache byte-identical | Y | Y | Y |
| Persist: parsed path-table reuses cached rows (#825) | N/A — `--on-file` is a CLI flag, not an SDK surface | Y (`persist_parsed_path_table.rs`) | N/A — same |

### Extensions

Expand Down
4 changes: 2 additions & 2 deletions docs/howto/define-tables.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,8 @@ templates, with the trigger mistakes that fail silently, are in

## Going further

- The parser mechanics — placeholders, stdout protocol, per-file failure
isolation — are the [`on-file` hook contract](../reference/hooks.md#on-file);
- The parser mechanics — placeholders, stdout protocol, failure semantics —
are the [`on-file` hook contract](../reference/hooks.md#on-file);
[Extract rows from file contents](./extract-from-contents.md) is the fuller
recipe.
- Your directory layout encodes data (authors, dates, IDs)? Split the path in
Expand Down
18 changes: 7 additions & 11 deletions docs/howto/persist.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ By default the database is ephemeral: rebuilt from your files on every
startup and discarded on exit. The [`--persist [PATH]`](../reference/cli.md#dirsql-server)
flag keeps the SQLite index on disk instead, so a restart only re-parses
files that actually changed — the difference between seconds and
milliseconds on large trees, and between re-running and skipping expensive
[`on-file`](./extract-from-contents.md) commands.
milliseconds on large trees, and between re-running and skipping an expensive
declared-table [`on-file`](./extract-from-contents.md) command.

Whether and where to cache is a machine-local operational choice — it
belongs to the command you run, not to the shared `.dirsql.toml`. That is
Expand Down Expand Up @@ -58,15 +58,11 @@ exactly what a non-persistent startup does.
A run that changes nothing writes nothing: the cache file is read and left
exactly as it was.

This covers both kinds of table. Declared tables keep their indexed rows, and
a [path-table](../reference/path-tables.md) parsed with `--on-file` keeps each
file's parser output — so the second run over an unchanged tree spawns no
parser process at all. A path-table's cached rows are keyed by the tree, the
glob, the parser command and the `dirsql` version together, so changing any of
them parses afresh rather than serving rows the current command never
produced. A *stat* path-table caches nothing, because there is nothing to
save: its columns are the metadata the scan already collected, and `content`
is read live by design.
This covers declared tables, which keep their indexed rows. A
[path-table](../reference/path-tables.md) is not cached: a *stat* path-table
has nothing to save, since its columns are the metadata the scan already
collected and `content` is read live by design, and a path-table parsed with
`--on-file` runs its parser on every start.

Persistence is a startup-time optimization, not a change in meaning: the
database remains a derived view of your files, and queries return the same
Expand Down
6 changes: 1 addition & 5 deletions docs/reference/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -223,11 +223,7 @@ every statement, so it is reported once and exits `1` before the first prompt.
| Code | Meaning |
|---|---|
| `0` | Clean EOF (Ctrl-D, `exit`, `quit`, or the end of a piped script) — **including when statements failed**. Matches interactive `sqlite3`; use [`dirsql query`](#dirsql-query) when a script needs a statement's exit status. |
| `1` | The index could not be built (a bad `-c`, an unresolvable `--on-file`), or stdin could not be read. Nothing was executed. |

`23` (partial scan) is not produced here: skipped files are reported before the
first prompt, and a session's exit code describes the session rather than one
scan.
| `1` | The index could not be built (a bad `-c`, an unresolvable `--on-file`, a failed `on-file` command), or stdin could not be read. Nothing was executed. |

## `dirsql server`

Expand Down
6 changes: 3 additions & 3 deletions docs/reference/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -330,9 +330,9 @@ integral number → `INTEGER`, any other number → `REAL`; a string → `TEXT`;
nested array or object → its JSON text as `TEXT`.

A row's columns are exactly the keys the command emits, narrowed to the DDL;
dirsql merges nothing else in. Output that is not a JSON array of objects is a
per-file failure: the file is skipped with a stderr warning and the scan
continues (see [failure semantics](./hooks.md#failure-semantics)).
dirsql merges nothing else in. Output that is not a JSON array of objects fails
the table, and a failed table fails the build (see
[failure semantics](./hooks.md#failure-semantics)).

## Composing multiple configs

Expand Down
21 changes: 10 additions & 11 deletions docs/reference/hooks.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,8 @@ on-file = "timeout 30 my-extractor"
```

When the wrapper kills an overrunning command, the run exits non-zero and
the ordinary [failure semantics](#failure-semantics) apply — the file is
skipped, the scan continues.
the ordinary [failure semantics](#failure-semantics) apply — the table's
command failed, so the build fails.

::: warning Windows
Windows's built-in `timeout` command is a *sleep*, not a bound — it cannot
Expand All @@ -94,18 +94,17 @@ A hook run fails when the command:
- exits zero but prints no non-empty stdout line,
- or prints output that does not parse as a JSON array of row objects.

What a failure *means*: **per-file isolation.** The file contributes no rows
and is reported as skipped; the scan indexes every other file and commits.
The CLI names up to ten skipped files on stderr, then `... and N more`, and
exits `23` — distinct from `0` (clean) and `1` (the run failed), so a caller
can tell a partial index from a complete one. A row the table rejects under
`strict` counts as the same kind of failure.
What a failure *means*: the command ran once for the whole table, so its
failure is the table's, and a table that cannot be filled fails the build.
The error names the table and carries the command's exit status and stderr
tail; the CLI prints it and exits `1`. A row the table rejects under `strict`
counts as the same kind of failure.

## `on-file` contract

Runs once per file matched by the table's `glob`, at initial scan and on
every watched change. The command reads the file itself and prints a JSON
array of row objects; see [`[[table]]`](./config.md#table) for the
Runs once per table, over every file matched by the table's `glob`, at
initial scan and on every watched change. The command reads the files itself
and prints a JSON array of row objects; see [`[[table]]`](./config.md#table) for the
row-mapping rules.

The same command is attachable two ways, over the same contract: the
Expand Down
16 changes: 6 additions & 10 deletions docs/reference/path-tables.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,18 +156,14 @@ placeholder, same timeout. Its output *is* the table:
across the emitted rows. The stat columns (`path`, `size`, …) are **not**
reachable on a parsed path-table — a parser that wants the path emits it (it
has the paths). The two modes stay cleanly separate.
- **Failures are isolated per file.** A file whose parser fails (spawn, non-zero
exit, timeout, or no output) or whose output is not a JSON array of rows
contributes no rows; a one-line warning naming the file goes to stderr and the
scan continues. The schema is inferred from the files that did parse.
- **A parser failure is the table's failure.** A run that fails (spawn,
non-zero exit, a `timeout(1)` kill, or no output) or whose output is not a
JSON array of rows fails the query with an error naming the table; there is
no partial table.
- **The skip rules still apply.** A parsed scan honors the same `node_modules`
/`.git`/`ignore` rules a stat scan does (see below).
- **`--persist` skips the parser for unchanged files.** With a
[persistent cache](/howto/persist), each file's parser output is stored
against its stat metadata, so a later run over an unchanged tree serves the
rows from the cache and spawns no process. Change the file, the glob, the
parser command, or the dirsql version and that file (or that whole table) is
parsed again.
- **The parser runs on every start.** A parsed path-table is not kept in the
[persistent cache](/howto/persist); declared tables are.

`--on-file` applies to **every** path-table in the query and may be given **at
most once**. For different parsers per file set, define named tables in a
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"command": "just test-e2e",
"ran_at": 1791160145,
"exit_code": 0,
"commit": "aca220b1da45e03e5ff612678b4cdca085dbdedf",
"branch": "claude/1216-on-file-cleanup"
}
133 changes: 0 additions & 133 deletions packages/python/tests/e2e/persist_query_test.py

This file was deleted.

10 changes: 4 additions & 6 deletions packages/python/tests/integration/binding/scan_failures_test.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
"""Binding-tier tests for the scan's record of files it skipped (#715).

Since #714 a file whose ``on_file`` hook raises, or whose row the table
rejects, is skipped rather than failing the scan. The CLI reports those skips
on stderr and exits 23; a Python caller had no equivalent, so an incomplete
index was indistinguishable from a complete one -- the regression this closes.
"""Binding-tier tests for the scan's record of files a per-file ``on_file``
hook skipped: one whose hook raises, or whose row the table rejects, is skipped
rather than failing the scan, and the caller can tell an incomplete index from
a complete one.
"""

import os
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
**Removed** The partial-scan exit code `23` and the `dirsql: skipping ...`
diagnostics. An `on-file` command runs once per table, so a run that fails,
prints no output, or prints something other than a JSON array of row objects
is a build error naming the table: the CLI prints it and exits `1`, and the
SDK constructors return it. `scan_failures()` now records only programmatic
per-file hooks. Also removed is the persisted row cache for a path-table
parsed with `--on-file`: the parser runs on every start, and the cache file's
`_dirsql_parsed_rows` table is no longer created or read.
Loading
Loading