dirsql 0.3.61__tar.gz → 0.3.63__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.61 → dirsql-0.3.63}/Cargo.lock +1 -1
- {dirsql-0.3.61 → dirsql-0.3.63}/PKG-INFO +1 -1
- {dirsql-0.3.61 → dirsql-0.3.63}/docs/.vitepress/config.ts +1 -1
- {dirsql-0.3.61/packages/python → dirsql-0.3.63}/docs/getting-started.md +1 -1
- {dirsql-0.3.61 → dirsql-0.3.63}/docs/howto/persist.md +2 -2
- {dirsql-0.3.61 → dirsql-0.3.63}/docs/index.md +1 -1
- {dirsql-0.3.61/packages/rust → dirsql-0.3.63}/docs/reference/config.md +1 -1
- {dirsql-0.3.61 → dirsql-0.3.63}/docs/reference/sdk.md +1 -1
- {dirsql-0.3.61 → dirsql-0.3.63}/packages/python/Cargo.toml +1 -1
- {dirsql-0.3.61 → dirsql-0.3.63}/packages/python/docs/.vitepress/config.ts +1 -1
- {dirsql-0.3.61 → dirsql-0.3.63/packages/python}/docs/getting-started.md +1 -1
- {dirsql-0.3.61 → dirsql-0.3.63}/packages/python/docs/howto/persist.md +2 -2
- {dirsql-0.3.61/packages/rust → dirsql-0.3.63/packages/python}/docs/index.md +1 -1
- {dirsql-0.3.61 → dirsql-0.3.63/packages/python}/docs/reference/config.md +1 -1
- {dirsql-0.3.61/packages/rust → dirsql-0.3.63/packages/python}/docs/reference/sdk.md +1 -1
- {dirsql-0.3.61 → dirsql-0.3.63}/packages/python/e2e-attestation.json +2 -2
- {dirsql-0.3.61 → dirsql-0.3.63}/packages/rust/docs/getting-started.md +1 -1
- {dirsql-0.3.61 → dirsql-0.3.63}/packages/rust/docs/howto/persist.md +2 -2
- {dirsql-0.3.61/packages/python → dirsql-0.3.63/packages/rust}/docs/index.md +1 -1
- {dirsql-0.3.61/packages/python → dirsql-0.3.63/packages/rust}/docs/reference/config.md +1 -1
- {dirsql-0.3.61/packages/python → dirsql-0.3.63/packages/rust}/docs/reference/sdk.md +1 -1
- {dirsql-0.3.61 → dirsql-0.3.63}/packages/rust/src/config.rs +2 -1
- {dirsql-0.3.61 → dirsql-0.3.63}/packages/rust/src/db.rs +153 -3
- {dirsql-0.3.61 → dirsql-0.3.63}/packages/rust/src/lib.rs +113 -109
- {dirsql-0.3.61 → dirsql-0.3.63}/packages/rust/src/persist.rs +7 -151
- {dirsql-0.3.61 → dirsql-0.3.63}/Cargo.toml +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/README.md +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/dirsql/__init__.py +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/dirsql/_async.py +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/dirsql/_dirsql.pyi +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/dirsql/cli/__init__.py +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/dirsql/cli/binary_path.py +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/dirsql/cli/interpret/__init__.py +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/dirsql/cli/is_windows.py +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/dirsql/cli/main.py +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/dirsql/cli/resolve_config_extensions.py +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/dirsql/py.typed +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/dirsql/resolve_config_extensions.py +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/dirsql/resolve_extension.py +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/docs/.claude/CLAUDE.md +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/docs/.vitepress/theme/index.ts +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/docs/.vitepress/theme/lang.ts +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/docs/AGENTS.md +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/docs/explanation.md +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/docs/howto/columns-from-paths.md +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/docs/howto/define-tables.md +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/docs/howto/embed.md +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/docs/howto/extract-from-contents.md +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/docs/howto/load-extension.md +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/docs/howto/react-to-changes.md +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/docs/howto/search-by-meaning.md +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/docs/howto/skip-files.md +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/docs/migrations.md +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/docs/package.json +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/docs/playwright.config.ts +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/docs/pnpm-lock.yaml +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/docs/pnpm-workspace.yaml +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/docs/reference/cli.md +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/docs/reference/columns.md +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/docs/reference/hooks.md +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/docs/reference/http-api.md +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/docs/tests/integration/home.spec.ts +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/docs/tests/integration/language-flag.spec.ts +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/docs/tests/integration/sidebar.spec.ts +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/docs/tests/unit/config.test.ts +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/docs/tests/unit/lang.test.ts +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/docs/vitest.config.ts +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/packages/python/README.md +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/packages/python/conftest.py +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/packages/python/docs/.claude/CLAUDE.md +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/packages/python/docs/.vitepress/theme/index.ts +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/packages/python/docs/.vitepress/theme/lang.ts +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/packages/python/docs/AGENTS.md +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/packages/python/docs/explanation.md +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/packages/python/docs/howto/columns-from-paths.md +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/packages/python/docs/howto/define-tables.md +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/packages/python/docs/howto/embed.md +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/packages/python/docs/howto/extract-from-contents.md +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/packages/python/docs/howto/load-extension.md +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/packages/python/docs/howto/react-to-changes.md +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/packages/python/docs/howto/search-by-meaning.md +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/packages/python/docs/howto/skip-files.md +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/packages/python/docs/migrations.md +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/packages/python/docs/package.json +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/packages/python/docs/playwright.config.ts +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/packages/python/docs/pnpm-lock.yaml +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/packages/python/docs/pnpm-workspace.yaml +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/packages/python/docs/reference/cli.md +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/packages/python/docs/reference/columns.md +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/packages/python/docs/reference/hooks.md +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/packages/python/docs/reference/http-api.md +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/packages/python/docs/tests/integration/home.spec.ts +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/packages/python/docs/tests/integration/language-flag.spec.ts +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/packages/python/docs/tests/integration/sidebar.spec.ts +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/packages/python/docs/tests/unit/config.test.ts +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/packages/python/docs/tests/unit/lang.test.ts +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/packages/python/docs/vitest.config.ts +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/packages/python/src/lib.rs +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/packages/python/tests/__init__.py +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/packages/python/tests/binding/__init__.py +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/packages/python/tests/conftest.py +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/packages/python/tests/e2e/__init__.py +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/packages/python/tests/integration/__init__.py +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/packages/python/tests/smoke/__init__.py +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/packages/rust/Cargo.toml +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/packages/rust/README.md +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/packages/rust/benches/db_bench.rs +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/packages/rust/benches/differ_bench.rs +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/packages/rust/benches/matcher_bench.rs +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/packages/rust/benches/scanner_bench.rs +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/packages/rust/docs/explanation.md +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/packages/rust/docs/howto/columns-from-paths.md +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/packages/rust/docs/howto/define-tables.md +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/packages/rust/docs/howto/embed.md +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/packages/rust/docs/howto/extract-from-contents.md +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/packages/rust/docs/howto/load-extension.md +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/packages/rust/docs/howto/react-to-changes.md +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/packages/rust/docs/howto/search-by-meaning.md +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/packages/rust/docs/howto/skip-files.md +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/packages/rust/docs/migrations.md +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/packages/rust/docs/reference/cli.md +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/packages/rust/docs/reference/columns.md +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/packages/rust/docs/reference/hooks.md +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/packages/rust/docs/reference/http-api.md +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/packages/rust/src/bin/dirsql.rs +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/packages/rust/src/cli/init.rs +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/packages/rust/src/cli/mod.rs +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/packages/rust/src/cli/router.rs +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/packages/rust/src/cli/serialize.rs +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/packages/rust/src/cli/server.rs +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/packages/rust/src/command.rs +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/packages/rust/src/differ.rs +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/packages/rust/src/matcher.rs +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/packages/rust/src/scanner.rs +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/packages/rust/src/watcher.rs +0 -0
- {dirsql-0.3.61 → dirsql-0.3.63}/pyproject.toml +0 -0
|
@@ -2,7 +2,7 @@ import { defineConfig } from 'vitepress'
|
|
|
2
2
|
|
|
3
3
|
export default defineConfig({
|
|
4
4
|
title: 'dirsql',
|
|
5
|
-
description: 'Ephemeral SQL index over a local directory. Watches a filesystem, ingests structured files into an
|
|
5
|
+
description: 'Ephemeral SQL index over a local directory. Watches a filesystem, ingests structured files into an ephemeral SQLite database, and exposes a SQL query interface.',
|
|
6
6
|
base: '/dirsql/',
|
|
7
7
|
|
|
8
8
|
themeConfig: {
|
|
@@ -85,7 +85,7 @@ The first run downloads the package (`npx` asks for confirmation — answer
|
|
|
85
85
|
Running at localhost:7117
|
|
86
86
|
```
|
|
87
87
|
|
|
88
|
-
That one command scanned the directory, built an
|
|
88
|
+
That one command scanned the directory, built an ephemeral SQLite database
|
|
89
89
|
with one row per file, and started an HTTP server. Leave it running and
|
|
90
90
|
open a **second terminal** for the next step.
|
|
91
91
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Keep the index across restarts
|
|
2
2
|
|
|
3
|
-
By default the database
|
|
4
|
-
|
|
3
|
+
By default the database is ephemeral: rebuilt from your files on every
|
|
4
|
+
startup and discarded on exit. [`persist`](../reference/config.md#dirsql-keys) keeps the
|
|
5
5
|
SQLite index on disk instead, so a restart only re-parses files that
|
|
6
6
|
actually changed — the difference between seconds and milliseconds on large
|
|
7
7
|
trees, and between re-running and skipping expensive
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
layout: home
|
|
3
3
|
hero:
|
|
4
4
|
name: dirsql
|
|
5
|
-
tagline: Ephemeral SQL index over a local directory. `dirsql` watches a filesystem, ingests structured files into an
|
|
5
|
+
tagline: Ephemeral SQL index over a local directory. `dirsql` watches a filesystem, ingests structured files into an ephemeral SQLite database, and exposes a SQL query interface. The filesystem is always the source of truth.
|
|
6
6
|
actions:
|
|
7
7
|
- theme: brand
|
|
8
8
|
text: Get Started
|
|
@@ -20,7 +20,7 @@ set.
|
|
|
20
20
|
|---|---|---|---|
|
|
21
21
|
| `root` | string | config file's parent directory | Directory to index. Relative values resolve against the config file's parent. An explicit `root` passed to an SDK constructor overrides this (a warning is emitted on stderr). |
|
|
22
22
|
| `ignore` | array of strings | `[]` | Glob patterns matched against root-relative paths. Matched files are skipped entirely — excluded from the initial scan and from watch events. |
|
|
23
|
-
| `persist` | boolean | `false` | Keep the SQLite index on disk between runs. When `false`, the
|
|
23
|
+
| `persist` | boolean | `false` | Keep the SQLite index on disk between runs. When `false`, the index is ephemeral: rebuilt from your files on every startup and discarded on exit. |
|
|
24
24
|
| `persist_path` | string | `<root>/.dirsql/cache.db` | Location of the on-disk cache. Relative values resolve against the config file's parent. Ignored unless `persist = true`. |
|
|
25
25
|
| `pre-query` | string | none | Server-wide command hook: the raw `POST /query` request body is passed to this command as `{args}`, and the plain-text SQL it prints is executed instead of parsing the body as `{"sql": …}`. CLI server only; the SDKs ignore it. Must be non-empty. See [Command hooks](./hooks.md#pre-query). |
|
|
26
26
|
| `post-query` | string | none | Server-wide command hook: each successful `POST /query` result set is handed to this command (as a JSON array on stdin, and as `{args}` up to 96 KiB), and the JSON body it prints is returned instead of the bare row array. CLI server only; the SDKs ignore it. Must be non-empty. See [Command hooks](./hooks.md#post-query). |
|
|
@@ -109,7 +109,7 @@ directory" error.
|
|
|
109
109
|
When both an explicit `root` and a config root are present, the explicit
|
|
110
110
|
value wins and a warning is emitted on stderr.
|
|
111
111
|
- `persist` — Keep the SQLite index on disk between runs (default off:
|
|
112
|
-
|
|
112
|
+
ephemeral, rebuilt every startup). The cache lives at
|
|
113
113
|
`<root>/.dirsql/cache.db` by default; on restart, only files whose stat
|
|
114
114
|
changed are re-parsed. `persist = true` in the config also enables it.
|
|
115
115
|
- `persist_path` / `persistPath` — Override the cache location. An
|
|
@@ -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.63"
|
|
8
8
|
edition.workspace = true
|
|
9
9
|
publish = false
|
|
10
10
|
readme = "README.md"
|
|
@@ -2,7 +2,7 @@ import { defineConfig } from 'vitepress'
|
|
|
2
2
|
|
|
3
3
|
export default defineConfig({
|
|
4
4
|
title: 'dirsql',
|
|
5
|
-
description: 'Ephemeral SQL index over a local directory. Watches a filesystem, ingests structured files into an
|
|
5
|
+
description: 'Ephemeral SQL index over a local directory. Watches a filesystem, ingests structured files into an ephemeral SQLite database, and exposes a SQL query interface.',
|
|
6
6
|
base: '/dirsql/',
|
|
7
7
|
|
|
8
8
|
themeConfig: {
|
|
@@ -85,7 +85,7 @@ The first run downloads the package (`npx` asks for confirmation — answer
|
|
|
85
85
|
Running at localhost:7117
|
|
86
86
|
```
|
|
87
87
|
|
|
88
|
-
That one command scanned the directory, built an
|
|
88
|
+
That one command scanned the directory, built an ephemeral SQLite database
|
|
89
89
|
with one row per file, and started an HTTP server. Leave it running and
|
|
90
90
|
open a **second terminal** for the next step.
|
|
91
91
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Keep the index across restarts
|
|
2
2
|
|
|
3
|
-
By default the database
|
|
4
|
-
|
|
3
|
+
By default the database is ephemeral: rebuilt from your files on every
|
|
4
|
+
startup and discarded on exit. [`persist`](../reference/config.md#dirsql-keys) keeps the
|
|
5
5
|
SQLite index on disk instead, so a restart only re-parses files that
|
|
6
6
|
actually changed — the difference between seconds and milliseconds on large
|
|
7
7
|
trees, and between re-running and skipping expensive
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
layout: home
|
|
3
3
|
hero:
|
|
4
4
|
name: dirsql
|
|
5
|
-
tagline: Ephemeral SQL index over a local directory. `dirsql` watches a filesystem, ingests structured files into an
|
|
5
|
+
tagline: Ephemeral SQL index over a local directory. `dirsql` watches a filesystem, ingests structured files into an ephemeral SQLite database, and exposes a SQL query interface. The filesystem is always the source of truth.
|
|
6
6
|
actions:
|
|
7
7
|
- theme: brand
|
|
8
8
|
text: Get Started
|
|
@@ -20,7 +20,7 @@ set.
|
|
|
20
20
|
|---|---|---|---|
|
|
21
21
|
| `root` | string | config file's parent directory | Directory to index. Relative values resolve against the config file's parent. An explicit `root` passed to an SDK constructor overrides this (a warning is emitted on stderr). |
|
|
22
22
|
| `ignore` | array of strings | `[]` | Glob patterns matched against root-relative paths. Matched files are skipped entirely — excluded from the initial scan and from watch events. |
|
|
23
|
-
| `persist` | boolean | `false` | Keep the SQLite index on disk between runs. When `false`, the
|
|
23
|
+
| `persist` | boolean | `false` | Keep the SQLite index on disk between runs. When `false`, the index is ephemeral: rebuilt from your files on every startup and discarded on exit. |
|
|
24
24
|
| `persist_path` | string | `<root>/.dirsql/cache.db` | Location of the on-disk cache. Relative values resolve against the config file's parent. Ignored unless `persist = true`. |
|
|
25
25
|
| `pre-query` | string | none | Server-wide command hook: the raw `POST /query` request body is passed to this command as `{args}`, and the plain-text SQL it prints is executed instead of parsing the body as `{"sql": …}`. CLI server only; the SDKs ignore it. Must be non-empty. See [Command hooks](./hooks.md#pre-query). |
|
|
26
26
|
| `post-query` | string | none | Server-wide command hook: each successful `POST /query` result set is handed to this command (as a JSON array on stdin, and as `{args}` up to 96 KiB), and the JSON body it prints is returned instead of the bare row array. CLI server only; the SDKs ignore it. Must be non-empty. See [Command hooks](./hooks.md#post-query). |
|
|
@@ -109,7 +109,7 @@ directory" error.
|
|
|
109
109
|
When both an explicit `root` and a config root are present, the explicit
|
|
110
110
|
value wins and a warning is emitted on stderr.
|
|
111
111
|
- `persist` — Keep the SQLite index on disk between runs (default off:
|
|
112
|
-
|
|
112
|
+
ephemeral, rebuilt every startup). The cache lives at
|
|
113
113
|
`<root>/.dirsql/cache.db` by default; on restart, only files whose stat
|
|
114
114
|
changed are re-parsed. `persist = true` in the config also enables it.
|
|
115
115
|
- `persist_path` / `persistPath` — Override the cache location. An
|
|
@@ -85,7 +85,7 @@ The first run downloads the package (`npx` asks for confirmation — answer
|
|
|
85
85
|
Running at localhost:7117
|
|
86
86
|
```
|
|
87
87
|
|
|
88
|
-
That one command scanned the directory, built an
|
|
88
|
+
That one command scanned the directory, built an ephemeral SQLite database
|
|
89
89
|
with one row per file, and started an HTTP server. Leave it running and
|
|
90
90
|
open a **second terminal** for the next step.
|
|
91
91
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Keep the index across restarts
|
|
2
2
|
|
|
3
|
-
By default the database
|
|
4
|
-
|
|
3
|
+
By default the database is ephemeral: rebuilt from your files on every
|
|
4
|
+
startup and discarded on exit. [`persist`](../reference/config.md#dirsql-keys) keeps the
|
|
5
5
|
SQLite index on disk instead, so a restart only re-parses files that
|
|
6
6
|
actually changed — the difference between seconds and milliseconds on large
|
|
7
7
|
trees, and between re-running and skipping expensive
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
layout: home
|
|
3
3
|
hero:
|
|
4
4
|
name: dirsql
|
|
5
|
-
tagline: Ephemeral SQL index over a local directory. `dirsql` watches a filesystem, ingests structured files into an
|
|
5
|
+
tagline: Ephemeral SQL index over a local directory. `dirsql` watches a filesystem, ingests structured files into an ephemeral SQLite database, and exposes a SQL query interface. The filesystem is always the source of truth.
|
|
6
6
|
actions:
|
|
7
7
|
- theme: brand
|
|
8
8
|
text: Get Started
|
|
@@ -20,7 +20,7 @@ set.
|
|
|
20
20
|
|---|---|---|---|
|
|
21
21
|
| `root` | string | config file's parent directory | Directory to index. Relative values resolve against the config file's parent. An explicit `root` passed to an SDK constructor overrides this (a warning is emitted on stderr). |
|
|
22
22
|
| `ignore` | array of strings | `[]` | Glob patterns matched against root-relative paths. Matched files are skipped entirely — excluded from the initial scan and from watch events. |
|
|
23
|
-
| `persist` | boolean | `false` | Keep the SQLite index on disk between runs. When `false`, the
|
|
23
|
+
| `persist` | boolean | `false` | Keep the SQLite index on disk between runs. When `false`, the index is ephemeral: rebuilt from your files on every startup and discarded on exit. |
|
|
24
24
|
| `persist_path` | string | `<root>/.dirsql/cache.db` | Location of the on-disk cache. Relative values resolve against the config file's parent. Ignored unless `persist = true`. |
|
|
25
25
|
| `pre-query` | string | none | Server-wide command hook: the raw `POST /query` request body is passed to this command as `{args}`, and the plain-text SQL it prints is executed instead of parsing the body as `{"sql": …}`. CLI server only; the SDKs ignore it. Must be non-empty. See [Command hooks](./hooks.md#pre-query). |
|
|
26
26
|
| `post-query` | string | none | Server-wide command hook: each successful `POST /query` result set is handed to this command (as a JSON array on stdin, and as `{args}` up to 96 KiB), and the JSON body it prints is returned instead of the bare row array. CLI server only; the SDKs ignore it. Must be non-empty. See [Command hooks](./hooks.md#post-query). |
|
|
@@ -109,7 +109,7 @@ directory" error.
|
|
|
109
109
|
When both an explicit `root` and a config root are present, the explicit
|
|
110
110
|
value wins and a warning is emitted on stderr.
|
|
111
111
|
- `persist` — Keep the SQLite index on disk between runs (default off:
|
|
112
|
-
|
|
112
|
+
ephemeral, rebuilt every startup). The cache lives at
|
|
113
113
|
`<root>/.dirsql/cache.db` by default; on restart, only files whose stat
|
|
114
114
|
changed are re-parsed. `persist = true` in the config also enables it.
|
|
115
115
|
- `persist_path` / `persistPath` — Override the cache location. An
|
|
@@ -204,7 +204,8 @@ pub struct Config {
|
|
|
204
204
|
pub ignore: Vec<String>,
|
|
205
205
|
pub tables: Vec<TableConfig>,
|
|
206
206
|
/// Enable persistent on-disk SQLite cache. When false (the default), the
|
|
207
|
-
/// database is rebuilt
|
|
207
|
+
/// database is rebuilt from scratch on every startup (in an anonymous
|
|
208
|
+
/// disk-backed temp database, #402).
|
|
208
209
|
pub persist: bool,
|
|
209
210
|
/// Optional override for the on-disk cache location. Resolved relative
|
|
210
211
|
/// to the config file's parent directory when relative.
|
|
@@ -105,14 +105,25 @@ pub struct Db {
|
|
|
105
105
|
}
|
|
106
106
|
|
|
107
107
|
impl Db {
|
|
108
|
+
/// Open the default, ephemeral `Db`: an **anonymous disk-backed temp
|
|
109
|
+
/// database** (`Connection::open("")`, issue #402), not `:memory:`.
|
|
110
|
+
///
|
|
111
|
+
/// SQLite creates a private temp file and deletes it immediately after
|
|
112
|
+
/// opening, so the OS reclaims it even on a crash or SIGKILL, and there
|
|
113
|
+
/// is never a name to collide on. Pages spill to disk as the index
|
|
114
|
+
/// grows — only the page cache stays resident — so memory no longer
|
|
115
|
+
/// scales with the indexed corpus. The file lands in the directory
|
|
116
|
+
/// SQLite's VFS picks (`SQLITE_TMPDIR` → `TMPDIR` → `/var/tmp` →
|
|
117
|
+
/// `/usr/tmp` → `/tmp`); export `SQLITE_TMPDIR` to steer it off a
|
|
118
|
+
/// tmpfs mount.
|
|
108
119
|
pub fn new() -> Result<Self> {
|
|
109
|
-
let conn = Connection::
|
|
120
|
+
let conn = Connection::open("")?;
|
|
110
121
|
ensure_internal_rows_table(&conn)?;
|
|
111
122
|
Ok(Self { conn })
|
|
112
123
|
}
|
|
113
124
|
|
|
114
|
-
/// Open a `Db` backed by
|
|
115
|
-
/// cache path;
|
|
125
|
+
/// Open a `Db` backed by a named on-disk SQLite file. Used by the
|
|
126
|
+
/// persistent cache path; the anonymous temp database is the default.
|
|
116
127
|
pub fn open(path: &Path) -> Result<Self> {
|
|
117
128
|
let conn = Connection::open(path)?;
|
|
118
129
|
ensure_internal_rows_table(&conn)?;
|
|
@@ -312,6 +323,56 @@ impl Db {
|
|
|
312
323
|
Ok(())
|
|
313
324
|
}
|
|
314
325
|
|
|
326
|
+
/// Read back the rows a given file produced for `table`, ordered by row
|
|
327
|
+
/// index. Ownership and ordering come from the `_dirsql_internal_rows`
|
|
328
|
+
/// mapping (joined on `rowid`); user columns are qualified with the table
|
|
329
|
+
/// alias so a user column named like a mapping column stays unambiguous.
|
|
330
|
+
///
|
|
331
|
+
/// This is the watcher's diffing source (#401): `handle_upsert` /
|
|
332
|
+
/// `handle_delete` snapshot a file's previous rows here instead of holding
|
|
333
|
+
/// a corpus-wide in-memory copy. A row read back compares equal to the
|
|
334
|
+
/// normalized row that was inserted as long as the extract's value types
|
|
335
|
+
/// match the declared column affinities (SQLite coerces on insert
|
|
336
|
+
/// otherwise, e.g. `Integer(5)` into a TEXT column comes back
|
|
337
|
+
/// `Text("5")`).
|
|
338
|
+
pub fn get_rows_by_file(
|
|
339
|
+
&self,
|
|
340
|
+
table: &str,
|
|
341
|
+
file_path: &str,
|
|
342
|
+
) -> Result<Vec<HashMap<String, Value>>> {
|
|
343
|
+
let user_columns = self.get_table_columns(table)?;
|
|
344
|
+
let mut col_list = user_columns
|
|
345
|
+
.iter()
|
|
346
|
+
.map(|c| format!("t.\"{c}\""))
|
|
347
|
+
.collect::<Vec<_>>()
|
|
348
|
+
.join(", ");
|
|
349
|
+
if col_list.is_empty() {
|
|
350
|
+
// No columns can only happen for a nonexistent table (SQLite
|
|
351
|
+
// requires ≥1 declared column); keep the SELECT list valid and
|
|
352
|
+
// let SQLite report "no such table".
|
|
353
|
+
col_list = "1".to_string();
|
|
354
|
+
}
|
|
355
|
+
let sql = format!(
|
|
356
|
+
"SELECT {col_list} FROM \"{table}\" AS t \
|
|
357
|
+
JOIN _dirsql_internal_rows AS m ON m.rowid_ref = t.rowid \
|
|
358
|
+
WHERE m.table_name = ?1 AND m.file_path = ?2 ORDER BY m.row_index"
|
|
359
|
+
);
|
|
360
|
+
let mut stmt = self.conn.prepare(&sql)?;
|
|
361
|
+
let rows = stmt.query_map(rusqlite::params![table, file_path], |row| {
|
|
362
|
+
let mut map = HashMap::new();
|
|
363
|
+
for (i, name) in user_columns.iter().enumerate() {
|
|
364
|
+
let v: rusqlite::types::Value = row.get(i)?;
|
|
365
|
+
map.insert(name.clone(), Value::from(v));
|
|
366
|
+
}
|
|
367
|
+
Ok(map)
|
|
368
|
+
})?;
|
|
369
|
+
let mut out = Vec::new();
|
|
370
|
+
for row in rows {
|
|
371
|
+
out.push(row?);
|
|
372
|
+
}
|
|
373
|
+
Ok(out)
|
|
374
|
+
}
|
|
375
|
+
|
|
315
376
|
/// Delete all rows that were produced by a given file path.
|
|
316
377
|
///
|
|
317
378
|
/// The user-row deletes and the matching `_dirsql_internal_rows` mapping
|
|
@@ -1206,6 +1267,95 @@ mod tests {
|
|
|
1206
1267
|
assert_eq!(deleted, 0);
|
|
1207
1268
|
}
|
|
1208
1269
|
|
|
1270
|
+
// --- Db::new backing store (#402) ---
|
|
1271
|
+
|
|
1272
|
+
#[test]
|
|
1273
|
+
fn new_is_disk_backed_not_memory() {
|
|
1274
|
+
// An in-memory SQLite database is pinned to journal_mode=memory; a
|
|
1275
|
+
// file-backed one — including the anonymous temp database `Db::new`
|
|
1276
|
+
// opens — defaults to journal_mode=delete. This pins #402: the
|
|
1277
|
+
// default Db must never be `:memory:`, so index pages spill to disk
|
|
1278
|
+
// instead of scaling resident memory with the corpus.
|
|
1279
|
+
let db = Db::new().unwrap();
|
|
1280
|
+
let mode: String = db
|
|
1281
|
+
.conn()
|
|
1282
|
+
.query_row("PRAGMA journal_mode", [], |row| row.get(0))
|
|
1283
|
+
.unwrap();
|
|
1284
|
+
assert_eq!(mode, "delete");
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
// --- get_rows_by_file: the watcher's diffing source (#401) ---
|
|
1288
|
+
|
|
1289
|
+
#[test]
|
|
1290
|
+
fn get_rows_by_file_returns_rows_in_row_index_order() {
|
|
1291
|
+
let db = Db::new().unwrap();
|
|
1292
|
+
db.create_table("CREATE TABLE t (id TEXT)").unwrap();
|
|
1293
|
+
// Insert with row indices out of insertion order; the read must
|
|
1294
|
+
// follow row_index, not rowid / insertion order.
|
|
1295
|
+
let second = HashMap::from([("id".into(), Value::Text("second".into()))]);
|
|
1296
|
+
let first = HashMap::from([("id".into(), Value::Text("first".into()))]);
|
|
1297
|
+
db.insert_row("t", &second, "a.json", 1).unwrap();
|
|
1298
|
+
db.insert_row("t", &first, "a.json", 0).unwrap();
|
|
1299
|
+
|
|
1300
|
+
let rows = db.get_rows_by_file("t", "a.json").unwrap();
|
|
1301
|
+
assert_eq!(rows.len(), 2);
|
|
1302
|
+
assert_eq!(rows[0]["id"], Value::Text("first".into()));
|
|
1303
|
+
assert_eq!(rows[1]["id"], Value::Text("second".into()));
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
#[test]
|
|
1307
|
+
fn get_rows_by_file_scopes_by_file_and_table() {
|
|
1308
|
+
let db = Db::new().unwrap();
|
|
1309
|
+
db.create_table("CREATE TABLE t (id TEXT)").unwrap();
|
|
1310
|
+
db.create_table("CREATE TABLE u (id TEXT)").unwrap();
|
|
1311
|
+
let t_row = HashMap::from([("id".into(), Value::Text("t-row".into()))]);
|
|
1312
|
+
let u_row = HashMap::from([("id".into(), Value::Text("u-row".into()))]);
|
|
1313
|
+
let other = HashMap::from([("id".into(), Value::Text("other-file".into()))]);
|
|
1314
|
+
db.insert_row("t", &t_row, "a.json", 0).unwrap();
|
|
1315
|
+
db.insert_row("u", &u_row, "a.json", 0).unwrap();
|
|
1316
|
+
db.insert_row("t", &other, "b.json", 0).unwrap();
|
|
1317
|
+
|
|
1318
|
+
let rows = db.get_rows_by_file("t", "a.json").unwrap();
|
|
1319
|
+
assert_eq!(rows.len(), 1);
|
|
1320
|
+
assert_eq!(rows[0]["id"], Value::Text("t-row".into()));
|
|
1321
|
+
|
|
1322
|
+
// A file with no rows in this table reads back empty.
|
|
1323
|
+
let none = db.get_rows_by_file("u", "b.json").unwrap();
|
|
1324
|
+
assert!(none.is_empty());
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1327
|
+
#[test]
|
|
1328
|
+
fn get_rows_by_file_round_trips_all_value_variants() {
|
|
1329
|
+
// The watcher's positional diff compares a read-back row against a
|
|
1330
|
+
// freshly normalized one, so every Value variant must survive
|
|
1331
|
+
// insert -> select unchanged when the declared column affinity
|
|
1332
|
+
// matches the value type.
|
|
1333
|
+
let db = Db::new().unwrap();
|
|
1334
|
+
db.create_table("CREATE TABLE t (i INTEGER, r REAL, s TEXT, b BLOB, n TEXT)")
|
|
1335
|
+
.unwrap();
|
|
1336
|
+
let row = HashMap::from([
|
|
1337
|
+
("i".to_string(), Value::Integer(42)),
|
|
1338
|
+
("r".to_string(), Value::Real(1.5)),
|
|
1339
|
+
("s".to_string(), Value::Text("hello".into())),
|
|
1340
|
+
("b".to_string(), Value::Blob(vec![0, 1, 2])),
|
|
1341
|
+
("n".to_string(), Value::Null),
|
|
1342
|
+
]);
|
|
1343
|
+
db.insert_row("t", &row, "a.json", 0).unwrap();
|
|
1344
|
+
|
|
1345
|
+
let rows = db.get_rows_by_file("t", "a.json").unwrap();
|
|
1346
|
+
assert_eq!(rows.len(), 1);
|
|
1347
|
+
assert_eq!(rows[0], row);
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
#[test]
|
|
1351
|
+
fn get_rows_by_file_missing_table_returns_error() {
|
|
1352
|
+
// A nonexistent table has no columns, so the SELECT list falls back
|
|
1353
|
+
// to `1` and SQLite reports the missing table.
|
|
1354
|
+
let db = Db::new().unwrap();
|
|
1355
|
+
let err = db.get_rows_by_file("ghost", "a.json").unwrap_err();
|
|
1356
|
+
assert!(err.to_string().contains("no such table"), "got: {err}");
|
|
1357
|
+
}
|
|
1358
|
+
|
|
1209
1359
|
// --- Error path: Db::open on an unopenable path ---
|
|
1210
1360
|
|
|
1211
1361
|
#[test]
|