dirsql 0.3.71__tar.gz → 0.3.72__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.71 → dirsql-0.3.72}/Cargo.lock +1 -1
- {dirsql-0.3.71 → dirsql-0.3.72}/PKG-INFO +1 -1
- {dirsql-0.3.71 → dirsql-0.3.72}/dirsql/_async.py +7 -10
- {dirsql-0.3.71 → dirsql-0.3.72}/dirsql/cli/main.py +1 -1
- {dirsql-0.3.71 → dirsql-0.3.72}/dirsql/cli/resolve_config_extensions.py +7 -9
- {dirsql-0.3.71 → dirsql-0.3.72}/dirsql/resolve_config_extensions.py +8 -12
- {dirsql-0.3.71 → dirsql-0.3.72}/dirsql/resolve_extension.py +9 -18
- {dirsql-0.3.71 → dirsql-0.3.72}/packages/python/Cargo.toml +1 -1
- {dirsql-0.3.71 → dirsql-0.3.72}/packages/python/conftest.py +4 -10
- {dirsql-0.3.71 → dirsql-0.3.72}/packages/python/e2e-attestation.json +2 -2
- {dirsql-0.3.71 → dirsql-0.3.72}/packages/python/src/lib.rs +11 -26
- {dirsql-0.3.71 → dirsql-0.3.72}/packages/python/tests/conftest.py +0 -1
- {dirsql-0.3.71 → dirsql-0.3.72}/packages/rust/benches/differ_bench.rs +0 -3
- {dirsql-0.3.71 → dirsql-0.3.72}/packages/rust/benches/scanner_bench.rs +0 -2
- {dirsql-0.3.71 → dirsql-0.3.72}/packages/rust/src/bin/dirsql.rs +5 -10
- {dirsql-0.3.71 → dirsql-0.3.72}/packages/rust/src/cli/execute.rs +1 -1
- {dirsql-0.3.71 → dirsql-0.3.72}/packages/rust/src/cli/init.rs +5 -9
- {dirsql-0.3.71 → dirsql-0.3.72}/packages/rust/src/cli/mod.rs +8 -27
- {dirsql-0.3.71 → dirsql-0.3.72}/packages/rust/src/cli/serialize.rs +1 -9
- {dirsql-0.3.71 → dirsql-0.3.72}/packages/rust/src/cli/server.rs +2 -7
- {dirsql-0.3.71 → dirsql-0.3.72}/packages/rust/src/command.rs +5 -19
- {dirsql-0.3.71 → dirsql-0.3.72}/packages/rust/src/config.rs +14 -58
- {dirsql-0.3.71 → dirsql-0.3.72}/packages/rust/src/db.rs +58 -195
- {dirsql-0.3.71 → dirsql-0.3.72}/packages/rust/src/differ.rs +3 -40
- {dirsql-0.3.71 → dirsql-0.3.72}/packages/rust/src/lib.rs +115 -435
- {dirsql-0.3.71 → dirsql-0.3.72}/packages/rust/src/matcher.rs +1 -15
- {dirsql-0.3.71 → dirsql-0.3.72}/packages/rust/src/persist.rs +18 -57
- {dirsql-0.3.71 → dirsql-0.3.72}/packages/rust/src/scanner.rs +3 -13
- {dirsql-0.3.71 → dirsql-0.3.72}/packages/rust/src/watcher.rs +7 -11
- {dirsql-0.3.71 → dirsql-0.3.72}/Cargo.toml +0 -0
- {dirsql-0.3.71 → dirsql-0.3.72}/README.md +0 -0
- {dirsql-0.3.71 → dirsql-0.3.72}/dirsql/__init__.py +0 -0
- {dirsql-0.3.71 → dirsql-0.3.72}/dirsql/_dirsql.pyi +0 -0
- {dirsql-0.3.71 → dirsql-0.3.72}/dirsql/cli/__init__.py +0 -0
- {dirsql-0.3.71 → dirsql-0.3.72}/dirsql/cli/binary_path.py +0 -0
- {dirsql-0.3.71 → dirsql-0.3.72}/dirsql/cli/is_windows.py +0 -0
- {dirsql-0.3.71 → dirsql-0.3.72}/dirsql/py.typed +0 -0
- {dirsql-0.3.71 → dirsql-0.3.72}/docs/.claude/CLAUDE.md +0 -0
- {dirsql-0.3.71 → dirsql-0.3.72}/docs/.vitepress/config.ts +0 -0
- {dirsql-0.3.71 → dirsql-0.3.72}/docs/.vitepress/theme/index.ts +0 -0
- {dirsql-0.3.71 → dirsql-0.3.72}/docs/.vitepress/theme/lang.ts +0 -0
- {dirsql-0.3.71 → dirsql-0.3.72}/docs/AGENTS.md +0 -0
- {dirsql-0.3.71 → dirsql-0.3.72}/docs/explanation.md +0 -0
- {dirsql-0.3.71 → dirsql-0.3.72}/docs/getting-started.md +0 -0
- {dirsql-0.3.71 → dirsql-0.3.72}/docs/howto/columns-from-paths.md +0 -0
- {dirsql-0.3.71 → dirsql-0.3.72}/docs/howto/define-tables.md +0 -0
- {dirsql-0.3.71 → dirsql-0.3.72}/docs/howto/embed.md +0 -0
- {dirsql-0.3.71 → dirsql-0.3.72}/docs/howto/extract-from-contents.md +0 -0
- {dirsql-0.3.71 → dirsql-0.3.72}/docs/howto/load-extension.md +0 -0
- {dirsql-0.3.71 → dirsql-0.3.72}/docs/howto/persist.md +0 -0
- {dirsql-0.3.71 → dirsql-0.3.72}/docs/howto/react-to-changes.md +0 -0
- {dirsql-0.3.71 → dirsql-0.3.72}/docs/howto/search-by-meaning.md +0 -0
- {dirsql-0.3.71 → dirsql-0.3.72}/docs/howto/skip-files.md +0 -0
- {dirsql-0.3.71 → dirsql-0.3.72}/docs/index.md +0 -0
- {dirsql-0.3.71 → dirsql-0.3.72}/docs/migrations.md +0 -0
- {dirsql-0.3.71 → dirsql-0.3.72}/docs/package.json +0 -0
- {dirsql-0.3.71 → dirsql-0.3.72}/docs/playwright.config.ts +0 -0
- {dirsql-0.3.71 → dirsql-0.3.72}/docs/pnpm-lock.yaml +0 -0
- {dirsql-0.3.71 → dirsql-0.3.72}/docs/pnpm-workspace.yaml +0 -0
- {dirsql-0.3.71 → dirsql-0.3.72}/docs/reference/cli.md +0 -0
- {dirsql-0.3.71 → dirsql-0.3.72}/docs/reference/columns.md +0 -0
- {dirsql-0.3.71 → dirsql-0.3.72}/docs/reference/config.md +0 -0
- {dirsql-0.3.71 → dirsql-0.3.72}/docs/reference/hooks.md +0 -0
- {dirsql-0.3.71 → dirsql-0.3.72}/docs/reference/http-api.md +0 -0
- {dirsql-0.3.71 → dirsql-0.3.72}/docs/reference/sdk.md +0 -0
- {dirsql-0.3.71 → dirsql-0.3.72}/docs/tests/integration/home.spec.ts +0 -0
- {dirsql-0.3.71 → dirsql-0.3.72}/docs/tests/integration/language-flag.spec.ts +0 -0
- {dirsql-0.3.71 → dirsql-0.3.72}/docs/tests/integration/sidebar.spec.ts +0 -0
- {dirsql-0.3.71 → dirsql-0.3.72}/docs/tests/unit/config.test.ts +0 -0
- {dirsql-0.3.71 → dirsql-0.3.72}/docs/tests/unit/lang.test.ts +0 -0
- {dirsql-0.3.71 → dirsql-0.3.72}/docs/vitest.config.ts +0 -0
- {dirsql-0.3.71 → dirsql-0.3.72}/packages/python/README.md +0 -0
- {dirsql-0.3.71 → dirsql-0.3.72}/packages/python/docs/.claude/CLAUDE.md +0 -0
- {dirsql-0.3.71 → dirsql-0.3.72}/packages/python/docs/.vitepress/config.ts +0 -0
- {dirsql-0.3.71 → dirsql-0.3.72}/packages/python/docs/.vitepress/theme/index.ts +0 -0
- {dirsql-0.3.71 → dirsql-0.3.72}/packages/python/docs/.vitepress/theme/lang.ts +0 -0
- {dirsql-0.3.71 → dirsql-0.3.72}/packages/python/docs/AGENTS.md +0 -0
- {dirsql-0.3.71 → dirsql-0.3.72}/packages/python/docs/explanation.md +0 -0
- {dirsql-0.3.71 → dirsql-0.3.72}/packages/python/docs/getting-started.md +0 -0
- {dirsql-0.3.71 → dirsql-0.3.72}/packages/python/docs/howto/columns-from-paths.md +0 -0
- {dirsql-0.3.71 → dirsql-0.3.72}/packages/python/docs/howto/define-tables.md +0 -0
- {dirsql-0.3.71 → dirsql-0.3.72}/packages/python/docs/howto/embed.md +0 -0
- {dirsql-0.3.71 → dirsql-0.3.72}/packages/python/docs/howto/extract-from-contents.md +0 -0
- {dirsql-0.3.71 → dirsql-0.3.72}/packages/python/docs/howto/load-extension.md +0 -0
- {dirsql-0.3.71 → dirsql-0.3.72}/packages/python/docs/howto/persist.md +0 -0
- {dirsql-0.3.71 → dirsql-0.3.72}/packages/python/docs/howto/react-to-changes.md +0 -0
- {dirsql-0.3.71 → dirsql-0.3.72}/packages/python/docs/howto/search-by-meaning.md +0 -0
- {dirsql-0.3.71 → dirsql-0.3.72}/packages/python/docs/howto/skip-files.md +0 -0
- {dirsql-0.3.71 → dirsql-0.3.72}/packages/python/docs/index.md +0 -0
- {dirsql-0.3.71 → dirsql-0.3.72}/packages/python/docs/migrations.md +0 -0
- {dirsql-0.3.71 → dirsql-0.3.72}/packages/python/docs/package.json +0 -0
- {dirsql-0.3.71 → dirsql-0.3.72}/packages/python/docs/playwright.config.ts +0 -0
- {dirsql-0.3.71 → dirsql-0.3.72}/packages/python/docs/pnpm-lock.yaml +0 -0
- {dirsql-0.3.71 → dirsql-0.3.72}/packages/python/docs/pnpm-workspace.yaml +0 -0
- {dirsql-0.3.71 → dirsql-0.3.72}/packages/python/docs/reference/cli.md +0 -0
- {dirsql-0.3.71 → dirsql-0.3.72}/packages/python/docs/reference/columns.md +0 -0
- {dirsql-0.3.71 → dirsql-0.3.72}/packages/python/docs/reference/config.md +0 -0
- {dirsql-0.3.71 → dirsql-0.3.72}/packages/python/docs/reference/hooks.md +0 -0
- {dirsql-0.3.71 → dirsql-0.3.72}/packages/python/docs/reference/http-api.md +0 -0
- {dirsql-0.3.71 → dirsql-0.3.72}/packages/python/docs/reference/sdk.md +0 -0
- {dirsql-0.3.71 → dirsql-0.3.72}/packages/python/docs/tests/integration/home.spec.ts +0 -0
- {dirsql-0.3.71 → dirsql-0.3.72}/packages/python/docs/tests/integration/language-flag.spec.ts +0 -0
- {dirsql-0.3.71 → dirsql-0.3.72}/packages/python/docs/tests/integration/sidebar.spec.ts +0 -0
- {dirsql-0.3.71 → dirsql-0.3.72}/packages/python/docs/tests/unit/config.test.ts +0 -0
- {dirsql-0.3.71 → dirsql-0.3.72}/packages/python/docs/tests/unit/lang.test.ts +0 -0
- {dirsql-0.3.71 → dirsql-0.3.72}/packages/python/docs/vitest.config.ts +0 -0
- {dirsql-0.3.71 → dirsql-0.3.72}/packages/python/tests/__init__.py +0 -0
- {dirsql-0.3.71 → dirsql-0.3.72}/packages/python/tests/binding/__init__.py +0 -0
- {dirsql-0.3.71 → dirsql-0.3.72}/packages/python/tests/e2e/__init__.py +0 -0
- {dirsql-0.3.71 → dirsql-0.3.72}/packages/python/tests/integration/__init__.py +0 -0
- {dirsql-0.3.71 → dirsql-0.3.72}/packages/python/tests/smoke/__init__.py +0 -0
- {dirsql-0.3.71 → dirsql-0.3.72}/packages/rust/Cargo.toml +0 -0
- {dirsql-0.3.71 → dirsql-0.3.72}/packages/rust/README.md +0 -0
- {dirsql-0.3.71 → dirsql-0.3.72}/packages/rust/benches/db_bench.rs +0 -0
- {dirsql-0.3.71 → dirsql-0.3.72}/packages/rust/benches/matcher_bench.rs +0 -0
- {dirsql-0.3.71 → dirsql-0.3.72}/packages/rust/docs/explanation.md +0 -0
- {dirsql-0.3.71 → dirsql-0.3.72}/packages/rust/docs/getting-started.md +0 -0
- {dirsql-0.3.71 → dirsql-0.3.72}/packages/rust/docs/howto/columns-from-paths.md +0 -0
- {dirsql-0.3.71 → dirsql-0.3.72}/packages/rust/docs/howto/define-tables.md +0 -0
- {dirsql-0.3.71 → dirsql-0.3.72}/packages/rust/docs/howto/embed.md +0 -0
- {dirsql-0.3.71 → dirsql-0.3.72}/packages/rust/docs/howto/extract-from-contents.md +0 -0
- {dirsql-0.3.71 → dirsql-0.3.72}/packages/rust/docs/howto/load-extension.md +0 -0
- {dirsql-0.3.71 → dirsql-0.3.72}/packages/rust/docs/howto/persist.md +0 -0
- {dirsql-0.3.71 → dirsql-0.3.72}/packages/rust/docs/howto/react-to-changes.md +0 -0
- {dirsql-0.3.71 → dirsql-0.3.72}/packages/rust/docs/howto/search-by-meaning.md +0 -0
- {dirsql-0.3.71 → dirsql-0.3.72}/packages/rust/docs/howto/skip-files.md +0 -0
- {dirsql-0.3.71 → dirsql-0.3.72}/packages/rust/docs/index.md +0 -0
- {dirsql-0.3.71 → dirsql-0.3.72}/packages/rust/docs/migrations.md +0 -0
- {dirsql-0.3.71 → dirsql-0.3.72}/packages/rust/docs/reference/cli.md +0 -0
- {dirsql-0.3.71 → dirsql-0.3.72}/packages/rust/docs/reference/columns.md +0 -0
- {dirsql-0.3.71 → dirsql-0.3.72}/packages/rust/docs/reference/config.md +0 -0
- {dirsql-0.3.71 → dirsql-0.3.72}/packages/rust/docs/reference/hooks.md +0 -0
- {dirsql-0.3.71 → dirsql-0.3.72}/packages/rust/docs/reference/http-api.md +0 -0
- {dirsql-0.3.71 → dirsql-0.3.72}/packages/rust/docs/reference/sdk.md +0 -0
- {dirsql-0.3.71 → dirsql-0.3.72}/packages/rust/src/cli/router.rs +0 -0
- {dirsql-0.3.71 → dirsql-0.3.72}/pyproject.toml +0 -0
|
@@ -59,7 +59,7 @@ class DirSQL:
|
|
|
59
59
|
at startup. Any ``[[dirsql.extension]]`` entries in a ``config`` file are
|
|
60
60
|
appended after the programmatic ones. A ``path`` (programmatic or
|
|
61
61
|
config-file) may be a bare **package name**, resolved from the installed
|
|
62
|
-
package in the runtime env
|
|
62
|
+
package in the runtime env.
|
|
63
63
|
"""
|
|
64
64
|
|
|
65
65
|
def __init__(
|
|
@@ -98,15 +98,13 @@ class DirSQL:
|
|
|
98
98
|
"""Resolve extensions and construct the Rust-backed instance.
|
|
99
99
|
|
|
100
100
|
Runs on a worker thread (via ``asyncio.to_thread``): both the
|
|
101
|
-
package-name resolution
|
|
102
|
-
core's initial scan are blocking work.
|
|
101
|
+
package-name resolution and the core's initial scan are blocking.
|
|
103
102
|
|
|
104
103
|
When the ``config`` file names an extension by bare package name, the
|
|
105
104
|
SDK resolves every one of the config's ``[[dirsql.extension]]`` entries
|
|
106
|
-
itself
|
|
107
|
-
core's
|
|
108
|
-
|
|
109
|
-
sees the unresolvable bare name).
|
|
105
|
+
itself -- appended after the programmatic ones -- and suppresses the
|
|
106
|
+
core's own config-extension loading so the entries are not loaded a
|
|
107
|
+
second time (the core cannot resolve a bare name).
|
|
110
108
|
"""
|
|
111
109
|
extensions = self._resolved_extensions()
|
|
112
110
|
suppress = False
|
|
@@ -129,9 +127,8 @@ class DirSQL:
|
|
|
129
127
|
def _resolved_extensions(self):
|
|
130
128
|
"""Resolve each programmatic extension's ``path`` to a loadable file.
|
|
131
129
|
|
|
132
|
-
A bare package name is resolved to the loadable installed in the
|
|
133
|
-
env
|
|
134
|
-
the Rust builder, which takes programmatic paths as-is). Config-file
|
|
130
|
+
A bare package name is resolved to the loadable installed in the
|
|
131
|
+
runtime env; path-looking values pass through verbatim. Config-file
|
|
135
132
|
``[[dirsql.extension]]`` entries are handled by ``_build_db``.
|
|
136
133
|
"""
|
|
137
134
|
if not self._extensions:
|
|
@@ -24,7 +24,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
24
24
|
return 1
|
|
25
25
|
|
|
26
26
|
# Resolve any package-name extensions in a TOML config here (the binary
|
|
27
|
-
# can't) and pass them as `--extension` flags; a no-op otherwise
|
|
27
|
+
# can't) and pass them as `--extension` flags; a no-op otherwise.
|
|
28
28
|
try:
|
|
29
29
|
argv = with_resolved_extensions(argv)
|
|
30
30
|
except Exception as exc:
|
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
"""Launcher-side resolution of a TOML config's ``[[dirsql.extension]]`` entries.
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
loads those and ignores the config's own extension entries (the Rust
|
|
11
|
-
``--extension`` flag / ``suppress_config_extensions``).
|
|
3
|
+
The compiled ``dirsql`` binary loads a config's extensions literally -- it
|
|
4
|
+
has no ``importlib``, so it cannot resolve a bare **package name**. When a
|
|
5
|
+
TOML config names an extension by package name, the shared SDK resolver
|
|
6
|
+
(:mod:`dirsql.resolve_config_extensions`) resolves every one of its
|
|
7
|
+
extensions and this launcher passes the resolved literal paths to the binary
|
|
8
|
+
via repeatable ``--extension`` flags; the binary then loads those and ignores
|
|
9
|
+
the config's own extension entries.
|
|
12
10
|
|
|
13
11
|
Native-language configs (``.py`` / ``.js`` / ``.mjs`` / ``.cjs``) are untouched:
|
|
14
12
|
the binary dispatches those to ``dirsql interpret``, whose handshake already
|
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
"""SDK-side resolution of a TOML config's ``[[dirsql.extension]]`` entries.
|
|
2
2
|
|
|
3
|
-
The Rust core
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
config's entries are not loaded a second time.
|
|
3
|
+
The Rust core loads a config's extensions literally -- it has no
|
|
4
|
+
``importlib``, so it cannot resolve a bare **package name**. When a TOML
|
|
5
|
+
config names an extension by package name, the SDK resolves every one of its
|
|
6
|
+
extensions here, hands the core the resolved literal paths, and suppresses
|
|
7
|
+
the core's own config-extension loading (``suppress_config_extensions``) so
|
|
8
|
+
the config's entries are not loaded a second time.
|
|
10
9
|
|
|
11
10
|
Shared by the ``DirSQL`` constructor (``config=`` path) and the CLI launcher
|
|
12
|
-
(
|
|
13
|
-
into ``--extension`` flags for the binary).
|
|
11
|
+
(which converts the resolved specs into ``--extension`` flags).
|
|
14
12
|
"""
|
|
15
13
|
|
|
16
14
|
from __future__ import annotations
|
|
@@ -45,9 +43,7 @@ def resolve_config_extension_specs(config_path):
|
|
|
45
43
|
entries = cfg.get("extension")
|
|
46
44
|
if not isinstance(entries, list):
|
|
47
45
|
return None
|
|
48
|
-
# Only intervene when at least one path is a bare package name
|
|
49
|
-
# with only literal paths (or no entries at all) keeps the core's existing
|
|
50
|
-
# behavior untouched.
|
|
46
|
+
# Only intervene when at least one path is a bare package name.
|
|
51
47
|
if not any(
|
|
52
48
|
isinstance(e, dict)
|
|
53
49
|
and isinstance(e.get("path"), str)
|
|
@@ -1,23 +1,17 @@
|
|
|
1
1
|
"""Resolve an extension entry's ``path`` to a concrete loadable file.
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
name
|
|
5
|
-
names a package installed in the runtime env, and dirsql discovers the loadable
|
|
6
|
-
file *inside* that package.
|
|
7
|
-
|
|
8
|
-
Resolution is an ordered probe (file-first, then package), so every literal
|
|
9
|
-
path from #225 keeps its old behavior and only a bare name reaches the package
|
|
10
|
-
machinery:
|
|
3
|
+
Resolution is an ordered probe (file-first, then package), so only a bare
|
|
4
|
+
package name reaches the package machinery:
|
|
11
5
|
|
|
12
6
|
1. **Path-looking** (contains a separator, or ends in ``.so`` / ``.dylib`` /
|
|
13
7
|
``.dll`` / ``.pyd``) -- returned as a file path: made absolute against
|
|
14
8
|
``base`` when ``resolve_relative`` is set (config-file entries), else
|
|
15
|
-
verbatim (programmatic entries
|
|
9
|
+
verbatim (programmatic entries).
|
|
16
10
|
2. **Bare name** -- a same-named local file under ``base`` *shadows* the
|
|
17
|
-
package
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
11
|
+
package; otherwise the package dir is located via
|
|
12
|
+
:func:`importlib.util.find_spec` and the current platform's loadable is
|
|
13
|
+
globbed from inside it. Zero matches and multiple matches are both hard
|
|
14
|
+
errors -- the caller must disambiguate with a literal path.
|
|
21
15
|
"""
|
|
22
16
|
|
|
23
17
|
import glob as _glob
|
|
@@ -25,9 +19,8 @@ import importlib.util
|
|
|
25
19
|
import os
|
|
26
20
|
import sys
|
|
27
21
|
|
|
28
|
-
# Suffixes that mark a value as "already a file path"
|
|
29
|
-
# never attempted
|
|
30
|
-
# a package directory.
|
|
22
|
+
# Suffixes that mark a value as "already a file path", so package resolution
|
|
23
|
+
# is never attempted.
|
|
31
24
|
_LOADABLE_SUFFIXES = (".so", ".dylib", ".dll", ".pyd")
|
|
32
25
|
|
|
33
26
|
|
|
@@ -73,8 +66,6 @@ def _resolve_package(name):
|
|
|
73
66
|
matches.update(_glob.glob(os.path.join(d, "**", pat), recursive=True))
|
|
74
67
|
found = sorted(matches)
|
|
75
68
|
|
|
76
|
-
# Exactly one loadable resolves; unpacking (rather than a guarded
|
|
77
|
-
# subscript) makes the zero- and multiple-match failures each observable.
|
|
78
69
|
try:
|
|
79
70
|
(single,) = found
|
|
80
71
|
except ValueError:
|
|
@@ -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.72"
|
|
8
8
|
edition.workspace = true
|
|
9
9
|
publish = false
|
|
10
10
|
readme = "README.md"
|
|
@@ -1,15 +1,9 @@
|
|
|
1
1
|
"""Stub the compiled `_dirsql` extension for unit tests that don't need it.
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
imports real types from `dirsql._dirsql`.
|
|
8
|
-
|
|
9
|
-
The stub is installed ONLY when the real extension can't be imported.
|
|
10
|
-
When `maturin develop` has been run (as in CI), the real extension wins
|
|
11
|
-
and tests that depend on it — `_async_test.py`, the integration suites —
|
|
12
|
-
see the real bindings.
|
|
3
|
+
Must live here (not in a test file) so the stub is in place before pytest
|
|
4
|
+
imports `dirsql.__init__`, which imports real types from `dirsql._dirsql`.
|
|
5
|
+
Installed only when the real extension can't be imported; when `maturin
|
|
6
|
+
develop` has been run, the real bindings win.
|
|
13
7
|
"""
|
|
14
8
|
|
|
15
9
|
from __future__ import annotations
|
|
@@ -20,8 +20,6 @@ mod python {
|
|
|
20
20
|
use std::path::PathBuf;
|
|
21
21
|
use std::time::Duration;
|
|
22
22
|
|
|
23
|
-
// -- Public PyO3 classes ------------------------------------------------
|
|
24
|
-
|
|
25
23
|
/// A table definition. Mirrors `dirsql::Table` but holds a Python
|
|
26
24
|
/// callable for `extract`.
|
|
27
25
|
#[pyclass(name = "Table", frozen)]
|
|
@@ -34,15 +32,11 @@ mod python {
|
|
|
34
32
|
extract: Py<PyAny>,
|
|
35
33
|
#[pyo3(get)]
|
|
36
34
|
strict: bool,
|
|
37
|
-
/// Parsed table name (from `ddl`)
|
|
38
|
-
/// match `CREATE TABLE <name> (...)`.
|
|
39
|
-
///
|
|
40
|
-
///
|
|
41
|
-
///
|
|
42
|
-
/// rather than raising at construction so `DirSQL.ready()` keeps
|
|
43
|
-
/// surfacing malformed DDLs as the loud failure path (the Rust
|
|
44
|
-
/// core's `DirSqlError::Ddl`); callers that care about the name
|
|
45
|
-
/// before that point can check `t.name is None` themselves.
|
|
35
|
+
/// Parsed table name (from `ddl`) via `dirsql::db::parse_table_name`,
|
|
36
|
+
/// or `None` if the DDL doesn't match `CREATE TABLE <name> (...)`.
|
|
37
|
+
/// `None` rather than a construction error so `DirSQL.ready()` keeps
|
|
38
|
+
/// surfacing malformed DDLs as the loud failure path (the core's
|
|
39
|
+
/// `DirSqlError::Ddl`).
|
|
46
40
|
#[pyo3(get)]
|
|
47
41
|
name: Option<String>,
|
|
48
42
|
}
|
|
@@ -64,11 +58,9 @@ mod python {
|
|
|
64
58
|
}
|
|
65
59
|
|
|
66
60
|
/// Marshals a Python `{"path": str, "entrypoint"?: str}` mapping from the
|
|
67
|
-
/// `extensions=` constructor argument into a [`dirsql::Extension`].
|
|
68
|
-
/// the
|
|
69
|
-
///
|
|
70
|
-
/// programmatic surface does not resolve relative paths), matching
|
|
71
|
-
/// `DirSQLBuilder::extensions`.
|
|
61
|
+
/// `extensions=` constructor argument into a [`dirsql::Extension`]. Paths
|
|
62
|
+
/// are taken verbatim; the programmatic surface does not resolve relative
|
|
63
|
+
/// paths.
|
|
72
64
|
#[derive(FromPyObject)]
|
|
73
65
|
struct PyExtensionSpec {
|
|
74
66
|
#[pyo3(item)]
|
|
@@ -117,8 +109,8 @@ mod python {
|
|
|
117
109
|
/// `suppress_config_extensions` skips the core's own loading of the
|
|
118
110
|
/// config's `[[dirsql.extension]]` entries; the SDK sets it after
|
|
119
111
|
/// resolving those entries itself (package names need `importlib`,
|
|
120
|
-
/// which the core lacks
|
|
121
|
-
///
|
|
112
|
+
/// which the core lacks) and passing the resolved literal paths via
|
|
113
|
+
/// `extensions`, so the entries are not loaded twice.
|
|
122
114
|
#[new]
|
|
123
115
|
#[pyo3(signature = (root=None, *, tables=None, ignore=None, config=None, persist=false, persist_path=None, extensions=None, suppress_config_extensions=false))]
|
|
124
116
|
fn new(
|
|
@@ -205,8 +197,6 @@ mod python {
|
|
|
205
197
|
}
|
|
206
198
|
}
|
|
207
199
|
|
|
208
|
-
// -- Helpers ------------------------------------------------------------
|
|
209
|
-
|
|
210
200
|
fn build_table(py: Python<'_>, t: &PyTable) -> Table {
|
|
211
201
|
let extract_ref = t.extract.clone_ref(py);
|
|
212
202
|
let mut table = Table::try_new(
|
|
@@ -260,10 +250,7 @@ mod python {
|
|
|
260
250
|
/// Pure, GIL-free intermediate for a row event. [`row_event_to_plain`]
|
|
261
251
|
/// builds it from a core [`RowEvent`] (unit-testable without a Python
|
|
262
252
|
/// interpreter); [`row_event_to_py`] then marshals it into the
|
|
263
|
-
/// Python-facing [`PyRowEvent`] (the GIL step).
|
|
264
|
-
/// variant -> action / field-selection mapping testable at the unit tier,
|
|
265
|
-
/// mirroring the napi binding's pure `row_event_to_js`. The value-level
|
|
266
|
-
/// `Row -> PyDict` conversion stays GIL-bound (binding-tier covered).
|
|
253
|
+
/// Python-facing [`PyRowEvent`] (the GIL step).
|
|
267
254
|
struct PlainRowEvent {
|
|
268
255
|
table: Option<String>,
|
|
269
256
|
action: &'static str,
|
|
@@ -406,8 +393,6 @@ mod python {
|
|
|
406
393
|
}
|
|
407
394
|
}
|
|
408
395
|
|
|
409
|
-
// -- Module registration ------------------------------------------------
|
|
410
|
-
|
|
411
396
|
#[pymodule]
|
|
412
397
|
#[pyo3(name = "_dirsql")]
|
|
413
398
|
fn py_dirsql_module(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
|
@@ -15,7 +15,6 @@ def tmp_dir():
|
|
|
15
15
|
@pytest.fixture
|
|
16
16
|
def jsonl_dir(tmp_dir):
|
|
17
17
|
"""Create a temp dir with JSONL files for testing."""
|
|
18
|
-
# Create a simple JSONL file
|
|
19
18
|
os.makedirs(os.path.join(tmp_dir, "comments", "abc"), exist_ok=True)
|
|
20
19
|
os.makedirs(os.path.join(tmp_dir, "comments", "def"), exist_ok=True)
|
|
21
20
|
|
|
@@ -46,7 +46,6 @@ fn bench_diff_single_line_change(c: &mut Criterion) {
|
|
|
46
46
|
for count in [10, 100, 1000] {
|
|
47
47
|
let old = make_rows(count);
|
|
48
48
|
let mut new = old.clone();
|
|
49
|
-
// Change one row in the middle
|
|
50
49
|
new[count / 2].insert("name".to_string(), Value::Text("CHANGED".to_string()));
|
|
51
50
|
group.bench_with_input(BenchmarkId::from_parameter(count), &count, |b, _| {
|
|
52
51
|
b.iter(|| {
|
|
@@ -62,7 +61,6 @@ fn bench_diff_append(c: &mut Criterion) {
|
|
|
62
61
|
for count in [10, 100, 1000] {
|
|
63
62
|
let old = make_rows(count);
|
|
64
63
|
let mut new = old.clone();
|
|
65
|
-
// Append 10% more rows
|
|
66
64
|
let extra = make_rows(count + count / 10);
|
|
67
65
|
new.extend_from_slice(&extra[count..]);
|
|
68
66
|
group.bench_with_input(BenchmarkId::from_parameter(count), &count, |b, _| {
|
|
@@ -78,7 +76,6 @@ fn bench_diff_full_replace(c: &mut Criterion) {
|
|
|
78
76
|
let mut group = c.benchmark_group("differ/full_replace");
|
|
79
77
|
for count in [10, 100, 1000] {
|
|
80
78
|
let old = make_rows(count);
|
|
81
|
-
// Completely different rows trigger full replace
|
|
82
79
|
let new: Vec<HashMap<String, Value>> = (0..count)
|
|
83
80
|
.map(|i| {
|
|
84
81
|
HashMap::from([
|
|
@@ -7,7 +7,6 @@ use tempfile::TempDir;
|
|
|
7
7
|
fn create_temp_tree(file_count: usize) -> TempDir {
|
|
8
8
|
let dir = TempDir::new().unwrap();
|
|
9
9
|
for i in 0..file_count {
|
|
10
|
-
// Distribute files across subdirectories
|
|
11
10
|
let subdir = dir.path().join(format!("dir_{}", i % 10));
|
|
12
11
|
fs::create_dir_all(&subdir).unwrap();
|
|
13
12
|
fs::write(
|
|
@@ -16,7 +15,6 @@ fn create_temp_tree(file_count: usize) -> TempDir {
|
|
|
16
15
|
)
|
|
17
16
|
.unwrap();
|
|
18
17
|
}
|
|
19
|
-
// Add some non-matching files
|
|
20
18
|
for i in 0..file_count / 5 {
|
|
21
19
|
fs::write(dir.path().join(format!("readme_{i}.md")), "# not matched").unwrap();
|
|
22
20
|
}
|
|
@@ -53,8 +53,8 @@ struct Cli {
|
|
|
53
53
|
///
|
|
54
54
|
/// Intended for the language launcher (pip/npm), not end users: the
|
|
55
55
|
/// launcher resolves config extensions — including bare **package names**,
|
|
56
|
-
/// which need an interpreter this compiled binary lacks
|
|
57
|
-
///
|
|
56
|
+
/// which need an interpreter this compiled binary lacks — and passes the
|
|
57
|
+
/// resolved literal paths here. When any are present, the TOML
|
|
58
58
|
/// config's own extension entries are not loaded (the launcher already
|
|
59
59
|
/// merged and resolved them). Used by server mode and by the `query`
|
|
60
60
|
/// subcommand.
|
|
@@ -198,7 +198,6 @@ async fn run_server(cli: Cli) -> ExitCode {
|
|
|
198
198
|
// Echo back the user-facing hostname (not the resolved IP SocketAddr).
|
|
199
199
|
println!("Running at {host}:{}", handle.local_addr().port());
|
|
200
200
|
|
|
201
|
-
// Await ctrl-c / SIGTERM; then drain.
|
|
202
201
|
if let Err(err) = wait_for_shutdown().await {
|
|
203
202
|
eprintln!("dirsql: signal handler error: {err}");
|
|
204
203
|
}
|
|
@@ -233,9 +232,9 @@ fn load_state(cli: &Cli) -> AppState {
|
|
|
233
232
|
|
|
234
233
|
// Launcher-resolved extensions (`--extension`) override the TOML config's
|
|
235
234
|
// own `[[dirsql.extension]]` entries: the launcher has already merged and
|
|
236
|
-
// resolved them (including package names the compiled binary can't
|
|
237
|
-
//
|
|
238
|
-
//
|
|
235
|
+
// resolved them (including package names the compiled binary can't
|
|
236
|
+
// resolve), so suppress the config's extension loading and supply the
|
|
237
|
+
// resolved literal paths instead.
|
|
239
238
|
let build = if cli.extension.is_empty() {
|
|
240
239
|
DirSQL::from_config_path(&resolved)
|
|
241
240
|
} else {
|
|
@@ -428,10 +427,6 @@ mod tests {
|
|
|
428
427
|
|
|
429
428
|
#[test]
|
|
430
429
|
fn default_files_table_declares_filesystem_fact_columns_over_recursive_glob() {
|
|
431
|
-
// The zero-config fallback table is pure data: a fixed DDL naming only
|
|
432
|
-
// the auto-injected filesystem-fact columns and a `**/*` glob that
|
|
433
|
-
// matches every file at any depth. The extract closure is never
|
|
434
|
-
// invoked here, so this stays a pure unit test.
|
|
435
430
|
let table = default_files_table();
|
|
436
431
|
assert_eq!(table.glob, "**/*");
|
|
437
432
|
assert!(table.ddl.starts_with("CREATE TABLE files ("));
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
//! the `pre-query` hook, the query timeout, [`DirSQL::query`], row
|
|
5
5
|
//! serialization, the `post-query` hook, and error classification —
|
|
6
6
|
//! lives here exactly once. The HTTP handler and the one-shot
|
|
7
|
-
//! `dirsql query` subcommand
|
|
7
|
+
//! `dirsql query` subcommand are thin transport adapters over
|
|
8
8
|
//! [`execute_query`], so the two surfaces cannot drift behaviorally:
|
|
9
9
|
//! per-surface code only maps [`QueryFailure`] to a status code or an
|
|
10
10
|
//! exit code.
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
//!
|
|
10
10
|
//! Failure modes:
|
|
11
11
|
//! - Output already exists and `--force` was not passed: bail before
|
|
12
|
-
//! spawning the agent (
|
|
12
|
+
//! spawning the agent (never burn a paid LLM call first).
|
|
13
13
|
//! - `claude` is not on PATH: surface a descriptive error pointing at
|
|
14
14
|
//! the install docs.
|
|
15
15
|
//! - `claude` exits non-zero: surface its stderr; do not write any
|
|
@@ -150,8 +150,7 @@ mod tests {
|
|
|
150
150
|
assert!(msg.contains("claude"));
|
|
151
151
|
}
|
|
152
152
|
|
|
153
|
-
///
|
|
154
|
-
/// `--force` was not passed (the temp dir itself is an existing path).
|
|
153
|
+
/// The temp dir itself serves as the already-existing output path.
|
|
155
154
|
#[test]
|
|
156
155
|
fn run_bails_when_output_exists_without_force() {
|
|
157
156
|
let dir = tempfile::tempdir().unwrap();
|
|
@@ -167,10 +166,9 @@ mod tests {
|
|
|
167
166
|
);
|
|
168
167
|
}
|
|
169
168
|
|
|
170
|
-
// The spawn / non-zero-exit / write failure arms of `run`
|
|
171
|
-
//
|
|
172
|
-
//
|
|
173
|
-
// here (the values are constructed inline, no process or fs I/O).
|
|
169
|
+
// The spawn / non-zero-exit / write failure arms of `run` need a real
|
|
170
|
+
// `claude` and filesystem, so they live at the e2e tier; only their pure
|
|
171
|
+
// `Display` renderings are covered here.
|
|
174
172
|
|
|
175
173
|
#[test]
|
|
176
174
|
fn spawn_error_display_mentions_spawn() {
|
|
@@ -196,8 +194,6 @@ mod tests {
|
|
|
196
194
|
|
|
197
195
|
#[test]
|
|
198
196
|
fn invalid_utf8_error_display_mentions_utf8() {
|
|
199
|
-
// A non-UTF8 byte sequence yields the `FromUtf8Error` that `#[from]`
|
|
200
|
-
// converts into `InitError::InvalidUtf8`.
|
|
201
197
|
let utf8_err = String::from_utf8(vec![0xff, 0xfe]).unwrap_err();
|
|
202
198
|
let err: InitError = utf8_err.into();
|
|
203
199
|
let msg = format!("{err}");
|
|
@@ -39,10 +39,6 @@ pub mod server;
|
|
|
39
39
|
|
|
40
40
|
pub use server::{serve, serve_with_state};
|
|
41
41
|
|
|
42
|
-
// ---------------------------------------------------------------------------
|
|
43
|
-
// Public types
|
|
44
|
-
// ---------------------------------------------------------------------------
|
|
45
|
-
|
|
46
42
|
/// A server-wide `pre-query` command hook, carrying the command template plus
|
|
47
43
|
/// the directory it runs in (the config file's parent). When set on a
|
|
48
44
|
/// [`ServerConfig`], the server passes each `POST /query` request body to the
|
|
@@ -57,7 +53,7 @@ pub struct PreQuery {
|
|
|
57
53
|
pub config_dir: PathBuf,
|
|
58
54
|
/// Per-run timeout. Defaults to the shared 30-second
|
|
59
55
|
/// [`DEFAULT_COMMAND_TIMEOUT`]; override it via [`Self::with_timeout`]
|
|
60
|
-
/// (the CLI wires the global `[dirsql].hook-timeout` here
|
|
56
|
+
/// (the CLI wires the global `[dirsql].hook-timeout` here).
|
|
61
57
|
pub timeout: Duration,
|
|
62
58
|
}
|
|
63
59
|
|
|
@@ -95,7 +91,7 @@ pub struct PostQuery {
|
|
|
95
91
|
pub config_dir: PathBuf,
|
|
96
92
|
/// Per-run timeout. Defaults to the shared 30-second
|
|
97
93
|
/// [`DEFAULT_COMMAND_TIMEOUT`]; override it via [`Self::with_timeout`]
|
|
98
|
-
/// (the CLI wires the global `[dirsql].hook-timeout` here
|
|
94
|
+
/// (the CLI wires the global `[dirsql].hook-timeout` here).
|
|
99
95
|
pub timeout: Duration,
|
|
100
96
|
}
|
|
101
97
|
|
|
@@ -202,10 +198,8 @@ impl From<DirSQL> for AppState {
|
|
|
202
198
|
}
|
|
203
199
|
}
|
|
204
200
|
|
|
205
|
-
///
|
|
206
|
-
///
|
|
207
|
-
/// instead of typing the variant name. The string is the diagnostic that
|
|
208
|
-
/// `/query` and `/events` echo back as a 503 body.
|
|
201
|
+
/// The string is the diagnostic that `/query` and `/events` echo back as a
|
|
202
|
+
/// 503 body.
|
|
209
203
|
impl From<String> for AppState {
|
|
210
204
|
fn from(reason: String) -> Self {
|
|
211
205
|
Self::Unavailable(reason)
|
|
@@ -266,14 +260,9 @@ pub enum ServerError {
|
|
|
266
260
|
mod tests {
|
|
267
261
|
use super::*;
|
|
268
262
|
|
|
269
|
-
// `From<DirSQL> for AppState`
|
|
270
|
-
//
|
|
271
|
-
//
|
|
272
|
-
// temp directory (so the initial scan runs) and asserts the public
|
|
273
|
-
// `AppState::Ready` variant. It lived here once but needed
|
|
274
|
-
// `std::fs::write` to populate the scanned directory, which the
|
|
275
|
-
// `testing-conventions` `unit lint` isolation rule forbids in a unit
|
|
276
|
-
// test (effectful std). The pure config-default test below stays inline.
|
|
263
|
+
// `From<DirSQL> for AppState` needs a real scanned directory, so its
|
|
264
|
+
// `Ready`-arm test lives in `tests/cli_integration.rs` (unit-lint
|
|
265
|
+
// isolation forbids the fs setup here).
|
|
277
266
|
|
|
278
267
|
#[test]
|
|
279
268
|
fn default_config_binds_localhost_7117_with_30s_timeout() {
|
|
@@ -287,8 +276,6 @@ mod tests {
|
|
|
287
276
|
|
|
288
277
|
#[test]
|
|
289
278
|
fn pre_query_constructor_carries_command_and_dir() {
|
|
290
|
-
// `PreQuery::new` is pure data plumbing: the command template and the
|
|
291
|
-
// working directory it will run in, with the shared default timeout.
|
|
292
279
|
let pq = PreQuery::new("to_sql.py {args}", "/proj");
|
|
293
280
|
assert_eq!(pq.command, "to_sql.py {args}");
|
|
294
281
|
assert_eq!(pq.config_dir, PathBuf::from("/proj"));
|
|
@@ -311,8 +298,6 @@ mod tests {
|
|
|
311
298
|
|
|
312
299
|
#[test]
|
|
313
300
|
fn post_query_constructor_carries_command_and_dir() {
|
|
314
|
-
// `PostQuery::new` is pure data plumbing: the command template and the
|
|
315
|
-
// working directory it will run in, with the shared default timeout.
|
|
316
301
|
let pq = PostQuery::new("jq '{results: .}'", "/proj");
|
|
317
302
|
assert_eq!(pq.command, "jq '{results: .}'");
|
|
318
303
|
assert_eq!(pq.config_dir, PathBuf::from("/proj"));
|
|
@@ -336,8 +321,6 @@ mod tests {
|
|
|
336
321
|
|
|
337
322
|
#[test]
|
|
338
323
|
fn with_query_timeout_overrides_the_default() {
|
|
339
|
-
// The builder replaces the 30s default with the supplied timeout and
|
|
340
|
-
// leaves the rest of the config untouched.
|
|
341
324
|
let cfg = ServerConfig::bind("127.0.0.1", 8080).with_query_timeout(Duration::from_secs(5));
|
|
342
325
|
assert_eq!(cfg.query_timeout, Duration::from_secs(5));
|
|
343
326
|
assert_eq!(cfg.host, "127.0.0.1");
|
|
@@ -346,9 +329,7 @@ mod tests {
|
|
|
346
329
|
|
|
347
330
|
#[test]
|
|
348
331
|
fn app_state_from_string_builds_the_unavailable_arm() {
|
|
349
|
-
// `
|
|
350
|
-
// verbatim (the `/query`/`/events` 503 body). `AppState` isn't `Debug`,
|
|
351
|
-
// so match rather than assert on a rendering.
|
|
332
|
+
// `AppState` isn't `Debug`, so match instead of asserting on a rendering.
|
|
352
333
|
let state: AppState = "config failed to load".to_string().into();
|
|
353
334
|
match state {
|
|
354
335
|
AppState::Unavailable(reason) => assert_eq!(reason, "config failed to load"),
|
|
@@ -30,8 +30,7 @@ fn cell_to_json(value: &CellValue) -> Value {
|
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
/// Hex-encode a byte slice
|
|
34
|
-
/// output. Lightweight; pulls in no dep just to emit hex.
|
|
33
|
+
/// Hex-encode a byte slice, for `BLOB` SQLite values in JSON output.
|
|
35
34
|
fn hex_encode(bytes: &[u8]) -> String {
|
|
36
35
|
let mut out = String::with_capacity(bytes.len() * 2);
|
|
37
36
|
for b in bytes {
|
|
@@ -93,10 +92,6 @@ fn event_to_value(event: &RowEvent) -> Value {
|
|
|
93
92
|
}
|
|
94
93
|
}
|
|
95
94
|
|
|
96
|
-
// ---------------------------------------------------------------------------
|
|
97
|
-
// Unit tests
|
|
98
|
-
// ---------------------------------------------------------------------------
|
|
99
|
-
|
|
100
95
|
#[cfg(test)]
|
|
101
96
|
mod tests {
|
|
102
97
|
use super::*;
|
|
@@ -130,7 +125,6 @@ mod tests {
|
|
|
130
125
|
Some("posts/a.json"),
|
|
131
126
|
);
|
|
132
127
|
assert!(parsed.get("old_row").unwrap().is_null());
|
|
133
|
-
// event_to_json is the string form of the value it builds.
|
|
134
128
|
assert_eq!(event_to_json(&event), parsed.to_string());
|
|
135
129
|
}
|
|
136
130
|
|
|
@@ -202,8 +196,6 @@ mod tests {
|
|
|
202
196
|
|
|
203
197
|
#[test]
|
|
204
198
|
fn blob_cell_becomes_hex_string() {
|
|
205
|
-
// The Blob arm of `cell_to_json` hex-encodes the bytes into a JSON
|
|
206
|
-
// string (BLOB columns aren't representable as JSON literals).
|
|
207
199
|
let json = cell_to_json(&CellValue::Blob(vec![0xde, 0xad, 0xbe, 0xef]));
|
|
208
200
|
assert_eq!(json.as_str(), Some("deadbeef"));
|
|
209
201
|
}
|
|
@@ -96,9 +96,8 @@ fn start_watch_task(db: DirSQL, tx: broadcast::Sender<String>) {
|
|
|
96
96
|
mod tests {
|
|
97
97
|
use super::*;
|
|
98
98
|
|
|
99
|
-
//
|
|
100
|
-
//
|
|
101
|
-
// without standing up a full index. Port 0 lets the OS pick a free port.
|
|
99
|
+
// An `Unavailable` state needs no DB/filesystem, so the real bind /
|
|
100
|
+
// graceful-shutdown plumbing runs without standing up an index.
|
|
102
101
|
#[tokio::test]
|
|
103
102
|
async fn serve_with_state_binds_an_ephemeral_port_then_shuts_down() {
|
|
104
103
|
let config = ServerConfig::bind("127.0.0.1".to_string(), 0);
|
|
@@ -115,10 +114,6 @@ mod tests {
|
|
|
115
114
|
handle.shutdown().await.expect("graceful shutdown");
|
|
116
115
|
}
|
|
117
116
|
|
|
118
|
-
// A `Ready` state drives the `serve` convenience wrapper plus
|
|
119
|
-
// `start_watch_task` (which attaches `DirSQL::watch` and pumps its stream
|
|
120
|
-
// into the broadcast channel). Built over an empty temp dir so the scan
|
|
121
|
-
// touches nothing; the port-0 bind and graceful shutdown are real.
|
|
122
117
|
#[tokio::test]
|
|
123
118
|
async fn serve_with_a_ready_db_attaches_the_watcher_then_shuts_down() {
|
|
124
119
|
let dir = tempfile::tempdir().unwrap();
|