dirsql 0.3.62__tar.gz → 0.3.64__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.62 → dirsql-0.3.64}/Cargo.lock +1 -1
- {dirsql-0.3.62 → dirsql-0.3.64}/PKG-INFO +1 -1
- {dirsql-0.3.62 → dirsql-0.3.64}/docs/.vitepress/config.ts +1 -1
- {dirsql-0.3.62/packages/python → dirsql-0.3.64}/docs/getting-started.md +1 -1
- {dirsql-0.3.62 → dirsql-0.3.64}/docs/howto/persist.md +2 -2
- {dirsql-0.3.62 → dirsql-0.3.64}/docs/index.md +1 -1
- {dirsql-0.3.62/packages/rust → dirsql-0.3.64}/docs/reference/config.md +1 -1
- {dirsql-0.3.62 → dirsql-0.3.64}/docs/reference/sdk.md +1 -1
- {dirsql-0.3.62 → dirsql-0.3.64}/packages/python/Cargo.toml +1 -1
- {dirsql-0.3.62 → dirsql-0.3.64}/packages/python/docs/.vitepress/config.ts +1 -1
- {dirsql-0.3.62 → dirsql-0.3.64/packages/python}/docs/getting-started.md +1 -1
- {dirsql-0.3.62 → dirsql-0.3.64}/packages/python/docs/howto/persist.md +2 -2
- {dirsql-0.3.62/packages/rust → dirsql-0.3.64/packages/python}/docs/index.md +1 -1
- {dirsql-0.3.62 → dirsql-0.3.64/packages/python}/docs/reference/config.md +1 -1
- {dirsql-0.3.62/packages/rust → dirsql-0.3.64/packages/python}/docs/reference/sdk.md +1 -1
- {dirsql-0.3.62 → dirsql-0.3.64}/packages/python/e2e-attestation.json +2 -2
- {dirsql-0.3.62 → dirsql-0.3.64}/packages/rust/docs/getting-started.md +1 -1
- {dirsql-0.3.62 → dirsql-0.3.64}/packages/rust/docs/howto/persist.md +2 -2
- {dirsql-0.3.62/packages/python → dirsql-0.3.64/packages/rust}/docs/index.md +1 -1
- {dirsql-0.3.62/packages/python → dirsql-0.3.64/packages/rust}/docs/reference/config.md +1 -1
- {dirsql-0.3.62/packages/python → dirsql-0.3.64/packages/rust}/docs/reference/sdk.md +1 -1
- {dirsql-0.3.62 → dirsql-0.3.64}/packages/rust/src/cli/serialize.rs +12 -8
- {dirsql-0.3.62 → dirsql-0.3.64}/packages/rust/src/config.rs +2 -1
- {dirsql-0.3.62 → dirsql-0.3.64}/packages/rust/src/db.rs +37 -93
- {dirsql-0.3.62 → dirsql-0.3.64}/packages/rust/src/lib.rs +24 -19
- {dirsql-0.3.62 → dirsql-0.3.64}/packages/rust/src/persist.rs +15 -7
- {dirsql-0.3.62 → dirsql-0.3.64}/packages/rust/src/watcher.rs +53 -33
- {dirsql-0.3.62 → dirsql-0.3.64}/Cargo.toml +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/README.md +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/dirsql/__init__.py +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/dirsql/_async.py +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/dirsql/_dirsql.pyi +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/dirsql/cli/__init__.py +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/dirsql/cli/binary_path.py +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/dirsql/cli/interpret/__init__.py +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/dirsql/cli/is_windows.py +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/dirsql/cli/main.py +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/dirsql/cli/resolve_config_extensions.py +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/dirsql/py.typed +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/dirsql/resolve_config_extensions.py +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/dirsql/resolve_extension.py +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/docs/.claude/CLAUDE.md +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/docs/.vitepress/theme/index.ts +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/docs/.vitepress/theme/lang.ts +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/docs/AGENTS.md +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/docs/explanation.md +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/docs/howto/columns-from-paths.md +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/docs/howto/define-tables.md +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/docs/howto/embed.md +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/docs/howto/extract-from-contents.md +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/docs/howto/load-extension.md +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/docs/howto/react-to-changes.md +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/docs/howto/search-by-meaning.md +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/docs/howto/skip-files.md +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/docs/migrations.md +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/docs/package.json +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/docs/playwright.config.ts +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/docs/pnpm-lock.yaml +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/docs/pnpm-workspace.yaml +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/docs/reference/cli.md +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/docs/reference/columns.md +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/docs/reference/hooks.md +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/docs/reference/http-api.md +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/docs/tests/integration/home.spec.ts +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/docs/tests/integration/language-flag.spec.ts +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/docs/tests/integration/sidebar.spec.ts +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/docs/tests/unit/config.test.ts +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/docs/tests/unit/lang.test.ts +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/docs/vitest.config.ts +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/packages/python/README.md +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/packages/python/conftest.py +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/packages/python/docs/.claude/CLAUDE.md +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/packages/python/docs/.vitepress/theme/index.ts +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/packages/python/docs/.vitepress/theme/lang.ts +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/packages/python/docs/AGENTS.md +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/packages/python/docs/explanation.md +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/packages/python/docs/howto/columns-from-paths.md +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/packages/python/docs/howto/define-tables.md +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/packages/python/docs/howto/embed.md +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/packages/python/docs/howto/extract-from-contents.md +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/packages/python/docs/howto/load-extension.md +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/packages/python/docs/howto/react-to-changes.md +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/packages/python/docs/howto/search-by-meaning.md +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/packages/python/docs/howto/skip-files.md +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/packages/python/docs/migrations.md +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/packages/python/docs/package.json +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/packages/python/docs/playwright.config.ts +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/packages/python/docs/pnpm-lock.yaml +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/packages/python/docs/pnpm-workspace.yaml +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/packages/python/docs/reference/cli.md +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/packages/python/docs/reference/columns.md +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/packages/python/docs/reference/hooks.md +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/packages/python/docs/reference/http-api.md +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/packages/python/docs/tests/integration/home.spec.ts +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/packages/python/docs/tests/integration/language-flag.spec.ts +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/packages/python/docs/tests/integration/sidebar.spec.ts +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/packages/python/docs/tests/unit/config.test.ts +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/packages/python/docs/tests/unit/lang.test.ts +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/packages/python/docs/vitest.config.ts +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/packages/python/src/lib.rs +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/packages/python/tests/__init__.py +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/packages/python/tests/binding/__init__.py +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/packages/python/tests/conftest.py +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/packages/python/tests/e2e/__init__.py +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/packages/python/tests/integration/__init__.py +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/packages/python/tests/smoke/__init__.py +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/packages/rust/Cargo.toml +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/packages/rust/README.md +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/packages/rust/benches/db_bench.rs +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/packages/rust/benches/differ_bench.rs +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/packages/rust/benches/matcher_bench.rs +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/packages/rust/benches/scanner_bench.rs +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/packages/rust/docs/explanation.md +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/packages/rust/docs/howto/columns-from-paths.md +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/packages/rust/docs/howto/define-tables.md +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/packages/rust/docs/howto/embed.md +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/packages/rust/docs/howto/extract-from-contents.md +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/packages/rust/docs/howto/load-extension.md +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/packages/rust/docs/howto/react-to-changes.md +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/packages/rust/docs/howto/search-by-meaning.md +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/packages/rust/docs/howto/skip-files.md +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/packages/rust/docs/migrations.md +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/packages/rust/docs/reference/cli.md +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/packages/rust/docs/reference/columns.md +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/packages/rust/docs/reference/hooks.md +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/packages/rust/docs/reference/http-api.md +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/packages/rust/src/bin/dirsql.rs +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/packages/rust/src/cli/init.rs +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/packages/rust/src/cli/mod.rs +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/packages/rust/src/cli/router.rs +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/packages/rust/src/cli/server.rs +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/packages/rust/src/command.rs +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/packages/rust/src/differ.rs +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/packages/rust/src/matcher.rs +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/packages/rust/src/scanner.rs +0 -0
- {dirsql-0.3.62 → dirsql-0.3.64}/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.64"
|
|
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
|
|
@@ -41,7 +41,11 @@ fn hex_encode(bytes: &[u8]) -> String {
|
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
pub(super) fn event_to_json(event: &RowEvent) -> String {
|
|
44
|
-
|
|
44
|
+
event_to_value(event).to_string()
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
fn event_to_value(event: &RowEvent) -> Value {
|
|
48
|
+
match event {
|
|
45
49
|
RowEvent::Insert {
|
|
46
50
|
table,
|
|
47
51
|
row,
|
|
@@ -86,8 +90,7 @@ pub(super) fn event_to_json(event: &RowEvent) -> String {
|
|
|
86
90
|
"file_path": file_path.to_string_lossy(),
|
|
87
91
|
"error": error,
|
|
88
92
|
}),
|
|
89
|
-
}
|
|
90
|
-
value.to_string()
|
|
93
|
+
}
|
|
91
94
|
}
|
|
92
95
|
|
|
93
96
|
// ---------------------------------------------------------------------------
|
|
@@ -119,8 +122,7 @@ mod tests {
|
|
|
119
122
|
row,
|
|
120
123
|
file_path: "posts/a.json".into(),
|
|
121
124
|
};
|
|
122
|
-
let
|
|
123
|
-
let parsed: Value = serde_json::from_str(&s).unwrap();
|
|
125
|
+
let parsed = event_to_value(&event);
|
|
124
126
|
assert_eq!(parsed.get("action").and_then(Value::as_str), Some("insert"));
|
|
125
127
|
assert_eq!(parsed.get("table").and_then(Value::as_str), Some("posts"));
|
|
126
128
|
assert_eq!(
|
|
@@ -128,6 +130,8 @@ mod tests {
|
|
|
128
130
|
Some("posts/a.json"),
|
|
129
131
|
);
|
|
130
132
|
assert!(parsed.get("old_row").unwrap().is_null());
|
|
133
|
+
// event_to_json is the string form of the value it builds.
|
|
134
|
+
assert_eq!(event_to_json(&event), parsed.to_string());
|
|
131
135
|
}
|
|
132
136
|
|
|
133
137
|
#[test]
|
|
@@ -142,7 +146,7 @@ mod tests {
|
|
|
142
146
|
new_row: new,
|
|
143
147
|
file_path: "posts/a.json".into(),
|
|
144
148
|
};
|
|
145
|
-
let parsed
|
|
149
|
+
let parsed = event_to_value(&event);
|
|
146
150
|
assert_eq!(
|
|
147
151
|
parsed.pointer("/row/id").and_then(Value::as_str),
|
|
148
152
|
Some("abc2")
|
|
@@ -160,7 +164,7 @@ mod tests {
|
|
|
160
164
|
file_path: PathBuf::from("bad.json"),
|
|
161
165
|
error: "parse failed".into(),
|
|
162
166
|
};
|
|
163
|
-
let parsed
|
|
167
|
+
let parsed = event_to_value(&event);
|
|
164
168
|
assert_eq!(parsed.get("action").and_then(Value::as_str), Some("error"));
|
|
165
169
|
assert_eq!(
|
|
166
170
|
parsed.get("error").and_then(Value::as_str),
|
|
@@ -177,7 +181,7 @@ mod tests {
|
|
|
177
181
|
row,
|
|
178
182
|
file_path: "posts/a.json".into(),
|
|
179
183
|
};
|
|
180
|
-
let parsed
|
|
184
|
+
let parsed = event_to_value(&event);
|
|
181
185
|
assert_eq!(parsed.get("action").and_then(Value::as_str), Some("delete"));
|
|
182
186
|
assert_eq!(parsed.get("table").and_then(Value::as_str), Some("posts"));
|
|
183
187
|
assert_eq!(
|
|
@@ -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)?;
|
|
@@ -613,7 +624,6 @@ impl From<rusqlite::types::Value> for Value {
|
|
|
613
624
|
#[cfg(test)]
|
|
614
625
|
mod tests {
|
|
615
626
|
use super::*;
|
|
616
|
-
use rusqlite::types::ToSql;
|
|
617
627
|
|
|
618
628
|
#[test]
|
|
619
629
|
fn create_table_from_ddl() {
|
|
@@ -955,95 +965,12 @@ mod tests {
|
|
|
955
965
|
assert_eq!(normalized.len(), 2);
|
|
956
966
|
}
|
|
957
967
|
|
|
958
|
-
//
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
// Branch-free: compare against the expected value (`ToSqlOutput: PartialEq`)
|
|
965
|
-
// rather than `matches!`, whose `_ => false` arm is a dead region here.
|
|
966
|
-
assert_eq!(
|
|
967
|
-
result,
|
|
968
|
-
rusqlite::types::ToSqlOutput::Owned(rusqlite::types::Value::Null)
|
|
969
|
-
);
|
|
970
|
-
}
|
|
971
|
-
|
|
972
|
-
#[test]
|
|
973
|
-
fn value_to_sql_integer() {
|
|
974
|
-
let v = Value::Integer(42);
|
|
975
|
-
let result = v.to_sql().unwrap();
|
|
976
|
-
// Branch-free: see `value_to_sql_null` -- compare values directly.
|
|
977
|
-
assert_eq!(
|
|
978
|
-
result,
|
|
979
|
-
rusqlite::types::ToSqlOutput::Owned(rusqlite::types::Value::Integer(42))
|
|
980
|
-
);
|
|
981
|
-
}
|
|
982
|
-
|
|
983
|
-
#[test]
|
|
984
|
-
fn value_to_sql_real() {
|
|
985
|
-
let v = Value::Real(1.5);
|
|
986
|
-
let result = v.to_sql().unwrap();
|
|
987
|
-
// Branch-free: `ToSqlOutput` derives `PartialEq`, so compare against
|
|
988
|
-
// the exact expected value instead of a `match` with a dead `_` arm.
|
|
989
|
-
// 1.5 is exactly representable in f64, so equality is precise here.
|
|
990
|
-
assert_eq!(
|
|
991
|
-
result,
|
|
992
|
-
rusqlite::types::ToSqlOutput::Owned(rusqlite::types::Value::Real(1.5))
|
|
993
|
-
);
|
|
994
|
-
}
|
|
995
|
-
|
|
996
|
-
#[test]
|
|
997
|
-
fn value_to_sql_text() {
|
|
998
|
-
let v = Value::Text("hello".into());
|
|
999
|
-
let result = v.to_sql().unwrap();
|
|
1000
|
-
assert!(matches!(
|
|
1001
|
-
result,
|
|
1002
|
-
rusqlite::types::ToSqlOutput::Owned(rusqlite::types::Value::Text(ref s)) if s == "hello"
|
|
1003
|
-
));
|
|
1004
|
-
}
|
|
1005
|
-
|
|
1006
|
-
#[test]
|
|
1007
|
-
fn value_to_sql_blob() {
|
|
1008
|
-
let v = Value::Blob(vec![1, 2, 3]);
|
|
1009
|
-
let result = v.to_sql().unwrap();
|
|
1010
|
-
assert!(matches!(
|
|
1011
|
-
result,
|
|
1012
|
-
rusqlite::types::ToSqlOutput::Owned(rusqlite::types::Value::Blob(ref b)) if b == &[1, 2, 3]
|
|
1013
|
-
));
|
|
1014
|
-
}
|
|
1015
|
-
|
|
1016
|
-
// --- Value::from coverage for all variants ---
|
|
1017
|
-
|
|
1018
|
-
#[test]
|
|
1019
|
-
fn value_from_sqlite_null() {
|
|
1020
|
-
let v = Value::from(rusqlite::types::Value::Null);
|
|
1021
|
-
assert_eq!(v, Value::Null);
|
|
1022
|
-
}
|
|
1023
|
-
|
|
1024
|
-
#[test]
|
|
1025
|
-
fn value_from_sqlite_integer() {
|
|
1026
|
-
let v = Value::from(rusqlite::types::Value::Integer(99));
|
|
1027
|
-
assert_eq!(v, Value::Integer(99));
|
|
1028
|
-
}
|
|
1029
|
-
|
|
1030
|
-
#[test]
|
|
1031
|
-
fn value_from_sqlite_real() {
|
|
1032
|
-
let v = Value::from(rusqlite::types::Value::Real(1.25));
|
|
1033
|
-
assert_eq!(v, Value::Real(1.25));
|
|
1034
|
-
}
|
|
1035
|
-
|
|
1036
|
-
#[test]
|
|
1037
|
-
fn value_from_sqlite_text() {
|
|
1038
|
-
let v = Value::from(rusqlite::types::Value::Text("world".into()));
|
|
1039
|
-
assert_eq!(v, Value::Text("world".into()));
|
|
1040
|
-
}
|
|
1041
|
-
|
|
1042
|
-
#[test]
|
|
1043
|
-
fn value_from_sqlite_blob() {
|
|
1044
|
-
let v = Value::from(rusqlite::types::Value::Blob(vec![10, 20]));
|
|
1045
|
-
assert_eq!(v, Value::Blob(vec![10, 20]));
|
|
1046
|
-
}
|
|
968
|
+
// `Value::to_sql` (our type -> SQLite param) and `From<rusqlite::types::Value>`
|
|
969
|
+
// (SQLite value -> our type) are the marshaling boundary. Every variant's
|
|
970
|
+
// arm is exercised end-to-end by the insert/query round-trip tests below
|
|
971
|
+
// (Real, Null, Blob each have a dedicated one; Text + Integer via
|
|
972
|
+
// `insert_and_query_rows`), so the arms stay covered without a unit test
|
|
973
|
+
// naming `rusqlite::types::*` directly (the `unit lint` isolation rule).
|
|
1047
974
|
|
|
1048
975
|
// --- Insert and query with real/blob values ---
|
|
1049
976
|
|
|
@@ -1256,6 +1183,23 @@ mod tests {
|
|
|
1256
1183
|
assert_eq!(deleted, 0);
|
|
1257
1184
|
}
|
|
1258
1185
|
|
|
1186
|
+
// --- Db::new backing store (#402) ---
|
|
1187
|
+
|
|
1188
|
+
#[test]
|
|
1189
|
+
fn new_is_disk_backed_not_memory() {
|
|
1190
|
+
// An in-memory SQLite database is pinned to journal_mode=memory; a
|
|
1191
|
+
// file-backed one — including the anonymous temp database `Db::new`
|
|
1192
|
+
// opens — defaults to journal_mode=delete. This pins #402: the
|
|
1193
|
+
// default Db must never be `:memory:`, so index pages spill to disk
|
|
1194
|
+
// instead of scaling resident memory with the corpus.
|
|
1195
|
+
let db = Db::new().unwrap();
|
|
1196
|
+
let mode: String = db
|
|
1197
|
+
.conn()
|
|
1198
|
+
.query_row("PRAGMA journal_mode", [], |row| row.get(0))
|
|
1199
|
+
.unwrap();
|
|
1200
|
+
assert_eq!(mode, "delete");
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1259
1203
|
// --- get_rows_by_file: the watcher's diffing source (#401) ---
|
|
1260
1204
|
|
|
1261
1205
|
#[test]
|
|
@@ -325,14 +325,14 @@ impl DirSQL {
|
|
|
325
325
|
.build()
|
|
326
326
|
}
|
|
327
327
|
|
|
328
|
-
/// Run a SQL query against the
|
|
328
|
+
/// Run a SQL query against the ephemeral database.
|
|
329
329
|
///
|
|
330
330
|
/// Only read-only statements are accepted. Each statement is prepared on
|
|
331
331
|
/// SQLite and then classified via `sqlite3_stmt_readonly`; anything that
|
|
332
332
|
/// SQLite itself flags as a write — `INSERT`, `UPDATE`, `DELETE`, `DROP`,
|
|
333
333
|
/// `CREATE`, `ALTER`, `REPLACE`, `VACUUM`, `ANALYZE`, etc. — is rejected
|
|
334
334
|
/// with [`DirSqlError::WriteForbidden`] before any rows are produced. This
|
|
335
|
-
/// keeps the
|
|
335
|
+
/// keeps the ephemeral index consistent with the on-disk files that back
|
|
336
336
|
/// it: mutations only happen through the watcher/indexer pipeline.
|
|
337
337
|
pub fn query(&self, sql: &str) -> Result<Vec<Row>> {
|
|
338
338
|
let db = self.inner.db.lock().map_err(DirSqlError::lock)?;
|
|
@@ -355,7 +355,7 @@ impl DirSQL {
|
|
|
355
355
|
|
|
356
356
|
/// Poll-based watch API. Blocks up to `timeout` waiting for the next
|
|
357
357
|
/// filesystem event, then drains any additional events that arrived during
|
|
358
|
-
/// processing, applying all of them to the
|
|
358
|
+
/// processing, applying all of them to the ephemeral database. Returns the
|
|
359
359
|
/// batch of [`RowEvent`]s produced (possibly empty). Safe to call in a
|
|
360
360
|
/// loop.
|
|
361
361
|
///
|
|
@@ -1731,6 +1731,20 @@ struct AsyncDirSqlInner {
|
|
|
1731
1731
|
ready_notify: tokio::sync::Notify,
|
|
1732
1732
|
}
|
|
1733
1733
|
|
|
1734
|
+
#[cfg(test)]
|
|
1735
|
+
impl AsyncDirSqlInner {
|
|
1736
|
+
/// Fresh inner with an empty `db` cell — the pre-`ready` state. Lets the unit
|
|
1737
|
+
/// tests build this fixture without naming the `tokio::sync` primitives (the
|
|
1738
|
+
/// `unit lint` isolation rule); real async runtime behavior is exercised in
|
|
1739
|
+
/// the integration/binding tiers.
|
|
1740
|
+
fn empty() -> Self {
|
|
1741
|
+
Self {
|
|
1742
|
+
db: tokio::sync::OnceCell::new(),
|
|
1743
|
+
ready_notify: tokio::sync::Notify::new(),
|
|
1744
|
+
}
|
|
1745
|
+
}
|
|
1746
|
+
}
|
|
1747
|
+
|
|
1734
1748
|
impl AsyncDirSQL {
|
|
1735
1749
|
/// Shortcut for `DirSQL::builder().root(root).tables(tables).build_async()`.
|
|
1736
1750
|
pub fn new(root: impl Into<PathBuf>, tables: Vec<Table>) -> Result<Self> {
|
|
@@ -1844,14 +1858,11 @@ mod readonly_tests {
|
|
|
1844
1858
|
assert!(matches!(err, DirSqlError::WriteForbidden), "got: {err:?}");
|
|
1845
1859
|
}
|
|
1846
1860
|
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
)));
|
|
1853
|
-
assert!(matches!(err, DirSqlError::Core(_)), "got: {err:?}");
|
|
1854
|
-
}
|
|
1861
|
+
// `map_db_error` treats every non-`WriteForbidden` variant identically (the
|
|
1862
|
+
// `other => Core` arm), so `map_db_error_leaves_schema_mismatch_as_core`
|
|
1863
|
+
// below fully covers that arm without a unit test naming `rusqlite::Error`.
|
|
1864
|
+
// The Sqlite-error mapping is additionally exercised end-to-end in the
|
|
1865
|
+
// integration/binding tiers.
|
|
1855
1866
|
|
|
1856
1867
|
#[test]
|
|
1857
1868
|
fn map_db_error_leaves_schema_mismatch_as_core() {
|
|
@@ -3197,10 +3208,7 @@ mod internal_tests {
|
|
|
3197
3208
|
/// empty `OnceCell` so the state is deterministic (no build race).
|
|
3198
3209
|
#[test]
|
|
3199
3210
|
fn async_dirsql_sync_before_ready_is_not_ready() {
|
|
3200
|
-
let inner = Arc::new(AsyncDirSqlInner
|
|
3201
|
-
db: tokio::sync::OnceCell::new(),
|
|
3202
|
-
ready_notify: tokio::sync::Notify::new(),
|
|
3203
|
-
});
|
|
3211
|
+
let inner = Arc::new(AsyncDirSqlInner::empty());
|
|
3204
3212
|
let adb = AsyncDirSQL { inner };
|
|
3205
3213
|
// `sync` returns `Result<DirSQL, _>`; DirSQL is not Debug, so match
|
|
3206
3214
|
// rather than `unwrap_err`.
|
|
@@ -3215,10 +3223,7 @@ mod internal_tests {
|
|
|
3215
3223
|
/// "init failed" error carrying the underlying diagnostic.
|
|
3216
3224
|
#[tokio::test]
|
|
3217
3225
|
async fn async_dirsql_surfaces_init_failure() {
|
|
3218
|
-
let inner = Arc::new(AsyncDirSqlInner
|
|
3219
|
-
db: tokio::sync::OnceCell::new(),
|
|
3220
|
-
ready_notify: tokio::sync::Notify::new(),
|
|
3221
|
-
});
|
|
3226
|
+
let inner = Arc::new(AsyncDirSqlInner::empty());
|
|
3222
3227
|
inner
|
|
3223
3228
|
.db
|
|
3224
3229
|
.set(Err(DirSqlError::Ddl("boom".into())))
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
//! Persistent on-disk SQLite cache (issue #95).
|
|
2
2
|
//!
|
|
3
|
-
//! Stores the
|
|
4
|
-
//! startups only re-parse files that have actually changed. Uses
|
|
5
|
-
//! racy-stat algorithm as `git status`: a per-file `(size, mtime,
|
|
6
|
-
//! inode, dev)` tuple is compared against the cache, with a
|
|
7
|
-
//! confirmation for files whose mtime falls inside the racy
|
|
3
|
+
//! Stores the SQLite database at a named on-disk path between runs so
|
|
4
|
+
//! subsequent startups only re-parse files that have actually changed. Uses
|
|
5
|
+
//! the same racy-stat algorithm as `git status`: a per-file `(size, mtime,
|
|
6
|
+
//! ctime, inode, dev)` tuple is compared against the cache, with a
|
|
7
|
+
//! content-hash confirmation for files whose mtime falls inside the racy
|
|
8
|
+
//! window.
|
|
8
9
|
//!
|
|
9
|
-
//! See `docs/
|
|
10
|
+
//! See `docs/howto/persist.md` for the user-facing contract.
|
|
10
11
|
|
|
11
12
|
use rusqlite::{Connection, params};
|
|
12
13
|
use std::collections::{BTreeMap, HashMap};
|
|
@@ -563,7 +564,14 @@ mod tests {
|
|
|
563
564
|
let p = dir.path().join("a.txt");
|
|
564
565
|
fs::write(&p, b"hello").unwrap();
|
|
565
566
|
let h = hash_file(&p).unwrap();
|
|
566
|
-
|
|
567
|
+
// Golden BLAKE3 digest of b"hello" (pins the exact algorithm + output;
|
|
568
|
+
// hardcoded so the unit test does not call the blake3 crate itself).
|
|
569
|
+
// hex: ea8f163db38682925e4491c5e58d4bb3506ef8c14eb78a86e908c5624a67200f
|
|
570
|
+
let expected: [u8; 32] = [
|
|
571
|
+
0xea, 0x8f, 0x16, 0x3d, 0xb3, 0x86, 0x82, 0x92, 0x5e, 0x44, 0x91, 0xc5, 0xe5, 0x8d,
|
|
572
|
+
0x4b, 0xb3, 0x50, 0x6e, 0xf8, 0xc1, 0x4e, 0xb7, 0x8a, 0x86, 0xe9, 0x08, 0xc5, 0x62,
|
|
573
|
+
0x4a, 0x67, 0x20, 0x0f,
|
|
574
|
+
];
|
|
567
575
|
assert_eq!(h, expected);
|
|
568
576
|
}
|
|
569
577
|
|
|
@@ -84,6 +84,48 @@ fn translate_event(event: &Event) -> Vec<FileEvent> {
|
|
|
84
84
|
results
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
+
// Test fixtures: build `notify::Event`s for each top-level `EventKind` without
|
|
88
|
+
// the unit tests below naming the `notify::event::*` inner-kind types (the
|
|
89
|
+
// `unit lint` isolation rule). `translate_event` matches only the outer
|
|
90
|
+
// `EventKind` variant, so the inner kind here is an arbitrary valid value.
|
|
91
|
+
#[cfg(test)]
|
|
92
|
+
fn create_event(paths: Vec<PathBuf>) -> Event {
|
|
93
|
+
Event {
|
|
94
|
+
kind: EventKind::Create(notify::event::CreateKind::File),
|
|
95
|
+
paths,
|
|
96
|
+
attrs: Default::default(),
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
#[cfg(test)]
|
|
101
|
+
fn modify_event(paths: Vec<PathBuf>) -> Event {
|
|
102
|
+
Event {
|
|
103
|
+
kind: EventKind::Modify(notify::event::ModifyKind::Data(
|
|
104
|
+
notify::event::DataChange::Content,
|
|
105
|
+
)),
|
|
106
|
+
paths,
|
|
107
|
+
attrs: Default::default(),
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
#[cfg(test)]
|
|
112
|
+
fn remove_event(paths: Vec<PathBuf>) -> Event {
|
|
113
|
+
Event {
|
|
114
|
+
kind: EventKind::Remove(notify::event::RemoveKind::File),
|
|
115
|
+
paths,
|
|
116
|
+
attrs: Default::default(),
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
#[cfg(test)]
|
|
121
|
+
fn access_event(paths: Vec<PathBuf>) -> Event {
|
|
122
|
+
Event {
|
|
123
|
+
kind: EventKind::Access(notify::event::AccessKind::Read),
|
|
124
|
+
paths,
|
|
125
|
+
attrs: Default::default(),
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
87
129
|
#[cfg(test)]
|
|
88
130
|
mod tests {
|
|
89
131
|
use super::*;
|
|
@@ -92,16 +134,13 @@ mod tests {
|
|
|
92
134
|
// files (`std::fs`, `std::thread`, `Instant::now`) live in
|
|
93
135
|
// `tests/watcher.rs` -- they're integration tests, and keeping them out of
|
|
94
136
|
// this inline module is what the `unit lint` isolation rule requires. Only
|
|
95
|
-
// the pure `translate_event` mapping tests belong here
|
|
137
|
+
// the pure `translate_event` mapping tests belong here; they build their
|
|
138
|
+
// `notify::Event` inputs via the `*_event` fixtures above so no unit test
|
|
139
|
+
// names a `notify::event::*` type directly.
|
|
96
140
|
|
|
97
141
|
#[test]
|
|
98
142
|
fn translate_event_maps_create() {
|
|
99
|
-
let
|
|
100
|
-
kind: EventKind::Create(notify::event::CreateKind::File),
|
|
101
|
-
paths: vec![PathBuf::from("/tmp/test.txt")],
|
|
102
|
-
attrs: Default::default(),
|
|
103
|
-
};
|
|
104
|
-
let results = translate_event(&event);
|
|
143
|
+
let results = translate_event(&create_event(vec![PathBuf::from("/tmp/test.txt")]));
|
|
105
144
|
assert_eq!(
|
|
106
145
|
results,
|
|
107
146
|
vec![FileEvent::Created(PathBuf::from("/tmp/test.txt"))]
|
|
@@ -110,12 +149,7 @@ mod tests {
|
|
|
110
149
|
|
|
111
150
|
#[test]
|
|
112
151
|
fn translate_event_maps_remove() {
|
|
113
|
-
let
|
|
114
|
-
kind: EventKind::Remove(notify::event::RemoveKind::File),
|
|
115
|
-
paths: vec![PathBuf::from("/tmp/gone.txt")],
|
|
116
|
-
attrs: Default::default(),
|
|
117
|
-
};
|
|
118
|
-
let results = translate_event(&event);
|
|
152
|
+
let results = translate_event(&remove_event(vec![PathBuf::from("/tmp/gone.txt")]));
|
|
119
153
|
assert_eq!(
|
|
120
154
|
results,
|
|
121
155
|
vec![FileEvent::Deleted(PathBuf::from("/tmp/gone.txt"))]
|
|
@@ -124,25 +158,13 @@ mod tests {
|
|
|
124
158
|
|
|
125
159
|
#[test]
|
|
126
160
|
fn translate_event_ignores_access_events() {
|
|
127
|
-
let
|
|
128
|
-
kind: EventKind::Access(notify::event::AccessKind::Read),
|
|
129
|
-
paths: vec![PathBuf::from("/tmp/read.txt")],
|
|
130
|
-
attrs: Default::default(),
|
|
131
|
-
};
|
|
132
|
-
let results = translate_event(&event);
|
|
161
|
+
let results = translate_event(&access_event(vec![PathBuf::from("/tmp/read.txt")]));
|
|
133
162
|
assert!(results.is_empty());
|
|
134
163
|
}
|
|
135
164
|
|
|
136
165
|
#[test]
|
|
137
166
|
fn translate_event_maps_modify() {
|
|
138
|
-
let
|
|
139
|
-
kind: EventKind::Modify(notify::event::ModifyKind::Data(
|
|
140
|
-
notify::event::DataChange::Content,
|
|
141
|
-
)),
|
|
142
|
-
paths: vec![PathBuf::from("/tmp/changed.txt")],
|
|
143
|
-
attrs: Default::default(),
|
|
144
|
-
};
|
|
145
|
-
let results = translate_event(&event);
|
|
167
|
+
let results = translate_event(&modify_event(vec![PathBuf::from("/tmp/changed.txt")]));
|
|
146
168
|
assert_eq!(
|
|
147
169
|
results,
|
|
148
170
|
vec![FileEvent::Modified(PathBuf::from("/tmp/changed.txt"))]
|
|
@@ -151,12 +173,10 @@ mod tests {
|
|
|
151
173
|
|
|
152
174
|
#[test]
|
|
153
175
|
fn translate_event_multiple_paths() {
|
|
154
|
-
let
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
};
|
|
159
|
-
let results = translate_event(&event);
|
|
176
|
+
let results = translate_event(&create_event(vec![
|
|
177
|
+
PathBuf::from("/tmp/a.txt"),
|
|
178
|
+
PathBuf::from("/tmp/b.txt"),
|
|
179
|
+
]));
|
|
160
180
|
assert_eq!(results.len(), 2);
|
|
161
181
|
assert_eq!(results[0], FileEvent::Created(PathBuf::from("/tmp/a.txt")));
|
|
162
182
|
assert_eq!(results[1], FileEvent::Created(PathBuf::from("/tmp/b.txt")));
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dirsql-0.3.62 → dirsql-0.3.64}/packages/python/docs/tests/integration/language-flag.spec.ts
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|