dirsql 0.3.58__tar.gz → 0.3.59__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.58 → dirsql-0.3.59}/Cargo.lock +1 -1
- {dirsql-0.3.58 → dirsql-0.3.59}/PKG-INFO +1 -1
- {dirsql-0.3.58 → dirsql-0.3.59}/docs/cli/config.md +23 -5
- {dirsql-0.3.58 → dirsql-0.3.59}/packages/python/Cargo.toml +1 -1
- {dirsql-0.3.58/packages/rust → dirsql-0.3.59/packages/python}/docs/cli/config.md +23 -5
- {dirsql-0.3.58 → dirsql-0.3.59}/packages/python/e2e-attestation.json +2 -2
- {dirsql-0.3.58/packages/python → dirsql-0.3.59/packages/rust}/docs/cli/config.md +23 -5
- {dirsql-0.3.58 → dirsql-0.3.59}/packages/rust/src/bin/dirsql.rs +14 -4
- {dirsql-0.3.58 → dirsql-0.3.59}/packages/rust/src/cli/mod.rs +45 -4
- {dirsql-0.3.58 → dirsql-0.3.59}/packages/rust/src/cli/router.rs +10 -15
- {dirsql-0.3.58 → dirsql-0.3.59}/packages/rust/src/command.rs +5 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/packages/rust/src/config.rs +118 -15
- {dirsql-0.3.58 → dirsql-0.3.59}/packages/rust/src/lib.rs +27 -10
- {dirsql-0.3.58 → dirsql-0.3.59}/Cargo.toml +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/README.md +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/dirsql/__init__.py +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/dirsql/_async.py +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/dirsql/_dirsql.pyi +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/dirsql/cli/__init__.py +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/dirsql/cli/binary_path.py +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/dirsql/cli/interpret/__init__.py +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/dirsql/cli/is_windows.py +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/dirsql/cli/main.py +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/dirsql/cli/resolve_config_extensions.py +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/dirsql/py.typed +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/dirsql/resolve_config_extensions.py +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/dirsql/resolve_extension.py +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/docs/.claude/CLAUDE.md +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/docs/.vitepress/config.ts +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/docs/.vitepress/theme/index.ts +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/docs/.vitepress/theme/lang.ts +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/docs/AGENTS.md +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/docs/api/index.md +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/docs/cli/http-api.md +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/docs/cli/index.md +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/docs/cli/init.md +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/docs/cli/server.md +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/docs/getting-started.md +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/docs/guide/async.md +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/docs/guide/crdt.md +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/docs/guide/persistence.md +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/docs/guide/querying.md +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/docs/guide/tables.md +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/docs/guide/watching.md +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/docs/index.md +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/docs/migrations.md +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/docs/package.json +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/docs/playwright.config.ts +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/docs/pnpm-lock.yaml +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/docs/pnpm-workspace.yaml +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/docs/tests/integration/home.spec.ts +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/docs/tests/integration/language-flag.spec.ts +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/docs/tests/integration/sidebar.spec.ts +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/docs/tests/unit/config.test.ts +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/docs/tests/unit/lang.test.ts +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/docs/vitest.config.ts +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/packages/python/README.md +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/packages/python/conftest.py +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/packages/python/docs/.claude/CLAUDE.md +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/packages/python/docs/.vitepress/config.ts +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/packages/python/docs/.vitepress/theme/index.ts +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/packages/python/docs/.vitepress/theme/lang.ts +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/packages/python/docs/AGENTS.md +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/packages/python/docs/api/index.md +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/packages/python/docs/cli/http-api.md +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/packages/python/docs/cli/index.md +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/packages/python/docs/cli/init.md +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/packages/python/docs/cli/server.md +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/packages/python/docs/getting-started.md +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/packages/python/docs/guide/async.md +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/packages/python/docs/guide/crdt.md +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/packages/python/docs/guide/persistence.md +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/packages/python/docs/guide/querying.md +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/packages/python/docs/guide/tables.md +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/packages/python/docs/guide/watching.md +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/packages/python/docs/index.md +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/packages/python/docs/migrations.md +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/packages/python/docs/package.json +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/packages/python/docs/playwright.config.ts +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/packages/python/docs/pnpm-lock.yaml +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/packages/python/docs/pnpm-workspace.yaml +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/packages/python/docs/tests/integration/home.spec.ts +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/packages/python/docs/tests/integration/language-flag.spec.ts +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/packages/python/docs/tests/integration/sidebar.spec.ts +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/packages/python/docs/tests/unit/config.test.ts +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/packages/python/docs/tests/unit/lang.test.ts +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/packages/python/docs/vitest.config.ts +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/packages/python/src/lib.rs +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/packages/python/tests/__init__.py +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/packages/python/tests/binding/__init__.py +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/packages/python/tests/conftest.py +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/packages/python/tests/e2e/__init__.py +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/packages/python/tests/integration/__init__.py +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/packages/python/tests/smoke/__init__.py +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/packages/rust/Cargo.toml +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/packages/rust/README.md +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/packages/rust/benches/db_bench.rs +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/packages/rust/benches/differ_bench.rs +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/packages/rust/benches/matcher_bench.rs +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/packages/rust/benches/scanner_bench.rs +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/packages/rust/docs/api/index.md +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/packages/rust/docs/cli/http-api.md +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/packages/rust/docs/cli/index.md +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/packages/rust/docs/cli/init.md +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/packages/rust/docs/cli/server.md +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/packages/rust/docs/getting-started.md +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/packages/rust/docs/guide/async.md +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/packages/rust/docs/guide/crdt.md +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/packages/rust/docs/guide/persistence.md +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/packages/rust/docs/guide/querying.md +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/packages/rust/docs/guide/tables.md +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/packages/rust/docs/guide/watching.md +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/packages/rust/docs/index.md +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/packages/rust/docs/migrations.md +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/packages/rust/src/cli/init.rs +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/packages/rust/src/cli/serialize.rs +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/packages/rust/src/cli/server.rs +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/packages/rust/src/db.rs +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/packages/rust/src/differ.rs +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/packages/rust/src/matcher.rs +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/packages/rust/src/persist.rs +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/packages/rust/src/scanner.rs +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/packages/rust/src/watcher.rs +0 -0
- {dirsql-0.3.58 → dirsql-0.3.59}/pyproject.toml +0 -0
|
@@ -244,6 +244,13 @@ file is skipped (it contributes no rows) and a one-line warning naming the file
|
|
|
244
244
|
and the error is written to stderr. One bad file never aborts the scan; the
|
|
245
245
|
other files' rows are indexed normally.
|
|
246
246
|
|
|
247
|
+
**Timeout.** Each run is bounded by the default 30-second timeout. When a
|
|
248
|
+
command legitimately needs longer — it downloads a model on first run, or calls
|
|
249
|
+
a rate-limited API — raise the bound with the global
|
|
250
|
+
[`[dirsql].hook-timeout`](#command-execution) key (positive whole seconds). A
|
|
251
|
+
run exceeding the bound is killed and handled with the per-file error isolation
|
|
252
|
+
above (the file is skipped with a stderr warning).
|
|
253
|
+
|
|
247
254
|
See [Command execution](#command-execution) for the full contract (argv
|
|
248
255
|
splitting, injection safety, cwd, environment, timeout, and output framing).
|
|
249
256
|
|
|
@@ -279,7 +286,8 @@ key and the `{"sql": …}` contract returns.
|
|
|
279
286
|
**On failure** — a non-zero exit, a timeout, or a spawn error — the request
|
|
280
287
|
returns `500 Internal Server Error` with the command's stderr tail in the JSON
|
|
281
288
|
`error` body. The command runs in the config file's directory and is bounded by
|
|
282
|
-
a
|
|
289
|
+
a **30-second** timeout by default; a slow translator (e.g. LLM-backed) can
|
|
290
|
+
raise it with the global [`[dirsql].hook-timeout`](#command-execution) key.
|
|
283
291
|
|
|
284
292
|
#### The hook owns SQL safety
|
|
285
293
|
|
|
@@ -361,7 +369,8 @@ returns.
|
|
|
361
369
|
**On failure** — a non-zero exit, a timeout, or a spawn error — the request
|
|
362
370
|
returns `500 Internal Server Error` with the command's stderr tail in the JSON
|
|
363
371
|
`error` body. The command runs in the config file's directory and is bounded by
|
|
364
|
-
a
|
|
372
|
+
a **30-second** timeout by default; raise it with the global
|
|
373
|
+
[`[dirsql].hook-timeout`](#command-execution) key.
|
|
365
374
|
|
|
366
375
|
See [Command execution](#command-execution) for the full contract (argv
|
|
367
376
|
splitting, injection safety, cwd, environment, timeout, and output framing).
|
|
@@ -409,9 +418,18 @@ Config keys that run an external command — today `on-file`, `pre-query`, and
|
|
|
409
418
|
- **Output framing.** The command's result is the **last non-empty line of
|
|
410
419
|
stdout**; any log/chatter lines above it are ignored. stderr is never data —
|
|
411
420
|
it is captured only to enrich error messages.
|
|
412
|
-
- **Timeout.** Each command run is bounded by a
|
|
413
|
-
|
|
414
|
-
|
|
421
|
+
- **Timeout.** Each command run is bounded by a **30-second** timeout by
|
|
422
|
+
default; a command that exceeds it is killed and treated as a failure. Raise
|
|
423
|
+
(or tighten) it for **all** hooks — `on-file`, `pre-query`, and `post-query`
|
|
424
|
+
alike — with a single global `[dirsql].hook-timeout` key, in positive whole
|
|
425
|
+
seconds:
|
|
426
|
+
|
|
427
|
+
```toml
|
|
428
|
+
[dirsql]
|
|
429
|
+
hook-timeout = 300
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
Zero and negative values are rejected as a config error.
|
|
415
433
|
- **Errors.** A non-zero exit, a timeout, a spawn failure, or output that does
|
|
416
434
|
not parse as expected is a per-file failure: the file is skipped with a
|
|
417
435
|
stderr warning and the scan continues.
|
|
@@ -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.59"
|
|
8
8
|
edition.workspace = true
|
|
9
9
|
publish = false
|
|
10
10
|
readme = "README.md"
|
|
@@ -244,6 +244,13 @@ file is skipped (it contributes no rows) and a one-line warning naming the file
|
|
|
244
244
|
and the error is written to stderr. One bad file never aborts the scan; the
|
|
245
245
|
other files' rows are indexed normally.
|
|
246
246
|
|
|
247
|
+
**Timeout.** Each run is bounded by the default 30-second timeout. When a
|
|
248
|
+
command legitimately needs longer — it downloads a model on first run, or calls
|
|
249
|
+
a rate-limited API — raise the bound with the global
|
|
250
|
+
[`[dirsql].hook-timeout`](#command-execution) key (positive whole seconds). A
|
|
251
|
+
run exceeding the bound is killed and handled with the per-file error isolation
|
|
252
|
+
above (the file is skipped with a stderr warning).
|
|
253
|
+
|
|
247
254
|
See [Command execution](#command-execution) for the full contract (argv
|
|
248
255
|
splitting, injection safety, cwd, environment, timeout, and output framing).
|
|
249
256
|
|
|
@@ -279,7 +286,8 @@ key and the `{"sql": …}` contract returns.
|
|
|
279
286
|
**On failure** — a non-zero exit, a timeout, or a spawn error — the request
|
|
280
287
|
returns `500 Internal Server Error` with the command's stderr tail in the JSON
|
|
281
288
|
`error` body. The command runs in the config file's directory and is bounded by
|
|
282
|
-
a
|
|
289
|
+
a **30-second** timeout by default; a slow translator (e.g. LLM-backed) can
|
|
290
|
+
raise it with the global [`[dirsql].hook-timeout`](#command-execution) key.
|
|
283
291
|
|
|
284
292
|
#### The hook owns SQL safety
|
|
285
293
|
|
|
@@ -361,7 +369,8 @@ returns.
|
|
|
361
369
|
**On failure** — a non-zero exit, a timeout, or a spawn error — the request
|
|
362
370
|
returns `500 Internal Server Error` with the command's stderr tail in the JSON
|
|
363
371
|
`error` body. The command runs in the config file's directory and is bounded by
|
|
364
|
-
a
|
|
372
|
+
a **30-second** timeout by default; raise it with the global
|
|
373
|
+
[`[dirsql].hook-timeout`](#command-execution) key.
|
|
365
374
|
|
|
366
375
|
See [Command execution](#command-execution) for the full contract (argv
|
|
367
376
|
splitting, injection safety, cwd, environment, timeout, and output framing).
|
|
@@ -409,9 +418,18 @@ Config keys that run an external command — today `on-file`, `pre-query`, and
|
|
|
409
418
|
- **Output framing.** The command's result is the **last non-empty line of
|
|
410
419
|
stdout**; any log/chatter lines above it are ignored. stderr is never data —
|
|
411
420
|
it is captured only to enrich error messages.
|
|
412
|
-
- **Timeout.** Each command run is bounded by a
|
|
413
|
-
|
|
414
|
-
|
|
421
|
+
- **Timeout.** Each command run is bounded by a **30-second** timeout by
|
|
422
|
+
default; a command that exceeds it is killed and treated as a failure. Raise
|
|
423
|
+
(or tighten) it for **all** hooks — `on-file`, `pre-query`, and `post-query`
|
|
424
|
+
alike — with a single global `[dirsql].hook-timeout` key, in positive whole
|
|
425
|
+
seconds:
|
|
426
|
+
|
|
427
|
+
```toml
|
|
428
|
+
[dirsql]
|
|
429
|
+
hook-timeout = 300
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
Zero and negative values are rejected as a config error.
|
|
415
433
|
- **Errors.** A non-zero exit, a timeout, a spawn failure, or output that does
|
|
416
434
|
not parse as expected is a per-file failure: the file is skipped with a
|
|
417
435
|
stderr warning and the scan continues.
|
|
@@ -244,6 +244,13 @@ file is skipped (it contributes no rows) and a one-line warning naming the file
|
|
|
244
244
|
and the error is written to stderr. One bad file never aborts the scan; the
|
|
245
245
|
other files' rows are indexed normally.
|
|
246
246
|
|
|
247
|
+
**Timeout.** Each run is bounded by the default 30-second timeout. When a
|
|
248
|
+
command legitimately needs longer — it downloads a model on first run, or calls
|
|
249
|
+
a rate-limited API — raise the bound with the global
|
|
250
|
+
[`[dirsql].hook-timeout`](#command-execution) key (positive whole seconds). A
|
|
251
|
+
run exceeding the bound is killed and handled with the per-file error isolation
|
|
252
|
+
above (the file is skipped with a stderr warning).
|
|
253
|
+
|
|
247
254
|
See [Command execution](#command-execution) for the full contract (argv
|
|
248
255
|
splitting, injection safety, cwd, environment, timeout, and output framing).
|
|
249
256
|
|
|
@@ -279,7 +286,8 @@ key and the `{"sql": …}` contract returns.
|
|
|
279
286
|
**On failure** — a non-zero exit, a timeout, or a spawn error — the request
|
|
280
287
|
returns `500 Internal Server Error` with the command's stderr tail in the JSON
|
|
281
288
|
`error` body. The command runs in the config file's directory and is bounded by
|
|
282
|
-
a
|
|
289
|
+
a **30-second** timeout by default; a slow translator (e.g. LLM-backed) can
|
|
290
|
+
raise it with the global [`[dirsql].hook-timeout`](#command-execution) key.
|
|
283
291
|
|
|
284
292
|
#### The hook owns SQL safety
|
|
285
293
|
|
|
@@ -361,7 +369,8 @@ returns.
|
|
|
361
369
|
**On failure** — a non-zero exit, a timeout, or a spawn error — the request
|
|
362
370
|
returns `500 Internal Server Error` with the command's stderr tail in the JSON
|
|
363
371
|
`error` body. The command runs in the config file's directory and is bounded by
|
|
364
|
-
a
|
|
372
|
+
a **30-second** timeout by default; raise it with the global
|
|
373
|
+
[`[dirsql].hook-timeout`](#command-execution) key.
|
|
365
374
|
|
|
366
375
|
See [Command execution](#command-execution) for the full contract (argv
|
|
367
376
|
splitting, injection safety, cwd, environment, timeout, and output framing).
|
|
@@ -409,9 +418,18 @@ Config keys that run an external command — today `on-file`, `pre-query`, and
|
|
|
409
418
|
- **Output framing.** The command's result is the **last non-empty line of
|
|
410
419
|
stdout**; any log/chatter lines above it are ignored. stderr is never data —
|
|
411
420
|
it is captured only to enrich error messages.
|
|
412
|
-
- **Timeout.** Each command run is bounded by a
|
|
413
|
-
|
|
414
|
-
|
|
421
|
+
- **Timeout.** Each command run is bounded by a **30-second** timeout by
|
|
422
|
+
default; a command that exceeds it is killed and treated as a failure. Raise
|
|
423
|
+
(or tighten) it for **all** hooks — `on-file`, `pre-query`, and `post-query`
|
|
424
|
+
alike — with a single global `[dirsql].hook-timeout` key, in positive whole
|
|
425
|
+
seconds:
|
|
426
|
+
|
|
427
|
+
```toml
|
|
428
|
+
[dirsql]
|
|
429
|
+
hook-timeout = 300
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
Zero and negative values are rejected as a config error.
|
|
415
433
|
- **Errors.** A non-zero exit, a timeout, a spawn failure, or output that does
|
|
416
434
|
not parse as expected is a per-file failure: the file is skipped with a
|
|
417
435
|
stderr warning and the scan continues.
|
|
@@ -228,9 +228,14 @@ fn load_pre_query(cli: &Cli) -> Option<PreQuery> {
|
|
|
228
228
|
return None;
|
|
229
229
|
}
|
|
230
230
|
let resolved = config_path.canonicalize().ok()?;
|
|
231
|
-
let
|
|
231
|
+
let config = dirsql::config::load_config(&resolved).ok()?;
|
|
232
|
+
let command = config.pre_query?;
|
|
232
233
|
let config_dir = resolved.parent()?.to_path_buf();
|
|
233
|
-
|
|
234
|
+
let mut pre_query = PreQuery::new(command, config_dir);
|
|
235
|
+
if let Some(timeout) = config.hook_timeout {
|
|
236
|
+
pre_query = pre_query.with_timeout(timeout);
|
|
237
|
+
}
|
|
238
|
+
Some(pre_query)
|
|
234
239
|
}
|
|
235
240
|
|
|
236
241
|
/// Extract the server-wide `post-query` hook from the config, if any.
|
|
@@ -245,9 +250,14 @@ fn load_post_query(cli: &Cli) -> Option<PostQuery> {
|
|
|
245
250
|
return None;
|
|
246
251
|
}
|
|
247
252
|
let resolved = config_path.canonicalize().ok()?;
|
|
248
|
-
let
|
|
253
|
+
let config = dirsql::config::load_config(&resolved).ok()?;
|
|
254
|
+
let command = config.post_query?;
|
|
249
255
|
let config_dir = resolved.parent()?.to_path_buf();
|
|
250
|
-
|
|
256
|
+
let mut post_query = PostQuery::new(command, config_dir);
|
|
257
|
+
if let Some(timeout) = config.hook_timeout {
|
|
258
|
+
post_query = post_query.with_timeout(timeout);
|
|
259
|
+
}
|
|
260
|
+
Some(post_query)
|
|
251
261
|
}
|
|
252
262
|
|
|
253
263
|
/// Zero-config fallback. When no `.dirsql.toml` is found, dirsql indexes the
|
|
@@ -27,6 +27,7 @@ use tokio::task::JoinError;
|
|
|
27
27
|
use tokio::task::JoinHandle;
|
|
28
28
|
|
|
29
29
|
use crate::DirSQL;
|
|
30
|
+
use crate::command::DEFAULT_COMMAND_TIMEOUT;
|
|
30
31
|
|
|
31
32
|
pub mod init;
|
|
32
33
|
pub mod router;
|
|
@@ -51,16 +52,29 @@ pub struct PreQuery {
|
|
|
51
52
|
pub command: String,
|
|
52
53
|
/// The command's working directory — the config file's parent.
|
|
53
54
|
pub config_dir: PathBuf,
|
|
55
|
+
/// Per-run timeout. Defaults to the shared 30-second
|
|
56
|
+
/// [`DEFAULT_COMMAND_TIMEOUT`]; override it via [`Self::with_timeout`]
|
|
57
|
+
/// (the CLI wires the global `[dirsql].hook-timeout` here, #351).
|
|
58
|
+
pub timeout: Duration,
|
|
54
59
|
}
|
|
55
60
|
|
|
56
61
|
impl PreQuery {
|
|
57
|
-
/// Build a [`PreQuery`] from a command template and its working directory
|
|
62
|
+
/// Build a [`PreQuery`] from a command template and its working directory,
|
|
63
|
+
/// with the default 30-second timeout.
|
|
58
64
|
pub fn new(command: impl Into<String>, config_dir: impl Into<PathBuf>) -> Self {
|
|
59
65
|
Self {
|
|
60
66
|
command: command.into(),
|
|
61
67
|
config_dir: config_dir.into(),
|
|
68
|
+
timeout: DEFAULT_COMMAND_TIMEOUT,
|
|
62
69
|
}
|
|
63
70
|
}
|
|
71
|
+
|
|
72
|
+
/// Override the per-run timeout (from the global `[dirsql].hook-timeout`).
|
|
73
|
+
/// A run exceeding it is killed and the request returns 500.
|
|
74
|
+
pub fn with_timeout(mut self, timeout: Duration) -> Self {
|
|
75
|
+
self.timeout = timeout;
|
|
76
|
+
self
|
|
77
|
+
}
|
|
64
78
|
}
|
|
65
79
|
|
|
66
80
|
/// A server-wide `post-query` command hook, carrying the command template plus
|
|
@@ -76,16 +90,29 @@ pub struct PostQuery {
|
|
|
76
90
|
pub command: String,
|
|
77
91
|
/// The command's working directory — the config file's parent.
|
|
78
92
|
pub config_dir: PathBuf,
|
|
93
|
+
/// Per-run timeout. Defaults to the shared 30-second
|
|
94
|
+
/// [`DEFAULT_COMMAND_TIMEOUT`]; override it via [`Self::with_timeout`]
|
|
95
|
+
/// (the CLI wires the global `[dirsql].hook-timeout` here, #351).
|
|
96
|
+
pub timeout: Duration,
|
|
79
97
|
}
|
|
80
98
|
|
|
81
99
|
impl PostQuery {
|
|
82
|
-
/// Build a [`PostQuery`] from a command template and its working directory
|
|
100
|
+
/// Build a [`PostQuery`] from a command template and its working directory,
|
|
101
|
+
/// with the default 30-second timeout.
|
|
83
102
|
pub fn new(command: impl Into<String>, config_dir: impl Into<PathBuf>) -> Self {
|
|
84
103
|
Self {
|
|
85
104
|
command: command.into(),
|
|
86
105
|
config_dir: config_dir.into(),
|
|
106
|
+
timeout: DEFAULT_COMMAND_TIMEOUT,
|
|
87
107
|
}
|
|
88
108
|
}
|
|
109
|
+
|
|
110
|
+
/// Override the per-run timeout (from the global `[dirsql].hook-timeout`).
|
|
111
|
+
/// A run exceeding it is killed and the request returns 500.
|
|
112
|
+
pub fn with_timeout(mut self, timeout: Duration) -> Self {
|
|
113
|
+
self.timeout = timeout;
|
|
114
|
+
self
|
|
115
|
+
}
|
|
89
116
|
}
|
|
90
117
|
|
|
91
118
|
/// Configure how the server binds. Defaults to `localhost:7117` with a
|
|
@@ -258,10 +285,17 @@ mod tests {
|
|
|
258
285
|
#[test]
|
|
259
286
|
fn pre_query_constructor_carries_command_and_dir() {
|
|
260
287
|
// `PreQuery::new` is pure data plumbing: the command template and the
|
|
261
|
-
// working directory it will run in.
|
|
288
|
+
// working directory it will run in, with the shared default timeout.
|
|
262
289
|
let pq = PreQuery::new("to_sql.py {args}", "/proj");
|
|
263
290
|
assert_eq!(pq.command, "to_sql.py {args}");
|
|
264
291
|
assert_eq!(pq.config_dir, PathBuf::from("/proj"));
|
|
292
|
+
assert_eq!(pq.timeout, Duration::from_secs(30));
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
#[test]
|
|
296
|
+
fn pre_query_with_timeout_overrides_the_default() {
|
|
297
|
+
let pq = PreQuery::new("cmd {args}", "/proj").with_timeout(Duration::from_secs(60));
|
|
298
|
+
assert_eq!(pq.timeout, Duration::from_secs(60));
|
|
265
299
|
}
|
|
266
300
|
|
|
267
301
|
#[test]
|
|
@@ -275,10 +309,17 @@ mod tests {
|
|
|
275
309
|
#[test]
|
|
276
310
|
fn post_query_constructor_carries_command_and_dir() {
|
|
277
311
|
// `PostQuery::new` is pure data plumbing: the command template and the
|
|
278
|
-
// working directory it will run in.
|
|
312
|
+
// working directory it will run in, with the shared default timeout.
|
|
279
313
|
let pq = PostQuery::new("jq '{results: .}'", "/proj");
|
|
280
314
|
assert_eq!(pq.command, "jq '{results: .}'");
|
|
281
315
|
assert_eq!(pq.config_dir, PathBuf::from("/proj"));
|
|
316
|
+
assert_eq!(pq.timeout, Duration::from_secs(30));
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
#[test]
|
|
320
|
+
fn post_query_with_timeout_overrides_the_default() {
|
|
321
|
+
let pq = PostQuery::new("reshape {args}", "/proj").with_timeout(Duration::from_secs(60));
|
|
322
|
+
assert_eq!(pq.timeout, Duration::from_secs(60));
|
|
282
323
|
}
|
|
283
324
|
|
|
284
325
|
#[test]
|
|
@@ -21,15 +21,6 @@ use super::{AppState, PostQuery, PreQuery};
|
|
|
21
21
|
use crate::command::{Placeholder, run_command};
|
|
22
22
|
use crate::{DirSQL, DirSqlError};
|
|
23
23
|
|
|
24
|
-
/// Fixed timeout for a server-wide `pre-query` command. There is no override
|
|
25
|
-
/// key yet; this module constant is the documented current default (mirrors
|
|
26
|
-
/// `on-file`'s `ON_FILE_TIMEOUT`).
|
|
27
|
-
const PRE_QUERY_TIMEOUT: Duration = Duration::from_secs(30);
|
|
28
|
-
|
|
29
|
-
/// Fixed timeout for a server-wide `post-query` command (mirrors
|
|
30
|
-
/// `PRE_QUERY_TIMEOUT`).
|
|
31
|
-
const POST_QUERY_TIMEOUT: Duration = Duration::from_secs(30);
|
|
32
|
-
|
|
33
24
|
/// Cap on the serialized result payload passed as the `{args}` argv token.
|
|
34
25
|
/// Beyond this, `{args}` is emptied and the operator is directed to stdin
|
|
35
26
|
/// (which always carries the full payload) — comfortably under Linux's 128 KiB
|
|
@@ -155,15 +146,17 @@ fn parse_sql_body(body: &str) -> Result<String, Response> {
|
|
|
155
146
|
async fn run_pre_query(pq: &PreQuery, raw_body: String) -> Result<String, Response> {
|
|
156
147
|
let command = pq.command.clone();
|
|
157
148
|
let config_dir = pq.config_dir.clone();
|
|
149
|
+
let timeout = pq.timeout;
|
|
158
150
|
// `run_command` is blocking — it spawns a child and joins drain threads —
|
|
159
|
-
// so run it off the async runtime. It enforces
|
|
160
|
-
// internally, so no outer
|
|
151
|
+
// so run it off the async runtime. It enforces the hook's timeout
|
|
152
|
+
// (the global `[dirsql].hook-timeout`, default 30s) internally, so no outer
|
|
153
|
+
// `tokio::time::timeout` is needed.
|
|
161
154
|
let outcome = tokio::task::spawn_blocking(move || {
|
|
162
155
|
run_command(
|
|
163
156
|
&command,
|
|
164
157
|
&[Placeholder::new("args", &raw_body)],
|
|
165
158
|
&config_dir,
|
|
166
|
-
|
|
159
|
+
timeout,
|
|
167
160
|
None,
|
|
168
161
|
)
|
|
169
162
|
})
|
|
@@ -198,9 +191,11 @@ async fn run_post_query(
|
|
|
198
191
|
.map_err(|err| error_response(StatusCode::INTERNAL_SERVER_ERROR, err.to_string()))?;
|
|
199
192
|
let command = pq.command.clone();
|
|
200
193
|
let config_dir = pq.config_dir.clone();
|
|
194
|
+
let timeout = pq.timeout;
|
|
201
195
|
// `run_command` is blocking — it spawns a child and joins drain threads —
|
|
202
|
-
// so run it off the async runtime. It enforces
|
|
203
|
-
// internally, so no outer
|
|
196
|
+
// so run it off the async runtime. It enforces the hook's timeout
|
|
197
|
+
// (the global `[dirsql].hook-timeout`, default 30s) internally, so no outer
|
|
198
|
+
// `tokio::time::timeout` is needed.
|
|
204
199
|
let outcome = tokio::task::spawn_blocking(move || {
|
|
205
200
|
let args_value = if payload.len() <= POST_QUERY_ARGS_MAX {
|
|
206
201
|
payload.clone()
|
|
@@ -217,7 +212,7 @@ async fn run_post_query(
|
|
|
217
212
|
&command,
|
|
218
213
|
&[Placeholder::new("args", &args_value)],
|
|
219
214
|
&config_dir,
|
|
220
|
-
|
|
215
|
+
timeout,
|
|
221
216
|
Some(payload.as_bytes()),
|
|
222
217
|
)
|
|
223
218
|
})
|
|
@@ -36,6 +36,11 @@ use std::time::Duration;
|
|
|
36
36
|
|
|
37
37
|
use wait_timeout::ChildExt;
|
|
38
38
|
|
|
39
|
+
/// The default timeout for every command-backed event (`on-file`,
|
|
40
|
+
/// `pre-query`, `post-query`) when the config declares no override
|
|
41
|
+
/// (`[dirsql].hook-timeout`, positive whole seconds).
|
|
42
|
+
pub const DEFAULT_COMMAND_TIMEOUT: Duration = Duration::from_secs(30);
|
|
43
|
+
|
|
39
44
|
/// A named placeholder substituted into a command's argv.
|
|
40
45
|
///
|
|
41
46
|
/// `name` is the bare identifier (no braces): a `name` of `path` matches the
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
use std::path::{Path, PathBuf};
|
|
2
|
+
use std::time::Duration;
|
|
2
3
|
|
|
3
4
|
use serde::Deserialize;
|
|
4
5
|
|
|
@@ -20,6 +21,9 @@ pub enum ConfigError {
|
|
|
20
21
|
#[error("Field '{0}' must not be empty")]
|
|
21
22
|
EmptyField(&'static str),
|
|
22
23
|
|
|
24
|
+
#[error("Field '{field}' must be a positive number of seconds, got {value}")]
|
|
25
|
+
InvalidTimeout { field: &'static str, value: i64 },
|
|
26
|
+
|
|
23
27
|
#[error("Cannot combine an empty list of configs")]
|
|
24
28
|
NoConfigs,
|
|
25
29
|
|
|
@@ -70,7 +74,7 @@ impl std::fmt::Display for Source {
|
|
|
70
74
|
/// returned unchanged. List-shaped config (`[[table]]`, `[[dirsql.extension]]`,
|
|
71
75
|
/// `ignore`) concatenates in input order. A table-name collision anywhere in
|
|
72
76
|
/// the combined set errors, naming both sources. Single-valued keys (`root`,
|
|
73
|
-
/// `persist`, `persist_path`, `pre-query`, `post-query`) defined by more than
|
|
77
|
+
/// `persist`, `persist_path`, `pre-query`, `post-query`, `hook-timeout`) defined by more than
|
|
74
78
|
/// one config error, naming both sources — no silent shadowing, no precedence;
|
|
75
79
|
/// defined in exactly one config they merge through unchanged. (`persist`
|
|
76
80
|
/// counts as defined only when `true`: the parsed [`Config`] cannot
|
|
@@ -100,6 +104,7 @@ pub fn combine_configs(configs: &[(Source, Config)]) -> Result<Config> {
|
|
|
100
104
|
let mut persist_path: Option<(&Source, PathBuf)> = None;
|
|
101
105
|
let mut pre_query: Option<(&Source, String)> = None;
|
|
102
106
|
let mut post_query: Option<(&Source, String)> = None;
|
|
107
|
+
let mut hook_timeout: Option<(&Source, Duration)> = None;
|
|
103
108
|
|
|
104
109
|
for (source, config) in configs {
|
|
105
110
|
for table in &config.tables {
|
|
@@ -136,6 +141,12 @@ pub fn combine_configs(configs: &[(Source, Config)]) -> Result<Config> {
|
|
|
136
141
|
config.post_query.as_ref(),
|
|
137
142
|
source,
|
|
138
143
|
)?;
|
|
144
|
+
merge_single(
|
|
145
|
+
"hook-timeout",
|
|
146
|
+
&mut hook_timeout,
|
|
147
|
+
config.hook_timeout.as_ref(),
|
|
148
|
+
source,
|
|
149
|
+
)?;
|
|
139
150
|
if config.persist {
|
|
140
151
|
if let Some(prior) = persist {
|
|
141
152
|
return Err(ConfigError::ConflictingKey {
|
|
@@ -157,6 +168,7 @@ pub fn combine_configs(configs: &[(Source, Config)]) -> Result<Config> {
|
|
|
157
168
|
extensions,
|
|
158
169
|
pre_query: pre_query.map(|(_, value)| value),
|
|
159
170
|
post_query: post_query.map(|(_, value)| value),
|
|
171
|
+
hook_timeout: hook_timeout.map(|(_, value)| value),
|
|
160
172
|
})
|
|
161
173
|
}
|
|
162
174
|
|
|
@@ -216,6 +228,11 @@ pub struct Config {
|
|
|
216
228
|
/// the rows as-is. See `dirsql::command` for the execution contract. Only
|
|
217
229
|
/// the CLI server consults this; the SDK ignores it.
|
|
218
230
|
pub post_query: Option<String>,
|
|
231
|
+
/// Optional timeout for every command-backed hook — `on-file`, `pre-query`,
|
|
232
|
+
/// and `post-query` alike (`[dirsql].hook-timeout`, positive seconds). One
|
|
233
|
+
/// global bound rather than a per-hook knob. When absent, hooks fall back to
|
|
234
|
+
/// the shared 30-second default ([`crate::command::DEFAULT_COMMAND_TIMEOUT`]).
|
|
235
|
+
pub hook_timeout: Option<Duration>,
|
|
219
236
|
}
|
|
220
237
|
|
|
221
238
|
/// A SQLite extension to load at startup.
|
|
@@ -265,7 +282,7 @@ struct RawConfig {
|
|
|
265
282
|
table: Option<Vec<RawTable>>,
|
|
266
283
|
}
|
|
267
284
|
|
|
268
|
-
#[derive(Deserialize)]
|
|
285
|
+
#[derive(Deserialize, Default)]
|
|
269
286
|
struct RawDirsql {
|
|
270
287
|
root: Option<PathBuf>,
|
|
271
288
|
ignore: Option<Vec<String>>,
|
|
@@ -276,6 +293,8 @@ struct RawDirsql {
|
|
|
276
293
|
pre_query: Option<String>,
|
|
277
294
|
#[serde(rename = "post-query")]
|
|
278
295
|
post_query: Option<String>,
|
|
296
|
+
#[serde(rename = "hook-timeout")]
|
|
297
|
+
hook_timeout: Option<i64>,
|
|
279
298
|
}
|
|
280
299
|
|
|
281
300
|
#[derive(Deserialize)]
|
|
@@ -303,19 +322,16 @@ pub fn load_config(path: &Path) -> Result<Config> {
|
|
|
303
322
|
pub fn load_config_str(content: &str) -> Result<Config> {
|
|
304
323
|
let raw: RawConfig = toml::from_str(content)?;
|
|
305
324
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
),
|
|
317
|
-
None => (None, Vec::new(), false, None, Vec::new(), None, None),
|
|
318
|
-
};
|
|
325
|
+
// A missing `[dirsql]` section behaves as an all-defaults one.
|
|
326
|
+
let d = raw.dirsql.unwrap_or_default();
|
|
327
|
+
let root = d.root;
|
|
328
|
+
let ignore = d.ignore.unwrap_or_default();
|
|
329
|
+
let persist = d.persist.unwrap_or(false);
|
|
330
|
+
let persist_path = d.persist_path;
|
|
331
|
+
let raw_extensions = d.extension.unwrap_or_default();
|
|
332
|
+
let raw_pre_query = d.pre_query;
|
|
333
|
+
let raw_post_query = d.post_query;
|
|
334
|
+
let hook_timeout = parse_timeout_secs("hook-timeout", d.hook_timeout)?;
|
|
319
335
|
|
|
320
336
|
// A present-but-empty `pre-query = ""` is as unusable as a missing key:
|
|
321
337
|
// reject it at parse time rather than spawning an empty command later
|
|
@@ -384,9 +400,23 @@ pub fn load_config_str(content: &str) -> Result<Config> {
|
|
|
384
400
|
extensions,
|
|
385
401
|
pre_query,
|
|
386
402
|
post_query,
|
|
403
|
+
hook_timeout,
|
|
387
404
|
})
|
|
388
405
|
}
|
|
389
406
|
|
|
407
|
+
/// Validate an optional timeout config value (whole seconds) and convert it
|
|
408
|
+
/// to a [`Duration`]. Zero and negative values are as unusable as a garbage
|
|
409
|
+
/// string — reject them at parse time with the offending field's name rather
|
|
410
|
+
/// than silently producing a command that can never run (`0`) or a bogus
|
|
411
|
+
/// cast (negative).
|
|
412
|
+
fn parse_timeout_secs(field: &'static str, raw: Option<i64>) -> Result<Option<Duration>> {
|
|
413
|
+
match raw {
|
|
414
|
+
Some(secs) if secs <= 0 => Err(ConfigError::InvalidTimeout { field, value: secs }),
|
|
415
|
+
Some(secs) => Ok(Some(Duration::from_secs(secs as u64))),
|
|
416
|
+
None => Ok(None),
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
390
420
|
#[cfg(test)]
|
|
391
421
|
mod tests {
|
|
392
422
|
use super::*;
|
|
@@ -1141,6 +1171,79 @@ glob = "c/*.json"
|
|
|
1141
1171
|
);
|
|
1142
1172
|
}
|
|
1143
1173
|
|
|
1174
|
+
#[test]
|
|
1175
|
+
fn hook_timeout_parses_to_duration_seconds() {
|
|
1176
|
+
let toml = r#"
|
|
1177
|
+
[dirsql]
|
|
1178
|
+
hook-timeout = 300
|
|
1179
|
+
"#;
|
|
1180
|
+
let config = load_config_str(toml).unwrap();
|
|
1181
|
+
assert_eq!(config.hook_timeout, Some(Duration::from_secs(300)));
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
#[test]
|
|
1185
|
+
fn hook_timeout_absent_is_none() {
|
|
1186
|
+
let toml = r#"
|
|
1187
|
+
[[table]]
|
|
1188
|
+
ddl = "CREATE TABLE t (x TEXT)"
|
|
1189
|
+
glob = "*.json"
|
|
1190
|
+
"#;
|
|
1191
|
+
let config = load_config_str(toml).unwrap();
|
|
1192
|
+
assert!(config.hook_timeout.is_none());
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
#[test]
|
|
1196
|
+
fn hook_timeout_zero_errors() {
|
|
1197
|
+
let toml = r#"
|
|
1198
|
+
[dirsql]
|
|
1199
|
+
hook-timeout = 0
|
|
1200
|
+
"#;
|
|
1201
|
+
let err = load_config_str(toml).unwrap_err();
|
|
1202
|
+
assert!(
|
|
1203
|
+
matches!(
|
|
1204
|
+
err,
|
|
1205
|
+
ConfigError::InvalidTimeout {
|
|
1206
|
+
field: "hook-timeout",
|
|
1207
|
+
value: 0
|
|
1208
|
+
}
|
|
1209
|
+
),
|
|
1210
|
+
"got: {err:?}"
|
|
1211
|
+
);
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
#[test]
|
|
1215
|
+
fn hook_timeout_negative_errors() {
|
|
1216
|
+
let toml = r#"
|
|
1217
|
+
[dirsql]
|
|
1218
|
+
hook-timeout = -5
|
|
1219
|
+
"#;
|
|
1220
|
+
let err = load_config_str(toml).unwrap_err();
|
|
1221
|
+
assert!(
|
|
1222
|
+
matches!(
|
|
1223
|
+
err,
|
|
1224
|
+
ConfigError::InvalidTimeout {
|
|
1225
|
+
field: "hook-timeout",
|
|
1226
|
+
value: -5
|
|
1227
|
+
}
|
|
1228
|
+
),
|
|
1229
|
+
"got: {err:?}"
|
|
1230
|
+
);
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
#[test]
|
|
1234
|
+
fn invalid_timeout_error_names_the_field_and_value() {
|
|
1235
|
+
// The message is the user's only pointer to the bad key — it must name
|
|
1236
|
+
// both the field and the offending value.
|
|
1237
|
+
let err = ConfigError::InvalidTimeout {
|
|
1238
|
+
field: "hook-timeout",
|
|
1239
|
+
value: -1,
|
|
1240
|
+
};
|
|
1241
|
+
assert_eq!(
|
|
1242
|
+
err.to_string(),
|
|
1243
|
+
"Field 'hook-timeout' must be a positive number of seconds, got -1"
|
|
1244
|
+
);
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1144
1247
|
#[test]
|
|
1145
1248
|
fn extension_empty_path_errors() {
|
|
1146
1249
|
// An empty `path = ""` is as unusable as a missing key — it must be
|
|
@@ -1457,10 +1457,6 @@ fn relative_path(root: &Path, path: &Path) -> String {
|
|
|
1457
1457
|
.to_string()
|
|
1458
1458
|
}
|
|
1459
1459
|
|
|
1460
|
-
/// Fixed timeout for an `on-file` command. There is no per-table timeout key
|
|
1461
|
-
/// yet (#327); this module constant is the documented current default.
|
|
1462
|
-
const ON_FILE_TIMEOUT: Duration = Duration::from_secs(30);
|
|
1463
|
-
|
|
1464
1460
|
/// Build [`Table`] objects from a parsed config.
|
|
1465
1461
|
///
|
|
1466
1462
|
/// A plain config-defined table produces one row per matched file built
|
|
@@ -1475,13 +1471,17 @@ const ON_FILE_TIMEOUT: Duration = Duration::from_secs(30);
|
|
|
1475
1471
|
/// array of row objects on stdout, which becomes the file's rows (filesystem
|
|
1476
1472
|
/// facts are still merged on top, user values winning). `config_dir` is the
|
|
1477
1473
|
/// command's working directory (the config file's parent) and `root` is the
|
|
1478
|
-
/// resolved index root used to compute the `{path}` placeholder.
|
|
1474
|
+
/// resolved index root used to compute the `{path}` placeholder. Each run is
|
|
1475
|
+
/// bounded by the global `[dirsql].hook-timeout` key when present (#351),
|
|
1476
|
+
/// falling back to the shared 30-second default
|
|
1477
|
+
/// ([`command::DEFAULT_COMMAND_TIMEOUT`]).
|
|
1479
1478
|
fn build_tables_from_config(
|
|
1480
1479
|
cfg: &config::Config,
|
|
1481
1480
|
config_dir: &Path,
|
|
1482
1481
|
root: &Path,
|
|
1483
1482
|
) -> Result<Vec<Table>> {
|
|
1484
1483
|
let mut tables = Vec::with_capacity(cfg.tables.len());
|
|
1484
|
+
let timeout = cfg.hook_timeout.unwrap_or(command::DEFAULT_COMMAND_TIMEOUT);
|
|
1485
1485
|
|
|
1486
1486
|
for table_cfg in &cfg.tables {
|
|
1487
1487
|
let mut table = match &table_cfg.on_file {
|
|
@@ -1495,7 +1495,9 @@ fn build_tables_from_config(
|
|
|
1495
1495
|
Table::new(
|
|
1496
1496
|
table_cfg.ddl.clone(),
|
|
1497
1497
|
table_cfg.glob.clone(),
|
|
1498
|
-
move |abs_path: &str|
|
|
1498
|
+
move |abs_path: &str| {
|
|
1499
|
+
run_on_file(&command, abs_path, &config_dir, &root, timeout)
|
|
1500
|
+
},
|
|
1499
1501
|
)
|
|
1500
1502
|
}
|
|
1501
1503
|
None => Table::new(
|
|
@@ -1527,8 +1529,15 @@ fn build_tables_from_config(
|
|
|
1527
1529
|
/// Per-file isolation: any failure — a spawn/exit/timeout error from
|
|
1528
1530
|
/// [`command::run_command`], or output that is not a JSON array of objects —
|
|
1529
1531
|
/// is logged to stderr and yields no rows (`vec![]`). Returning `Err` here
|
|
1530
|
-
/// would abort the whole scan, so it never does.
|
|
1531
|
-
|
|
1532
|
+
/// would abort the whole scan, so it never does. `timeout` bounds the run —
|
|
1533
|
+
/// the table's configured value, or the shared default.
|
|
1534
|
+
fn run_on_file(
|
|
1535
|
+
command: &str,
|
|
1536
|
+
abs_path: &str,
|
|
1537
|
+
config_dir: &Path,
|
|
1538
|
+
root: &Path,
|
|
1539
|
+
timeout: Duration,
|
|
1540
|
+
) -> Vec<Row> {
|
|
1532
1541
|
let abs = Path::new(abs_path);
|
|
1533
1542
|
let rel = abs
|
|
1534
1543
|
.strip_prefix(root)
|
|
@@ -1540,7 +1549,7 @@ fn run_on_file(command: &str, abs_path: &str, config_dir: &Path, root: &Path) ->
|
|
|
1540
1549
|
Placeholder::new("root", root.to_string_lossy().into_owned()),
|
|
1541
1550
|
];
|
|
1542
1551
|
|
|
1543
|
-
match command::run_command(command, &placeholders, config_dir,
|
|
1552
|
+
match command::run_command(command, &placeholders, config_dir, timeout, None) {
|
|
1544
1553
|
Ok(output) => match parse_command_rows(&output.payload) {
|
|
1545
1554
|
Ok(rows) => rows,
|
|
1546
1555
|
Err(message) => {
|
|
@@ -3097,6 +3106,7 @@ mod internal_tests {
|
|
|
3097
3106
|
&abs.to_string_lossy(),
|
|
3098
3107
|
dir.path(),
|
|
3099
3108
|
dir.path(),
|
|
3109
|
+
command::DEFAULT_COMMAND_TIMEOUT,
|
|
3100
3110
|
);
|
|
3101
3111
|
assert_eq!(rows.len(), 1);
|
|
3102
3112
|
assert_eq!(rows[0]["n"], Value::Integer(1));
|
|
@@ -3112,6 +3122,7 @@ mod internal_tests {
|
|
|
3112
3122
|
"/outside/f.txt",
|
|
3113
3123
|
dir.path(),
|
|
3114
3124
|
dir.path(),
|
|
3125
|
+
command::DEFAULT_COMMAND_TIMEOUT,
|
|
3115
3126
|
);
|
|
3116
3127
|
assert!(rows.is_empty());
|
|
3117
3128
|
}
|
|
@@ -3120,7 +3131,13 @@ mod internal_tests {
|
|
|
3120
3131
|
#[test]
|
|
3121
3132
|
fn run_on_file_returns_no_rows_on_non_json_output() {
|
|
3122
3133
|
let dir = TempDir::new().unwrap();
|
|
3123
|
-
let rows = run_on_file(
|
|
3134
|
+
let rows = run_on_file(
|
|
3135
|
+
"echo not-json",
|
|
3136
|
+
"/outside/f.txt",
|
|
3137
|
+
dir.path(),
|
|
3138
|
+
dir.path(),
|
|
3139
|
+
command::DEFAULT_COMMAND_TIMEOUT,
|
|
3140
|
+
);
|
|
3124
3141
|
assert!(rows.is_empty());
|
|
3125
3142
|
}
|
|
3126
3143
|
|
|
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
|
|
File without changes
|
|
File without changes
|
{dirsql-0.3.58 → dirsql-0.3.59}/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
|