Skip to content

refactor: extract the schema language into worktable_dsl - #79

Closed
pathscale wants to merge 2 commits into
masterfrom
feat/extract-dsl-lib
Closed

refactor: extract the schema language into worktable_dsl#79
pathscale wants to merge 2 commits into
masterfrom
feat/extract-dsl-lib

Conversation

@pathscale

Copy link
Copy Markdown
Owner

The problem

A WorkTable schema is written down exactly once, in a worktable! invocation. The parser that understands it — and understands it exactly as the compiler does — lived in worktable_codegen, which is declared proc-macro = true.

A proc-macro crate can export nothing but macros. So Columns, Index, PrimaryKey, Persistence, PartitionKey, Queries and the parser that produces them were unreachable from any other crate no matter how pub they were. mod common was not even public at the crate root.

Anything wanting to read a schema therefore had two options: re-implement the grammar and drift from it, or do without. A diagram, a migration tool, a documentation generator and a visual editor all want to read one. I started re-implementing it before finding this, which is the evidence that the trap is real.

codegen/src/lib.rs has been carrying // TODO: Refactor this codegen stuff because it's now too strange.

The change

model and parser move to a new plain library, worktable_dsl.

Nothing in them changed. Same code, same five dependencies — none added, none dropped. worktable_codegen depends on the new crate, so there is one grammar rather than a copy that can disagree with the compiler about what a schema means.

crate::common:: still resolves, through a thin module that re-exports the new crate. The 127 such paths across 67 files are untouched, so this reads as a move rather than a sweep.

What stayed behind, and why

name_generator remains in codegen. It invents Rust identifiers for generated code, which is not part of the schema language — and generators here define inherent impls on WorktableNameGenerator, which the orphan rule permits only in the crate that owns the type.

I had it in the extracted crate first and the build refused. The compiler makes the same argument the design does about where it belongs.

A property that had no owner until now

The integration test records something worth knowing before anyone builds on this.

Columns::columns_map is a std::collections::HashMap, whose iteration order Rust randomises per process. Two runs over the same input gave:

["answered", "project_id", "id"]
["project_id", "answered", "id"]

Nothing had noticed, and nothing needed to: the macro does not care what order it sees columns in, and the parser's own tests collect columns_map into another HashMap and assert membership. The property was never specified because no caller existed to depend on it.

A consumer does. A diagram or documentation page iterating that map renders a different table on every run and looks like its own bug.

field_positions already carries declaration order and is the field to sort by. The test asserts that and explains why, so the next consumer finds out here instead of by shipping it.

I have not changed the map type or insertion order. That changes what generated code sees, and it belongs to whoever owns the macro — not to a drive-by inside an extraction whose entire claim is that nothing changed.

Verification

  • whole workspace compiles
  • cargo fmt --all --check clean
  • the new integration test runs three times with the same result, where the earlier order-dependent assertion varied on every run
  • the test compiles as its own crate, so it fails loudly if this ever becomes a proc-macro crate again

meh added 2 commits September 1, 2026 14:04
A schema is written down once, in a `worktable!` invocation, and the parser
that understands it lived in `worktable_codegen`, which is
`proc-macro = true`. A proc-macro crate can export nothing but macros, so every
type describing a schema — the columns, the primary key, the indexes, the
queries — was unreachable from any other crate however public it was declared.
`mod common` was not public at that crate's root either.

So anything wanting to *read* a declaration had two options: re-implement the
grammar and drift from it, or do without. A diagram, a migration tool, a
documentation generator and an editor all want to read one. `lib.rs` has
carried `// TODO: Refactor this codegen stuff because it's now too strange.`

`model` and `parser` move to `worktable_dsl`, a plain library. Nothing in them
changed; the dependencies are the five they already used, none added, none
dropped. `worktable_codegen` now depends on it, so there is one grammar rather
than a copy that can disagree with the compiler about what a schema means.

`name_generator` stays in codegen. It invents Rust identifiers for generated
code, which is not the schema language, and generators here define inherent
`impl`s on `WorktableNameGenerator` — the orphan rule allows that only in the
crate owning the type. I had it in the extracted crate first and the compiler
made the same argument the design does.

`crate::common::` still resolves, through a thin module that re-exports the new
crate, so the 127 paths across 67 files are untouched and the diff stays a move
rather than a sweep.

An integration test reads a declaration from outside, which is the claim worth
pinning: it compiles as its own crate, so it stops building if this ever
becomes a proc-macro crate again.

It also records a property no caller existed to depend on before.
`Columns::columns_map` is a `std::collections::HashMap`, whose iteration order
Rust randomises per process; two runs of the same input gave
`["answered", "project_id", "id"]` and `["project_id", "answered", "id"]`. The
macro never cared, and the parser's own tests collect it into another `HashMap`
and assert membership, so nothing noticed. A consumer rendering columns in that
order draws a different table every run. `field_positions` already carries the
declaration order and is the field to sort by; the test asserts that, and says
so, so the next consumer learns it here rather than by shipping the bug.
`pub use worktable_dsl::{Parser, *}` failed clippy under `-D warnings` with
"unused import: `*`". `worktable_codegen` is a proc-macro crate, so its
`pub use` re-exports are not reachable from outside it -- the glob was only
ever visible within this crate, and nothing here needed what it brought in
beyond the `model` and `parser` modules the next line already re-exports.

Naming `Parser` alone keeps every `crate::common::` path working.
@pathscale

Copy link
Copy Markdown
Owner Author

Green now. The three failing checks were one cause: pub use worktable_dsl::{Parser, *} in codegen/src/common/mod.rs left an unused glob, and CI runs clippy with -D warnings. worktable_codegen is a proc-macro crate, so its pub use re-exports are not reachable outside it and the glob was only ever visible within the crate — redundant with the explicit model, parser re-export on the next line. Fixed in 6c76a6e.

Build and test (default) was a separate, unrelated thing: index::unique::tests::all_backends_preserve_disjoint_concurrent_mutations panicked at src/index/unique.rs:256 (266 passed, 1 failed). It passes locally on the same commit (267 passed) and master fails intermittently too — 2 of the last 8 runs, one of them on a different concurrency test, vacuum_parallel_with_upserts. So that is pre-existing flakiness in the concurrency suite, not this PR, and it is tracked separately.

Coordination note. There is an unpushed local branch feat/wt-designer-ir that contains this PR's commit rebased onto master, plus its own equivalent of the glob fix and two further commits (Read a schema as data, and write one back, Say what changed between two schemas, and what it costs). Whoever owns that branch: this PR is green and mergeable as it stands, so the cheapest path is probably to merge this first and rebase the designer work on top, rather than opening a second WorkTable PR that re-contains the extraction.

@pathscale

Copy link
Copy Markdown
Owner Author

Superseded by #87, which carries this extraction as its first two commits (refactor: extract the schema language into worktable_dsl and Drop the redundant glob from the codegen shim), rebased onto master. Nothing here is dropped.

@pathscale pathscale closed this Sep 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant