datanotes 3.4.2 → 3.5.0
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.
- package/README.md +1 -1
- package/dist/api/extensions.cjs +1088 -5
- package/dist/api/extensions.js +1088 -5
- package/dist/api/index.cjs +1088 -5
- package/dist/api/index.js +1088 -5
- package/dist/api/mcp.cjs +1088 -5
- package/dist/api/mcp.js +1088 -5
- package/dist/datanotes.cjs +4408 -1618
- package/dist/docs/administration/git-sync.md +17 -0
- package/dist/docs/administration/limits.md +29 -0
- package/dist/docs/administration/use-with-obsidian.md +12 -0
- package/dist/docs/api/operations/alter_table.md +6 -1
- package/dist/docs/api/operations/create_table.md +5 -0
- package/dist/docs/api/operations/describe_table.md +1 -1
- package/dist/docs/api/operations/pack_rows.md +45 -0
- package/dist/docs/api/operations/tags/auxiliary.md +2 -0
- package/dist/docs/api/operations/unpack_rows.md +40 -0
- package/dist/docs/api.md +3 -1
- package/dist/docs/guides/data-quality/check-constraints.md +10 -10
- package/dist/docs/guides/data-quality/hand-edits.md +8 -8
- package/dist/docs/guides/database-objects/bases.md +11 -11
- package/dist/docs/guides/database-objects/catalogs-and-schemas.md +10 -10
- package/dist/docs/guides/database-objects/columns.md +41 -41
- package/dist/docs/guides/database-objects/packs.md +563 -0
- package/dist/docs/guides/database-objects/relations.md +28 -28
- package/dist/docs/guides/database-objects/tables.md +28 -28
- package/dist/docs/guides/database-objects/views.md +44 -44
- package/dist/docs/guides/history/change-data-feed.md +34 -34
- package/dist/docs/guides/history/journal-and-undo.md +12 -12
- package/dist/docs/guides/history/restore.md +20 -20
- package/dist/docs/guides/history/table-history.md +31 -31
- package/dist/docs/guides/read-and-write/automatic-links.md +15 -15
- package/dist/docs/guides/read-and-write/import-notes.md +21 -21
- package/dist/docs/guides/read-and-write/joins-and-aggregates.md +7 -7
- package/dist/docs/guides/read-and-write/read-and-filter.md +10 -10
- package/dist/docs/guides/read-and-write/write-rows.md +10 -10
- package/dist/docs/guides/search/text.md +1 -1
- package/dist/docs/reference/cli.md +3 -1
- package/dist/docs/reference/operations/auxiliary.md +30 -1
- package/dist/docs/reference/operations/queries.md +1 -1
- package/dist/docs/reference/operations/rows.md +3 -3
- package/dist/docs/reference/operations/schema.md +8 -4
- package/dist/docs/reference/operations.md +55 -53
- package/dist/docs/reference/settings.md +1 -0
- package/dist/docs/release-notes.md +29 -0
- package/package.json +1 -1
|
@@ -0,0 +1,563 @@
|
|
|
1
|
+
# Packs of rows
|
|
2
|
+
|
|
3
|
+
> Keep the rows of a closed period in one note, a section per row: fewer files on disk and in git, the same rows for the engine.
|
|
4
|
+
|
|
5
|
+
A table can hold thousands of small rows a year, such as a reading log or a sleep log. Each row is a note, so the table folder holds thousands of files. A pack is one note that holds the rows of one period, such as a month, one `##` section per row.
|
|
6
|
+
|
|
7
|
+
The engine still sees one row at a time. `select_rows`, links, the link graph, the change feed and the Model Context Protocol (MCP) tools give each row at its own path, with its values, text and modification time. Only the files on disk change.
|
|
8
|
+
|
|
9
|
+
The examples on this page keep a log of reading sessions.
|
|
10
|
+
|
|
11
|
+
## How a pack reads
|
|
12
|
+
|
|
13
|
+
A pack is a Markdown note in the table’s folder, named `<table> <key>.md`, such as `reading 2025-03.md`. Its front matter names the table and the period. Each row is a `##` section:
|
|
14
|
+
|
|
15
|
+
```markdown
|
|
16
|
+
---
|
|
17
|
+
pack: main.public.reading
|
|
18
|
+
key: 2025-03
|
|
19
|
+
description: "Rows of main.public.reading for 2025-03. ..."
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
## 2025-03-02T2100
|
|
24
|
+
id:: 019a3c1e-7f3b-7c2a-9d41-6c0f1e2a3b4c
|
|
25
|
+
started_at:: 2025-03-02T21:00
|
|
26
|
+
book:: Moby-Dick
|
|
27
|
+
minutes:: 40
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
## 2025-03-16T2200
|
|
31
|
+
id:: 019a3c1e-8a10-7d55-b2e3-1f4a5b6c7d8e
|
|
32
|
+
started_at:: 2025-03-16T22:00
|
|
33
|
+
book:: Persuasion
|
|
34
|
+
minutes:: 30
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
Chapter 4, the walk to Winthrop.
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
* The heading is the row’s file name.
|
|
41
|
+
* A field is `key:: value`, its value YAML on one line, as in front matter.
|
|
42
|
+
* The row’s own text starts after the first empty line, with headings from `###` down.
|
|
43
|
+
|
|
44
|
+
The engine lists each row at the path it would have as a note of its own, such as `datanotes/main/public/reading/2025/2025-03-02T2100.md`. That path doesn’t exist on disk, but every operation finds the row there.
|
|
45
|
+
|
|
46
|
+
## Know which rows stay notes of their own
|
|
47
|
+
|
|
48
|
+
`pack_rows` leaves a row as a note of its own when:
|
|
49
|
+
|
|
50
|
+
* its period isn’t over yet, or ended less than `pack_after_days` days ago
|
|
51
|
+
* it changed in the last 24 hours
|
|
52
|
+
* its text has a `#` or `##` heading, which would end its section
|
|
53
|
+
* it isn’t at the path its values give, such as a name with a `-2` suffix
|
|
54
|
+
* its note isn’t in the form the engine writes, such as another YAML style or a comment
|
|
55
|
+
|
|
56
|
+
A dry run lists these rows in `excluded`, with the reason. A row the engine can’t pack now waits for the next run.
|
|
57
|
+
|
|
58
|
+
## Link to a packed row
|
|
59
|
+
|
|
60
|
+
A link to a packed row names the pack and the row’s heading: `[[reading 2025-03#2025-03-02T2100]]`. Obsidian follows it to the section. `pack_rows` rewrites the links other notes have to the rows in this form, and `unpack_rows` writes them back.
|
|
61
|
+
|
|
62
|
+
A link by the row’s name, such as `[[2025-03-02T2100]]`, still leads to the row for the engine. A link column takes both forms.
|
|
63
|
+
|
|
64
|
+
Links between rows of the same pack stay as they are written.
|
|
65
|
+
|
|
66
|
+
## Know who reads the files
|
|
67
|
+
|
|
68
|
+
Every reader that goes through the engine sees the rows: the HTTP API, the MCP tools, `datanotes/client` and the change feed. Programs that read the vault’s files directly see the packs instead:
|
|
69
|
+
|
|
70
|
+
* Obsidian, which shows each pack as one note
|
|
71
|
+
* scripts and services that count or open the files of a table folder, with `find`, `grep` or fixed paths
|
|
72
|
+
* agents with a shell, and git hosts such as GitHub
|
|
73
|
+
|
|
74
|
+
To count the rows of a table, call `select_rows`. On disk, count the headings of its packs with `grep -c '^## '`.
|
|
75
|
+
|
|
76
|
+
Before you set `pack` on a table, check what reads its files without the engine.
|
|
77
|
+
|
|
78
|
+
## Packs need the engine service
|
|
79
|
+
|
|
80
|
+
The engine service, `datanotes serve`, keeps the packs. Obsidian’s plugin doesn’t, so it refuses the `pack` option and `pack_rows`.
|
|
81
|
+
|
|
82
|
+
The service also packs on its own. Once a day, after 10 minutes without writes, it runs `pack_rows` on every table with a `pack` option. The setting `autoPack: false` turns this off.
|
|
83
|
+
|
|
84
|
+
## Merge packs changed on two devices
|
|
85
|
+
|
|
86
|
+
Two devices that add or change different rows of one pack shouldn’t get a git conflict. The engine has a git merge driver that merges a pack row by row, by `id`:
|
|
87
|
+
|
|
88
|
+
1. A row changed on one side only takes that side.
|
|
89
|
+
2. A row added on one side is added.
|
|
90
|
+
3. A row removed on one side, and unchanged on the other, is removed.
|
|
91
|
+
4. A row changed differently on both sides is a conflict in its section only.
|
|
92
|
+
|
|
93
|
+
[Git sync](https://datanotes.dev/administration/git-sync/) registers the driver in the vault’s repository. To register it by hand, see that page.
|
|
94
|
+
|
|
95
|
+
## Choose the period of a pack
|
|
96
|
+
|
|
97
|
+
A table’s `pack` option names a date or date-time column and a period, such as `{started_at:YYYY-MM}`. Once a month is over, one note holds all its rows. `pack_after_days` sets how many days after the end of a period its rows wait, 7 by default.
|
|
98
|
+
|
|
99
|
+
**`create_table`**
|
|
100
|
+
|
|
101
|
+
```json
|
|
102
|
+
{
|
|
103
|
+
"name": "reading",
|
|
104
|
+
"description": "Reading sessions: when I read, which book and for how long",
|
|
105
|
+
"time_column": "started_at",
|
|
106
|
+
"partition": "{started_at:YYYY}",
|
|
107
|
+
"pack": "{started_at:YYYY-MM}",
|
|
108
|
+
"pack_after_days": 3,
|
|
109
|
+
"columns": [
|
|
110
|
+
{
|
|
111
|
+
"name": "started_at",
|
|
112
|
+
"type": "datetime",
|
|
113
|
+
"required": true,
|
|
114
|
+
"description": "When the session started"
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
"name": "book",
|
|
118
|
+
"type": "string",
|
|
119
|
+
"required": true,
|
|
120
|
+
"description": "The book I read"
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
"name": "minutes",
|
|
124
|
+
"type": "integer",
|
|
125
|
+
"min": 1,
|
|
126
|
+
"description": "How long I read, in minutes"
|
|
127
|
+
}
|
|
128
|
+
]
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Answer:
|
|
133
|
+
|
|
134
|
+
```json
|
|
135
|
+
{
|
|
136
|
+
"ok": true,
|
|
137
|
+
"migration_id": "20261026T080043-create_table-create-table-main-public-readi"
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
`datanotes/metastore.yaml` now reads:
|
|
142
|
+
|
|
143
|
+
```yaml
|
|
144
|
+
# Metastore (Datanotes): catalogs, schemas, tables, columns, views.
|
|
145
|
+
id: 01a17739-dd7c-7689-914e-27b8f67b394c
|
|
146
|
+
config:
|
|
147
|
+
history: true
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
catalogs:
|
|
151
|
+
main:
|
|
152
|
+
schemas:
|
|
153
|
+
public:
|
|
154
|
+
tables:
|
|
155
|
+
reading:
|
|
156
|
+
description: "Reading sessions: when I read, which book and for how long"
|
|
157
|
+
options:
|
|
158
|
+
partition: "{started_at:YYYY}"
|
|
159
|
+
filename: "{started_at:YYYY-MM-DD[T]HHmm}"
|
|
160
|
+
archive: _archive
|
|
161
|
+
pack: "{started_at:YYYY-MM}"
|
|
162
|
+
pack_after_days: 3
|
|
163
|
+
columns:
|
|
164
|
+
- name: id
|
|
165
|
+
type: string
|
|
166
|
+
required: true
|
|
167
|
+
description: "Unique id of the row (UUIDv7): set when the row is created, never changes"
|
|
168
|
+
unique: true
|
|
169
|
+
identity: uuid
|
|
170
|
+
dense: true
|
|
171
|
+
- name: started_at
|
|
172
|
+
type: datetime
|
|
173
|
+
required: true
|
|
174
|
+
description: When the session started
|
|
175
|
+
- name: book
|
|
176
|
+
type: string
|
|
177
|
+
required: true
|
|
178
|
+
description: The book I read
|
|
179
|
+
- name: minutes
|
|
180
|
+
type: integer
|
|
181
|
+
min: 1
|
|
182
|
+
description: How long I read, in minutes
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
`alter_table` changes them while no row is in a pack. Here the rows wait one day:
|
|
186
|
+
|
|
187
|
+
**`alter_table`**
|
|
188
|
+
|
|
189
|
+
```json
|
|
190
|
+
{ "table": "reading", "pack": "{started_at:YYYY-MM}", "pack_after_days": 1 }
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
Answer:
|
|
194
|
+
|
|
195
|
+
```json
|
|
196
|
+
{ "ok": true, "planned_texts": ["datanotes/metastore.yaml"] }
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
The template takes one date or date-time column and a format made of `YYYY`, `MM` and `DD`. Anything else is refused, with the table’s date columns in the hint:
|
|
200
|
+
|
|
201
|
+
**`alter_table`**
|
|
202
|
+
|
|
203
|
+
```json
|
|
204
|
+
{ "table": "reading", "pack": "{minutes:YYYY-MM}" }
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
Refused:
|
|
208
|
+
|
|
209
|
+
```text
|
|
210
|
+
main.public.reading: "pack": "minutes" is a integer column, not a date
|
|
211
|
+
Hint: A pack holds the rows of a period: name a date or date-time column. The date columns of the table: started_at. For example: "{started_at:YYYY-MM}".
|
|
212
|
+
Docs: https://datanotes.dev/guides/database-objects/packs.md#choose-the-period-of-a-pack (offline: read_docs("guides/database-objects/packs#choose-the-period-of-a-pack"))
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
## Pack and unpack rows
|
|
216
|
+
|
|
217
|
+
`pack_rows` puts the rows of every closed period into their pack. A row changed in the last 24 hours waits for the next run. A dry run shows the packs first, and the rows that stay notes of their own, with the reason.
|
|
218
|
+
|
|
219
|
+
Four sessions of March and April 2025. The last one has a heading of its own:
|
|
220
|
+
|
|
221
|
+
**`batch`**
|
|
222
|
+
|
|
223
|
+
```json
|
|
224
|
+
{
|
|
225
|
+
"operations": [
|
|
226
|
+
{
|
|
227
|
+
"op": "insert_row",
|
|
228
|
+
"table": "reading",
|
|
229
|
+
"values": { "started_at": "2025-03-02T21:00", "book": "Moby-Dick", "minutes": 40 }
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
"op": "insert_row",
|
|
233
|
+
"table": "reading",
|
|
234
|
+
"values": { "started_at": "2025-03-09T21:30", "book": "Moby-Dick", "minutes": 25 }
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
"op": "insert_row",
|
|
238
|
+
"table": "reading",
|
|
239
|
+
"values": { "started_at": "2025-03-16T22:00", "book": "Persuasion", "minutes": 30 },
|
|
240
|
+
"body": "Chapter 4, the walk to Winthrop.\n"
|
|
241
|
+
},
|
|
242
|
+
{
|
|
243
|
+
"op": "insert_row",
|
|
244
|
+
"table": "reading",
|
|
245
|
+
"values": { "started_at": "2025-04-06T21:00", "book": "Persuasion", "minutes": 35 },
|
|
246
|
+
"body": "# Notes\n\nAnne and Wentworth meet again.\n"
|
|
247
|
+
}
|
|
248
|
+
]
|
|
249
|
+
}
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
The notes in `datanotes/main/public/reading` haven’t changed since 2025-05-01.
|
|
253
|
+
|
|
254
|
+
**`pack_rows`**
|
|
255
|
+
|
|
256
|
+
```json
|
|
257
|
+
{ "table": "reading", "dry_run": true }
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
Answer:
|
|
261
|
+
|
|
262
|
+
```json
|
|
263
|
+
{
|
|
264
|
+
"ok": true,
|
|
265
|
+
"dry_run": true,
|
|
266
|
+
"packs": [
|
|
267
|
+
{
|
|
268
|
+
"pack": "datanotes/main/public/reading/reading 2025-03.md",
|
|
269
|
+
"key": "2025-03",
|
|
270
|
+
"rows": [
|
|
271
|
+
"datanotes/main/public/reading/2025/2025-03-02T2100.md",
|
|
272
|
+
"datanotes/main/public/reading/2025/2025-03-09T2130.md",
|
|
273
|
+
"datanotes/main/public/reading/2025/2025-03-16T2200.md"
|
|
274
|
+
]
|
|
275
|
+
}
|
|
276
|
+
],
|
|
277
|
+
"excluded": [
|
|
278
|
+
{
|
|
279
|
+
"path": "datanotes/main/public/reading/2025/2025-04-06T2100.md",
|
|
280
|
+
"reason": "its text has a # or ## heading: \"# Notes\""
|
|
281
|
+
}
|
|
282
|
+
],
|
|
283
|
+
"open_rows": 0
|
|
284
|
+
}
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
The same call without `dry_run` writes the pack. The three notes of March go, and one note holds them:
|
|
288
|
+
|
|
289
|
+
**`pack_rows`**
|
|
290
|
+
|
|
291
|
+
```json
|
|
292
|
+
{ "table": "reading" }
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
Answer:
|
|
296
|
+
|
|
297
|
+
```json
|
|
298
|
+
{
|
|
299
|
+
"ok": true,
|
|
300
|
+
"migration_id": "20261026T080044-pack_rows-pack-3-rows-of-reading",
|
|
301
|
+
"packs": [
|
|
302
|
+
{
|
|
303
|
+
"pack": "datanotes/main/public/reading/reading 2025-03.md",
|
|
304
|
+
"key": "2025-03",
|
|
305
|
+
"rows": [
|
|
306
|
+
"datanotes/main/public/reading/2025/2025-03-02T2100.md",
|
|
307
|
+
"datanotes/main/public/reading/2025/2025-03-09T2130.md",
|
|
308
|
+
"datanotes/main/public/reading/2025/2025-03-16T2200.md"
|
|
309
|
+
]
|
|
310
|
+
}
|
|
311
|
+
]
|
|
312
|
+
}
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
`datanotes/main/public/reading/reading 2025-03.md` now reads:
|
|
316
|
+
|
|
317
|
+
```markdown
|
|
318
|
+
---
|
|
319
|
+
pack: main.public.reading
|
|
320
|
+
key: 2025-03
|
|
321
|
+
description: "Rows of main.public.reading for 2025-03. Each ## section is a row, its fields are key:: value. Read them with select_rows."
|
|
322
|
+
---
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
## 2025-03-02T2100
|
|
326
|
+
id:: 01a17739-dd92-7321-9568-708a1a72dffe
|
|
327
|
+
started_at:: 2025-03-02T21:00
|
|
328
|
+
book:: Moby-Dick
|
|
329
|
+
minutes:: 40
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
## 2025-03-09T2130
|
|
337
|
+
id:: 01a17739-dd97-79de-b866-231408f32289
|
|
338
|
+
started_at:: 2025-03-09T21:30
|
|
339
|
+
book:: Moby-Dick
|
|
340
|
+
minutes:: 25
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
## 2025-03-16T2200
|
|
348
|
+
id:: 01a17739-dd9b-780a-9860-0eb45495b7d9
|
|
349
|
+
started_at:: 2025-03-16T22:00
|
|
350
|
+
book:: Persuasion
|
|
351
|
+
minutes:: 30
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
Chapter 4, the walk to Winthrop.
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
Nothing changes for the engine and its readers. `select_rows` gives each row at its own path, with its values and text:
|
|
358
|
+
|
|
359
|
+
**`select_rows`**
|
|
360
|
+
|
|
361
|
+
```json
|
|
362
|
+
{
|
|
363
|
+
"table": "reading",
|
|
364
|
+
"columns": ["started_at", "book", "minutes", "file_modified"],
|
|
365
|
+
"order_by": "started_at"
|
|
366
|
+
}
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
Answer:
|
|
370
|
+
|
|
371
|
+
```json
|
|
372
|
+
{
|
|
373
|
+
"table": "main.public.reading",
|
|
374
|
+
"total": 4,
|
|
375
|
+
"rows": [
|
|
376
|
+
{
|
|
377
|
+
"path": "datanotes/main/public/reading/2025/2025-03-02T2100.md",
|
|
378
|
+
"started_at": "2025-03-02T21:00",
|
|
379
|
+
"book": "Moby-Dick",
|
|
380
|
+
"minutes": 40,
|
|
381
|
+
"file_modified": "2025-05-01T00:00:00Z"
|
|
382
|
+
},
|
|
383
|
+
{
|
|
384
|
+
"path": "datanotes/main/public/reading/2025/2025-03-09T2130.md",
|
|
385
|
+
"started_at": "2025-03-09T21:30",
|
|
386
|
+
"book": "Moby-Dick",
|
|
387
|
+
"minutes": 25,
|
|
388
|
+
"file_modified": "2025-05-01T00:00:00Z"
|
|
389
|
+
},
|
|
390
|
+
{
|
|
391
|
+
"path": "datanotes/main/public/reading/2025/2025-03-16T2200.md",
|
|
392
|
+
"started_at": "2025-03-16T22:00",
|
|
393
|
+
"book": "Persuasion",
|
|
394
|
+
"minutes": 30,
|
|
395
|
+
"file_modified": "2025-05-01T00:00:00Z"
|
|
396
|
+
},
|
|
397
|
+
{
|
|
398
|
+
"path": "datanotes/main/public/reading/2025/2025-04-06T2100.md",
|
|
399
|
+
"started_at": "2025-04-06T21:00",
|
|
400
|
+
"book": "Persuasion",
|
|
401
|
+
"minutes": 35,
|
|
402
|
+
"file_modified": "2025-05-01T00:00:00Z"
|
|
403
|
+
}
|
|
404
|
+
]
|
|
405
|
+
}
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
`unpack_rows` takes rows out again, as notes of their own with the text and modification time they had. `where` picks some rows, `key` one pack:
|
|
409
|
+
|
|
410
|
+
**`unpack_rows`**
|
|
411
|
+
|
|
412
|
+
```json
|
|
413
|
+
{ "table": "reading", "where": { "book": "Persuasion" } }
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
Answer:
|
|
417
|
+
|
|
418
|
+
```json
|
|
419
|
+
{
|
|
420
|
+
"ok": true,
|
|
421
|
+
"packs": [
|
|
422
|
+
{
|
|
423
|
+
"pack": "datanotes/main/public/reading/reading 2025-03.md",
|
|
424
|
+
"key": "2025-03",
|
|
425
|
+
"rows": ["datanotes/main/public/reading/2025/2025-03-16T2200.md"]
|
|
426
|
+
}
|
|
427
|
+
]
|
|
428
|
+
}
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
Both operations are journaled: `undo_migration` takes a pack apart, or packs the rows again. The change feed and the webhooks see no change of the rows, and no new version of the table is written.
|
|
432
|
+
|
|
433
|
+
## Read and write a packed row
|
|
434
|
+
|
|
435
|
+
A packed row is still a row. A write changes its section of the pack. When the new values move it to another period or path, the row comes out as a note of its own.
|
|
436
|
+
|
|
437
|
+
**`update_row`**
|
|
438
|
+
|
|
439
|
+
```json
|
|
440
|
+
{
|
|
441
|
+
"table": "reading",
|
|
442
|
+
"path": "datanotes/main/public/reading/2025/2025-03-09T2130.md",
|
|
443
|
+
"set": { "minutes": 45 }
|
|
444
|
+
}
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
Answer:
|
|
448
|
+
|
|
449
|
+
```json
|
|
450
|
+
{
|
|
451
|
+
"ok": true,
|
|
452
|
+
"migration_id": "20261026T080044-update_row-update-reading",
|
|
453
|
+
"planned_edits": [
|
|
454
|
+
{
|
|
455
|
+
"path": "datanotes/main/public/reading/2025/2025-03-09T2130.md",
|
|
456
|
+
"set": { "minutes": 45 },
|
|
457
|
+
"remove": []
|
|
458
|
+
}
|
|
459
|
+
]
|
|
460
|
+
}
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
`datanotes/main/public/reading/reading 2025-03.md` now reads:
|
|
464
|
+
|
|
465
|
+
```markdown
|
|
466
|
+
---
|
|
467
|
+
pack: main.public.reading
|
|
468
|
+
key: 2025-03
|
|
469
|
+
description: "Rows of main.public.reading for 2025-03. Each ## section is a row, its fields are key:: value. Read them with select_rows."
|
|
470
|
+
---
|
|
471
|
+
|
|
472
|
+
|
|
473
|
+
## 2025-03-02T2100
|
|
474
|
+
id:: 01a17739-dd92-7321-9568-708a1a72dffe
|
|
475
|
+
started_at:: 2025-03-02T21:00
|
|
476
|
+
book:: Moby-Dick
|
|
477
|
+
minutes:: 40
|
|
478
|
+
|
|
479
|
+
|
|
480
|
+
|
|
481
|
+
|
|
482
|
+
|
|
483
|
+
|
|
484
|
+
## 2025-03-09T2130
|
|
485
|
+
id:: 01a17739-dd97-79de-b866-231408f32289
|
|
486
|
+
started_at:: 2025-03-09T21:30
|
|
487
|
+
book:: Moby-Dick
|
|
488
|
+
minutes:: 45
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
## Unpack before you change the table’s layout
|
|
492
|
+
|
|
493
|
+
A packed row keeps the path its values give it. So the rows must be notes of their own before the table moves, before its rows change folder or name, and before its `pack` option changes:
|
|
494
|
+
|
|
495
|
+
**`alter_table`**
|
|
496
|
+
|
|
497
|
+
```json
|
|
498
|
+
{ "table": "reading", "partition": "{started_at:YYYY/MM}" }
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
Refused:
|
|
502
|
+
|
|
503
|
+
```text
|
|
504
|
+
main.public.reading has 2 rows in 1 pack: the table moves or its rows change folder or name only while every row is a note of its own
|
|
505
|
+
Hint: Run unpack_rows on main.public.reading first, then alter the table, then pack_rows again.
|
|
506
|
+
Docs: https://datanotes.dev/guides/database-objects/packs.md#unpack-before-you-change-the-tables-layout (offline: read_docs("guides/database-objects/packs#unpack-before-you-change-the-tables-layout"))
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
**`unpack_rows`**
|
|
510
|
+
|
|
511
|
+
```json
|
|
512
|
+
{ "table": "reading" }
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
Answer:
|
|
516
|
+
|
|
517
|
+
```json
|
|
518
|
+
{
|
|
519
|
+
"ok": true,
|
|
520
|
+
"packs": [
|
|
521
|
+
{
|
|
522
|
+
"pack": "datanotes/main/public/reading/reading 2025-03.md",
|
|
523
|
+
"key": "2025-03",
|
|
524
|
+
"rows": [
|
|
525
|
+
"datanotes/main/public/reading/2025/2025-03-02T2100.md",
|
|
526
|
+
"datanotes/main/public/reading/2025/2025-03-09T2130.md"
|
|
527
|
+
]
|
|
528
|
+
}
|
|
529
|
+
]
|
|
530
|
+
}
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
**`alter_table`**
|
|
534
|
+
|
|
535
|
+
```json
|
|
536
|
+
{ "table": "reading", "partition": "{started_at:YYYY/MM}" }
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
Answer:
|
|
540
|
+
|
|
541
|
+
```json
|
|
542
|
+
{
|
|
543
|
+
"ok": true,
|
|
544
|
+
"planned_moves": [
|
|
545
|
+
{
|
|
546
|
+
"from": "datanotes/main/public/reading/2025/2025-03-02T2100.md",
|
|
547
|
+
"to": "datanotes/main/public/reading/2025/03/2025-03-02T2100.md"
|
|
548
|
+
},
|
|
549
|
+
{
|
|
550
|
+
"from": "datanotes/main/public/reading/2025/2025-03-09T2130.md",
|
|
551
|
+
"to": "datanotes/main/public/reading/2025/03/2025-03-09T2130.md"
|
|
552
|
+
},
|
|
553
|
+
{
|
|
554
|
+
"from": "datanotes/main/public/reading/2025/2025-03-16T2200.md",
|
|
555
|
+
"to": "datanotes/main/public/reading/2025/03/2025-03-16T2200.md"
|
|
556
|
+
},
|
|
557
|
+
{
|
|
558
|
+
"from": "datanotes/main/public/reading/2025/2025-04-06T2100.md",
|
|
559
|
+
"to": "datanotes/main/public/reading/2025/04/2025-04-06T2100.md"
|
|
560
|
+
}
|
|
561
|
+
]
|
|
562
|
+
}
|
|
563
|
+
```
|