dirsql 0.4.34__tar.gz → 0.4.35__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.34 → dirsql-0.4.35}/Cargo.lock +2 -2
- {dirsql-0.4.34 → dirsql-0.4.35}/PKG-INFO +1 -1
- {dirsql-0.4.34 → dirsql-0.4.35}/docs/reference/cli.md +18 -0
- {dirsql-0.4.34/packages/rust → dirsql-0.4.35}/docs/reference/config.md +9 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/python/Cargo.toml +1 -1
- {dirsql-0.4.34/packages/rust → dirsql-0.4.35/packages/python}/docs/reference/cli.md +18 -0
- {dirsql-0.4.34 → dirsql-0.4.35/packages/python}/docs/reference/config.md +9 -0
- dirsql-0.4.35/packages/python/e2e-attestations/claude-tackle-1034-cachesplit.json +7 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/python/testing-conventions.toml +1 -1
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/rust/Cargo.toml +1 -1
- {dirsql-0.4.34/packages/python → dirsql-0.4.35/packages/rust}/docs/reference/cli.md +18 -0
- {dirsql-0.4.34/packages/python → dirsql-0.4.35/packages/rust}/docs/reference/config.md +9 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/rust/src/functions.rs +204 -32
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/rust/src/progress.rs +146 -21
- {dirsql-0.4.34 → dirsql-0.4.35}/Cargo.toml +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/README.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/dirsql/__init__.py +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/dirsql/_async.py +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/dirsql/_dirsql.pyi +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/dirsql/cli/__init__.py +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/dirsql/cli/discover_plugins/__init__.py +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/dirsql/cli/discover_plugins/discovered_fragments.py +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/dirsql/cli/discover_plugins/discovery_disabled.py +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/dirsql/cli/discover_plugins/fragment_path.py +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/dirsql/cli/discover_plugins/user_passed_config.py +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/dirsql/cli/discover_plugins/with_discovered_plugins.py +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/dirsql/cli/main.py +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/dirsql/cli/resolve_config_extensions.py +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/dirsql/py.typed +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/dirsql/resolve_config_extensions.py +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/dirsql/resolve_extension.py +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/docs/.claude/CLAUDE.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/docs/.vitepress/config.ts +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/docs/.vitepress/theme/index.ts +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/docs/.vitepress/theme/lang.ts +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/docs/AGENTS.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/docs/explanation.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/docs/getting-started.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/docs/howto/columns-from-paths.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/docs/howto/define-tables.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/docs/howto/embed.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/docs/howto/extract-from-contents.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/docs/howto/load-extension.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/docs/howto/parse-files-into-columns.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/docs/howto/persist.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/docs/howto/query-json.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/docs/howto/query-without-config.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/docs/howto/react-to-changes.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/docs/howto/search-by-meaning.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/docs/howto/search-indexes.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/docs/howto/skip-files.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/docs/howto/write-a-plugin.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/docs/index.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/docs/package.json +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/docs/plugins.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/docs/pnpm-lock.yaml +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/docs/pnpm-workspace.yaml +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/docs/reference/columns.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/docs/reference/hooks.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/docs/reference/http-api.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/docs/reference/path-tables.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/docs/reference/sdk.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/python/CHANGELOG.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/python/MIGRATIONS.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/python/README.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/python/changelog.d/2026-07-13-drop-cwd-config-auto-detect.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/python/changelog.d/2026-07-13-plugin-discovery.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/python/changelog.d/2026-07-13-rename-extract-to-on-file.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/python/changelog.d/2026-07-13-repeatable-config.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/python/changelog.d/2026-07-13-sdk-default-baked-in-config.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/python/changelog.d/2026-07-20-drop-implicit-files-table.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/python/changelog.d/2026-07-21-remove-glob-captures.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/python/changelog.d/2026-07-24-hookless-table-config-error.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/python/changelog.d/2026-07-24-remove-stat-fact-injection.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/python/changelog.d/2026-07-29-restore-python-310.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/python/changelog.d/2026-08-02-release-profile.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/python/changelog.d/2026-08-02-scan-failures.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/python/changelog.d/2026-08-03-discovery-ext-resolution.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/python/changelog.d/2026-08-03-manylinux-dynamic-binary.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/python/changelog.d/2026-08-03-python-no-ignore.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/python/changelog.d/2026-08-04-cast-lints.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/python/changelog.d/2026-08-04-cli-in-process.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/python/changelog.d/2026-08-20-curtaincall-dev-dependency.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/python/changelog.d/2026-08-20-declared-table-name.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/python/changelog.d/README.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/python/conftest.py +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/python/docs/.claude/CLAUDE.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/python/docs/.vitepress/config.ts +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/python/docs/.vitepress/theme/index.ts +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/python/docs/.vitepress/theme/lang.ts +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/python/docs/AGENTS.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/python/docs/explanation.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/python/docs/getting-started.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/python/docs/howto/columns-from-paths.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/python/docs/howto/define-tables.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/python/docs/howto/embed.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/python/docs/howto/extract-from-contents.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/python/docs/howto/load-extension.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/python/docs/howto/parse-files-into-columns.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/python/docs/howto/persist.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/python/docs/howto/query-json.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/python/docs/howto/query-without-config.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/python/docs/howto/react-to-changes.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/python/docs/howto/search-by-meaning.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/python/docs/howto/search-indexes.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/python/docs/howto/skip-files.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/python/docs/howto/write-a-plugin.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/python/docs/index.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/python/docs/package.json +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/python/docs/plugins.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/python/docs/pnpm-lock.yaml +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/python/docs/pnpm-workspace.yaml +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/python/docs/reference/columns.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/python/docs/reference/hooks.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/python/docs/reference/http-api.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/python/docs/reference/path-tables.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/python/docs/reference/sdk.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/python/e2e-attestations/cc-keen-knuth-f11gyq.json +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/python/e2e-attestations/claude-819-oneshot-timeout.json +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/python/e2e-attestations/claude-827-unquote-doubled-quotes.json +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/python/e2e-attestations/claude-epic-953-slice2-reedline.json +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/python/e2e-attestations/claude-epic-953-slice3-format.json +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/python/e2e-attestations/claude-epic-953-stacked-prs-x59wm3.json +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/python/e2e-attestations/claude-github-issue-825-e4mhhp.json +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/python/e2e-attestations/claude-issue-951-0eease.json +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/python/e2e-attestations/claude-issue-962-0jjrfv.json +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/python/e2e-attestations/claude-issue-986-red-test-e7sjrb.json +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/python/e2e-attestations/claude-tackle-956-xzen74.json +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/python/e2e-attestations/claude-tackle-957-lrm0z6-worker-calls.json +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/python/e2e-attestations/claude-tackle-957-lrm0z6.json +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/python/e2e-attestations/claude-tackle-975-n8mc2h.json +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/python/migrations.d/2026-07-13-drop-cwd-config-auto-detect.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/python/migrations.d/2026-07-13-rename-extract-to-on-file.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/python/migrations.d/2026-07-13-sdk-default-baked-in-config.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/python/migrations.d/2026-07-20-drop-implicit-files-table.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/python/migrations.d/2026-07-21-remove-glob-captures.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/python/migrations.d/2026-07-24-hookless-table-config-error.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/python/migrations.d/2026-07-24-remove-stat-fact-injection.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/python/migrations.d/2026-07-29-restore-python-310.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/python/migrations.d/2026-08-04-cli-in-process.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/python/migrations.d/2026-08-20-declared-table-name.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/python/migrations.d/README.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/python/src/lib.rs +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/python/tests/__init__.py +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/python/tests/conftest.py +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/python/tests/e2e/__init__.py +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/python/tests/e2e/fixtures/dirsql_plugin_fixture/__init__.py +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/python/tests/e2e/fixtures/dirsql_plugin_fixture/dirsql.toml +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/python/tests/integration/__init__.py +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/python/tests/integration/binding/__init__.py +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/python/tests/integration/hermetic/__init__.py +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/rust/CHANGELOG.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/rust/MIGRATIONS.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/rust/README.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/rust/benches/db_bench.rs +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/rust/benches/differ_bench.rs +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/rust/benches/matcher_bench.rs +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/rust/benches/scanner_bench.rs +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/rust/docs/explanation.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/rust/docs/getting-started.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/rust/docs/howto/columns-from-paths.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/rust/docs/howto/define-tables.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/rust/docs/howto/embed.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/rust/docs/howto/extract-from-contents.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/rust/docs/howto/load-extension.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/rust/docs/howto/parse-files-into-columns.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/rust/docs/howto/persist.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/rust/docs/howto/query-json.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/rust/docs/howto/query-without-config.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/rust/docs/howto/react-to-changes.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/rust/docs/howto/search-by-meaning.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/rust/docs/howto/search-indexes.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/rust/docs/howto/skip-files.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/rust/docs/howto/write-a-plugin.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/rust/docs/index.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/rust/docs/plugins.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/rust/docs/reference/columns.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/rust/docs/reference/hooks.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/rust/docs/reference/http-api.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/rust/docs/reference/path-tables.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/rust/docs/reference/sdk.md +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/rust/src/bin/dirsql.rs +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/rust/src/cli/execute.rs +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/rust/src/cli/init.rs +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/rust/src/cli/mod.rs +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/rust/src/cli/repl.rs +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/rust/src/cli/router.rs +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/rust/src/cli/run.rs +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/rust/src/cli/serialize.rs +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/rust/src/cli/server.rs +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/rust/src/cli/table.rs +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/rust/src/command.rs +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/rust/src/config.rs +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/rust/src/db.rs +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/rust/src/default_config.toml +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/rust/src/differ.rs +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/rust/src/infer.rs +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/rust/src/lib.rs +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/rust/src/matcher.rs +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/rust/src/parsed_cache.rs +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/rust/src/parsed_vtab.rs +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/rust/src/path_table.rs +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/rust/src/persist.rs +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/rust/src/scanner.rs +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/rust/src/sql_literal.rs +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/rust/src/vtab.rs +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/packages/rust/src/watcher.rs +0 -0
- {dirsql-0.4.34 → dirsql-0.4.35}/pyproject.toml +0 -0
|
@@ -540,7 +540,7 @@ checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8"
|
|
|
540
540
|
|
|
541
541
|
[[package]]
|
|
542
542
|
name = "dirsql"
|
|
543
|
-
version = "0.4.
|
|
543
|
+
version = "0.4.35"
|
|
544
544
|
dependencies = [
|
|
545
545
|
"assert_cmd",
|
|
546
546
|
"axum",
|
|
@@ -583,7 +583,7 @@ dependencies = [
|
|
|
583
583
|
|
|
584
584
|
[[package]]
|
|
585
585
|
name = "dirsql-py-ext"
|
|
586
|
-
version = "0.4.
|
|
586
|
+
version = "0.4.35"
|
|
587
587
|
dependencies = [
|
|
588
588
|
"dirsql",
|
|
589
589
|
"pyo3",
|
|
@@ -498,6 +498,24 @@ and, when the query ends:
|
|
|
498
498
|
dirsql: ran 41231 worker calls in 2m41s
|
|
499
499
|
```
|
|
500
500
|
|
|
501
|
+
A worker that keeps a cache of its own can say so per response
|
|
502
|
+
(`{"meta": {"cached": true}}` — see [Worker
|
|
503
|
+
protocol](./config.md#worker-protocol)), and those round trips are then split
|
|
504
|
+
out, because the number that answers *"will this be fast next time"* is not
|
|
505
|
+
the total:
|
|
506
|
+
|
|
507
|
+
```
|
|
508
|
+
dirsql: running 9204 worker calls (8811 cached)
|
|
509
|
+
dirsql: ran 41231 worker calls in 2m41s (38104 cached)
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
Both figures count **worker round trips, not rows**. A function declared
|
|
513
|
+
`deterministic = true` lets SQLite reuse one answer for identical arguments
|
|
514
|
+
within a query, so repeated content never reaches the worker at all and both
|
|
515
|
+
numbers sit below the row count on a corpus with duplicates. The split covers
|
|
516
|
+
only what the worker itself reports serving from cache. A worker that sends no
|
|
517
|
+
`meta` gets the unsplit line above, not a `(0 cached)`.
|
|
518
|
+
|
|
501
519
|
A query that calls no worker prints nothing, whatever the setting — the
|
|
502
520
|
reporting only ever speaks when there is something to report.
|
|
503
521
|
|
|
@@ -161,6 +161,15 @@ request line in, one response line out, per call:
|
|
|
161
161
|
JSON-text vectors) — or `{"err": "message"}`, which **fails the query**
|
|
162
162
|
with that message. An `{"err": ...}` response leaves the healthy worker
|
|
163
163
|
running; only transport failures (timeout, crash) recycle it.
|
|
164
|
+
- **Optional `"meta"`**, alongside `"ok"`: a second top-level object carrying
|
|
165
|
+
facts about how the answer was produced rather than what it is. One key is
|
|
166
|
+
read today — `{"ok": [...], "meta": {"cached": true}}` says the worker
|
|
167
|
+
answered from a cache of its own, which dirsql reports as the `(N cached)`
|
|
168
|
+
split on its [worker-call progress line](./cli.md#progress-reporting). The
|
|
169
|
+
field is **optional and advisory**: a worker that omits it is a worker that
|
|
170
|
+
reports no cache hits, keys dirsql does not recognize are ignored (they
|
|
171
|
+
always have been), and a `meta` of an unexpected shape is ignored too rather
|
|
172
|
+
than failing a query over a progress counter.
|
|
164
173
|
- **stderr passes through** to `dirsql`'s stderr, so a worker's progress
|
|
165
174
|
bars and download logs reach the terminal.
|
|
166
175
|
|
|
@@ -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.35"
|
|
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
|
|
@@ -498,6 +498,24 @@ and, when the query ends:
|
|
|
498
498
|
dirsql: ran 41231 worker calls in 2m41s
|
|
499
499
|
```
|
|
500
500
|
|
|
501
|
+
A worker that keeps a cache of its own can say so per response
|
|
502
|
+
(`{"meta": {"cached": true}}` — see [Worker
|
|
503
|
+
protocol](./config.md#worker-protocol)), and those round trips are then split
|
|
504
|
+
out, because the number that answers *"will this be fast next time"* is not
|
|
505
|
+
the total:
|
|
506
|
+
|
|
507
|
+
```
|
|
508
|
+
dirsql: running 9204 worker calls (8811 cached)
|
|
509
|
+
dirsql: ran 41231 worker calls in 2m41s (38104 cached)
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
Both figures count **worker round trips, not rows**. A function declared
|
|
513
|
+
`deterministic = true` lets SQLite reuse one answer for identical arguments
|
|
514
|
+
within a query, so repeated content never reaches the worker at all and both
|
|
515
|
+
numbers sit below the row count on a corpus with duplicates. The split covers
|
|
516
|
+
only what the worker itself reports serving from cache. A worker that sends no
|
|
517
|
+
`meta` gets the unsplit line above, not a `(0 cached)`.
|
|
518
|
+
|
|
501
519
|
A query that calls no worker prints nothing, whatever the setting — the
|
|
502
520
|
reporting only ever speaks when there is something to report.
|
|
503
521
|
|
|
@@ -161,6 +161,15 @@ request line in, one response line out, per call:
|
|
|
161
161
|
JSON-text vectors) — or `{"err": "message"}`, which **fails the query**
|
|
162
162
|
with that message. An `{"err": ...}` response leaves the healthy worker
|
|
163
163
|
running; only transport failures (timeout, crash) recycle it.
|
|
164
|
+
- **Optional `"meta"`**, alongside `"ok"`: a second top-level object carrying
|
|
165
|
+
facts about how the answer was produced rather than what it is. One key is
|
|
166
|
+
read today — `{"ok": [...], "meta": {"cached": true}}` says the worker
|
|
167
|
+
answered from a cache of its own, which dirsql reports as the `(N cached)`
|
|
168
|
+
split on its [worker-call progress line](./cli.md#progress-reporting). The
|
|
169
|
+
field is **optional and advisory**: a worker that omits it is a worker that
|
|
170
|
+
reports no cache hits, keys dirsql does not recognize are ignored (they
|
|
171
|
+
always have been), and a `meta` of an unexpected shape is ignored too rather
|
|
172
|
+
than failing a query over a progress counter.
|
|
164
173
|
- **stderr passes through** to `dirsql`'s stderr, so a worker's progress
|
|
165
174
|
bars and download logs reach the terminal.
|
|
166
175
|
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
extra_scope = ["packages/rust/src", "packages/python/src"]
|
|
11
11
|
|
|
12
12
|
[python]
|
|
13
|
-
one_function_per_file = { max_lines =
|
|
13
|
+
one_function_per_file = { max_lines = 16 }
|
|
14
14
|
|
|
15
15
|
# Same floor as the root file, which this lane no longer reads.
|
|
16
16
|
[python.coverage]
|
|
@@ -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.35"
|
|
10
10
|
edition.workspace = true
|
|
11
11
|
# Literal `license` (not `license.workspace = true`) because
|
|
12
12
|
# putitoutthere's preflight check reads `[package].license` directly
|
|
@@ -498,6 +498,24 @@ and, when the query ends:
|
|
|
498
498
|
dirsql: ran 41231 worker calls in 2m41s
|
|
499
499
|
```
|
|
500
500
|
|
|
501
|
+
A worker that keeps a cache of its own can say so per response
|
|
502
|
+
(`{"meta": {"cached": true}}` — see [Worker
|
|
503
|
+
protocol](./config.md#worker-protocol)), and those round trips are then split
|
|
504
|
+
out, because the number that answers *"will this be fast next time"* is not
|
|
505
|
+
the total:
|
|
506
|
+
|
|
507
|
+
```
|
|
508
|
+
dirsql: running 9204 worker calls (8811 cached)
|
|
509
|
+
dirsql: ran 41231 worker calls in 2m41s (38104 cached)
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
Both figures count **worker round trips, not rows**. A function declared
|
|
513
|
+
`deterministic = true` lets SQLite reuse one answer for identical arguments
|
|
514
|
+
within a query, so repeated content never reaches the worker at all and both
|
|
515
|
+
numbers sit below the row count on a corpus with duplicates. The split covers
|
|
516
|
+
only what the worker itself reports serving from cache. A worker that sends no
|
|
517
|
+
`meta` gets the unsplit line above, not a `(0 cached)`.
|
|
518
|
+
|
|
501
519
|
A query that calls no worker prints nothing, whatever the setting — the
|
|
502
520
|
reporting only ever speaks when there is something to report.
|
|
503
521
|
|
|
@@ -161,6 +161,15 @@ request line in, one response line out, per call:
|
|
|
161
161
|
JSON-text vectors) — or `{"err": "message"}`, which **fails the query**
|
|
162
162
|
with that message. An `{"err": ...}` response leaves the healthy worker
|
|
163
163
|
running; only transport failures (timeout, crash) recycle it.
|
|
164
|
+
- **Optional `"meta"`**, alongside `"ok"`: a second top-level object carrying
|
|
165
|
+
facts about how the answer was produced rather than what it is. One key is
|
|
166
|
+
read today — `{"ok": [...], "meta": {"cached": true}}` says the worker
|
|
167
|
+
answered from a cache of its own, which dirsql reports as the `(N cached)`
|
|
168
|
+
split on its [worker-call progress line](./cli.md#progress-reporting). The
|
|
169
|
+
field is **optional and advisory**: a worker that omits it is a worker that
|
|
170
|
+
reports no cache hits, keys dirsql does not recognize are ignored (they
|
|
171
|
+
always have been), and a `meta` of an unexpected shape is ignored too rather
|
|
172
|
+
than failing a query over a progress counter.
|
|
164
173
|
- **stderr passes through** to `dirsql`'s stderr, so a worker's progress
|
|
165
174
|
bars and download logs reach the terminal.
|
|
166
175
|
|
|
@@ -17,7 +17,12 @@
|
|
|
17
17
|
//! `{"$bytes": "<base64>"}`.
|
|
18
18
|
//! - Response: `{"ok": <value>}` (same scalar encodings; a JSON array or any
|
|
19
19
|
//! other object is bound as TEXT, its JSON text) or `{"err": "message"}`,
|
|
20
|
-
//! which fails the query with that message.
|
|
20
|
+
//! which fails the query with that message. An `{"ok": ...}` response may
|
|
21
|
+
//! carry an optional `"meta"` object alongside it; the one key read today is
|
|
22
|
+
//! `{"meta": {"cached": true}}`, which says the worker answered from its own
|
|
23
|
+
//! cache and feeds the progress line's cache split. `meta` is advisory —
|
|
24
|
+
//! every other key, and a `meta` of a shape this parser does not recognize,
|
|
25
|
+
//! is ignored rather than failing the query.
|
|
21
26
|
//! - The worker's stderr is inherited, passing straight through to dirsql's
|
|
22
27
|
//! stderr.
|
|
23
28
|
//!
|
|
@@ -85,9 +90,13 @@ pub(crate) fn register_all(
|
|
|
85
90
|
args.push(Value::from(rusqlite::types::Value::from(ctx.get_raw(i))));
|
|
86
91
|
}
|
|
87
92
|
calls.record();
|
|
88
|
-
worker
|
|
93
|
+
let reply = worker
|
|
89
94
|
.call(&args)
|
|
90
|
-
.map_err(|message| rusqlite::Error::UserFunctionError(message.into()))
|
|
95
|
+
.map_err(|message| rusqlite::Error::UserFunctionError(message.into()))?;
|
|
96
|
+
if reply.cached {
|
|
97
|
+
calls.mark_cached();
|
|
98
|
+
}
|
|
99
|
+
Ok(reply.value)
|
|
91
100
|
},
|
|
92
101
|
)?;
|
|
93
102
|
}
|
|
@@ -115,6 +124,10 @@ pub(crate) struct CallReporter {
|
|
|
115
124
|
|
|
116
125
|
struct CallState {
|
|
117
126
|
count: u64,
|
|
127
|
+
/// How many of those round trips the worker flagged as cache hits. Core
|
|
128
|
+
/// cannot derive this: a cache hit *is* a round trip, so only the worker
|
|
129
|
+
/// knows which of its answers cost anything.
|
|
130
|
+
cached: u64,
|
|
118
131
|
progress: Box<dyn CallProgress>,
|
|
119
132
|
}
|
|
120
133
|
|
|
@@ -125,7 +138,11 @@ impl CallReporter {
|
|
|
125
138
|
|
|
126
139
|
fn with_progress(progress: Box<dyn CallProgress>) -> Self {
|
|
127
140
|
Self {
|
|
128
|
-
state: Mutex::new(CallState {
|
|
141
|
+
state: Mutex::new(CallState {
|
|
142
|
+
count: 0,
|
|
143
|
+
cached: 0,
|
|
144
|
+
progress,
|
|
145
|
+
}),
|
|
129
146
|
}
|
|
130
147
|
}
|
|
131
148
|
|
|
@@ -135,29 +152,48 @@ impl CallReporter {
|
|
|
135
152
|
pub(crate) fn phase(&self) -> CallPhase<'_> {
|
|
136
153
|
if let Ok(mut state) = self.state.lock() {
|
|
137
154
|
state.count = 0;
|
|
155
|
+
state.cached = 0;
|
|
138
156
|
state.progress.restart();
|
|
139
157
|
}
|
|
140
158
|
CallPhase(self)
|
|
141
159
|
}
|
|
142
160
|
|
|
143
|
-
/// Record one round trip
|
|
144
|
-
///
|
|
161
|
+
/// Record one round trip, before it is made — a slow worker's first call
|
|
162
|
+
/// is exactly when a user most wants the line to appear. A poisoned mutex
|
|
163
|
+
/// costs the counter, never the call: reporting is not worth failing a
|
|
164
|
+
/// query over.
|
|
145
165
|
fn record(&self) {
|
|
146
166
|
if let Ok(mut state) = self.state.lock() {
|
|
147
167
|
state.count += 1;
|
|
148
|
-
|
|
149
|
-
|
|
168
|
+
state.report();
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/// Mark a round trip that has come back as one the worker served from its
|
|
173
|
+
/// own cache. Separate from [`record`](Self::record) because cachedness is
|
|
174
|
+
/// only knowable once the response is in hand.
|
|
175
|
+
fn mark_cached(&self) {
|
|
176
|
+
if let Ok(mut state) = self.state.lock() {
|
|
177
|
+
state.cached += 1;
|
|
178
|
+
state.report();
|
|
150
179
|
}
|
|
151
180
|
}
|
|
152
181
|
|
|
153
182
|
fn end(&self) {
|
|
154
183
|
if let Ok(mut state) = self.state.lock() {
|
|
155
|
-
let count = state.count;
|
|
156
|
-
state.progress.finish(count);
|
|
184
|
+
let (count, cached) = (state.count, state.cached);
|
|
185
|
+
state.progress.finish(count, cached);
|
|
157
186
|
}
|
|
158
187
|
}
|
|
159
188
|
}
|
|
160
189
|
|
|
190
|
+
impl CallState {
|
|
191
|
+
fn report(&mut self) {
|
|
192
|
+
let (count, cached) = (self.count, self.cached);
|
|
193
|
+
self.progress.update(count, cached);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
161
197
|
/// The open phase. Ends when it drops.
|
|
162
198
|
pub(crate) struct CallPhase<'a>(&'a CallReporter);
|
|
163
199
|
|
|
@@ -241,7 +277,7 @@ impl Worker {
|
|
|
241
277
|
/// with. A transport failure (spawn error, crash, timeout) drops the
|
|
242
278
|
/// worker so the next call starts fresh; a protocol-level `{"err": ...}`
|
|
243
279
|
/// leaves the healthy worker running.
|
|
244
|
-
fn call(&self, args: &[Value]) -> Result<
|
|
280
|
+
fn call(&self, args: &[Value]) -> Result<Reply, String> {
|
|
245
281
|
let mut inner = self
|
|
246
282
|
.inner
|
|
247
283
|
.lock()
|
|
@@ -291,9 +327,9 @@ impl Worker {
|
|
|
291
327
|
}
|
|
292
328
|
|
|
293
329
|
/// Decode one response line: `{"ok": <value>}` or `{"err": "message"}`.
|
|
294
|
-
fn decode(&self, line: &str) -> Result<
|
|
330
|
+
fn decode(&self, line: &str) -> Result<Reply, String> {
|
|
295
331
|
match parse_response(line) {
|
|
296
|
-
Ok(Response::Ok
|
|
332
|
+
Ok(Response::Ok { value, cached }) => Ok(Reply { value, cached }),
|
|
297
333
|
Ok(Response::Err(message)) => Err(message),
|
|
298
334
|
Err(defect) => Err(format!(
|
|
299
335
|
"function `{}` worker sent an invalid response ({defect}): {line}",
|
|
@@ -325,9 +361,17 @@ fn value_to_json(value: &Value) -> serde_json::Value {
|
|
|
325
361
|
}
|
|
326
362
|
}
|
|
327
363
|
|
|
364
|
+
/// A completed round trip: the value to bind, plus whatever the response said
|
|
365
|
+
/// about how it was produced.
|
|
366
|
+
#[derive(Debug)]
|
|
367
|
+
pub(crate) struct Reply {
|
|
368
|
+
value: Value,
|
|
369
|
+
cached: bool,
|
|
370
|
+
}
|
|
371
|
+
|
|
328
372
|
#[derive(Debug)]
|
|
329
373
|
enum Response {
|
|
330
|
-
Ok
|
|
374
|
+
Ok { value: Value, cached: bool },
|
|
331
375
|
Err(String),
|
|
332
376
|
}
|
|
333
377
|
|
|
@@ -348,7 +392,22 @@ fn parse_response(line: &str) -> Result<Response, String> {
|
|
|
348
392
|
let value = object
|
|
349
393
|
.get("ok")
|
|
350
394
|
.ok_or_else(|| "expected an \"ok\" or \"err\" key".to_string())?;
|
|
351
|
-
Ok(Response::Ok
|
|
395
|
+
Ok(Response::Ok {
|
|
396
|
+
value: json_to_sql_value(value)?,
|
|
397
|
+
cached: cached_flag(object),
|
|
398
|
+
})
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/// Whether the response flagged itself as served from the worker's own cache:
|
|
402
|
+
/// `{"meta": {"cached": true}}`. Optional and advisory — it feeds the progress
|
|
403
|
+
/// line and nothing else, so an absent, misshapen or non-boolean value reads as
|
|
404
|
+
/// "not cached" rather than failing a query over a counter.
|
|
405
|
+
fn cached_flag(object: &serde_json::Map<String, serde_json::Value>) -> bool {
|
|
406
|
+
object
|
|
407
|
+
.get("meta")
|
|
408
|
+
.and_then(|meta| meta.get("cached"))
|
|
409
|
+
.and_then(serde_json::Value::as_bool)
|
|
410
|
+
.unwrap_or(false)
|
|
352
411
|
}
|
|
353
412
|
|
|
354
413
|
/// Wire JSON → SQL value: string → TEXT, integral number → INTEGER, other
|
|
@@ -539,12 +598,12 @@ mod tests {
|
|
|
539
598
|
struct RecordingProgress(Recorder);
|
|
540
599
|
|
|
541
600
|
impl CallProgress for RecordingProgress {
|
|
542
|
-
fn update(&mut self, done: u64) {
|
|
543
|
-
self.0.push(format!("update {done}"));
|
|
601
|
+
fn update(&mut self, done: u64, cached: u64) {
|
|
602
|
+
self.0.push(format!("update {done}/{cached}"));
|
|
544
603
|
}
|
|
545
604
|
|
|
546
|
-
fn finish(&mut self, done: u64) {
|
|
547
|
-
self.0.push(format!("finish {done}"));
|
|
605
|
+
fn finish(&mut self, done: u64, cached: u64) {
|
|
606
|
+
self.0.push(format!("finish {done}/{cached}"));
|
|
548
607
|
}
|
|
549
608
|
|
|
550
609
|
fn restart(&mut self) {
|
|
@@ -565,7 +624,7 @@ mod tests {
|
|
|
565
624
|
reporter.record();
|
|
566
625
|
reporter.record();
|
|
567
626
|
|
|
568
|
-
assert_eq!(recorder.events(), ["update 1", "update 2"]);
|
|
627
|
+
assert_eq!(recorder.events(), ["update 1/0", "update 2/0"]);
|
|
569
628
|
}
|
|
570
629
|
|
|
571
630
|
/// The phase is what the count belongs to: a second query must not carry
|
|
@@ -583,7 +642,13 @@ mod tests {
|
|
|
583
642
|
|
|
584
643
|
assert_eq!(
|
|
585
644
|
recorder.events(),
|
|
586
|
-
[
|
|
645
|
+
[
|
|
646
|
+
"update 1/0",
|
|
647
|
+
"update 2/0",
|
|
648
|
+
"restart",
|
|
649
|
+
"update 1/0",
|
|
650
|
+
"finish 1/0"
|
|
651
|
+
],
|
|
587
652
|
"the new phase counts from one, not from three"
|
|
588
653
|
);
|
|
589
654
|
}
|
|
@@ -600,18 +665,76 @@ mod tests {
|
|
|
600
665
|
reporter.record();
|
|
601
666
|
assert_eq!(
|
|
602
667
|
recorder.events(),
|
|
603
|
-
["restart", "update 1", "update 2"],
|
|
668
|
+
["restart", "update 1/0", "update 2/0"],
|
|
604
669
|
"nothing is finished while the phase is open"
|
|
605
670
|
);
|
|
606
671
|
}
|
|
607
672
|
|
|
608
673
|
assert_eq!(
|
|
609
674
|
recorder.events(),
|
|
610
|
-
["restart", "update 1", "update 2", "finish 2"],
|
|
675
|
+
["restart", "update 1/0", "update 2/0", "finish 2/0"],
|
|
611
676
|
"the closed phase reports the count it accumulated"
|
|
612
677
|
);
|
|
613
678
|
}
|
|
614
679
|
|
|
680
|
+
/// A cache hit is a round trip like any other, so it counts twice over:
|
|
681
|
+
/// once in the total and once in the split.
|
|
682
|
+
#[test]
|
|
683
|
+
fn a_cached_round_trip_counts_in_both_the_total_and_the_split() {
|
|
684
|
+
let (reporter, recorder) = reporter();
|
|
685
|
+
|
|
686
|
+
reporter.record();
|
|
687
|
+
reporter.mark_cached();
|
|
688
|
+
|
|
689
|
+
assert_eq!(recorder.events(), ["update 1/0", "update 1/1"]);
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
/// The split counts only what the worker flagged. A run that computes
|
|
693
|
+
/// everything must not read as a run that cached everything.
|
|
694
|
+
#[test]
|
|
695
|
+
fn an_uncached_round_trip_leaves_the_split_at_zero() {
|
|
696
|
+
let (reporter, recorder) = reporter();
|
|
697
|
+
|
|
698
|
+
{
|
|
699
|
+
let _phase = reporter.phase();
|
|
700
|
+
reporter.record();
|
|
701
|
+
reporter.record();
|
|
702
|
+
reporter.mark_cached();
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
assert_eq!(
|
|
706
|
+
recorder.events(),
|
|
707
|
+
[
|
|
708
|
+
"restart",
|
|
709
|
+
"update 1/0",
|
|
710
|
+
"update 2/0",
|
|
711
|
+
"update 2/1",
|
|
712
|
+
"finish 2/1"
|
|
713
|
+
],
|
|
714
|
+
"one of the two round trips was flagged, not both"
|
|
715
|
+
);
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
/// A phase owns its split as well as its count: a warm second query must
|
|
719
|
+
/// not inherit the first query's cache hits.
|
|
720
|
+
#[test]
|
|
721
|
+
fn opening_a_phase_restarts_the_cache_split_too() {
|
|
722
|
+
let (reporter, recorder) = reporter();
|
|
723
|
+
reporter.record();
|
|
724
|
+
reporter.mark_cached();
|
|
725
|
+
|
|
726
|
+
{
|
|
727
|
+
let _phase = reporter.phase();
|
|
728
|
+
reporter.record();
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
assert_eq!(
|
|
732
|
+
recorder.events().last().unwrap(),
|
|
733
|
+
"finish 1/0",
|
|
734
|
+
"the new phase starts with no cache hits"
|
|
735
|
+
);
|
|
736
|
+
}
|
|
737
|
+
|
|
615
738
|
/// The production constructor wires a real [`Progress`] through the same
|
|
616
739
|
/// trait, so a whole phase runs against it here — the double proves the
|
|
617
740
|
/// orchestration, this proves the wiring is connected to something real.
|
|
@@ -624,9 +747,12 @@ mod tests {
|
|
|
624
747
|
let _phase = reporter.phase();
|
|
625
748
|
reporter.record();
|
|
626
749
|
reporter.record();
|
|
750
|
+
reporter.mark_cached();
|
|
627
751
|
}
|
|
628
752
|
|
|
629
|
-
|
|
753
|
+
let state = reporter.state.lock().unwrap();
|
|
754
|
+
assert_eq!(state.count, 2);
|
|
755
|
+
assert_eq!(state.cached, 1);
|
|
630
756
|
}
|
|
631
757
|
|
|
632
758
|
// --- wire encoding -----------------------------------------------------
|
|
@@ -659,8 +785,16 @@ mod tests {
|
|
|
659
785
|
// --- response decoding -------------------------------------------------
|
|
660
786
|
|
|
661
787
|
fn ok_value(line: &str) -> Value {
|
|
788
|
+
ok_response(line).0
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
fn ok_cached(line: &str) -> bool {
|
|
792
|
+
ok_response(line).1
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
fn ok_response(line: &str) -> (Value, bool) {
|
|
662
796
|
match parse_response(line).unwrap() {
|
|
663
|
-
Response::Ok
|
|
797
|
+
Response::Ok { value, cached } => (value, cached),
|
|
664
798
|
Response::Err(m) => panic!("expected ok, got err {m:?}"),
|
|
665
799
|
}
|
|
666
800
|
}
|
|
@@ -688,10 +822,48 @@ mod tests {
|
|
|
688
822
|
fn response_err_carries_the_message() {
|
|
689
823
|
match parse_response(r#"{"err": "boom"}"#).unwrap() {
|
|
690
824
|
Response::Err(m) => assert_eq!(m, "boom"),
|
|
691
|
-
Response::Ok
|
|
825
|
+
Response::Ok { value, .. } => panic!("expected err, got ok {value:?}"),
|
|
692
826
|
}
|
|
693
827
|
}
|
|
694
828
|
|
|
829
|
+
/// The signal core cannot derive for itself: a cache hit and a computed
|
|
830
|
+
/// answer are the same round trip on the wire, so the worker has to say.
|
|
831
|
+
#[test]
|
|
832
|
+
fn a_response_flagged_cached_is_read_as_a_cache_hit() {
|
|
833
|
+
assert!(ok_cached(r#"{"ok": 1, "meta": {"cached": true}}"#));
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
#[test]
|
|
837
|
+
fn a_response_without_meta_is_not_a_cache_hit() {
|
|
838
|
+
assert!(!ok_cached(r#"{"ok": 1}"#));
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
#[test]
|
|
842
|
+
fn a_response_flagged_uncached_is_not_a_cache_hit() {
|
|
843
|
+
assert!(!ok_cached(r#"{"ok": 1, "meta": {"cached": false}}"#));
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
/// `meta` is advisory: it drives a progress counter, so no shape of it is
|
|
847
|
+
/// worth failing a query over. Anything unreadable means "not cached".
|
|
848
|
+
#[test]
|
|
849
|
+
fn an_unreadable_meta_reads_as_uncached_rather_than_failing() {
|
|
850
|
+
assert!(!ok_cached(r#"{"ok": 1, "meta": "not-an-object"}"#));
|
|
851
|
+
assert!(!ok_cached(r#"{"ok": 1, "meta": {}}"#));
|
|
852
|
+
assert!(!ok_cached(r#"{"ok": 1, "meta": {"cached": "yes"}}"#));
|
|
853
|
+
assert!(!ok_cached(r#"{"ok": 1, "meta": null}"#));
|
|
854
|
+
assert!(!ok_cached(r#"{"ok": 1, "meta": [{"cached": true}]}"#));
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
/// The value is the `ok` field and nothing else — `meta` never leaks into
|
|
858
|
+
/// what the query binds.
|
|
859
|
+
#[test]
|
|
860
|
+
fn meta_does_not_change_the_decoded_value() {
|
|
861
|
+
assert_eq!(
|
|
862
|
+
ok_value(r#"{"ok": "hi", "meta": {"cached": true}}"#),
|
|
863
|
+
text("hi")
|
|
864
|
+
);
|
|
865
|
+
}
|
|
866
|
+
|
|
695
867
|
#[test]
|
|
696
868
|
fn response_defects_are_named() {
|
|
697
869
|
assert!(
|
|
@@ -842,8 +1014,8 @@ mod tests {
|
|
|
842
1014
|
]],
|
|
843
1015
|
false,
|
|
844
1016
|
);
|
|
845
|
-
assert_eq!(worker.call(&[text("a")]).unwrap(), text("A"));
|
|
846
|
-
assert_eq!(worker.call(&[text("b")]).unwrap(), text("B"));
|
|
1017
|
+
assert_eq!(worker.call(&[text("a")]).unwrap().value, text("A"));
|
|
1018
|
+
assert_eq!(worker.call(&[text("b")]).unwrap().value, text("B"));
|
|
847
1019
|
assert_eq!(*spawns.lock().unwrap(), 1);
|
|
848
1020
|
}
|
|
849
1021
|
|
|
@@ -879,7 +1051,7 @@ mod tests {
|
|
|
879
1051
|
false,
|
|
880
1052
|
);
|
|
881
1053
|
assert_eq!(worker.call(&[]).unwrap_err(), "boom");
|
|
882
|
-
assert_eq!(worker.call(&[]).unwrap(), Value::Integer(1));
|
|
1054
|
+
assert_eq!(worker.call(&[]).unwrap().value, Value::Integer(1));
|
|
883
1055
|
assert_eq!(*spawns.lock().unwrap(), 1, "err response must not respawn");
|
|
884
1056
|
}
|
|
885
1057
|
|
|
@@ -897,7 +1069,7 @@ mod tests {
|
|
|
897
1069
|
assert!(err.contains("`embed`"), "got: {err}");
|
|
898
1070
|
assert!(err.contains("`timeout`"), "got: {err}");
|
|
899
1071
|
// The next call starts a fresh worker.
|
|
900
|
-
assert_eq!(worker.call(&[]).unwrap(), Value::Integer(1));
|
|
1072
|
+
assert_eq!(worker.call(&[]).unwrap().value, Value::Integer(1));
|
|
901
1073
|
assert_eq!(*spawns.lock().unwrap(), 2);
|
|
902
1074
|
}
|
|
903
1075
|
|
|
@@ -913,7 +1085,7 @@ mod tests {
|
|
|
913
1085
|
let err = worker.call(&[]).unwrap_err();
|
|
914
1086
|
assert!(err.contains("exited before replying"), "got: {err}");
|
|
915
1087
|
assert!(err.contains("stderr"), "got: {err}");
|
|
916
|
-
assert_eq!(worker.call(&[]).unwrap(), Value::Integer(1));
|
|
1088
|
+
assert_eq!(worker.call(&[]).unwrap().value, Value::Integer(1));
|
|
917
1089
|
assert_eq!(*spawns.lock().unwrap(), 2);
|
|
918
1090
|
}
|
|
919
1091
|
|
|
@@ -955,7 +1127,7 @@ mod tests {
|
|
|
955
1127
|
Box::new(|| Ok(Box::new(EchoTransport))),
|
|
956
1128
|
);
|
|
957
1129
|
assert_eq!(
|
|
958
|
-
worker.call(&[text("x"), Value::Integer(3)]).unwrap(),
|
|
1130
|
+
worker.call(&[text("x"), Value::Integer(3)]).unwrap().value,
|
|
959
1131
|
Value::Null
|
|
960
1132
|
);
|
|
961
1133
|
}
|