1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
//! The code generator: a live schema in, readable model `.rs` files out.
//!
//! keelson-gen is an **independent CLI emitting `.rs` files** (the bob/sqlc
//! stance), not a proc macro: generated code is meant to be read, diffed and
//! stepped through. The pipeline is introspect → resolve → emit:
//! [`introspect::introspect`] turns a connection string into a plain
//! [`schema::Schema`] IR, `resolve` applies the whole [`config::Config`]
//! (filters, renames, relations, hooks, the type map) so emission is
//! decision-free, and `emit` renders TokenStreams built with `quote`,
//! formatted with `prettyplease`. What it emits is exactly what the
//! hand-written spec models in `keelson-models/tests/spec_psql.rs` /
//! `spec_sqlite.rs` / `spec_mysql.rs` fix — those files are the
//! authoritative specification, and this crate's tests run the generated code
//! through the same assertions. The factory half is specified the same way,
//! by `keelson-factory/tests/spec_*.rs`.
//!
//! The main entry point is [`run`]; [`generate`] returns the files without
//! writing them, and [`generate_from_schema`] skips introspection for
//! callers (and tests) that already hold an IR.
//!
//! The generator has a second, independent output: [`queries`] turns
//! **hand-written `.sql` files** into typed modules (the sqlc-shaped half),
//! keyed off the `[queries]` section of the same config and reading the same
//! [`schema::Schema`]. Its docs carry the nullability decision table and the
//! two-faces design; nothing in the model pipeline depends on it.
//!
//! # Where this sits
//!
//! Layer 4 of keelson, and the only crate here you run rather than link: it is
//! a CLI (`cargo install keelson-gen`) that reads a live schema and writes `.rs`
//! files against [keelson-models](https://docs.rs/keelson-models) and
//! [keelson-factory](https://docs.rs/keelson-factory), in the dialect of
//! [keelson-psql](https://docs.rs/keelson-psql), [keelson-mysql](https://docs.rs/keelson-mysql) or
//! [keelson-sqlite](https://docs.rs/keelson-sqlite). It emits no DDL and tracks no migration
//! history — point it at the database your migration tool produced. The whole
//! map is the [keelson](https://docs.rs/keelson) facade crate.
//!
//! # The decisions, recorded
//!
//! **Introspection: direct catalog queries, not sea-schema.** sea-schema
//! (SeaORM's introspection crate) was evaluated against querying
//! `pg_catalog` / `sqlite_master` directly, per dialect:
//!
//! - *Weight:* sea-schema brings sea-query plus an async runtime coupling
//! (its discovery API is async over sqlx), where this crate otherwise
//! needs only the sync `rusqlite` and `postgres` clients **already in the
//! workspace** for keelson-sqlcheck's live judges. A dependency that heavy
//! must earn its keep, and here it cannot:
//! - *Lossy middle layer:* sea-schema normalises types into its own
//! `ColumnType` enum, which would have to be translated back into type
//! *names* to feed `docs/type-mappings.md`'s table and the config's
//! `db_type` matchers. Querying `format_type(...)` / the declared SQLite
//! type text hands the type map its keys verbatim.
//! - *Determinism:* owning the catalog queries means owning every `ORDER
//! BY`; byte-identical output is a contract, not a hope.
//!
//! So: SQLite via `sqlite_master` + `pragma_table_info` /
//! `pragma_foreign_key_list` (rusqlite), PostgreSQL via `pg_catalog` +
//! `format_type` (postgres), MySQL via `information_schema` + `COLUMN_TYPE`
//! (mysql) — the same pattern, and the same reason for taking each type
//! spelling verbatim (`COLUMN_TYPE`, not `DATA_TYPE`, because only the
//! former distinguishes `tinyint(1)` from `tinyint`).
//!
//! **Schema provenance is the user's migration flow.** keelson-gen takes a
//! connection string and reads what is there; it neither parses migration
//! files nor tracks schema history. Point it at the database your
//! migrations produced.
//!
//! **Determinism.** Same schema + same config ⇒ byte-identical files:
//! tables sorted by name, columns in catalog order, foreign keys sorted by
//! column list, one bundled formatter (prettyplease — the user's rustfmt
//! version never touches the output), a fixed header with no timestamps.
//! Pinned by a generate-twice test.
//!
//! **Generated files are never hand-edited (the sqlc stance), and hooks
//! live outside them.** bob regenerates wholesale and so does keelson-gen:
//! every emitted file starts with `@generated … DO NOT EDIT`. The spec
//! models show application-written hooks *inside* the `Table` impl, which a
//! wholesale regenerator would clobber — the recorded resolution is
//! **config-declared hook delegation**: `[tables.users] hooks =
//! ["before_insert", …]` makes the generator emit an override of exactly
//! those trait methods, each a one-line delegation to
//! `<hooks.module>::users::before_insert(…)` — a module the application
//! writes by hand, outside the generated directory. Unlisted hooks stay
//! trait defaults (nothing is emitted, per the models crate's design), a
//! listed-but-unwritten hook is a compile error naming the missing path,
//! and regeneration can never eat application code because application code
//! never lives in a generated file.
//!
//! **Overrides must bind, at one named line.** Every column whose type came
//! from `[types.map]` or `[[types.override]]` emits
//! `const _: () = keelson_exec::assert_bind::<T>();` under a doc comment
//! naming the column — a replacement type that cannot bind fails to compile
//! on that line, not in an inference swamp (the contract
//! `keelson_exec::Bind` was built for).
//!
//! **Dialects.** PostgreSQL and SQLite are identical in shape (both have
//! `RETURNING` and `DEFAULT VALUES`); the machinery differences live
//! entirely in which crate the statements come from. MySQL is deliberately
//! *not* a copy of that path, because it has no `RETURNING` anywhere: its
//! `Table` body writes a plain `INSERT` (an all-unset setter being MySQL's
//! `VALUES ()`), and the model hands out its **marker** from `table()`
//! rather than `ModelTable`, with inherent verbs that can be honoured —
//! `insert(…).one()` inserts and then re-`SELECT`s by key (the setter's own
//! primary key, else `last_insert_id`), `update`/`delete` offer `exec` and
//! no `all`. The read-back is two statements and says so, in the generated
//! docs and in `keelson-models/tests/spec_mysql.rs`, which is the
//! specification this emits.
//!
//! **Every to-one relation field is `Option<Box<Row>>`.** A generated `Rel`
//! holds the target's whole row, so two models that reference each other
//! to-one hold each other by value — which is a recursive type of infinite
//! size, a compile error in the emitted code that no user of this generator
//! could work around. Two base tables with mutual single-column foreign keys
//! are enough to produce it. Boxing is therefore unconditional rather than
//! applied only to the edges that close a cycle: a field's type must not be
//! a function of the whole schema graph, or adding an unrelated foreign key
//! would change an existing struct and break code at a distance. To-many
//! fields stay `Vec<Row>`, which carries its own indirection. The argument
//! is recorded in full in `emit/model.rs`.
//!
//! **Factories are opt-in output, not a second generator.** `[output]
//! factories = true` adds one `factories.rs` — a keelson-factory template
//! module per writable table, exactly as `keelson-factory/tests/spec_*.rs`
//! specifies, writing through the *model's* insert path so hooks fire. It is
//! off by default: a production crate has no reason to carry test-data
//! machinery it never calls. The per-column default rule (unique columns
//! take sequences, defaulted columns are omitted, the rest are faked) is
//! recorded in `emit/factory.rs`.
//!
//! **Views are configured, not inferred** (`docs/views.md`). A view has no
//! foreign keys and usually no primary key, so the catalog cannot say how it
//! relates to anything or what identifies a row of it. Neither is guessed:
//! a relation touching a view is a `[[relationships]]` declaration carrying
//! an explicit `cardinality`, validated against the introspected schema so a
//! typo is a generation-time error naming the TOML key; and identity is
//! simply not required for reads, because the loaders group by the declared
//! join column rather than by a row identity. A keyless view therefore
//! *holds* and *is the target of* relations while getting less than a table
//! — no `Pk`, no `Setter`, no `INSERT`/`UPDATE`/`DELETE`, no keyed read-back
//! on MySQL, no factory. It earns the write surface only by declaring
//! `[tables.<name>] key`, and only when the engine says writes reach it,
//! which the three engines decide differently (PostgreSQL's
//! `pg_relation_is_updatable`, MySQL's `IS_UPDATABLE`, SQLite's `INSTEAD OF`
//! triggers).
//!
//! **Recorded limitations.** Multi-column foreign keys are introspected but
//! emit no relation (composite keys still work as `Pk` tuples); a base table
//! whose primary key falls to the column filters demotes to a view model;
//! `[output] factories = true` cannot cover a writable view and says so.
use ;
pub use Config;
pub use ;
pub use ResolvedType;
/// Introspect the configured database and render every generated file as
/// `(file name, contents)`, `mod.rs` first — without touching the
/// filesystem.
/// Render from an already-held [`schema::Schema`] — the seam tests and
/// build scripts use to skip the database.
/// Write rendered files into `out_dir`, creating it if needed. Returns the
/// written paths. Stale files from earlier runs are removed only if they
/// carry the `@generated` header, so a hand-written file dropped into the
/// directory by mistake is never deleted silently.
/// The documented main entry: introspect, render, write to the configured
/// output directory. This is what the `keelson-gen` binary calls.