dirsql 0.3.73__tar.gz → 0.3.75__tar.gz
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.
- {dirsql-0.3.73 → dirsql-0.3.75}/Cargo.lock +1 -1
- {dirsql-0.3.73 → dirsql-0.3.75}/PKG-INFO +1 -1
- {dirsql-0.3.73 → dirsql-0.3.75}/docs/AGENTS.md +1 -1
- {dirsql-0.3.73 → dirsql-0.3.75}/docs/getting-started.md +18 -18
- {dirsql-0.3.73/packages/rust → dirsql-0.3.75}/docs/howto/columns-from-paths.md +5 -5
- {dirsql-0.3.73/packages/python → dirsql-0.3.75}/docs/howto/define-tables.md +4 -4
- {dirsql-0.3.73 → dirsql-0.3.75}/docs/howto/embed.md +1 -1
- {dirsql-0.3.73/packages/python → dirsql-0.3.75}/docs/howto/extract-from-contents.md +5 -5
- {dirsql-0.3.73/packages/rust → dirsql-0.3.75}/docs/howto/react-to-changes.md +1 -1
- {dirsql-0.3.73/packages/rust → dirsql-0.3.75}/docs/howto/search-by-meaning.md +4 -4
- {dirsql-0.3.73/packages/rust → dirsql-0.3.75}/docs/howto/skip-files.md +3 -3
- {dirsql-0.3.73/packages/rust → dirsql-0.3.75}/docs/reference/cli.md +16 -22
- dirsql-0.3.75/docs/reference/columns.md +80 -0
- {dirsql-0.3.73/packages/rust → dirsql-0.3.75}/docs/reference/config.md +4 -4
- {dirsql-0.3.73 → dirsql-0.3.75}/packages/python/Cargo.toml +1 -1
- {dirsql-0.3.73 → dirsql-0.3.75}/packages/python/docs/AGENTS.md +1 -1
- {dirsql-0.3.73/packages/rust → dirsql-0.3.75/packages/python}/docs/getting-started.md +18 -18
- {dirsql-0.3.73 → dirsql-0.3.75}/packages/python/docs/howto/columns-from-paths.md +5 -5
- {dirsql-0.3.73/packages/rust → dirsql-0.3.75/packages/python}/docs/howto/define-tables.md +4 -4
- {dirsql-0.3.73 → dirsql-0.3.75}/packages/python/docs/howto/embed.md +1 -1
- {dirsql-0.3.73/packages/rust → dirsql-0.3.75/packages/python}/docs/howto/extract-from-contents.md +5 -5
- {dirsql-0.3.73 → dirsql-0.3.75/packages/python}/docs/howto/react-to-changes.md +1 -1
- {dirsql-0.3.73 → dirsql-0.3.75}/packages/python/docs/howto/search-by-meaning.md +4 -4
- {dirsql-0.3.73 → dirsql-0.3.75/packages/python}/docs/howto/skip-files.md +3 -3
- {dirsql-0.3.73 → dirsql-0.3.75/packages/python}/docs/reference/cli.md +16 -22
- dirsql-0.3.75/packages/python/docs/reference/columns.md +80 -0
- {dirsql-0.3.73 → dirsql-0.3.75/packages/python}/docs/reference/config.md +4 -4
- {dirsql-0.3.73 → dirsql-0.3.75}/packages/python/e2e-attestation.json +2 -2
- {dirsql-0.3.73/packages/python → dirsql-0.3.75/packages/rust}/docs/getting-started.md +18 -18
- {dirsql-0.3.73 → dirsql-0.3.75/packages/rust}/docs/howto/columns-from-paths.md +5 -5
- {dirsql-0.3.73 → dirsql-0.3.75/packages/rust}/docs/howto/define-tables.md +4 -4
- {dirsql-0.3.73 → dirsql-0.3.75}/packages/rust/docs/howto/embed.md +1 -1
- {dirsql-0.3.73 → dirsql-0.3.75/packages/rust}/docs/howto/extract-from-contents.md +5 -5
- {dirsql-0.3.73/packages/python → dirsql-0.3.75/packages/rust}/docs/howto/react-to-changes.md +1 -1
- {dirsql-0.3.73 → dirsql-0.3.75/packages/rust}/docs/howto/search-by-meaning.md +4 -4
- {dirsql-0.3.73/packages/python → dirsql-0.3.75/packages/rust}/docs/howto/skip-files.md +3 -3
- {dirsql-0.3.73/packages/python → dirsql-0.3.75/packages/rust}/docs/reference/cli.md +16 -22
- dirsql-0.3.75/packages/rust/docs/reference/columns.md +80 -0
- {dirsql-0.3.73/packages/python → dirsql-0.3.75/packages/rust}/docs/reference/config.md +4 -4
- {dirsql-0.3.73 → dirsql-0.3.75}/packages/rust/src/bin/dirsql.rs +18 -23
- dirsql-0.3.75/packages/rust/src/cli/init.rs +95 -0
- {dirsql-0.3.73 → dirsql-0.3.75}/packages/rust/src/cli/mod.rs +11 -0
- {dirsql-0.3.73 → dirsql-0.3.75}/packages/rust/src/config.rs +28 -28
- {dirsql-0.3.73 → dirsql-0.3.75}/packages/rust/src/db.rs +1 -1
- dirsql-0.3.75/packages/rust/src/default_config.toml +3 -0
- {dirsql-0.3.73 → dirsql-0.3.75}/packages/rust/src/lib.rs +24 -24
- dirsql-0.3.73/docs/reference/columns.md +0 -68
- dirsql-0.3.73/packages/python/docs/reference/columns.md +0 -68
- dirsql-0.3.73/packages/rust/docs/reference/columns.md +0 -68
- dirsql-0.3.73/packages/rust/src/cli/init.rs +0 -207
- {dirsql-0.3.73 → dirsql-0.3.75}/Cargo.toml +0 -0
- {dirsql-0.3.73 → dirsql-0.3.75}/README.md +0 -0
- {dirsql-0.3.73 → dirsql-0.3.75}/dirsql/__init__.py +0 -0
- {dirsql-0.3.73 → dirsql-0.3.75}/dirsql/_async.py +0 -0
- {dirsql-0.3.73 → dirsql-0.3.75}/dirsql/_dirsql.pyi +0 -0
- {dirsql-0.3.73 → dirsql-0.3.75}/dirsql/cli/__init__.py +0 -0
- {dirsql-0.3.73 → dirsql-0.3.75}/dirsql/cli/binary_path.py +0 -0
- {dirsql-0.3.73 → dirsql-0.3.75}/dirsql/cli/is_windows.py +0 -0
- {dirsql-0.3.73 → dirsql-0.3.75}/dirsql/cli/main.py +0 -0
- {dirsql-0.3.73 → dirsql-0.3.75}/dirsql/cli/resolve_config_extensions.py +0 -0
- {dirsql-0.3.73 → dirsql-0.3.75}/dirsql/py.typed +0 -0
- {dirsql-0.3.73 → dirsql-0.3.75}/dirsql/resolve_config_extensions.py +0 -0
- {dirsql-0.3.73 → dirsql-0.3.75}/dirsql/resolve_extension.py +0 -0
- {dirsql-0.3.73 → dirsql-0.3.75}/docs/.claude/CLAUDE.md +0 -0
- {dirsql-0.3.73 → dirsql-0.3.75}/docs/.vitepress/config.ts +0 -0
- {dirsql-0.3.73 → dirsql-0.3.75}/docs/.vitepress/theme/index.ts +0 -0
- {dirsql-0.3.73 → dirsql-0.3.75}/docs/.vitepress/theme/lang.ts +0 -0
- {dirsql-0.3.73 → dirsql-0.3.75}/docs/explanation.md +0 -0
- {dirsql-0.3.73 → dirsql-0.3.75}/docs/howto/load-extension.md +0 -0
- {dirsql-0.3.73 → dirsql-0.3.75}/docs/howto/persist.md +0 -0
- {dirsql-0.3.73 → dirsql-0.3.75}/docs/index.md +0 -0
- {dirsql-0.3.73 → dirsql-0.3.75}/docs/migrations.md +0 -0
- {dirsql-0.3.73 → dirsql-0.3.75}/docs/package.json +0 -0
- {dirsql-0.3.73 → dirsql-0.3.75}/docs/playwright.config.ts +0 -0
- {dirsql-0.3.73 → dirsql-0.3.75}/docs/pnpm-lock.yaml +0 -0
- {dirsql-0.3.73 → dirsql-0.3.75}/docs/pnpm-workspace.yaml +0 -0
- {dirsql-0.3.73 → dirsql-0.3.75}/docs/reference/hooks.md +0 -0
- {dirsql-0.3.73 → dirsql-0.3.75}/docs/reference/http-api.md +0 -0
- {dirsql-0.3.73 → dirsql-0.3.75}/docs/reference/sdk.md +0 -0
- {dirsql-0.3.73 → dirsql-0.3.75}/docs/tests/integration/home.spec.ts +0 -0
- {dirsql-0.3.73 → dirsql-0.3.75}/docs/tests/integration/language-flag.spec.ts +0 -0
- {dirsql-0.3.73 → dirsql-0.3.75}/docs/tests/integration/sidebar.spec.ts +0 -0
- {dirsql-0.3.73 → dirsql-0.3.75}/docs/tests/unit/config.test.ts +0 -0
- {dirsql-0.3.73 → dirsql-0.3.75}/docs/tests/unit/lang.test.ts +0 -0
- {dirsql-0.3.73 → dirsql-0.3.75}/docs/vitest.config.ts +0 -0
- {dirsql-0.3.73 → dirsql-0.3.75}/packages/python/README.md +0 -0
- {dirsql-0.3.73 → dirsql-0.3.75}/packages/python/conftest.py +0 -0
- {dirsql-0.3.73 → dirsql-0.3.75}/packages/python/docs/.claude/CLAUDE.md +0 -0
- {dirsql-0.3.73 → dirsql-0.3.75}/packages/python/docs/.vitepress/config.ts +0 -0
- {dirsql-0.3.73 → dirsql-0.3.75}/packages/python/docs/.vitepress/theme/index.ts +0 -0
- {dirsql-0.3.73 → dirsql-0.3.75}/packages/python/docs/.vitepress/theme/lang.ts +0 -0
- {dirsql-0.3.73 → dirsql-0.3.75}/packages/python/docs/explanation.md +0 -0
- {dirsql-0.3.73 → dirsql-0.3.75}/packages/python/docs/howto/load-extension.md +0 -0
- {dirsql-0.3.73 → dirsql-0.3.75}/packages/python/docs/howto/persist.md +0 -0
- {dirsql-0.3.73 → dirsql-0.3.75}/packages/python/docs/index.md +0 -0
- {dirsql-0.3.73 → dirsql-0.3.75}/packages/python/docs/migrations.md +0 -0
- {dirsql-0.3.73 → dirsql-0.3.75}/packages/python/docs/package.json +0 -0
- {dirsql-0.3.73 → dirsql-0.3.75}/packages/python/docs/playwright.config.ts +0 -0
- {dirsql-0.3.73 → dirsql-0.3.75}/packages/python/docs/pnpm-lock.yaml +0 -0
- {dirsql-0.3.73 → dirsql-0.3.75}/packages/python/docs/pnpm-workspace.yaml +0 -0
- {dirsql-0.3.73 → dirsql-0.3.75}/packages/python/docs/reference/hooks.md +0 -0
- {dirsql-0.3.73 → dirsql-0.3.75}/packages/python/docs/reference/http-api.md +0 -0
- {dirsql-0.3.73 → dirsql-0.3.75}/packages/python/docs/reference/sdk.md +0 -0
- {dirsql-0.3.73 → dirsql-0.3.75}/packages/python/docs/tests/integration/home.spec.ts +0 -0
- {dirsql-0.3.73 → dirsql-0.3.75}/packages/python/docs/tests/integration/language-flag.spec.ts +0 -0
- {dirsql-0.3.73 → dirsql-0.3.75}/packages/python/docs/tests/integration/sidebar.spec.ts +0 -0
- {dirsql-0.3.73 → dirsql-0.3.75}/packages/python/docs/tests/unit/config.test.ts +0 -0
- {dirsql-0.3.73 → dirsql-0.3.75}/packages/python/docs/tests/unit/lang.test.ts +0 -0
- {dirsql-0.3.73 → dirsql-0.3.75}/packages/python/docs/vitest.config.ts +0 -0
- {dirsql-0.3.73 → dirsql-0.3.75}/packages/python/src/lib.rs +0 -0
- {dirsql-0.3.73 → dirsql-0.3.75}/packages/python/tests/__init__.py +0 -0
- {dirsql-0.3.73 → dirsql-0.3.75}/packages/python/tests/binding/__init__.py +0 -0
- {dirsql-0.3.73 → dirsql-0.3.75}/packages/python/tests/conftest.py +0 -0
- {dirsql-0.3.73 → dirsql-0.3.75}/packages/python/tests/e2e/__init__.py +0 -0
- {dirsql-0.3.73 → dirsql-0.3.75}/packages/python/tests/integration/__init__.py +0 -0
- {dirsql-0.3.73 → dirsql-0.3.75}/packages/python/tests/smoke/__init__.py +0 -0
- {dirsql-0.3.73 → dirsql-0.3.75}/packages/rust/Cargo.toml +0 -0
- {dirsql-0.3.73 → dirsql-0.3.75}/packages/rust/README.md +0 -0
- {dirsql-0.3.73 → dirsql-0.3.75}/packages/rust/benches/db_bench.rs +0 -0
- {dirsql-0.3.73 → dirsql-0.3.75}/packages/rust/benches/differ_bench.rs +0 -0
- {dirsql-0.3.73 → dirsql-0.3.75}/packages/rust/benches/matcher_bench.rs +0 -0
- {dirsql-0.3.73 → dirsql-0.3.75}/packages/rust/benches/scanner_bench.rs +0 -0
- {dirsql-0.3.73 → dirsql-0.3.75}/packages/rust/docs/explanation.md +0 -0
- {dirsql-0.3.73 → dirsql-0.3.75}/packages/rust/docs/howto/load-extension.md +0 -0
- {dirsql-0.3.73 → dirsql-0.3.75}/packages/rust/docs/howto/persist.md +0 -0
- {dirsql-0.3.73 → dirsql-0.3.75}/packages/rust/docs/index.md +0 -0
- {dirsql-0.3.73 → dirsql-0.3.75}/packages/rust/docs/migrations.md +0 -0
- {dirsql-0.3.73 → dirsql-0.3.75}/packages/rust/docs/reference/hooks.md +0 -0
- {dirsql-0.3.73 → dirsql-0.3.75}/packages/rust/docs/reference/http-api.md +0 -0
- {dirsql-0.3.73 → dirsql-0.3.75}/packages/rust/docs/reference/sdk.md +0 -0
- {dirsql-0.3.73 → dirsql-0.3.75}/packages/rust/src/cli/execute.rs +0 -0
- {dirsql-0.3.73 → dirsql-0.3.75}/packages/rust/src/cli/router.rs +0 -0
- {dirsql-0.3.73 → dirsql-0.3.75}/packages/rust/src/cli/serialize.rs +0 -0
- {dirsql-0.3.73 → dirsql-0.3.75}/packages/rust/src/cli/server.rs +0 -0
- {dirsql-0.3.73 → dirsql-0.3.75}/packages/rust/src/command.rs +0 -0
- {dirsql-0.3.73 → dirsql-0.3.75}/packages/rust/src/differ.rs +0 -0
- {dirsql-0.3.73 → dirsql-0.3.75}/packages/rust/src/matcher.rs +0 -0
- {dirsql-0.3.73 → dirsql-0.3.75}/packages/rust/src/persist.rs +0 -0
- {dirsql-0.3.73 → dirsql-0.3.75}/packages/rust/src/scanner.rs +0 -0
- {dirsql-0.3.73 → dirsql-0.3.75}/packages/rust/src/watcher.rs +0 -0
- {dirsql-0.3.73 → dirsql-0.3.75}/pyproject.toml +0 -0
|
@@ -57,7 +57,7 @@ migrated -- a page survives only if a slot wants its content):
|
|
|
57
57
|
`dirsql` in an application.
|
|
58
58
|
- **Reference** (`reference/`) -- CLI flags and defaults; the complete
|
|
59
59
|
`.dirsql.toml` schema; the command hook contract (placeholders, stdout
|
|
60
|
-
protocol, exit codes, timeouts);
|
|
60
|
+
protocol, exit codes, timeouts); stat columns and glob captures; the
|
|
61
61
|
HTTP API; the SDK page (`reference/sdk.md`, one page with
|
|
62
62
|
Python/TypeScript/Rust code-groups -- the sole SDK home); plus the
|
|
63
63
|
Migrations include (`migrations.md`).
|
|
@@ -112,30 +112,30 @@ through `jq` to pretty-print. Now select some columns:
|
|
|
112
112
|
```bash
|
|
113
113
|
curl -s http://localhost:7117/query \
|
|
114
114
|
-H 'content-type: application/json' \
|
|
115
|
-
-d '{"sql":"SELECT
|
|
115
|
+
-d '{"sql":"SELECT path, size FROM files ORDER BY path"}' \
|
|
116
116
|
| jq
|
|
117
117
|
```
|
|
118
118
|
|
|
119
119
|
```json
|
|
120
120
|
[
|
|
121
121
|
{
|
|
122
|
-
"
|
|
123
|
-
"
|
|
122
|
+
"path": "notes/alice/ideas.md",
|
|
123
|
+
"size": 52
|
|
124
124
|
},
|
|
125
125
|
{
|
|
126
|
-
"
|
|
127
|
-
"
|
|
126
|
+
"path": "notes/alice/welcome.md",
|
|
127
|
+
"size": 66
|
|
128
128
|
},
|
|
129
129
|
{
|
|
130
|
-
"
|
|
131
|
-
"
|
|
130
|
+
"path": "notes/bob/reading-list.md",
|
|
131
|
+
"size": 41
|
|
132
132
|
}
|
|
133
133
|
]
|
|
134
134
|
```
|
|
135
135
|
|
|
136
|
-
`
|
|
137
|
-
for every file — see [
|
|
138
|
-
for the full list. (The `
|
|
136
|
+
`path` and `size` are two of the built-in file columns `dirsql` collects
|
|
137
|
+
for every file — see [stat columns](./reference/columns.md#stat-columns)
|
|
138
|
+
for the full list. (The `size` values are byte counts; they match the
|
|
139
139
|
output above because you pasted the files exactly.)
|
|
140
140
|
|
|
141
141
|
You have a working SQL database over your files. Next, teach it the
|
|
@@ -152,7 +152,7 @@ In your second terminal, still inside `my-notes`, create a `.dirsql.toml`:
|
|
|
152
152
|
```bash
|
|
153
153
|
cat > .dirsql.toml <<'EOF'
|
|
154
154
|
[[table]]
|
|
155
|
-
ddl = "CREATE TABLE notes (author TEXT,
|
|
155
|
+
ddl = "CREATE TABLE notes (author TEXT, basename TEXT, size INTEGER)"
|
|
156
156
|
glob = "notes/{author}/*.md"
|
|
157
157
|
EOF
|
|
158
158
|
```
|
|
@@ -192,25 +192,25 @@ terminal:
|
|
|
192
192
|
```bash
|
|
193
193
|
curl -s http://localhost:7117/query \
|
|
194
194
|
-H 'content-type: application/json' \
|
|
195
|
-
-d '{"sql":"SELECT author,
|
|
195
|
+
-d '{"sql":"SELECT author, basename, size FROM notes ORDER BY author, basename"}' \
|
|
196
196
|
| jq
|
|
197
197
|
```
|
|
198
198
|
|
|
199
199
|
```json
|
|
200
200
|
[
|
|
201
201
|
{
|
|
202
|
-
"
|
|
203
|
-
"
|
|
202
|
+
"basename": "ideas.md",
|
|
203
|
+
"size": 52,
|
|
204
204
|
"author": "alice"
|
|
205
205
|
},
|
|
206
206
|
{
|
|
207
|
-
"
|
|
208
|
-
"
|
|
207
|
+
"basename": "welcome.md",
|
|
208
|
+
"size": 66,
|
|
209
209
|
"author": "alice"
|
|
210
210
|
},
|
|
211
211
|
{
|
|
212
|
-
"
|
|
213
|
-
"
|
|
212
|
+
"basename": "reading-list.md",
|
|
213
|
+
"size": 41,
|
|
214
214
|
"author": "bob"
|
|
215
215
|
}
|
|
216
216
|
]
|
|
@@ -18,7 +18,7 @@ Capture both directory levels in `.dirsql.toml`:
|
|
|
18
18
|
|
|
19
19
|
```toml
|
|
20
20
|
[[table]]
|
|
21
|
-
ddl = "CREATE TABLE photos (year TEXT, month TEXT,
|
|
21
|
+
ddl = "CREATE TABLE photos (year TEXT, month TEXT, basename TEXT)"
|
|
22
22
|
glob = "photos/{year}/{month}/*.jpg"
|
|
23
23
|
```
|
|
24
24
|
|
|
@@ -30,11 +30,11 @@ within one path segment) are in
|
|
|
30
30
|
## 2. Query the captured columns
|
|
31
31
|
|
|
32
32
|
```bash
|
|
33
|
-
dirsql query "SELECT year, month,
|
|
33
|
+
dirsql query "SELECT year, month, basename FROM photos ORDER BY year, month"
|
|
34
34
|
```
|
|
35
35
|
|
|
36
36
|
```json
|
|
37
|
-
[{"
|
|
37
|
+
[{"basename":"beach.jpg","month":"05","year":"2024"},{"basename":"hike.jpg","month":"11","year":"2024"},{"basename":"snow.jpg","month":"01","year":"2025"}]
|
|
38
38
|
```
|
|
39
39
|
|
|
40
40
|
Captures are real SQL columns, so aggregation works:
|
|
@@ -49,8 +49,8 @@ dirsql query "SELECT year, COUNT(*) AS photos FROM photos GROUP BY year"
|
|
|
49
49
|
|
|
50
50
|
## Going further
|
|
51
51
|
|
|
52
|
-
- Captures combine freely with [
|
|
53
|
-
(`
|
|
52
|
+
- Captures combine freely with [stat columns](../reference/columns.md#stat-columns)
|
|
53
|
+
(`basename` above) — both are filesystem facts merged onto every row.
|
|
54
54
|
- The [tutorial](../getting-started.md) walks the same idea with an
|
|
55
55
|
`{author}` capture, starting from zero.
|
|
56
56
|
- When the value you need lives inside the file rather than in its path,
|
|
@@ -13,14 +13,14 @@ directory you want to index, create a `.dirsql.toml` with one
|
|
|
13
13
|
|
|
14
14
|
```toml
|
|
15
15
|
[[table]]
|
|
16
|
-
ddl = "CREATE TABLE posts (
|
|
16
|
+
ddl = "CREATE TABLE posts (path TEXT, size INTEGER, mtime INTEGER)"
|
|
17
17
|
glob = "posts/**/*.md"
|
|
18
18
|
```
|
|
19
19
|
|
|
20
20
|
- `glob` selects the files: every `.md` under `posts/`, at any depth,
|
|
21
21
|
relative to the directory containing the config.
|
|
22
22
|
- `ddl` is a plain SQLite `CREATE TABLE` naming the columns you want. Here
|
|
23
|
-
all three are [
|
|
23
|
+
all three are [stat columns](../reference/columns.md#stat-columns) —
|
|
24
24
|
filesystem facts `dirsql` computes for every file. Facts are opt-in by
|
|
25
25
|
DDL: only the ones you declare become columns.
|
|
26
26
|
|
|
@@ -29,11 +29,11 @@ glob = "posts/**/*.md"
|
|
|
29
29
|
Each matched file is one row:
|
|
30
30
|
|
|
31
31
|
```bash
|
|
32
|
-
dirsql query "SELECT
|
|
32
|
+
dirsql query "SELECT path, size FROM posts ORDER BY path"
|
|
33
33
|
```
|
|
34
34
|
|
|
35
35
|
```json
|
|
36
|
-
[{"
|
|
36
|
+
[{"path":"posts/2024/hello.md","size":21},{"path":"posts/2025/again.md","size":55}]
|
|
37
37
|
```
|
|
38
38
|
|
|
39
39
|
Files that don't match the glob (a `README.txt` next to `posts/`, say) are
|
|
@@ -35,7 +35,7 @@ comments/t2/c1.json # {"body": "following up", "author": "alice"}
|
|
|
35
35
|
|
|
36
36
|
Unlike a config-file table, a programmatic table takes an `extract`
|
|
37
37
|
callback — your code reads each matched file and returns its rows, with
|
|
38
|
-
[glob captures and
|
|
38
|
+
[glob captures and stat columns](../reference/columns.md) merged on
|
|
39
39
|
automatically (here, `{thread}` from the path):
|
|
40
40
|
|
|
41
41
|
::: code-group
|
|
@@ -18,7 +18,7 @@ stdout works. With [`jq`](https://jqlang.org/):
|
|
|
18
18
|
|
|
19
19
|
```toml
|
|
20
20
|
[[table]]
|
|
21
|
-
ddl = "CREATE TABLE books (title TEXT, author TEXT, year INTEGER,
|
|
21
|
+
ddl = "CREATE TABLE books (title TEXT, author TEXT, year INTEGER, path TEXT)"
|
|
22
22
|
glob = "books/*.json"
|
|
23
23
|
on-file = "jq -c '[{title, author, year}]' {path}"
|
|
24
24
|
```
|
|
@@ -32,14 +32,14 @@ by every hook.
|
|
|
32
32
|
## 2. Query the extracted columns
|
|
33
33
|
|
|
34
34
|
```bash
|
|
35
|
-
dirsql query "SELECT title, author, year,
|
|
35
|
+
dirsql query "SELECT title, author, year, path FROM books ORDER BY year"
|
|
36
36
|
```
|
|
37
37
|
|
|
38
38
|
```json
|
|
39
|
-
[{"
|
|
39
|
+
[{"path":"books/bleak-house.json","author":"Charles Dickens","title":"Bleak House","year":1852},{"path":"books/middlemarch.json","author":"George Eliot","title":"Middlemarch","year":1871}]
|
|
40
40
|
```
|
|
41
41
|
|
|
42
|
-
Filesystem facts are still merged onto every row — `
|
|
42
|
+
Filesystem facts are still merged onto every row — `path` above comes from
|
|
43
43
|
`dirsql`, not from `jq`. When the command emits a key that collides with a
|
|
44
44
|
fact, the command wins
|
|
45
45
|
([precedence](../reference/columns.md#precedence)).
|
|
@@ -51,7 +51,7 @@ row per line, slurp it:
|
|
|
51
51
|
|
|
52
52
|
```toml
|
|
53
53
|
[[table]]
|
|
54
|
-
ddl = "CREATE TABLE events (event TEXT, user TEXT,
|
|
54
|
+
ddl = "CREATE TABLE events (event TEXT, user TEXT, path TEXT)"
|
|
55
55
|
glob = "logs/*.jsonl"
|
|
56
56
|
on-file = "jq -c -s '.' {path}"
|
|
57
57
|
```
|
|
@@ -33,7 +33,7 @@ The stream delivers the resulting row change:
|
|
|
33
33
|
|
|
34
34
|
```
|
|
35
35
|
event: row
|
|
36
|
-
data: {"action":"insert","file_path":"inbox/two.txt","old_row":null,"row":{"
|
|
36
|
+
data: {"action":"insert","file_path":"inbox/two.txt","old_row":null,"row":{"basename":"two.txt","ctime":1783170226,"dir":"inbox","ext":"txt","mtime":1783170226,"path":"inbox/two.txt","size":7},"table":"files"}
|
|
37
37
|
```
|
|
38
38
|
|
|
39
39
|
Edits arrive as `update` events carrying both the old and new row;
|
|
@@ -66,7 +66,7 @@ model = StaticModel.from_pretrained("minishlab/potion-base-8M")
|
|
|
66
66
|
vector = model.encode([body["q"]])[0]
|
|
67
67
|
needle = json.dumps([round(float(x), 6) for x in vector])
|
|
68
68
|
print(
|
|
69
|
-
"SELECT
|
|
69
|
+
"SELECT path, ROUND(vec_distance_cosine(embedding, '%s'), 3) AS distance "
|
|
70
70
|
"FROM notes ORDER BY distance LIMIT 3" % needle
|
|
71
71
|
)
|
|
72
72
|
```
|
|
@@ -89,7 +89,7 @@ path = "sqlite_vec" # Python module name; see note below
|
|
|
89
89
|
entrypoint = "sqlite3_vec_init"
|
|
90
90
|
|
|
91
91
|
[[table]]
|
|
92
|
-
ddl = "CREATE TABLE notes (
|
|
92
|
+
ddl = "CREATE TABLE notes (path TEXT, text TEXT, embedding TEXT)"
|
|
93
93
|
glob = "notes/*.md"
|
|
94
94
|
on-file = "uv run --with model2vec python embed.py {path}"
|
|
95
95
|
```
|
|
@@ -110,7 +110,7 @@ uvx --with sqlite-vec dirsql query '{"q": "how do I cook pasta?"}'
|
|
|
110
110
|
```
|
|
111
111
|
|
|
112
112
|
```json
|
|
113
|
-
[{"
|
|
113
|
+
[{"path":"notes/pasta.md","distance":0.315},{"path":"notes/tomatoes.md","distance":0.881},{"path":"notes/branches.md","distance":0.92}]
|
|
114
114
|
```
|
|
115
115
|
|
|
116
116
|
```bash
|
|
@@ -118,7 +118,7 @@ uvx --with sqlite-vec dirsql query '{"q": "reviewing code on github"}'
|
|
|
118
118
|
```
|
|
119
119
|
|
|
120
120
|
```json
|
|
121
|
-
[{"
|
|
121
|
+
[{"path":"notes/branches.md","distance":0.51},{"path":"notes/pasta.md","distance":1.033},{"path":"notes/tomatoes.md","distance":1.074}]
|
|
122
122
|
```
|
|
123
123
|
|
|
124
124
|
Neither question shares a keyword with its top note — "cook" appears
|
|
@@ -22,7 +22,7 @@ Exclude the noise in `.dirsql.toml`:
|
|
|
22
22
|
ignore = ["notes/drafts/**", "**/*.tmp"]
|
|
23
23
|
|
|
24
24
|
[[table]]
|
|
25
|
-
ddl = "CREATE TABLE notes (
|
|
25
|
+
ddl = "CREATE TABLE notes (path TEXT)"
|
|
26
26
|
glob = "notes/**/*"
|
|
27
27
|
```
|
|
28
28
|
|
|
@@ -32,11 +32,11 @@ ignored file never reaches any table — even one whose glob would match it.
|
|
|
32
32
|
## 2. Confirm what made it in
|
|
33
33
|
|
|
34
34
|
```bash
|
|
35
|
-
dirsql query "SELECT
|
|
35
|
+
dirsql query "SELECT path FROM notes ORDER BY path"
|
|
36
36
|
```
|
|
37
37
|
|
|
38
38
|
```json
|
|
39
|
-
[{"
|
|
39
|
+
[{"path":"notes/final.md"}]
|
|
40
40
|
```
|
|
41
41
|
|
|
42
42
|
## Notes
|
|
@@ -5,8 +5,8 @@ The `dirsql` binary has three modes:
|
|
|
5
5
|
| Invocation | Behavior |
|
|
6
6
|
|---|---|
|
|
7
7
|
| `dirsql` (no subcommand) | Start a long-lived HTTP server exposing a SQL view of a directory. See [HTTP API](./http-api.md). |
|
|
8
|
-
| `dirsql query "<sql>"` |
|
|
9
|
-
| `dirsql init` | Generate a
|
|
8
|
+
| `dirsql query "<sql>"` | Query the file system as JSON |
|
|
9
|
+
| `dirsql init` | Generate a `.dirsql.toml` |
|
|
10
10
|
|
|
11
11
|
## Installation
|
|
12
12
|
|
|
@@ -68,12 +68,12 @@ default `./.dirsql.toml`) with a single table named `files`:
|
|
|
68
68
|
|
|
69
69
|
- Glob: `**/*` — every file under the root, at any depth, no ignores.
|
|
70
70
|
- One row per file, with all seven
|
|
71
|
-
[
|
|
72
|
-
`
|
|
71
|
+
[stat columns](./columns.md): `path`, `basename`, `dir`, `ext`,
|
|
72
|
+
`size`, `mtime`, `ctime`.
|
|
73
73
|
|
|
74
74
|
```bash
|
|
75
75
|
curl -s localhost:7117/query -H 'content-type: application/json' \
|
|
76
|
-
-d '{"sql":"SELECT
|
|
76
|
+
-d '{"sql":"SELECT basename, size FROM files ORDER BY size DESC LIMIT 5"}'
|
|
77
77
|
```
|
|
78
78
|
|
|
79
79
|
A config file, when present, fully overrules this default. A *missing*
|
|
@@ -100,12 +100,11 @@ with a JSON body describing the failure:
|
|
|
100
100
|
|
|
101
101
|
## `dirsql query`
|
|
102
102
|
|
|
103
|
-
Run
|
|
104
|
-
docs verification snippets — without booting the server and `curl`ing it:
|
|
103
|
+
Run a SQL query from the shell:
|
|
105
104
|
|
|
106
105
|
```bash
|
|
107
|
-
dirsql query "SELECT
|
|
108
|
-
# [{"
|
|
106
|
+
dirsql query "SELECT basename, size FROM files ORDER BY size DESC LIMIT 5"
|
|
107
|
+
# [{"basename":"model.bin","size":104857600}, …]
|
|
109
108
|
|
|
110
109
|
dirsql query "SELECT COUNT(*) AS n FROM posts" | jq '.[0].n'
|
|
111
110
|
```
|
|
@@ -140,34 +139,29 @@ stderr, with exit code `1`.
|
|
|
140
139
|
|
|
141
140
|
## `dirsql init`
|
|
142
141
|
|
|
143
|
-
|
|
144
|
-
directory. The generated config contains only filesystem-fact tables
|
|
145
|
-
(`[[table]]` entries whose columns come from [glob captures and virtual
|
|
146
|
-
columns](./columns.md)) — never content-derived columns.
|
|
142
|
+
Writes a starter `.dirsql.toml`:
|
|
147
143
|
|
|
148
144
|
```bash
|
|
149
145
|
dirsql init
|
|
150
146
|
```
|
|
151
147
|
|
|
148
|
+
You can further tweak this config as needed.
|
|
149
|
+
|
|
152
150
|
### Flags
|
|
153
151
|
|
|
154
152
|
| Flag | Default | Description |
|
|
155
153
|
|---|---|---|
|
|
156
|
-
| `--root <path>` | current directory | Directory
|
|
157
|
-
| `--output <path>` | `<root>/.dirsql.toml` | Where to write the
|
|
154
|
+
| `--root <path>` | current directory | Directory the default `--output` path is resolved against. |
|
|
155
|
+
| `--output <path>` | `<root>/.dirsql.toml` | Where to write the config. |
|
|
158
156
|
| `--force` | off | Overwrite the output file if it already exists. |
|
|
159
157
|
|
|
160
158
|
### Requirements and failure modes
|
|
161
159
|
|
|
162
|
-
`init` requires `claude` on `PATH`, signed in; there is no separate API key.
|
|
163
160
|
All failures exit `1` with a message on stderr:
|
|
164
161
|
|
|
165
162
|
| Condition | Behavior |
|
|
166
163
|
|---|---|
|
|
167
|
-
| Output file exists and `--force` not passed | Fails
|
|
168
|
-
|
|
|
169
|
-
| `claude` exits non-zero | Fails with `claude`'s stderr; no partial config is written. |
|
|
170
|
-
| `claude` produces non-UTF-8 output | Fails; nothing is written. |
|
|
164
|
+
| Output file exists and `--force` not passed | Fails; nothing is written. |
|
|
165
|
+
| Output path unwritable (e.g. missing parent directory) | Fails with the underlying I/O error. |
|
|
171
166
|
|
|
172
|
-
On success, `
|
|
173
|
-
`init` exits `0`.
|
|
167
|
+
On success, `init` exits `0`.
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# Stat columns and glob captures
|
|
2
|
+
|
|
3
|
+
Every table — config-defined or programmatic — gets filesystem facts merged
|
|
4
|
+
onto its rows automatically: seven **stat columns** derived from the file's
|
|
5
|
+
path and stat metadata, plus one column per **`{name}` capture** in the
|
|
6
|
+
table's glob.
|
|
7
|
+
|
|
8
|
+
These are ordinary, physically stored `TEXT`/`INTEGER` columns, computed
|
|
9
|
+
once per file at scan time and written like any other column value — not
|
|
10
|
+
SQLite's `GENERATED ... VIRTUAL` columns (computed on the fly, never stored)
|
|
11
|
+
and not part of a `CREATE VIRTUAL TABLE` (dirsql tables are always real
|
|
12
|
+
tables). "Stat" describes where the value comes from — the file's path and
|
|
13
|
+
`stat` metadata, as opposed to its content — not how it's stored.
|
|
14
|
+
|
|
15
|
+
Facts are **opt-in by DDL**: only facts whose name appears as a column in
|
|
16
|
+
the table's `CREATE TABLE` are populated; the rest are silently dropped.
|
|
17
|
+
Declaring them requires nothing else. The names below aren't a protected or
|
|
18
|
+
enforced namespace — nothing stops you from declaring a column with one of
|
|
19
|
+
these names for an unrelated purpose, in which case dirsql's computed value
|
|
20
|
+
lands there like any other fact (unless your own row source — an `on-file`
|
|
21
|
+
command or SDK `extract` callback — supplies its own value for that name;
|
|
22
|
+
see [Precedence](#precedence)).
|
|
23
|
+
|
|
24
|
+
## Stat columns
|
|
25
|
+
|
|
26
|
+
| Column | Type | Value |
|
|
27
|
+
|---|---|---|
|
|
28
|
+
| `path` | TEXT | The file's path relative to the scan root (e.g. `posts/hello.md`). |
|
|
29
|
+
| `basename` | TEXT | The filename, including extension (`hello.md`). |
|
|
30
|
+
| `dir` | TEXT | The parent directory relative to the root (`posts`); the empty string for files directly under the root. |
|
|
31
|
+
| `ext` | TEXT | The file extension without the leading dot (`md`). Original case is preserved — `Photo.JPG` yields `JPG`; use `LOWER(ext)` for case-insensitive matching. `NULL` when the file has no extension. |
|
|
32
|
+
| `size` | INTEGER | File size in bytes. |
|
|
33
|
+
| `mtime` | INTEGER | Last-modified time, Unix seconds. |
|
|
34
|
+
| `ctime` | INTEGER | Creation (birth) time, Unix seconds. `NULL` when the platform or filesystem cannot supply it. |
|
|
35
|
+
|
|
36
|
+
A fact that cannot be computed (an unreadable file's `size`/`mtime`/
|
|
37
|
+
`ctime`, a missing extension's `ext`) is absent from the row: `NULL` in
|
|
38
|
+
the default relaxed mode, a missing-column error for a
|
|
39
|
+
[`strict`](./config.md#table) table that declares it.
|
|
40
|
+
|
|
41
|
+
```sql
|
|
42
|
+
SELECT basename, size
|
|
43
|
+
FROM posts
|
|
44
|
+
WHERE mtime > strftime('%s', '2024-01-01')
|
|
45
|
+
ORDER BY mtime DESC;
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Glob captures
|
|
49
|
+
|
|
50
|
+
A `{name}` segment in a table's glob captures part of each matched path as
|
|
51
|
+
a TEXT column named `name`:
|
|
52
|
+
|
|
53
|
+
```toml
|
|
54
|
+
[[table]]
|
|
55
|
+
ddl = "CREATE TABLE comments (thread_id TEXT, basename TEXT, mtime INTEGER)"
|
|
56
|
+
glob = "_comments/{thread_id}/*.jsonl"
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
A file at `_comments/abc123/2024-05-05.jsonl` produces a row with
|
|
60
|
+
`thread_id = "abc123"`.
|
|
61
|
+
|
|
62
|
+
- A capture name must be a valid identifier: a letter or underscore
|
|
63
|
+
followed by letters, digits, or underscores (`[a-zA-Z_][a-zA-Z0-9_]*`).
|
|
64
|
+
- A capture matches **within a single path segment** — one or more
|
|
65
|
+
characters, never a `/`. For matching purposes, `{name}` behaves like
|
|
66
|
+
`*`.
|
|
67
|
+
- A glob may contain multiple captures (`{year}/{month}/*.jpg`).
|
|
68
|
+
- Like stat columns, a capture populates a column only when the DDL
|
|
69
|
+
declares a column of the same name.
|
|
70
|
+
|
|
71
|
+
## Precedence
|
|
72
|
+
|
|
73
|
+
Values produced by a table's own row source — an `on-file` command's JSON
|
|
74
|
+
output or an SDK `extract` callback's return value — **win** over
|
|
75
|
+
auto-injected facts of the same name. An extract that explicitly emits
|
|
76
|
+
`path` is honored.
|
|
77
|
+
|
|
78
|
+
Injection order per row: stat columns first, then glob captures, then
|
|
79
|
+
the row source's own values, each layer overwriting the previous, all
|
|
80
|
+
filtered to the DDL's declared columns.
|
|
@@ -97,7 +97,7 @@ per-file command.
|
|
|
97
97
|
|---|---|---|
|
|
98
98
|
| `ddl` | yes | A SQLite `CREATE TABLE` statement. The table name is parsed from it. Only columns declared here are populated; auto-injected facts not in the DDL are dropped. |
|
|
99
99
|
| `glob` | yes | Glob pattern matched against root-relative paths. May contain `{name}` [capture segments](./columns.md#glob-captures). First matching table wins when a file matches several globs. |
|
|
100
|
-
| `strict` | no (default `false`) | When `true`, rows whose keys do not exactly match the declared columns are rejected with an error: extra keys error, and every declared column must be supplied (by the command/extract output, a glob capture, or a
|
|
100
|
+
| `strict` | no (default `false`) | When `true`, rows whose keys do not exactly match the declared columns are rejected with an error: extra keys error, and every declared column must be supplied (by the command/extract output, a glob capture, or a stat column). When `false`, extra keys are dropped and missing columns become `NULL`. |
|
|
101
101
|
| `on-file` | no | A command run once per matched file; its stdout (a JSON array of row objects) becomes the file's rows. Must be non-empty. See [Command hooks](./hooks.md#on-file). |
|
|
102
102
|
|
|
103
103
|
Without `on-file`, a table produces exactly one row per matched file, built
|
|
@@ -108,7 +108,7 @@ callback.
|
|
|
108
108
|
|
|
109
109
|
```toml
|
|
110
110
|
[[table]]
|
|
111
|
-
ddl = "CREATE TABLE comments (thread_id TEXT,
|
|
111
|
+
ddl = "CREATE TABLE comments (thread_id TEXT, basename TEXT, mtime INTEGER)"
|
|
112
112
|
glob = "_comments/{thread_id}/*.jsonl"
|
|
113
113
|
|
|
114
114
|
[[table]]
|
|
@@ -157,10 +157,10 @@ path = "sqlite_vec" # Python module name; on Node use the
|
|
|
157
157
|
entrypoint = "sqlite3_vec_init"
|
|
158
158
|
|
|
159
159
|
[[table]]
|
|
160
|
-
ddl = "CREATE TABLE comments (thread_id TEXT,
|
|
160
|
+
ddl = "CREATE TABLE comments (thread_id TEXT, basename TEXT, mtime INTEGER)"
|
|
161
161
|
glob = "_comments/{thread_id}/*.jsonl"
|
|
162
162
|
|
|
163
163
|
[[table]]
|
|
164
|
-
ddl = "CREATE TABLE documents (
|
|
164
|
+
ddl = "CREATE TABLE documents (path TEXT, basename TEXT, size INTEGER)"
|
|
165
165
|
glob = "**/index.md"
|
|
166
166
|
```
|
|
@@ -4,7 +4,7 @@ name = "dirsql-py-ext"
|
|
|
4
4
|
# pypi/maturin handler can rewrite it via `write-version` before
|
|
5
5
|
# `maturin build`. `pyproject.toml` declares `dynamic = ["version"]`
|
|
6
6
|
# and maturin reads this field. Mirrors `packages/rust/Cargo.toml`.
|
|
7
|
-
version = "0.3.
|
|
7
|
+
version = "0.3.75"
|
|
8
8
|
edition.workspace = true
|
|
9
9
|
publish = false
|
|
10
10
|
readme = "README.md"
|
|
@@ -57,7 +57,7 @@ migrated -- a page survives only if a slot wants its content):
|
|
|
57
57
|
`dirsql` in an application.
|
|
58
58
|
- **Reference** (`reference/`) -- CLI flags and defaults; the complete
|
|
59
59
|
`.dirsql.toml` schema; the command hook contract (placeholders, stdout
|
|
60
|
-
protocol, exit codes, timeouts);
|
|
60
|
+
protocol, exit codes, timeouts); stat columns and glob captures; the
|
|
61
61
|
HTTP API; the SDK page (`reference/sdk.md`, one page with
|
|
62
62
|
Python/TypeScript/Rust code-groups -- the sole SDK home); plus the
|
|
63
63
|
Migrations include (`migrations.md`).
|
|
@@ -112,30 +112,30 @@ through `jq` to pretty-print. Now select some columns:
|
|
|
112
112
|
```bash
|
|
113
113
|
curl -s http://localhost:7117/query \
|
|
114
114
|
-H 'content-type: application/json' \
|
|
115
|
-
-d '{"sql":"SELECT
|
|
115
|
+
-d '{"sql":"SELECT path, size FROM files ORDER BY path"}' \
|
|
116
116
|
| jq
|
|
117
117
|
```
|
|
118
118
|
|
|
119
119
|
```json
|
|
120
120
|
[
|
|
121
121
|
{
|
|
122
|
-
"
|
|
123
|
-
"
|
|
122
|
+
"path": "notes/alice/ideas.md",
|
|
123
|
+
"size": 52
|
|
124
124
|
},
|
|
125
125
|
{
|
|
126
|
-
"
|
|
127
|
-
"
|
|
126
|
+
"path": "notes/alice/welcome.md",
|
|
127
|
+
"size": 66
|
|
128
128
|
},
|
|
129
129
|
{
|
|
130
|
-
"
|
|
131
|
-
"
|
|
130
|
+
"path": "notes/bob/reading-list.md",
|
|
131
|
+
"size": 41
|
|
132
132
|
}
|
|
133
133
|
]
|
|
134
134
|
```
|
|
135
135
|
|
|
136
|
-
`
|
|
137
|
-
for every file — see [
|
|
138
|
-
for the full list. (The `
|
|
136
|
+
`path` and `size` are two of the built-in file columns `dirsql` collects
|
|
137
|
+
for every file — see [stat columns](./reference/columns.md#stat-columns)
|
|
138
|
+
for the full list. (The `size` values are byte counts; they match the
|
|
139
139
|
output above because you pasted the files exactly.)
|
|
140
140
|
|
|
141
141
|
You have a working SQL database over your files. Next, teach it the
|
|
@@ -152,7 +152,7 @@ In your second terminal, still inside `my-notes`, create a `.dirsql.toml`:
|
|
|
152
152
|
```bash
|
|
153
153
|
cat > .dirsql.toml <<'EOF'
|
|
154
154
|
[[table]]
|
|
155
|
-
ddl = "CREATE TABLE notes (author TEXT,
|
|
155
|
+
ddl = "CREATE TABLE notes (author TEXT, basename TEXT, size INTEGER)"
|
|
156
156
|
glob = "notes/{author}/*.md"
|
|
157
157
|
EOF
|
|
158
158
|
```
|
|
@@ -192,25 +192,25 @@ terminal:
|
|
|
192
192
|
```bash
|
|
193
193
|
curl -s http://localhost:7117/query \
|
|
194
194
|
-H 'content-type: application/json' \
|
|
195
|
-
-d '{"sql":"SELECT author,
|
|
195
|
+
-d '{"sql":"SELECT author, basename, size FROM notes ORDER BY author, basename"}' \
|
|
196
196
|
| jq
|
|
197
197
|
```
|
|
198
198
|
|
|
199
199
|
```json
|
|
200
200
|
[
|
|
201
201
|
{
|
|
202
|
-
"
|
|
203
|
-
"
|
|
202
|
+
"basename": "ideas.md",
|
|
203
|
+
"size": 52,
|
|
204
204
|
"author": "alice"
|
|
205
205
|
},
|
|
206
206
|
{
|
|
207
|
-
"
|
|
208
|
-
"
|
|
207
|
+
"basename": "welcome.md",
|
|
208
|
+
"size": 66,
|
|
209
209
|
"author": "alice"
|
|
210
210
|
},
|
|
211
211
|
{
|
|
212
|
-
"
|
|
213
|
-
"
|
|
212
|
+
"basename": "reading-list.md",
|
|
213
|
+
"size": 41,
|
|
214
214
|
"author": "bob"
|
|
215
215
|
}
|
|
216
216
|
]
|
|
@@ -18,7 +18,7 @@ Capture both directory levels in `.dirsql.toml`:
|
|
|
18
18
|
|
|
19
19
|
```toml
|
|
20
20
|
[[table]]
|
|
21
|
-
ddl = "CREATE TABLE photos (year TEXT, month TEXT,
|
|
21
|
+
ddl = "CREATE TABLE photos (year TEXT, month TEXT, basename TEXT)"
|
|
22
22
|
glob = "photos/{year}/{month}/*.jpg"
|
|
23
23
|
```
|
|
24
24
|
|
|
@@ -30,11 +30,11 @@ within one path segment) are in
|
|
|
30
30
|
## 2. Query the captured columns
|
|
31
31
|
|
|
32
32
|
```bash
|
|
33
|
-
dirsql query "SELECT year, month,
|
|
33
|
+
dirsql query "SELECT year, month, basename FROM photos ORDER BY year, month"
|
|
34
34
|
```
|
|
35
35
|
|
|
36
36
|
```json
|
|
37
|
-
[{"
|
|
37
|
+
[{"basename":"beach.jpg","month":"05","year":"2024"},{"basename":"hike.jpg","month":"11","year":"2024"},{"basename":"snow.jpg","month":"01","year":"2025"}]
|
|
38
38
|
```
|
|
39
39
|
|
|
40
40
|
Captures are real SQL columns, so aggregation works:
|
|
@@ -49,8 +49,8 @@ dirsql query "SELECT year, COUNT(*) AS photos FROM photos GROUP BY year"
|
|
|
49
49
|
|
|
50
50
|
## Going further
|
|
51
51
|
|
|
52
|
-
- Captures combine freely with [
|
|
53
|
-
(`
|
|
52
|
+
- Captures combine freely with [stat columns](../reference/columns.md#stat-columns)
|
|
53
|
+
(`basename` above) — both are filesystem facts merged onto every row.
|
|
54
54
|
- The [tutorial](../getting-started.md) walks the same idea with an
|
|
55
55
|
`{author}` capture, starting from zero.
|
|
56
56
|
- When the value you need lives inside the file rather than in its path,
|