dirsql 0.4.59__tar.gz → 0.4.61__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.4.59 → dirsql-0.4.61}/Cargo.lock +2 -3
- {dirsql-0.4.59 → dirsql-0.4.61}/PKG-INFO +1 -1
- {dirsql-0.4.59/packages/rust → dirsql-0.4.61}/docs/reference/config.md +37 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/Cargo.toml +1 -1
- {dirsql-0.4.59 → dirsql-0.4.61/packages/python}/docs/reference/config.md +37 -0
- dirsql-0.4.61/packages/python/e2e-attestations/claude-1201-single-walk.json +7 -0
- dirsql-0.4.61/packages/python/e2e-attestations/claude-1204-embed-batch.json +7 -0
- dirsql-0.4.61/packages/python/e2e-attestations/claude-1207-win-argv-quote.json +7 -0
- dirsql-0.4.61/packages/python/e2e-attestations/claude-1208-win-nonverbatim-paths.json +7 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/rust/Cargo.toml +2 -3
- {dirsql-0.4.59/packages/python → dirsql-0.4.61/packages/rust}/docs/reference/config.md +37 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/rust/src/command.rs +254 -2
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/rust/src/config.rs +58 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/rust/src/db.rs +242 -10
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/rust/src/extension_resolution.rs +5 -1
- dirsql-0.4.61/packages/rust/src/functions.rs +1887 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/rust/src/lib.rs +46 -141
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/rust/src/parsed_vtab.rs +5 -5
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/rust/src/scanner.rs +223 -98
- dirsql-0.4.61/packages/rust/src/vtab.rs +708 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/rust/src/vtab_scaffold.rs +198 -12
- dirsql-0.4.59/packages/rust/src/functions.rs +0 -1040
- dirsql-0.4.59/packages/rust/src/vtab.rs +0 -498
- {dirsql-0.4.59 → dirsql-0.4.61}/Cargo.toml +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/README.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/dirsql/__init__.py +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/dirsql/_async.py +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/dirsql/_dirsql.pyi +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/dirsql/cli/__init__.py +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/dirsql/cli/discover_plugins/__init__.py +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/dirsql/cli/discover_plugins/discovered_fragments.py +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/dirsql/cli/discover_plugins/discovery_disabled.py +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/dirsql/cli/discover_plugins/fragment_path.py +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/dirsql/cli/discover_plugins/user_passed_config.py +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/dirsql/cli/discover_plugins/with_discovered_plugins.py +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/dirsql/cli/main.py +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/dirsql/cli/resolve_config_extensions.py +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/dirsql/plan_entry_path.py +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/dirsql/py.typed +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/dirsql/read_config.py +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/dirsql/resolve_configs_extension_specs.py +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/dirsql/resolve_extension.py +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/dirsql/resolve_package.py +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/docs/.claude/CLAUDE.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/docs/.vitepress/config.ts +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/docs/.vitepress/theme/index.ts +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/docs/.vitepress/theme/lang.ts +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/docs/AGENTS.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/docs/explanation.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/docs/getting-started.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/docs/howto/columns-from-paths.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/docs/howto/define-tables.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/docs/howto/embed.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/docs/howto/extract-from-contents.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/docs/howto/load-extension.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/docs/howto/parse-files-into-columns.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/docs/howto/persist.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/docs/howto/query-json.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/docs/howto/query-without-config.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/docs/howto/react-to-changes.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/docs/howto/search-by-meaning.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/docs/howto/search-indexes.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/docs/howto/skip-files.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/docs/howto/write-a-plugin.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/docs/index.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/docs/package.json +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/docs/plugins.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/docs/pnpm-lock.yaml +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/docs/pnpm-workspace.yaml +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/docs/reference/cli.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/docs/reference/columns.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/docs/reference/hooks.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/docs/reference/http-api.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/docs/reference/path-tables.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/docs/reference/sdk.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/CHANGELOG.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/MIGRATIONS.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/README.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/changelog.d/2026-07-13-drop-cwd-config-auto-detect.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/changelog.d/2026-07-13-plugin-discovery.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/changelog.d/2026-07-13-rename-extract-to-on-file.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/changelog.d/2026-07-13-repeatable-config.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/changelog.d/2026-07-13-sdk-default-baked-in-config.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/changelog.d/2026-07-20-drop-implicit-files-table.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/changelog.d/2026-07-21-remove-glob-captures.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/changelog.d/2026-07-24-hookless-table-config-error.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/changelog.d/2026-07-24-remove-stat-fact-injection.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/changelog.d/2026-07-29-restore-python-310.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/changelog.d/2026-08-02-release-profile.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/changelog.d/2026-08-02-scan-failures.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/changelog.d/2026-08-03-discovery-ext-resolution.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/changelog.d/2026-08-03-manylinux-dynamic-binary.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/changelog.d/2026-08-03-python-no-ignore.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/changelog.d/2026-08-04-cast-lints.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/changelog.d/2026-08-04-cli-in-process.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/changelog.d/2026-08-20-curtaincall-dev-dependency.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/changelog.d/2026-08-20-declared-table-name.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/changelog.d/2026-09-04-config-paths-from-argv-module.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/changelog.d/2026-09-05-config-extension-loader-split.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/changelog.d/2026-09-05-resolve-config-extensions-split.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/changelog.d/2026-09-05-resolve-extension-split.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/changelog.d/2026-09-09-launcher-config-path-scan.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/changelog.d/2026-09-17-extension-resolution-via-core.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/changelog.d/2026-09-18-export-scan-failure.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/changelog.d/2026-09-18-flatten-row-event-in-core.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/changelog.d/2026-09-18-programmatic-extension-plan.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/changelog.d/2026-09-20-sigint-ends-a-run-in-progress.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/changelog.d/2026-09-24-query-rows-in-projection-order.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/changelog.d/README.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/conftest.py +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/docs/.claude/CLAUDE.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/docs/.vitepress/config.ts +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/docs/.vitepress/theme/index.ts +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/docs/.vitepress/theme/lang.ts +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/docs/AGENTS.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/docs/explanation.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/docs/getting-started.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/docs/howto/columns-from-paths.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/docs/howto/define-tables.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/docs/howto/embed.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/docs/howto/extract-from-contents.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/docs/howto/load-extension.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/docs/howto/parse-files-into-columns.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/docs/howto/persist.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/docs/howto/query-json.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/docs/howto/query-without-config.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/docs/howto/react-to-changes.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/docs/howto/search-by-meaning.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/docs/howto/search-indexes.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/docs/howto/skip-files.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/docs/howto/write-a-plugin.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/docs/index.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/docs/package.json +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/docs/plugins.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/docs/pnpm-lock.yaml +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/docs/pnpm-workspace.yaml +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/docs/reference/cli.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/docs/reference/columns.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/docs/reference/hooks.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/docs/reference/http-api.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/docs/reference/path-tables.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/docs/reference/sdk.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/e2e-attestations/cc-keen-knuth-f11gyq.json +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/e2e-attestations/claude-1056-py-ofpf9.json +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/e2e-attestations/claude-1066-p1-resolve-extension.json +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/e2e-attestations/claude-1078-argv-config-paths-core.json +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/e2e-attestations/claude-1079-p2-resolve-config-extensions.json +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/e2e-attestations/claude-1080-share-vtab-scaffolding.json +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/e2e-attestations/claude-1081-indicatif-progress.json +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/e2e-attestations/claude-1096-watch-moved-dir.json +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/e2e-attestations/claude-1109-watch-moved-out.json +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/e2e-attestations/claude-1119-ext-plan.json +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/e2e-attestations/claude-1120-py-ext.json +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/e2e-attestations/claude-1122-ext-cleanup.json +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/e2e-attestations/claude-1130-codec-crates.json +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/e2e-attestations/claude-1131-flatten-row-event.json +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/e2e-attestations/claude-1139-sse-flatten.json +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/e2e-attestations/claude-1140-scan-failure-export.json +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/e2e-attestations/claude-1148-clippy-all-targets.json +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/e2e-attestations/claude-1164-sigint-fatal.json +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/e2e-attestations/claude-1170-projection-order.json +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/e2e-attestations/claude-1172-on-file-path.json +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/e2e-attestations/claude-1173-py-projection-order.json +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/e2e-attestations/claude-1184-win-rel-paths.json +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/e2e-attestations/claude-1185-win-abs-table-names.json +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/e2e-attestations/claude-1186-win-test-harness.json +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/e2e-attestations/claude-819-oneshot-timeout.json +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/e2e-attestations/claude-827-unquote-doubled-quotes.json +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/e2e-attestations/claude-epic-953-slice2-reedline.json +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/e2e-attestations/claude-epic-953-slice3-format.json +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/e2e-attestations/claude-epic-953-stacked-prs-x59wm3.json +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/e2e-attestations/claude-github-issue-825-e4mhhp.json +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/e2e-attestations/claude-issue-951-0eease.json +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/e2e-attestations/claude-issue-962-0jjrfv.json +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/e2e-attestations/claude-issue-986-red-test-e7sjrb.json +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/e2e-attestations/claude-tackle-1034-cachesplit.json +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/e2e-attestations/claude-tackle-1042-py12.json +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/e2e-attestations/claude-tackle-956-xzen74.json +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/e2e-attestations/claude-tackle-957-lrm0z6-worker-calls.json +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/e2e-attestations/claude-tackle-957-lrm0z6.json +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/e2e-attestations/claude-tackle-975-n8mc2h.json +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/migrations.d/2026-07-13-drop-cwd-config-auto-detect.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/migrations.d/2026-07-13-rename-extract-to-on-file.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/migrations.d/2026-07-13-sdk-default-baked-in-config.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/migrations.d/2026-07-20-drop-implicit-files-table.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/migrations.d/2026-07-21-remove-glob-captures.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/migrations.d/2026-07-24-hookless-table-config-error.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/migrations.d/2026-07-24-remove-stat-fact-injection.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/migrations.d/2026-07-29-restore-python-310.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/migrations.d/2026-08-04-cli-in-process.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/migrations.d/2026-08-20-declared-table-name.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/migrations.d/2026-09-20-sigint-ends-a-run-in-progress.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/migrations.d/2026-09-24-query-rows-in-projection-order.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/migrations.d/README.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/src/lib.rs +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/testing-conventions.toml +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/tests/__init__.py +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/tests/conftest.py +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/tests/e2e/__init__.py +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/tests/e2e/fixtures/dirsql_plugin_fixture/__init__.py +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/tests/e2e/fixtures/dirsql_plugin_fixture/dirsql.toml +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/tests/e2e/speed_of_light.py +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/tests/integration/__init__.py +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/tests/integration/binding/__init__.py +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/python/tests/integration/hermetic/__init__.py +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/rust/CHANGELOG.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/rust/MIGRATIONS.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/rust/README.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/rust/benches/db_bench.rs +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/rust/benches/differ_bench.rs +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/rust/benches/matcher_bench.rs +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/rust/benches/scanner_bench.rs +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/rust/docs/explanation.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/rust/docs/getting-started.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/rust/docs/howto/columns-from-paths.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/rust/docs/howto/define-tables.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/rust/docs/howto/embed.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/rust/docs/howto/extract-from-contents.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/rust/docs/howto/load-extension.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/rust/docs/howto/parse-files-into-columns.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/rust/docs/howto/persist.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/rust/docs/howto/query-json.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/rust/docs/howto/query-without-config.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/rust/docs/howto/react-to-changes.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/rust/docs/howto/search-by-meaning.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/rust/docs/howto/search-indexes.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/rust/docs/howto/skip-files.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/rust/docs/howto/write-a-plugin.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/rust/docs/index.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/rust/docs/plugins.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/rust/docs/reference/cli.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/rust/docs/reference/columns.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/rust/docs/reference/hooks.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/rust/docs/reference/http-api.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/rust/docs/reference/path-tables.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/rust/docs/reference/sdk.md +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/rust/src/bin/dirsql.rs +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/rust/src/cli/execute.rs +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/rust/src/cli/init.rs +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/rust/src/cli/mod.rs +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/rust/src/cli/repl.rs +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/rust/src/cli/router.rs +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/rust/src/cli/run.rs +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/rust/src/cli/serialize.rs +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/rust/src/cli/server.rs +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/rust/src/cli/table.rs +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/rust/src/default_config.toml +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/rust/src/differ.rs +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/rust/src/infer.rs +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/rust/src/launcher.rs +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/rust/src/matcher.rs +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/rust/src/parsed_cache.rs +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/rust/src/path_table.rs +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/rust/src/persist.rs +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/rust/src/progress.rs +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/rust/src/row_event_flat.rs +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/rust/src/sql_literal.rs +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/packages/rust/src/watcher.rs +0 -0
- {dirsql-0.4.59 → dirsql-0.4.61}/pyproject.toml +0 -0
|
@@ -552,7 +552,7 @@ checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8"
|
|
|
552
552
|
|
|
553
553
|
[[package]]
|
|
554
554
|
name = "dirsql"
|
|
555
|
-
version = "0.4.
|
|
555
|
+
version = "0.4.61"
|
|
556
556
|
dependencies = [
|
|
557
557
|
"assert_cmd",
|
|
558
558
|
"axum",
|
|
@@ -585,7 +585,6 @@ dependencies = [
|
|
|
585
585
|
"tokio-stream",
|
|
586
586
|
"toml",
|
|
587
587
|
"typed-path",
|
|
588
|
-
"walkdir",
|
|
589
588
|
]
|
|
590
589
|
|
|
591
590
|
[[package]]
|
|
@@ -601,7 +600,7 @@ dependencies = [
|
|
|
601
600
|
|
|
602
601
|
[[package]]
|
|
603
602
|
name = "dirsql-py-ext"
|
|
604
|
-
version = "0.4.
|
|
603
|
+
version = "0.4.61"
|
|
605
604
|
dependencies = [
|
|
606
605
|
"dirsql",
|
|
607
606
|
"pyo3",
|
|
@@ -111,6 +111,7 @@ its `embed()` function exactly this way.
|
|
|
111
111
|
| `command` | yes (non-empty) | The worker command. Argv-split with the same no-shell quoting rules as [command hooks](./hooks.md#argv-not-a-shell); runs in the config file's directory. |
|
|
112
112
|
| `deterministic` | no (default `false`) | When `true`, the function is registered with `SQLITE_DETERMINISTIC`, letting SQLite cache and reuse results for identical arguments within a query. Only set it when the worker really is a pure function of its arguments. |
|
|
113
113
|
| `timeout` | no | Per-**call** time bound: a positive integer is whole seconds (`timeout = 600`), a string is an integer suffixed `s` or `ms` (`"600s"`, `"250ms"`). When absent, the function mechanism's own 30-second default applies. |
|
|
114
|
+
| `batch` | no | A positive integer: the most calls one **batched request** carries. When set, a statement's values are gathered and sent to the worker in bulk (see [Batching](#batching)); the worker must then speak the batched protocol shape as well as the single-call one. When absent, the worker only ever sees single calls. |
|
|
114
115
|
|
|
115
116
|
```toml
|
|
116
117
|
[[dirsql.function]]
|
|
@@ -173,6 +174,42 @@ request line in, one response line out, per call:
|
|
|
173
174
|
- **stderr passes through** to `dirsql`'s stderr, so a worker's progress
|
|
174
175
|
bars and download logs reach the terminal.
|
|
175
176
|
|
|
177
|
+
### Batching
|
|
178
|
+
|
|
179
|
+
One round trip per row is the wrong shape for a worker whose cost is
|
|
180
|
+
per-request rather than per-value — an embedding model encodes a list of
|
|
181
|
+
texts in little more time than one. A function declaring `batch = N` is sent
|
|
182
|
+
values in bulk instead:
|
|
183
|
+
|
|
184
|
+
- **Request:** `{"calls": [[<arg>, ...], ...]}` — up to `N` calls, each
|
|
185
|
+
encoded exactly as a single call's argument list.
|
|
186
|
+
- **Response:** `{"results": [<response>, ...]}` — one single-call response
|
|
187
|
+
(`{"ok": ...}` with its optional `"meta"`, or `{"err": ...}`) per call, in
|
|
188
|
+
order. A per-call `{"err": ...}` fails the query the way it would on a
|
|
189
|
+
single call. A top-level `{"err": "message"}` instead fails every call in
|
|
190
|
+
the request.
|
|
191
|
+
- The round trip is bounded by the per-call `timeout` **times the number of
|
|
192
|
+
calls** in the request.
|
|
193
|
+
|
|
194
|
+
A batching worker must still answer `{"call": [...]}`: `dirsql` gathers a
|
|
195
|
+
statement's values by running it twice. The first run makes one ordinary
|
|
196
|
+
round trip for the function's first value, binds that reply in place of every
|
|
197
|
+
later distinct argument tuple, and queues those tuples into batched requests
|
|
198
|
+
of at most `N`; the second run is served from the replies. A statement that
|
|
199
|
+
never calls a batched function runs once, exactly as before. Values are
|
|
200
|
+
deduplicated along the way, so the [progress line](./cli.md#progress-reporting)
|
|
201
|
+
counts distinct calls, whichever request shape carried them.
|
|
202
|
+
|
|
203
|
+
```toml
|
|
204
|
+
[[dirsql.function]]
|
|
205
|
+
name = "embed"
|
|
206
|
+
args = [1, 2]
|
|
207
|
+
command = "dirsql-plugin-embeddings worker"
|
|
208
|
+
deterministic = true
|
|
209
|
+
timeout = "600s"
|
|
210
|
+
batch = 256
|
|
211
|
+
```
|
|
212
|
+
|
|
176
213
|
## `[[table]]`
|
|
177
214
|
|
|
178
215
|
Each entry maps a glob pattern to a SQL table. A table's columns are exactly
|
|
@@ -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.4.
|
|
7
|
+
version = "0.4.61"
|
|
8
8
|
# abi3 note: the `abi3-py310` pyo3 feature (below) builds ONE stable-ABI
|
|
9
9
|
# `cp310-abi3` wheel per platform that loads on every CPython >= 3.10
|
|
10
10
|
# (matching `pyproject.toml`'s `requires-python = ">=3.10"`), instead of
|
|
@@ -111,6 +111,7 @@ its `embed()` function exactly this way.
|
|
|
111
111
|
| `command` | yes (non-empty) | The worker command. Argv-split with the same no-shell quoting rules as [command hooks](./hooks.md#argv-not-a-shell); runs in the config file's directory. |
|
|
112
112
|
| `deterministic` | no (default `false`) | When `true`, the function is registered with `SQLITE_DETERMINISTIC`, letting SQLite cache and reuse results for identical arguments within a query. Only set it when the worker really is a pure function of its arguments. |
|
|
113
113
|
| `timeout` | no | Per-**call** time bound: a positive integer is whole seconds (`timeout = 600`), a string is an integer suffixed `s` or `ms` (`"600s"`, `"250ms"`). When absent, the function mechanism's own 30-second default applies. |
|
|
114
|
+
| `batch` | no | A positive integer: the most calls one **batched request** carries. When set, a statement's values are gathered and sent to the worker in bulk (see [Batching](#batching)); the worker must then speak the batched protocol shape as well as the single-call one. When absent, the worker only ever sees single calls. |
|
|
114
115
|
|
|
115
116
|
```toml
|
|
116
117
|
[[dirsql.function]]
|
|
@@ -173,6 +174,42 @@ request line in, one response line out, per call:
|
|
|
173
174
|
- **stderr passes through** to `dirsql`'s stderr, so a worker's progress
|
|
174
175
|
bars and download logs reach the terminal.
|
|
175
176
|
|
|
177
|
+
### Batching
|
|
178
|
+
|
|
179
|
+
One round trip per row is the wrong shape for a worker whose cost is
|
|
180
|
+
per-request rather than per-value — an embedding model encodes a list of
|
|
181
|
+
texts in little more time than one. A function declaring `batch = N` is sent
|
|
182
|
+
values in bulk instead:
|
|
183
|
+
|
|
184
|
+
- **Request:** `{"calls": [[<arg>, ...], ...]}` — up to `N` calls, each
|
|
185
|
+
encoded exactly as a single call's argument list.
|
|
186
|
+
- **Response:** `{"results": [<response>, ...]}` — one single-call response
|
|
187
|
+
(`{"ok": ...}` with its optional `"meta"`, or `{"err": ...}`) per call, in
|
|
188
|
+
order. A per-call `{"err": ...}` fails the query the way it would on a
|
|
189
|
+
single call. A top-level `{"err": "message"}` instead fails every call in
|
|
190
|
+
the request.
|
|
191
|
+
- The round trip is bounded by the per-call `timeout` **times the number of
|
|
192
|
+
calls** in the request.
|
|
193
|
+
|
|
194
|
+
A batching worker must still answer `{"call": [...]}`: `dirsql` gathers a
|
|
195
|
+
statement's values by running it twice. The first run makes one ordinary
|
|
196
|
+
round trip for the function's first value, binds that reply in place of every
|
|
197
|
+
later distinct argument tuple, and queues those tuples into batched requests
|
|
198
|
+
of at most `N`; the second run is served from the replies. A statement that
|
|
199
|
+
never calls a batched function runs once, exactly as before. Values are
|
|
200
|
+
deduplicated along the way, so the [progress line](./cli.md#progress-reporting)
|
|
201
|
+
counts distinct calls, whichever request shape carried them.
|
|
202
|
+
|
|
203
|
+
```toml
|
|
204
|
+
[[dirsql.function]]
|
|
205
|
+
name = "embed"
|
|
206
|
+
args = [1, 2]
|
|
207
|
+
command = "dirsql-plugin-embeddings worker"
|
|
208
|
+
deterministic = true
|
|
209
|
+
timeout = "600s"
|
|
210
|
+
batch = 256
|
|
211
|
+
```
|
|
212
|
+
|
|
176
213
|
## `[[table]]`
|
|
177
214
|
|
|
178
215
|
Each entry maps a glob pattern to a SQL table. A table's columns are exactly
|
|
@@ -6,7 +6,7 @@ description = "Ephemeral SQL index over a local directory"
|
|
|
6
6
|
# version stays for the binding crates (publish = false). The literal
|
|
7
7
|
# intentionally lags the published version: putitoutthere rewrites it to
|
|
8
8
|
# the planned release version at build time and never commits it back.
|
|
9
|
-
version = "0.4.
|
|
9
|
+
version = "0.4.61"
|
|
10
10
|
edition.workspace = true
|
|
11
11
|
# Literal `license` (not `license.workspace = true`) because
|
|
12
12
|
# putitoutthere's preflight check reads `[package].license` directly
|
|
@@ -69,7 +69,7 @@ globset = "0.4"
|
|
|
69
69
|
# library consumer never pulls it.
|
|
70
70
|
hex = { version = "0.4", optional = true }
|
|
71
71
|
# Hierarchical `.gitignore` matching for path-table scans (the fd/ripgrep
|
|
72
|
-
# semantics), without adopting its walker: the scan
|
|
72
|
+
# semantics), without adopting its walker: the scan has its own.
|
|
73
73
|
ignore = "0.4"
|
|
74
74
|
indicatif = "0.18.6"
|
|
75
75
|
notify = "7"
|
|
@@ -87,7 +87,6 @@ tokio.workspace = true
|
|
|
87
87
|
tokio-stream = { version = "0.1", features = ["sync"], optional = true }
|
|
88
88
|
toml = "0.8"
|
|
89
89
|
typed-path = "0.12"
|
|
90
|
-
walkdir = "2"
|
|
91
90
|
|
|
92
91
|
[lints.rust]
|
|
93
92
|
# `deny`, not `forbid`: rusqlite's API imposes five unavoidable `unsafe`
|
|
@@ -111,6 +111,7 @@ its `embed()` function exactly this way.
|
|
|
111
111
|
| `command` | yes (non-empty) | The worker command. Argv-split with the same no-shell quoting rules as [command hooks](./hooks.md#argv-not-a-shell); runs in the config file's directory. |
|
|
112
112
|
| `deterministic` | no (default `false`) | When `true`, the function is registered with `SQLITE_DETERMINISTIC`, letting SQLite cache and reuse results for identical arguments within a query. Only set it when the worker really is a pure function of its arguments. |
|
|
113
113
|
| `timeout` | no | Per-**call** time bound: a positive integer is whole seconds (`timeout = 600`), a string is an integer suffixed `s` or `ms` (`"600s"`, `"250ms"`). When absent, the function mechanism's own 30-second default applies. |
|
|
114
|
+
| `batch` | no | A positive integer: the most calls one **batched request** carries. When set, a statement's values are gathered and sent to the worker in bulk (see [Batching](#batching)); the worker must then speak the batched protocol shape as well as the single-call one. When absent, the worker only ever sees single calls. |
|
|
114
115
|
|
|
115
116
|
```toml
|
|
116
117
|
[[dirsql.function]]
|
|
@@ -173,6 +174,42 @@ request line in, one response line out, per call:
|
|
|
173
174
|
- **stderr passes through** to `dirsql`'s stderr, so a worker's progress
|
|
174
175
|
bars and download logs reach the terminal.
|
|
175
176
|
|
|
177
|
+
### Batching
|
|
178
|
+
|
|
179
|
+
One round trip per row is the wrong shape for a worker whose cost is
|
|
180
|
+
per-request rather than per-value — an embedding model encodes a list of
|
|
181
|
+
texts in little more time than one. A function declaring `batch = N` is sent
|
|
182
|
+
values in bulk instead:
|
|
183
|
+
|
|
184
|
+
- **Request:** `{"calls": [[<arg>, ...], ...]}` — up to `N` calls, each
|
|
185
|
+
encoded exactly as a single call's argument list.
|
|
186
|
+
- **Response:** `{"results": [<response>, ...]}` — one single-call response
|
|
187
|
+
(`{"ok": ...}` with its optional `"meta"`, or `{"err": ...}`) per call, in
|
|
188
|
+
order. A per-call `{"err": ...}` fails the query the way it would on a
|
|
189
|
+
single call. A top-level `{"err": "message"}` instead fails every call in
|
|
190
|
+
the request.
|
|
191
|
+
- The round trip is bounded by the per-call `timeout` **times the number of
|
|
192
|
+
calls** in the request.
|
|
193
|
+
|
|
194
|
+
A batching worker must still answer `{"call": [...]}`: `dirsql` gathers a
|
|
195
|
+
statement's values by running it twice. The first run makes one ordinary
|
|
196
|
+
round trip for the function's first value, binds that reply in place of every
|
|
197
|
+
later distinct argument tuple, and queues those tuples into batched requests
|
|
198
|
+
of at most `N`; the second run is served from the replies. A statement that
|
|
199
|
+
never calls a batched function runs once, exactly as before. Values are
|
|
200
|
+
deduplicated along the way, so the [progress line](./cli.md#progress-reporting)
|
|
201
|
+
counts distinct calls, whichever request shape carried them.
|
|
202
|
+
|
|
203
|
+
```toml
|
|
204
|
+
[[dirsql.function]]
|
|
205
|
+
name = "embed"
|
|
206
|
+
args = [1, 2]
|
|
207
|
+
command = "dirsql-plugin-embeddings worker"
|
|
208
|
+
deterministic = true
|
|
209
|
+
timeout = "600s"
|
|
210
|
+
batch = 256
|
|
211
|
+
```
|
|
212
|
+
|
|
176
213
|
## `[[table]]`
|
|
177
214
|
|
|
178
215
|
Each entry maps a glob pattern to a SQL table. A table's columns are exactly
|
|
@@ -49,6 +49,63 @@ impl Placeholder {
|
|
|
49
49
|
value: value.into(),
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
|
+
|
|
53
|
+
/// A placeholder whose value is a filesystem path, handed to the child in
|
|
54
|
+
/// its plain (non-verbatim) form; see [`non_verbatim`].
|
|
55
|
+
pub fn path(name: impl Into<String>, path: &str) -> Self {
|
|
56
|
+
Self::new(name, non_verbatim(path))
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/// The plain form of a Windows verbatim path: `\\?\C:\x` becomes `C:\x` and
|
|
61
|
+
/// `\\?\UNC\server\share\x` becomes `\\server\share\x`. Anything else, and any
|
|
62
|
+
/// verbatim path whose plain spelling would name a different file, is returned
|
|
63
|
+
/// unchanged.
|
|
64
|
+
pub(crate) fn non_verbatim(path: &str) -> String {
|
|
65
|
+
plain_form(path).unwrap_or_else(|| path.to_string())
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
fn plain_form(path: &str) -> Option<String> {
|
|
69
|
+
let rest = path.strip_prefix(r"\\?\")?;
|
|
70
|
+
let (plain, tail) = if let Some(unc) = rest.strip_prefix(r"UNC\") {
|
|
71
|
+
if unc.split('\\').count() < 2 {
|
|
72
|
+
return None;
|
|
73
|
+
}
|
|
74
|
+
(format!(r"\\{unc}"), unc)
|
|
75
|
+
} else {
|
|
76
|
+
let drive = rest.as_bytes();
|
|
77
|
+
let is_drive = drive.len() >= 3
|
|
78
|
+
&& drive[0].is_ascii_alphabetic()
|
|
79
|
+
&& drive[1] == b':'
|
|
80
|
+
&& drive[2] == b'\\';
|
|
81
|
+
if !is_drive {
|
|
82
|
+
return None;
|
|
83
|
+
}
|
|
84
|
+
(rest.to_string(), &rest[3..])
|
|
85
|
+
};
|
|
86
|
+
// Win32 rejects or rewrites these, so the plain spelling would not name
|
|
87
|
+
// the same file; MAX_PATH counts UTF-16 units including the terminator.
|
|
88
|
+
let same_file = (tail.is_empty() || tail.split('\\').all(is_plain_component))
|
|
89
|
+
&& plain.encode_utf16().count() < 260;
|
|
90
|
+
same_file.then_some(plain)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
fn is_plain_component(name: &str) -> bool {
|
|
94
|
+
let invalid_char = |c: char| c < ' ' || r#"/<>:"|?*"#.contains(c);
|
|
95
|
+
let stem = name
|
|
96
|
+
.split_once('.')
|
|
97
|
+
.map_or(name, |(stem, _)| stem)
|
|
98
|
+
.trim_end_matches(' ');
|
|
99
|
+
let reserved = matches!(
|
|
100
|
+
stem.to_ascii_uppercase().as_bytes(),
|
|
101
|
+
b"CON"
|
|
102
|
+
| b"PRN"
|
|
103
|
+
| b"AUX"
|
|
104
|
+
| b"NUL"
|
|
105
|
+
| [b'C', b'O', b'M', b'1'..=b'9']
|
|
106
|
+
| [b'L', b'P', b'T', b'1'..=b'9']
|
|
107
|
+
);
|
|
108
|
+
!name.is_empty() && !name.ends_with(['.', ' ']) && !name.contains(invalid_char) && !reserved
|
|
52
109
|
}
|
|
53
110
|
|
|
54
111
|
/// A successful command run.
|
|
@@ -119,8 +176,8 @@ pub fn run_command(
|
|
|
119
176
|
let argv = build_argv(command, placeholders)?;
|
|
120
177
|
// `build_argv` guarantees a non-empty argv.
|
|
121
178
|
let mut cmd = Command::new(&argv[0]);
|
|
122
|
-
|
|
123
|
-
|
|
179
|
+
push_args(&mut cmd, &argv[1..]);
|
|
180
|
+
cmd.current_dir(cwd)
|
|
124
181
|
.stdout(Stdio::piped())
|
|
125
182
|
.stderr(Stdio::piped())
|
|
126
183
|
.stdin(if stdin_payload.is_some() {
|
|
@@ -193,6 +250,20 @@ pub fn run_command(
|
|
|
193
250
|
}
|
|
194
251
|
}
|
|
195
252
|
|
|
253
|
+
// One fn with cfg blocks rather than two cfg'd fns: cargo-mutants mutates
|
|
254
|
+
// the uncompiled twin too, and no test on a Linux gate can kill that mutant.
|
|
255
|
+
fn push_args(cmd: &mut Command, args: &[String]) {
|
|
256
|
+
#[cfg(windows)]
|
|
257
|
+
{
|
|
258
|
+
use std::os::windows::process::CommandExt;
|
|
259
|
+
for arg in args {
|
|
260
|
+
cmd.raw_arg(quote_windows_arg(arg));
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
#[cfg(not(windows))]
|
|
264
|
+
cmd.args(args);
|
|
265
|
+
}
|
|
266
|
+
|
|
196
267
|
fn spawn_error(program: &str, source: std::io::Error, cwd: &Path) -> CommandError {
|
|
197
268
|
// A missing cwd fails the child's chdir with `NotFound` too.
|
|
198
269
|
if !searched_path(program, source.kind()) || !cwd.is_dir() {
|
|
@@ -244,6 +315,35 @@ pub(crate) fn build_argv(
|
|
|
244
315
|
Ok(argv)
|
|
245
316
|
}
|
|
246
317
|
|
|
318
|
+
/// Quote `arg` for a Windows command line by the MS C-runtime rules, always
|
|
319
|
+
/// wrapped in double quotes: an MSYS/Cygwin child globs, brace-expands and
|
|
320
|
+
/// unescapes any unquoted word, while an MSVCRT child parses the quoted form
|
|
321
|
+
/// back to `arg` exactly.
|
|
322
|
+
#[cfg_attr(not(windows), allow(dead_code))]
|
|
323
|
+
pub(crate) fn quote_windows_arg(arg: &str) -> String {
|
|
324
|
+
let mut out = String::with_capacity(arg.len() + 2);
|
|
325
|
+
out.push('"');
|
|
326
|
+
let mut backslashes = 0;
|
|
327
|
+
for ch in arg.chars() {
|
|
328
|
+
match ch {
|
|
329
|
+
'\\' => backslashes += 1,
|
|
330
|
+
'"' => {
|
|
331
|
+
out.extend(std::iter::repeat_n('\\', backslashes * 2 + 1));
|
|
332
|
+
out.push('"');
|
|
333
|
+
backslashes = 0;
|
|
334
|
+
}
|
|
335
|
+
_ => {
|
|
336
|
+
out.extend(std::iter::repeat_n('\\', backslashes));
|
|
337
|
+
out.push(ch);
|
|
338
|
+
backslashes = 0;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
out.extend(std::iter::repeat_n('\\', backslashes * 2));
|
|
343
|
+
out.push('"');
|
|
344
|
+
out
|
|
345
|
+
}
|
|
346
|
+
|
|
247
347
|
/// Replace every `{name}` in `token` with its placeholder value in a single
|
|
248
348
|
/// left-to-right pass. Injected values are never re-scanned, so an untrusted
|
|
249
349
|
/// value containing `{…}` is inert. Unknown `{…}` sequences are left literal.
|
|
@@ -315,6 +415,117 @@ mod tests {
|
|
|
315
415
|
build_argv(command, placeholders).expect("valid command")
|
|
316
416
|
}
|
|
317
417
|
|
|
418
|
+
fn unchanged(path: &str) {
|
|
419
|
+
assert_eq!(non_verbatim(path), path);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
#[test]
|
|
423
|
+
fn non_verbatim_strips_a_verbatim_drive_path() {
|
|
424
|
+
assert_eq!(
|
|
425
|
+
non_verbatim(r"\\?\C:\Users\runner\in_cwd.txt"),
|
|
426
|
+
r"C:\Users\runner\in_cwd.txt"
|
|
427
|
+
);
|
|
428
|
+
assert_eq!(non_verbatim(r"\\?\d:\x"), r"d:\x");
|
|
429
|
+
assert_eq!(non_verbatim(r"\\?\C:\my file.txt"), r"C:\my file.txt");
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
#[test]
|
|
433
|
+
fn non_verbatim_strips_a_verbatim_drive_root() {
|
|
434
|
+
assert_eq!(non_verbatim(r"\\?\C:\"), r"C:\");
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
#[test]
|
|
438
|
+
fn non_verbatim_rewrites_a_verbatim_unc_path() {
|
|
439
|
+
assert_eq!(
|
|
440
|
+
non_verbatim(r"\\?\UNC\server\share\dir\f.txt"),
|
|
441
|
+
r"\\server\share\dir\f.txt"
|
|
442
|
+
);
|
|
443
|
+
assert_eq!(non_verbatim(r"\\?\UNC\server\share"), r"\\server\share");
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
#[test]
|
|
447
|
+
fn non_verbatim_leaves_plain_paths_unchanged() {
|
|
448
|
+
unchanged(r"C:\Users\runner\f.txt");
|
|
449
|
+
unchanged(r"\\server\share\f.txt");
|
|
450
|
+
unchanged("/tmp/root/f.txt");
|
|
451
|
+
unchanged("relative/f.txt");
|
|
452
|
+
unchanged("");
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
#[test]
|
|
456
|
+
fn non_verbatim_leaves_verbatim_forms_without_a_drive_or_unc_unchanged() {
|
|
457
|
+
unchanged(r"\\?\GLOBALROOT\Device\HarddiskVolume1\f.txt");
|
|
458
|
+
unchanged(r"\\?\Volume{0b1c2d3e-0000-0000-0000-100000000000}\f.txt");
|
|
459
|
+
unchanged(r"\\?\C:");
|
|
460
|
+
unchanged(r"\\?\C:x");
|
|
461
|
+
unchanged(r"\\?\1:\x");
|
|
462
|
+
unchanged(r"\\?\");
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
#[test]
|
|
466
|
+
fn non_verbatim_leaves_an_incomplete_unc_unchanged() {
|
|
467
|
+
unchanged(r"\\?\UNC\");
|
|
468
|
+
unchanged(r"\\?\UNC\server");
|
|
469
|
+
unchanged(r"\\?\UNC\server\");
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
#[test]
|
|
473
|
+
fn non_verbatim_leaves_components_win32_would_reinterpret_unchanged() {
|
|
474
|
+
unchanged(r"\\?\C:\a\..\b");
|
|
475
|
+
unchanged(r"\\?\C:\a\.\b");
|
|
476
|
+
unchanged(r"\\?\C:\a\\b");
|
|
477
|
+
unchanged(r"\\?\C:\a\");
|
|
478
|
+
unchanged(r"\\?\C:\trailing.");
|
|
479
|
+
unchanged(r"\\?\C:\trailing ");
|
|
480
|
+
unchanged(r"\\?\C:\a/b");
|
|
481
|
+
unchanged(r"\\?\C:\a?b");
|
|
482
|
+
unchanged(r"\\?\C:\a*b");
|
|
483
|
+
unchanged(r"\\?\C:\a:b");
|
|
484
|
+
unchanged(r#"\\?\C:\a"b"#);
|
|
485
|
+
unchanged(r"\\?\C:\a<b");
|
|
486
|
+
unchanged(r"\\?\C:\a>b");
|
|
487
|
+
unchanged(r"\\?\C:\a|b");
|
|
488
|
+
unchanged("\\\\?\\C:\\a\u{1}b");
|
|
489
|
+
unchanged(r"\\?\UNC\server\share\..\x");
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
#[test]
|
|
493
|
+
fn non_verbatim_leaves_reserved_device_names_unchanged() {
|
|
494
|
+
for name in [
|
|
495
|
+
"CON", "con", "PRN", "AUX", "NUL", "nul.txt", "NUL .txt", "COM1", "COM9", "LPT1",
|
|
496
|
+
"lpt9.log",
|
|
497
|
+
] {
|
|
498
|
+
unchanged(&format!(r"\\?\C:\dir\{name}"));
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
#[test]
|
|
503
|
+
fn non_verbatim_strips_names_that_only_resemble_device_names() {
|
|
504
|
+
for name in [
|
|
505
|
+
"CONSOLE", "NULL.txt", "COM0", "COM10", "LPT", "xCON", "AUXa",
|
|
506
|
+
] {
|
|
507
|
+
assert_eq!(
|
|
508
|
+
non_verbatim(&format!(r"\\?\C:\dir\{name}")),
|
|
509
|
+
format!(r"C:\dir\{name}")
|
|
510
|
+
);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
#[test]
|
|
515
|
+
fn non_verbatim_strips_up_to_max_path_and_no_further() {
|
|
516
|
+
let fits = format!(r"C:\{}", "a".repeat(256));
|
|
517
|
+
assert_eq!(fits.encode_utf16().count(), 259);
|
|
518
|
+
assert_eq!(non_verbatim(&format!(r"\\?\{fits}")), fits);
|
|
519
|
+
unchanged(&format!(r"\\?\C:\{}", "a".repeat(257)));
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
#[test]
|
|
523
|
+
fn path_placeholder_carries_the_non_verbatim_value() {
|
|
524
|
+
let placeholder = Placeholder::path("path", r"\\?\C:\a\f.txt");
|
|
525
|
+
assert_eq!(placeholder.name, "path");
|
|
526
|
+
assert_eq!(placeholder.value, r"C:\a\f.txt");
|
|
527
|
+
}
|
|
528
|
+
|
|
318
529
|
#[test]
|
|
319
530
|
fn splits_on_whitespace_without_placeholders() {
|
|
320
531
|
assert_eq!(argv("echo hello world", &[]), ["echo", "hello", "world"]);
|
|
@@ -402,6 +613,47 @@ mod tests {
|
|
|
402
613
|
assert_eq!(argv("run", &[Placeholder::new("path", "/tmp/x")]), ["run"]);
|
|
403
614
|
}
|
|
404
615
|
|
|
616
|
+
#[test]
|
|
617
|
+
fn windows_quoting_wraps_a_plain_arg_in_quotes() {
|
|
618
|
+
assert_eq!(quote_windows_arg("hello"), r#""hello""#);
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
#[test]
|
|
622
|
+
fn windows_quoting_keeps_an_empty_arg_as_an_empty_quoted_word() {
|
|
623
|
+
assert_eq!(quote_windows_arg(""), r#""""#);
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
#[test]
|
|
627
|
+
fn windows_quoting_quotes_glob_and_brace_characters() {
|
|
628
|
+
assert_eq!(quote_windows_arg("[{}]"), r#""[{}]""#);
|
|
629
|
+
assert_eq!(quote_windows_arg("a?b*c"), r#""a?b*c""#);
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
#[test]
|
|
633
|
+
fn windows_quoting_leaves_backslashes_not_before_a_quote_alone() {
|
|
634
|
+
assert_eq!(
|
|
635
|
+
quote_windows_arg(r"\\?\C:\Users\x.txt"),
|
|
636
|
+
r#""\\?\C:\Users\x.txt""#
|
|
637
|
+
);
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
#[test]
|
|
641
|
+
fn windows_quoting_doubles_trailing_backslashes_before_the_closing_quote() {
|
|
642
|
+
assert_eq!(quote_windows_arg(r"C:\dir\"), r#""C:\dir\\""#);
|
|
643
|
+
assert_eq!(quote_windows_arg(r"a\\"), r#""a\\\\""#);
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
#[test]
|
|
647
|
+
fn windows_quoting_escapes_embedded_quotes() {
|
|
648
|
+
assert_eq!(quote_windows_arg(r#"[{"n":1}]"#), r#""[{\"n\":1}]""#);
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
#[test]
|
|
652
|
+
fn windows_quoting_doubles_backslashes_before_an_embedded_quote() {
|
|
653
|
+
assert_eq!(quote_windows_arg(r#"a\"b"#), r#""a\\\"b""#);
|
|
654
|
+
assert_eq!(quote_windows_arg(r#"a\\"b"#), r#""a\\\\\"b""#);
|
|
655
|
+
}
|
|
656
|
+
|
|
405
657
|
#[test]
|
|
406
658
|
fn extract_payload_takes_the_last_non_empty_line() {
|
|
407
659
|
assert_eq!(
|
|
@@ -45,6 +45,9 @@ pub enum ConfigError {
|
|
|
45
45
|
)]
|
|
46
46
|
InvalidFunctionTimeout { name: String, value: String },
|
|
47
47
|
|
|
48
|
+
#[error("[[dirsql.function]] '{name}': 'batch' must be a positive integer, got {value}")]
|
|
49
|
+
InvalidFunctionBatch { name: String, value: String },
|
|
50
|
+
|
|
48
51
|
#[error("Field '{0}' must not be empty")]
|
|
49
52
|
EmptyField(&'static str),
|
|
50
53
|
|
|
@@ -212,6 +215,11 @@ pub struct FunctionSpec {
|
|
|
212
215
|
/// Optional per-round-trip-call timeout, overriding the function
|
|
213
216
|
/// mechanism's own 30-second default.
|
|
214
217
|
pub timeout: Option<Duration>,
|
|
218
|
+
/// Optional cap on the calls one batched request carries. When set, a
|
|
219
|
+
/// statement's values are sent to the worker in bulk (see
|
|
220
|
+
/// `dirsql::functions`); when absent the worker only ever sees single
|
|
221
|
+
/// calls.
|
|
222
|
+
pub batch: Option<usize>,
|
|
215
223
|
}
|
|
216
224
|
|
|
217
225
|
/// Configuration for a single table.
|
|
@@ -267,6 +275,9 @@ struct RawFunction {
|
|
|
267
275
|
command: Option<String>,
|
|
268
276
|
deterministic: Option<bool>,
|
|
269
277
|
timeout: Option<RawFunctionTimeout>,
|
|
278
|
+
// Any shape, so a non-integer hits the same actionable error as a
|
|
279
|
+
// non-positive one.
|
|
280
|
+
batch: Option<toml::Value>,
|
|
270
281
|
}
|
|
271
282
|
|
|
272
283
|
/// The `timeout` key accepts positive whole seconds (`timeout = 600`) or a
|
|
@@ -417,12 +428,27 @@ fn parse_function(raw: RawFunction) -> Result<FunctionSpec> {
|
|
|
417
428
|
Some(raw_timeout) => Some(parse_function_timeout(&name, &raw_timeout)?),
|
|
418
429
|
};
|
|
419
430
|
|
|
431
|
+
let batch = match raw.batch {
|
|
432
|
+
None => None,
|
|
433
|
+
Some(value) => Some(
|
|
434
|
+
value
|
|
435
|
+
.as_integer()
|
|
436
|
+
.and_then(|n| usize::try_from(n).ok())
|
|
437
|
+
.filter(|n| *n > 0)
|
|
438
|
+
.ok_or_else(|| ConfigError::InvalidFunctionBatch {
|
|
439
|
+
name: name.clone(),
|
|
440
|
+
value: value.to_string(),
|
|
441
|
+
})?,
|
|
442
|
+
),
|
|
443
|
+
};
|
|
444
|
+
|
|
420
445
|
Ok(FunctionSpec {
|
|
421
446
|
name,
|
|
422
447
|
args,
|
|
423
448
|
command,
|
|
424
449
|
deterministic: raw.deterministic.unwrap_or(false),
|
|
425
450
|
timeout,
|
|
451
|
+
batch,
|
|
426
452
|
})
|
|
427
453
|
}
|
|
428
454
|
|
|
@@ -1162,6 +1188,7 @@ args = [1, 2]
|
|
|
1162
1188
|
command = "dirsql-plugin-embeddings worker"
|
|
1163
1189
|
deterministic = true
|
|
1164
1190
|
timeout = "600s"
|
|
1191
|
+
batch = 64
|
|
1165
1192
|
"#;
|
|
1166
1193
|
let config = load_config_str(toml).unwrap();
|
|
1167
1194
|
assert_eq!(
|
|
@@ -1172,6 +1199,7 @@ timeout = "600s"
|
|
|
1172
1199
|
command: "dirsql-plugin-embeddings worker".to_string(),
|
|
1173
1200
|
deterministic: true,
|
|
1174
1201
|
timeout: Some(Duration::from_secs(600)),
|
|
1202
|
+
batch: Some(64),
|
|
1175
1203
|
}]
|
|
1176
1204
|
);
|
|
1177
1205
|
}
|
|
@@ -1187,6 +1215,36 @@ command = "worker"
|
|
|
1187
1215
|
let config = load_config_str(toml).unwrap();
|
|
1188
1216
|
assert!(!config.functions[0].deterministic);
|
|
1189
1217
|
assert!(config.functions[0].timeout.is_none());
|
|
1218
|
+
assert!(config.functions[0].batch.is_none());
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
/// `batch = 0` would send empty requests, a negative cap means nothing,
|
|
1222
|
+
/// and a string is not a count; each is the one actionable error.
|
|
1223
|
+
#[test]
|
|
1224
|
+
fn function_batch_rejects_anything_but_a_positive_integer() {
|
|
1225
|
+
for value in ["0", "-4", "\"many\"", "2.5"] {
|
|
1226
|
+
let toml = format!(
|
|
1227
|
+
r#"
|
|
1228
|
+
[[dirsql.function]]
|
|
1229
|
+
name = "f"
|
|
1230
|
+
args = [1]
|
|
1231
|
+
command = "worker"
|
|
1232
|
+
batch = {value}
|
|
1233
|
+
"#
|
|
1234
|
+
);
|
|
1235
|
+
let err = load_config_str(&toml).unwrap_err();
|
|
1236
|
+
match &err {
|
|
1237
|
+
ConfigError::InvalidFunctionBatch { name, value: got } => {
|
|
1238
|
+
assert_eq!(name, "f");
|
|
1239
|
+
assert_eq!(got, value);
|
|
1240
|
+
}
|
|
1241
|
+
other => panic!("batch = {value}: expected InvalidFunctionBatch, got {other:?}"),
|
|
1242
|
+
}
|
|
1243
|
+
assert_eq!(
|
|
1244
|
+
err.to_string(),
|
|
1245
|
+
format!("[[dirsql.function]] 'f': 'batch' must be a positive integer, got {value}")
|
|
1246
|
+
);
|
|
1247
|
+
}
|
|
1190
1248
|
}
|
|
1191
1249
|
|
|
1192
1250
|
#[test]
|