dirsql 0.4.130 → 0.4.131

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -12,12 +12,16 @@ as in [Define tables for your files](./define-tables.md).
12
12
 
13
13
  ## Why triggers, and why only two
14
14
 
15
- `dirsql` writes file rows with plain `INSERT` and `DELETE`; an update is a
16
- delete and an insert in one transaction, and there is no `UPDATE` path on user
17
- rows. So an insert trigger and a delete trigger cover every way a row can
15
+ `dirsql` writes the rows of a table with no key with plain `INSERT` and
16
+ `DELETE`; a changed row is a delete and an insert in one transaction. So an
17
+ insert trigger and a delete trigger cover every way a row can
18
18
  change — during the initial build, on each [watcher](./react-to-changes.md)
19
19
  event, and when a [persistent cache](./persist.md) reconciles a tree that
20
20
  moved on. There is no third trigger to write and no maintenance command to run.
21
+ A table with a `PRIMARY KEY`, or a `UNIQUE` column the command fills, has its
22
+ changed rows `UPDATE`d in place instead
23
+ ([hooks](../reference/hooks.md#on-file-contract)); give it an `AFTER UPDATE`
24
+ trigger as well.
21
25
 
22
26
  The triggers are also the part that goes wrong, which is why the templates
23
27
  spell them out rather than hiding them behind an abstraction.
@@ -236,6 +236,11 @@ rows — filtered to the DDL — are the table. For plain stat columns with no
236
236
  command, query the path directly with a [path-table](./path-tables.md)
237
237
  instead of declaring a table.
238
238
 
239
+ Tables' `on-file` commands run one table at a time, in declaration order:
240
+ a table's command finishes before the next table's starts, whatever order
241
+ their files sort in. That holds at startup and when a change under watch
242
+ re-runs several tables at once.
243
+
239
244
  ```toml
240
245
  [[table]]
241
246
  name = "comments"
@@ -302,11 +307,14 @@ dirsql query "SELECT text FROM messages_fts WHERE messages_fts MATCH 'deploy'" \
302
307
  -c ./.dirsql.toml
303
308
  ```
304
309
 
305
- Those two triggers are all a keyword index needs. dirsql writes file rows with
306
- plain `INSERT` and `DELETE` — an update is a delete and an insert in one
307
- transaction, and there is no `UPDATE` path on user rows — so triggers you
308
- declare here stay current through the initial load and every
309
- [watcher](../howto/react-to-changes.md) event. The same shape with a `vec0`
310
+ Those two triggers are all a keyword index needs. `messages` declares no key,
311
+ so dirsql writes its rows with plain `INSERT` and `DELETE` — a changed row is a
312
+ delete and an insert in one transaction — and triggers you declare here stay
313
+ current through the initial load and every
314
+ [watcher](../howto/react-to-changes.md) event. A table with a `PRIMARY KEY` or
315
+ `UNIQUE` column the command fills has its changed rows `UPDATE`d in place
316
+ instead (see [hooks](./hooks.md#on-file-contract)); give it an
317
+ `AFTER UPDATE` trigger too. The same shape with a `vec0`
310
318
  virtual table and an [`embed()`](#dirsql-function) call in the trigger gives
311
319
  you stored vectors.
312
320
 
@@ -337,6 +345,39 @@ Two consequences of `ddl` running **once, when the table is created**:
337
345
  That is the only invalidation lane: dirsql tracks no ownership of what the
338
346
  batch made.
339
347
 
348
+ ### Foreign keys
349
+
350
+ SQLite enforces a `REFERENCES` clause in a table's `ddl`. Tables fill in no
351
+ fixed order, so `dirsql` checks foreign keys once per unit of work rather than
352
+ per row: every constraint is deferred to the end of the unit, as if declared
353
+ `DEFERRABLE INITIALLY DEFERRED`. A unit is the initial build, or one batch of
354
+ [watcher](../howto/react-to-changes.md) events across all tables. A child may
355
+ land before its parent, and a parent's file may change while its children
356
+ point at it.
357
+
358
+ A row that still references nothing when the unit ends fails the whole unit,
359
+ and the error names each offending row, the file that produced it, and the
360
+ values that match nothing:
361
+
362
+ ```text
363
+ foreign key violation: `task` row from `projects/beta/tasks/t1.json` has project_id = 2, which matches no row in `project`
364
+ ```
365
+
366
+ - **Build:** the build fails and nothing is written.
367
+ - **Watch:** the batch is rolled back and reported as one error event, against
368
+ the child's table and file. The database stays as it was before the batch.
369
+ The rejected changes are retried with each later batch, as the files stand
370
+ then, and land with the first batch that resolves them, such as one that
371
+ repoints or deletes the orphaned child. Until then each later batch is also
372
+ tried on its own and lands if it leaves no orphan, and the error is reported
373
+ again. A table's `on-file` command re-reads all of its files, so a later
374
+ batch that touches the same table carries the orphaning change and waits
375
+ too.
376
+
377
+ `ON DELETE` and `ON UPDATE` actions are not deferred. `dirsql` applies a changed
378
+ file as a delete and an insert, so `ON DELETE CASCADE` deletes the children of a
379
+ parent whose file is edited or renamed, even though their files still exist.
380
+
340
381
  ### `on-file` row mapping
341
382
 
342
383
  The command prints one JSON object per line (NDJSON); each line becomes one
@@ -369,6 +410,8 @@ The configs load and merge in **argv order**:
369
410
  that config file's own directory** — so a relative command like
370
411
  `on-file = "sh ./extract.sh"` resolves against the config that declared it,
371
412
  wherever it lives.
413
+ - **`on-file` commands run in argv order**: every table of the first config,
414
+ in declaration order, before any table of the second.
372
415
  - Each config is **validated on its own** (the [parse errors](#parse-errors)
373
416
  below apply per file). There is no cross-file merge validation, with two
374
417
  structural exceptions: **two configs defining a table of the same name is
@@ -128,7 +128,22 @@ for path in sys.argv[1:]:
128
128
  There is no row-to-file attribution: dirsql does not know which file a row
129
129
  came from, and a row that needs the path carries it as a column the command
130
130
  emitted. In watch mode a change to any file under the table's glob re-runs
131
- the command over all of the table's files and replaces the table's rows.
131
+ the command over all of the table's files and writes only the difference,
132
+ comparing just the columns the command emits:
133
+
134
+ - A table with a key matches old and new rows by it. The key is the
135
+ `PRIMARY KEY`, or else a `UNIQUE` constraint (or unique index) whose columns
136
+ the command fills, non-NULL, in every row. A matched row that changed is
137
+ `UPDATE`d in place, so its rowid and every column the command leaves out (a
138
+ generated `INTEGER PRIMARY KEY`, a default) keep their values, and the watch
139
+ stream carries an `update`. A key that disappears is deleted and a new key is
140
+ inserted.
141
+ - A table without one has no row identity: a row the command still emits
142
+ stays put, and every other row is deleted or inserted.
143
+
144
+ With `CREATE TABLE experiments (id INTEGER PRIMARY KEY, folder_name TEXT UNIQUE, name TEXT)`
145
+ and a command that emits `folder_name` and `name`, renaming an experiment keeps
146
+ its `id`, so a table that stores that `id` still points at it.
132
147
 
133
148
  The same command is attachable two ways, over the same contract: the
134
149
  `on-file` config key on a `[[table]]`, and the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dirsql",
3
- "version": "0.4.130",
3
+ "version": "0.4.131",
4
4
  "description": "Ephemeral SQL index over a local directory",
5
5
  "license": "MIT",
6
6
  "repository": "https://github.com/thekevinscott/dirsql",
@@ -221,10 +221,10 @@
221
221
  ]
222
222
  },
223
223
  "optionalDependencies": {
224
- "@dirsql/lib-linux-x64-gnu": "0.4.130",
225
- "@dirsql/lib-linux-arm64-gnu": "0.4.130",
226
- "@dirsql/lib-darwin-x64": "0.4.130",
227
- "@dirsql/lib-darwin-arm64": "0.4.130",
228
- "@dirsql/lib-win32-x64-msvc": "0.4.130"
224
+ "@dirsql/lib-linux-x64-gnu": "0.4.131",
225
+ "@dirsql/lib-linux-arm64-gnu": "0.4.131",
226
+ "@dirsql/lib-darwin-x64": "0.4.131",
227
+ "@dirsql/lib-darwin-arm64": "0.4.131",
228
+ "@dirsql/lib-win32-x64-msvc": "0.4.131"
229
229
  }
230
230
  }