dirsql 0.4.2__tar.gz → 0.4.4__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.2 → dirsql-0.4.4}/Cargo.lock +1 -1
- {dirsql-0.4.2 → dirsql-0.4.4}/Cargo.toml +12 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/PKG-INFO +1 -1
- {dirsql-0.4.2/packages/rust → dirsql-0.4.4}/docs/reference/hooks.md +1 -1
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/python/Cargo.toml +1 -1
- dirsql-0.4.4/packages/python/changelog.d/2026-08-02-release-profile.md +5 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/python/docs/reference/hooks.md +1 -1
- dirsql-0.4.4/packages/python/e2e-attestations/claude-714-unify-file-failures.json +7 -0
- dirsql-0.4.4/packages/rust/changelog.d/2026-08-02-per-file-scan-failures.md +35 -0
- dirsql-0.4.4/packages/rust/changelog.d/2026-08-02-release-profile.md +7 -0
- {dirsql-0.4.2 → dirsql-0.4.4/packages/rust}/docs/reference/hooks.md +1 -1
- dirsql-0.4.4/packages/rust/migrations.d/2026-08-02-per-file-scan-failures.md +100 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/rust/src/bin/dirsql.rs +42 -1
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/rust/src/lib.rs +86 -75
- {dirsql-0.4.2 → dirsql-0.4.4}/README.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/dirsql/__init__.py +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/dirsql/_async.py +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/dirsql/_dirsql.pyi +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/dirsql/cli/__init__.py +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/dirsql/cli/binary_path.py +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/dirsql/cli/discover_plugins/__init__.py +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/dirsql/cli/discover_plugins/discovered_fragments.py +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/dirsql/cli/discover_plugins/discovery_disabled.py +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/dirsql/cli/discover_plugins/fragment_path.py +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/dirsql/cli/discover_plugins/user_passed_config.py +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/dirsql/cli/discover_plugins/with_discovered_plugins.py +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/dirsql/cli/is_windows.py +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/dirsql/cli/main.py +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/dirsql/cli/resolve_config_extensions.py +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/dirsql/py.typed +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/dirsql/resolve_config_extensions.py +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/dirsql/resolve_extension.py +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/docs/.claude/CLAUDE.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/docs/.vitepress/config.ts +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/docs/.vitepress/theme/index.ts +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/docs/.vitepress/theme/lang.ts +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/docs/AGENTS.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/docs/explanation.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/docs/getting-started.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/docs/howto/columns-from-paths.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/docs/howto/define-tables.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/docs/howto/embed.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/docs/howto/extract-from-contents.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/docs/howto/load-extension.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/docs/howto/parse-files-into-columns.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/docs/howto/persist.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/docs/howto/query-without-config.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/docs/howto/react-to-changes.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/docs/howto/search-by-meaning.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/docs/howto/skip-files.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/docs/howto/write-a-plugin.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/docs/index.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/docs/migrations.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/docs/package.json +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/docs/pnpm-lock.yaml +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/docs/pnpm-workspace.yaml +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/docs/reference/cli.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/docs/reference/columns.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/docs/reference/config.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/docs/reference/http-api.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/docs/reference/path-tables.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/docs/reference/sdk.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/python/CHANGELOG.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/python/MIGRATIONS.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/python/README.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/python/changelog.d/2026-07-13-drop-cwd-config-auto-detect.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/python/changelog.d/2026-07-13-plugin-discovery.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/python/changelog.d/2026-07-13-rename-extract-to-on-file.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/python/changelog.d/2026-07-13-repeatable-config.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/python/changelog.d/2026-07-13-sdk-default-baked-in-config.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/python/changelog.d/2026-07-20-drop-implicit-files-table.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/python/changelog.d/2026-07-21-remove-glob-captures.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/python/changelog.d/2026-07-24-hookless-table-config-error.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/python/changelog.d/2026-07-24-remove-stat-fact-injection.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/python/changelog.d/2026-07-29-restore-python-310.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/python/changelog.d/README.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/python/conftest.py +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/python/docs/.claude/CLAUDE.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/python/docs/.vitepress/config.ts +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/python/docs/.vitepress/theme/index.ts +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/python/docs/.vitepress/theme/lang.ts +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/python/docs/AGENTS.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/python/docs/explanation.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/python/docs/getting-started.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/python/docs/howto/columns-from-paths.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/python/docs/howto/define-tables.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/python/docs/howto/embed.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/python/docs/howto/extract-from-contents.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/python/docs/howto/load-extension.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/python/docs/howto/parse-files-into-columns.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/python/docs/howto/persist.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/python/docs/howto/query-without-config.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/python/docs/howto/react-to-changes.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/python/docs/howto/search-by-meaning.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/python/docs/howto/skip-files.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/python/docs/howto/write-a-plugin.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/python/docs/index.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/python/docs/migrations.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/python/docs/package.json +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/python/docs/pnpm-lock.yaml +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/python/docs/pnpm-workspace.yaml +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/python/docs/reference/cli.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/python/docs/reference/columns.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/python/docs/reference/config.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/python/docs/reference/http-api.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/python/docs/reference/path-tables.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/python/docs/reference/sdk.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/python/e2e-attestations/claude-710-accumulate-onfile.json +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/python/migrations.d/2026-07-13-drop-cwd-config-auto-detect.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/python/migrations.d/2026-07-13-rename-extract-to-on-file.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/python/migrations.d/2026-07-13-sdk-default-baked-in-config.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/python/migrations.d/2026-07-20-drop-implicit-files-table.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/python/migrations.d/2026-07-21-remove-glob-captures.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/python/migrations.d/2026-07-24-hookless-table-config-error.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/python/migrations.d/2026-07-24-remove-stat-fact-injection.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/python/migrations.d/2026-07-29-restore-python-310.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/python/migrations.d/README.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/python/src/lib.rs +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/python/tests/__init__.py +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/python/tests/conftest.py +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/python/tests/e2e/__init__.py +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/python/tests/e2e/fixtures/dirsql_plugin_fixture/__init__.py +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/python/tests/e2e/fixtures/dirsql_plugin_fixture/dirsql.toml +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/python/tests/integration/__init__.py +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/python/tests/integration/binding/__init__.py +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/python/tests/integration/hermetic/__init__.py +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/rust/CHANGELOG.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/rust/Cargo.toml +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/rust/MIGRATIONS.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/rust/README.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/rust/benches/db_bench.rs +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/rust/benches/differ_bench.rs +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/rust/benches/matcher_bench.rs +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/rust/benches/scanner_bench.rs +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/rust/changelog.d/2026-07-13-batch-ingest-transaction.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/rust/changelog.d/2026-07-13-chained-pre-post-query-hooks.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/rust/changelog.d/2026-07-13-drop-cwd-config-auto-detect.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/rust/changelog.d/2026-07-13-fan-out-file-table-matching.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/rust/changelog.d/2026-07-13-persist-wal.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/rust/changelog.d/2026-07-13-rename-extract-to-on-file.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/rust/changelog.d/2026-07-13-repeatable-builder-config.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/rust/changelog.d/2026-07-13-repeatable-cli-config.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/rust/changelog.d/2026-07-13-sdk-default-baked-in-config.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/rust/changelog.d/2026-07-13-subcommand-local-config-flags.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/rust/changelog.d/2026-07-19-path-table-query-fallback.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/rust/changelog.d/2026-07-19-path-table-vtab.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/rust/changelog.d/2026-07-20-drop-implicit-files-table.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/rust/changelog.d/2026-07-20-path-table-glob-semantics.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/rust/changelog.d/2026-07-20-schema-inference.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/rust/changelog.d/2026-07-21-on-file-flag.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/rust/changelog.d/2026-07-21-remove-glob-captures.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/rust/changelog.d/2026-07-23-init-escalation-example.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/rust/changelog.d/2026-07-23-query-default-cli.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/rust/changelog.d/2026-07-24-hookless-table-config-error.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/rust/changelog.d/2026-07-24-remove-stat-fact-injection.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/rust/changelog.d/2026-07-29-on-file-failures-accumulate.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/rust/changelog.d/README.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/rust/docs/explanation.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/rust/docs/getting-started.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/rust/docs/howto/columns-from-paths.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/rust/docs/howto/define-tables.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/rust/docs/howto/embed.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/rust/docs/howto/extract-from-contents.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/rust/docs/howto/load-extension.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/rust/docs/howto/parse-files-into-columns.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/rust/docs/howto/persist.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/rust/docs/howto/query-without-config.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/rust/docs/howto/react-to-changes.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/rust/docs/howto/search-by-meaning.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/rust/docs/howto/skip-files.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/rust/docs/howto/write-a-plugin.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/rust/docs/index.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/rust/docs/migrations.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/rust/docs/reference/cli.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/rust/docs/reference/columns.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/rust/docs/reference/config.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/rust/docs/reference/http-api.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/rust/docs/reference/path-tables.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/rust/docs/reference/sdk.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/rust/migrations.d/2026-07-13-drop-cwd-config-auto-detect.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/rust/migrations.d/2026-07-13-fan-out-file-table-matching.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/rust/migrations.d/2026-07-13-persist-wal.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/rust/migrations.d/2026-07-13-rename-extract-to-on-file.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/rust/migrations.d/2026-07-13-sdk-default-baked-in-config.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/rust/migrations.d/2026-07-13-subcommand-local-config-flags.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/rust/migrations.d/2026-07-20-drop-implicit-files-table.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/rust/migrations.d/2026-07-20-path-table-glob-semantics.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/rust/migrations.d/2026-07-21-remove-glob-captures.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/rust/migrations.d/2026-07-23-init-escalation-example.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/rust/migrations.d/2026-07-23-query-default-cli.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/rust/migrations.d/2026-07-24-hookless-table-config-error.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/rust/migrations.d/2026-07-24-remove-stat-fact-injection.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/rust/migrations.d/README.md +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/rust/src/cli/execute.rs +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/rust/src/cli/init.rs +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/rust/src/cli/mod.rs +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/rust/src/cli/router.rs +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/rust/src/cli/serialize.rs +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/rust/src/cli/server.rs +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/rust/src/command.rs +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/rust/src/config.rs +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/rust/src/db.rs +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/rust/src/default_config.toml +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/rust/src/differ.rs +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/rust/src/infer.rs +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/rust/src/matcher.rs +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/rust/src/parsed_vtab.rs +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/rust/src/path_table.rs +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/rust/src/persist.rs +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/rust/src/scanner.rs +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/rust/src/vtab.rs +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/packages/rust/src/watcher.rs +0 -0
- {dirsql-0.4.2 → dirsql-0.4.4}/pyproject.toml +0 -0
|
@@ -22,6 +22,18 @@ thiserror = "2"
|
|
|
22
22
|
tokio = { version = "1", features = ["rt", "sync"] }
|
|
23
23
|
futures-channel = "0.3"
|
|
24
24
|
|
|
25
|
+
# Cargo's default release profile ships every binary unstripped, without
|
|
26
|
+
# cross-crate optimization. The three published artifacts (CLI bin, pyo3
|
|
27
|
+
# extension, napi addon) are each a statically linked copy of the whole core,
|
|
28
|
+
# so the defaults cost ~31% of every download (#717).
|
|
29
|
+
#
|
|
30
|
+
# `panic` stays at the default `unwind`: pyo3 and napi turn Rust panics into
|
|
31
|
+
# Python/JS exceptions, which requires unwinding.
|
|
32
|
+
[profile.release]
|
|
33
|
+
strip = "symbols"
|
|
34
|
+
lto = "fat"
|
|
35
|
+
codegen-units = 1
|
|
36
|
+
|
|
25
37
|
[workspace.lints.rust]
|
|
26
38
|
unsafe_code = "forbid"
|
|
27
39
|
rust_2018_idioms = { level = "warn", priority = -1 }
|
|
@@ -86,7 +86,7 @@ What a failure *means* differs per hook:
|
|
|
86
86
|
|
|
87
87
|
| Hook | On failure |
|
|
88
88
|
|---|---|
|
|
89
|
-
| `on-file` | **Per-file isolation.** The file contributes no rows
|
|
89
|
+
| `on-file` | **Per-file isolation.** The file contributes no rows and is reported as skipped; the scan indexes every other file and commits. The CLI names up to ten skipped files on stderr, then `... and N more`, and exits `23` — distinct from `0` (clean) and `1` (the run failed), so a caller can tell a partial index from a complete one. A row the table rejects under `strict` counts as the same kind of failure. |
|
|
90
90
|
| `pre-query` | The request returns `500 Internal Server Error` with the command's stderr tail in the JSON `error` body. |
|
|
91
91
|
| `post-query` | The request returns `500 Internal Server Error` with the command's stderr tail (or, for unparseable output, `post-query did not return valid JSON: <err>`) in the JSON `error` body. |
|
|
92
92
|
|
|
@@ -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.4"
|
|
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
|
|
@@ -86,7 +86,7 @@ What a failure *means* differs per hook:
|
|
|
86
86
|
|
|
87
87
|
| Hook | On failure |
|
|
88
88
|
|---|---|
|
|
89
|
-
| `on-file` | **Per-file isolation.** The file contributes no rows
|
|
89
|
+
| `on-file` | **Per-file isolation.** The file contributes no rows and is reported as skipped; the scan indexes every other file and commits. The CLI names up to ten skipped files on stderr, then `... and N more`, and exits `23` — distinct from `0` (clean) and `1` (the run failed), so a caller can tell a partial index from a complete one. A row the table rejects under `strict` counts as the same kind of failure. |
|
|
90
90
|
| `pre-query` | The request returns `500 Internal Server Error` with the command's stderr tail in the JSON `error` body. |
|
|
91
91
|
| `post-query` | The request returns `500 Internal Server Error` with the command's stderr tail (or, for unparseable output, `post-query did not return valid JSON: <err>`) in the JSON `error` body. |
|
|
92
92
|
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
**Fixed**
|
|
2
|
+
|
|
3
|
+
A file the scan cannot index no longer costs the files around it, and a partial
|
|
4
|
+
index is now visible at the exit code instead of passing for a complete one
|
|
5
|
+
(#714).
|
|
6
|
+
|
|
7
|
+
Two failure modes behaved inconsistently. A hook that exited non-zero or emitted
|
|
8
|
+
malformed output was already skipped — `run_on_file` logged it and returned an
|
|
9
|
+
empty row set — but a row that failed **strict** normalization aborted the whole
|
|
10
|
+
scan, so one bad column cost every other file's rows. Both are now the same
|
|
11
|
+
thing: the file is skipped, the scan commits what it could index, and the
|
|
12
|
+
failure is recorded.
|
|
13
|
+
|
|
14
|
+
- `DirSQL::scan_failures()` returns the files the scan could not index, each
|
|
15
|
+
with its root-relative path and the hook's own message. Empty for a clean
|
|
16
|
+
scan.
|
|
17
|
+
- The CLI reports skips on **stderr**, at most ten by name followed by
|
|
18
|
+
`... and N more`, and exits **23** — rsync's "partial transfer due to error" —
|
|
19
|
+
when any file was skipped. stdout still carries only the query result, so
|
|
20
|
+
`dirsql "SELECT …" | jq` keeps working and can now tell a partial index from a
|
|
21
|
+
complete one. A clean scan still exits `0`; a failed run still exits `1`.
|
|
22
|
+
- `run_on_file` returns `Result` rather than logging and flattening to an empty
|
|
23
|
+
row set, so a skip and a file that legitimately produced no rows are no longer
|
|
24
|
+
indistinguishable.
|
|
25
|
+
|
|
26
|
+
SQLite, DDL and extension errors are unchanged: they still abort the scan and
|
|
27
|
+
roll back. Only per-file hook outcomes became recoverable.
|
|
28
|
+
|
|
29
|
+
A skipped file never reaches the persistent file index, so it is retried on the
|
|
30
|
+
next scan — the cache is incomplete, never wrong.
|
|
31
|
+
|
|
32
|
+
**Removed**
|
|
33
|
+
|
|
34
|
+
`DirSqlError::OnFile` and `DirSqlError::OnFileMany`. Nothing can produce them
|
|
35
|
+
now that a per-file failure is not a scan error.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
**Changed** — release builds now strip symbols, run fat LTO, and use a single
|
|
2
|
+
codegen unit, so the published `dirsql` binary is ~33% smaller (linux-x64:
|
|
3
|
+
9.17 MB → 6.14 MB; 3.49 MB → 2.80 MB gzipped).
|
|
4
|
+
|
|
5
|
+
Build-only: no API, CLI or behavior change. `panic` stays at the default
|
|
6
|
+
`unwind`, which the pyo3 and napi bindings need to turn Rust panics into
|
|
7
|
+
Python/JS exceptions. Release build time rises ~2m10s → ~2m38s.
|
|
@@ -86,7 +86,7 @@ What a failure *means* differs per hook:
|
|
|
86
86
|
|
|
87
87
|
| Hook | On failure |
|
|
88
88
|
|---|---|
|
|
89
|
-
| `on-file` | **Per-file isolation.** The file contributes no rows
|
|
89
|
+
| `on-file` | **Per-file isolation.** The file contributes no rows and is reported as skipped; the scan indexes every other file and commits. The CLI names up to ten skipped files on stderr, then `... and N more`, and exits `23` — distinct from `0` (clean) and `1` (the run failed), so a caller can tell a partial index from a complete one. A row the table rejects under `strict` counts as the same kind of failure. |
|
|
90
90
|
| `pre-query` | The request returns `500 Internal Server Error` with the command's stderr tail in the JSON `error` body. |
|
|
91
91
|
| `post-query` | The request returns `500 Internal Server Error` with the command's stderr tail (or, for unparseable output, `post-query did not return valid JSON: <err>`) in the JSON `error` body. |
|
|
92
92
|
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
## Summary
|
|
2
|
+
|
|
3
|
+
A per-file failure during the initial scan is no longer fatal (#714). A hook
|
|
4
|
+
that fails, or that returns a row the table rejects, costs that file alone: the
|
|
5
|
+
scan indexes everything else, commits, and records the failure. Previously a
|
|
6
|
+
strict-mode violation aborted the scan outright — one bad column cost every
|
|
7
|
+
other file's rows — while a non-zero hook exit was already silently skipped. The
|
|
8
|
+
two are now consistent.
|
|
9
|
+
|
|
10
|
+
The visible consequence is an exit code. A run that skipped files exits **23**
|
|
11
|
+
(rsync's "partial transfer due to error") instead of `0`, so a caller can tell a
|
|
12
|
+
partial index from a complete one. That distinction did not exist before, which
|
|
13
|
+
is why it is a breaking change for scripts rather than a pure improvement.
|
|
14
|
+
|
|
15
|
+
## Required changes
|
|
16
|
+
|
|
17
|
+
**`DirSqlError::OnFile` and `DirSqlError::OnFileMany` are removed.** Nothing can
|
|
18
|
+
produce them now. An exhaustive `match` on `DirSqlError` drops those arms:
|
|
19
|
+
|
|
20
|
+
```rust
|
|
21
|
+
// before
|
|
22
|
+
match err {
|
|
23
|
+
DirSqlError::OnFile { path, message } => report(path, message),
|
|
24
|
+
DirSqlError::OnFileMany { failures } => failures.iter().for_each(report_one),
|
|
25
|
+
other => return Err(other),
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// after — the build no longer fails for this reason; ask the database instead
|
|
29
|
+
let db = DirSQL::new(root, tables)?;
|
|
30
|
+
for failure in db.scan_failures() {
|
|
31
|
+
report(&failure.path, &failure.message);
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
**A script that treats any non-zero exit as failure now sees `23`.** If a
|
|
36
|
+
partial index should be acceptable, accept it explicitly:
|
|
37
|
+
|
|
38
|
+
```sh
|
|
39
|
+
# before: relied on `dirsql` exiting 0 while quietly skipping files
|
|
40
|
+
dirsql "SELECT * FROM docs" | jq .
|
|
41
|
+
|
|
42
|
+
# after: accept a partial index, still failing on a real error
|
|
43
|
+
dirsql "SELECT * FROM docs" | jq . || [ $? -eq 23 ]
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Under `set -e` and *without* that guard, a scan with skipped files now stops the
|
|
47
|
+
script — which is the point: it previously continued against a silently
|
|
48
|
+
incomplete index.
|
|
49
|
+
|
|
50
|
+
## Deprecations removed
|
|
51
|
+
|
|
52
|
+
_None._
|
|
53
|
+
|
|
54
|
+
## Behavior changes without code changes
|
|
55
|
+
|
|
56
|
+
**A strict-mode violation skips its file instead of aborting the scan.** A
|
|
57
|
+
`strict` table whose hook returns an unexpected or missing column used to fail
|
|
58
|
+
the build; it now yields a skipped file and a built database containing every
|
|
59
|
+
other file's rows. The rejected row is still never inserted, and the column name
|
|
60
|
+
still never reaches SQL — only the blast radius changed.
|
|
61
|
+
|
|
62
|
+
**Skipped files are reported by the CLI, capped.** At most ten are named on
|
|
63
|
+
stderr, followed by `... and N more`. Previously `run_on_file` printed one line
|
|
64
|
+
per failing file with no cap, and printed nothing at all for strict violations
|
|
65
|
+
(they aborted instead). Paths in these lines are now root-relative, where the
|
|
66
|
+
old `run_on_file` line printed the absolute path.
|
|
67
|
+
|
|
68
|
+
**A library consumer no longer gets stderr noise from the core.** `run_on_file`
|
|
69
|
+
returned `Vec<Row>` and logged its own failures; it now returns `Result` and the
|
|
70
|
+
scan records them. Read `DirSQL::scan_failures()` instead of scraping stderr.
|
|
71
|
+
|
|
72
|
+
**The persistent cache commits partially.** A scan with failures used to roll
|
|
73
|
+
back entirely; it now commits the files that parsed. The failed file never
|
|
74
|
+
reaches the file index, so the next scan treats it as unknown and retries it —
|
|
75
|
+
the cache is incomplete, never wrong. A caller that relied on "a failed build
|
|
76
|
+
leaves the cache untouched" no longer gets that.
|
|
77
|
+
|
|
78
|
+
**Watch-mode gains an error event it did not emit.** With `run_on_file` now
|
|
79
|
+
returning `Result`, a command hook that fails during live watching produces an
|
|
80
|
+
`error` row event, where it previously produced silence.
|
|
81
|
+
|
|
82
|
+
## Verification
|
|
83
|
+
|
|
84
|
+
```sh
|
|
85
|
+
# a directory with one good file and two whose on-file hook exits non-zero
|
|
86
|
+
dirsql "SELECT name FROM items" -c .dirsql.toml; echo "EXIT=$?"
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Expected: the good file's rows on stdout as JSON, one `dirsql: skipping …` line
|
|
90
|
+
per failed file on stderr, and `EXIT=23`. Before this change the same command
|
|
91
|
+
printed the rows and `EXIT=0`.
|
|
92
|
+
|
|
93
|
+
```sh
|
|
94
|
+
# a `strict = true` table where one file's hook emits an unexpected column
|
|
95
|
+
dirsql "SELECT name FROM items" -c strict.toml; echo "EXIT=$?"
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Expected: every well-formed file's rows on stdout, one skip line naming the bad
|
|
99
|
+
file, and `EXIT=23`. Before this change: no rows at all, a
|
|
100
|
+
`Schema mismatch: extra columns` error on stderr, and `EXIT=1`.
|
|
@@ -280,6 +280,7 @@ async fn run_query(args: QueryArgs) -> ExitCode {
|
|
|
280
280
|
}
|
|
281
281
|
};
|
|
282
282
|
let state = load_state(&args.common, parser);
|
|
283
|
+
let skipped = report_scan_failures(&state);
|
|
283
284
|
let pre_query = load_pre_queries(&args.common);
|
|
284
285
|
let post_query = load_post_queries(&args.common);
|
|
285
286
|
// Same default the server binds with; the pipeline enforces it.
|
|
@@ -296,7 +297,15 @@ async fn run_query(args: QueryArgs) -> ExitCode {
|
|
|
296
297
|
{
|
|
297
298
|
Ok(value) => {
|
|
298
299
|
println!("{value}");
|
|
299
|
-
|
|
300
|
+
// The query ran and its rows are on stdout, so this is not a
|
|
301
|
+
// failure -- but the index behind them is missing files, and a
|
|
302
|
+
// caller piping into `jq` under `set -e` has no other way to find
|
|
303
|
+
// that out.
|
|
304
|
+
if skipped {
|
|
305
|
+
ExitCode::from(PARTIAL_SCAN_EXIT)
|
|
306
|
+
} else {
|
|
307
|
+
ExitCode::SUCCESS
|
|
308
|
+
}
|
|
300
309
|
}
|
|
301
310
|
Err(failure) => {
|
|
302
311
|
eprintln!("dirsql query: {}", failure.message());
|
|
@@ -305,6 +314,38 @@ async fn run_query(args: QueryArgs) -> ExitCode {
|
|
|
305
314
|
}
|
|
306
315
|
}
|
|
307
316
|
|
|
317
|
+
/// Exit code for "the scan completed, but some files were skipped" -- distinct
|
|
318
|
+
/// from `1` so a script can tell a partial index from a run that failed.
|
|
319
|
+
/// Follows rsync's `23` ("partial transfer due to error").
|
|
320
|
+
const PARTIAL_SCAN_EXIT: u8 = 23;
|
|
321
|
+
|
|
322
|
+
/// How many skipped files to name before collapsing the rest into a count. A
|
|
323
|
+
/// directory of unreadable files should not bury the terminal.
|
|
324
|
+
const MAX_REPORTED_FAILURES: usize = 10;
|
|
325
|
+
|
|
326
|
+
/// Print the scan's skipped files to stderr, capped, and report whether there
|
|
327
|
+
/// were any. stdout is left for the query result alone.
|
|
328
|
+
fn report_scan_failures(state: &AppState) -> bool {
|
|
329
|
+
let AppState::Ready(db) = state else {
|
|
330
|
+
return false;
|
|
331
|
+
};
|
|
332
|
+
let failures = db.scan_failures();
|
|
333
|
+
if failures.is_empty() {
|
|
334
|
+
return false;
|
|
335
|
+
}
|
|
336
|
+
for failure in failures.iter().take(MAX_REPORTED_FAILURES) {
|
|
337
|
+
eprintln!("dirsql: skipping `{}`: {}", failure.path, failure.message);
|
|
338
|
+
}
|
|
339
|
+
if let Some(rest) = failures
|
|
340
|
+
.len()
|
|
341
|
+
.checked_sub(MAX_REPORTED_FAILURES)
|
|
342
|
+
.filter(|n| *n > 0)
|
|
343
|
+
{
|
|
344
|
+
eprintln!("dirsql: ... and {rest} more");
|
|
345
|
+
}
|
|
346
|
+
true
|
|
347
|
+
}
|
|
348
|
+
|
|
308
349
|
/// Synthesize the exact `POST /query` body for a positional SQL argument,
|
|
309
350
|
/// so the shared pipeline's intake validation and `pre-query` hook see
|
|
310
351
|
/// byte-for-byte what an HTTP client would send.
|
|
@@ -129,23 +129,6 @@ pub enum DirSqlError {
|
|
|
129
129
|
#[error("duplicate table name: {0}")]
|
|
130
130
|
DuplicateTable(String),
|
|
131
131
|
|
|
132
|
-
#[error("on-file error for {path}: {message}")]
|
|
133
|
-
OnFile { path: String, message: String },
|
|
134
|
-
|
|
135
|
-
/// Several files' on-file hooks failed during one scan. A scan attempts
|
|
136
|
-
/// every matched file, so more than one can fail; reporting only the first
|
|
137
|
-
/// would make the rest invisible until it was fixed and the scan re-run.
|
|
138
|
-
#[error(
|
|
139
|
-
"{} files failed their on-file hook:\n{}",
|
|
140
|
-
.failures.len(),
|
|
141
|
-
.failures
|
|
142
|
-
.iter()
|
|
143
|
-
.map(|OnFileFailure { path, message }| format!(" {path}: {message}"))
|
|
144
|
-
.collect::<Vec<_>>()
|
|
145
|
-
.join("\n")
|
|
146
|
-
)]
|
|
147
|
-
OnFileMany { failures: Vec<OnFileFailure> },
|
|
148
|
-
|
|
149
132
|
#[error("config error: {message}")]
|
|
150
133
|
Config {
|
|
151
134
|
message: String,
|
|
@@ -273,6 +256,8 @@ struct DirSqlInner {
|
|
|
273
256
|
matcher: TableMatcher,
|
|
274
257
|
on_file_map: HashMap<String, Arc<OnFileFn>>,
|
|
275
258
|
strict_map: HashMap<String, bool>,
|
|
259
|
+
/// Files the initial scan could not index. Empty for a clean scan.
|
|
260
|
+
scan_failures: Vec<OnFileFailure>,
|
|
276
261
|
watcher: Mutex<Option<Watcher>>,
|
|
277
262
|
/// Locks out [`DirSQL::watch`] once [`DirSQL::poll_events`] has run:
|
|
278
263
|
/// both would drain the same underlying watcher.
|
|
@@ -735,6 +720,18 @@ impl DirSQL {
|
|
|
735
720
|
})
|
|
736
721
|
}
|
|
737
722
|
|
|
723
|
+
/// The files the initial scan could not index, in scan order — each with
|
|
724
|
+
/// the path relative to the root and the hook's own error.
|
|
725
|
+
///
|
|
726
|
+
/// A hook that fails, or returns a row the table rejects, costs that file
|
|
727
|
+
/// and no other: the scan indexes everything else and records the failure
|
|
728
|
+
/// here. Empty for a clean scan, which is the signal a caller checks —
|
|
729
|
+
/// non-empty means the index is *incomplete*, not wrong, and the missing
|
|
730
|
+
/// files are retried on the next scan.
|
|
731
|
+
pub fn scan_failures(&self) -> &[OnFileFailure] {
|
|
732
|
+
&self.inner.scan_failures
|
|
733
|
+
}
|
|
734
|
+
|
|
738
735
|
/// Split-phase construction — part 2. Consumes the intermediate state from
|
|
739
736
|
/// [`prepare_resolved`](Self::prepare_resolved): creates the SQLite
|
|
740
737
|
/// database (or wires up the persistent on-disk one), runs each table's
|
|
@@ -873,9 +870,29 @@ impl DirSQL {
|
|
|
873
870
|
db.delete_rows_by_file(&table_name, &rel_path)
|
|
874
871
|
.map_err(map_db_error)?;
|
|
875
872
|
}
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
873
|
+
// Normalize every row before inserting any: a row the hook got
|
|
874
|
+
// wrong is that file's failure, and half-inserting a file would
|
|
875
|
+
// leave rows behind for a file the scan reports as skipped.
|
|
876
|
+
let mut normalized = Vec::with_capacity(raw_rows.len());
|
|
877
|
+
let mut rejected = None;
|
|
878
|
+
for raw_row in &raw_rows {
|
|
879
|
+
match db.normalize_row(&table_name, raw_row, strict) {
|
|
880
|
+
Ok(row) => normalized.push(row),
|
|
881
|
+
Err(e) => {
|
|
882
|
+
rejected = Some(e.to_string());
|
|
883
|
+
break;
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
if let Some(message) = rejected {
|
|
888
|
+
on_file_failures.push(OnFileFailure {
|
|
889
|
+
path: rel_path.clone(),
|
|
890
|
+
message,
|
|
891
|
+
});
|
|
892
|
+
continue;
|
|
893
|
+
}
|
|
894
|
+
for (row_index, row) in normalized.iter().enumerate() {
|
|
895
|
+
db.insert_row(&table_name, row, &rel_path, row_index)
|
|
879
896
|
.map_err(map_db_error)?;
|
|
880
897
|
}
|
|
881
898
|
|
|
@@ -904,26 +921,12 @@ impl DirSQL {
|
|
|
904
921
|
write_meta(db.conn(), meta).map_err(DirSqlError::sqlite)?;
|
|
905
922
|
}
|
|
906
923
|
|
|
907
|
-
//
|
|
908
|
-
//
|
|
909
|
-
//
|
|
910
|
-
//
|
|
911
|
-
//
|
|
912
|
-
|
|
913
|
-
0 => {}
|
|
914
|
-
1 => {
|
|
915
|
-
let only = on_file_failures.remove(0);
|
|
916
|
-
return Err(DirSqlError::OnFile {
|
|
917
|
-
path: only.path,
|
|
918
|
-
message: only.message,
|
|
919
|
-
});
|
|
920
|
-
}
|
|
921
|
-
_ => {
|
|
922
|
-
return Err(DirSqlError::OnFileMany {
|
|
923
|
-
failures: on_file_failures,
|
|
924
|
-
});
|
|
925
|
-
}
|
|
926
|
-
}
|
|
924
|
+
// A per-file failure no longer cancels the scan: what could be indexed
|
|
925
|
+
// is committed, and the failures ride on the built database for the
|
|
926
|
+
// caller to report (`scan_failures`). A file that failed never reached
|
|
927
|
+
// `upsert_file`, so it is absent from the cache index and is retried on
|
|
928
|
+
// the next scan -- the cache is incomplete, never wrong. SQLite, DDL
|
|
929
|
+
// and extension errors still propagate above, unchanged.
|
|
927
930
|
|
|
928
931
|
// Commit the ingest transaction.
|
|
929
932
|
_tx.commit().map_err(DirSqlError::sqlite)?;
|
|
@@ -940,6 +943,7 @@ impl DirSQL {
|
|
|
940
943
|
matcher,
|
|
941
944
|
on_file_map,
|
|
942
945
|
strict_map,
|
|
946
|
+
scan_failures: on_file_failures,
|
|
943
947
|
watcher: Mutex::new(None),
|
|
944
948
|
poll_used: AtomicBool::new(false),
|
|
945
949
|
watch_thread_started: AtomicBool::new(false),
|
|
@@ -1548,10 +1552,9 @@ fn build_tables_from_config(
|
|
|
1548
1552
|
let command = table_cfg.on_file.clone();
|
|
1549
1553
|
let config_dir = config_dir.to_path_buf();
|
|
1550
1554
|
let root = root.to_path_buf();
|
|
1551
|
-
// `Table::
|
|
1552
|
-
//
|
|
1553
|
-
|
|
1554
|
-
let mut table = Table::new(
|
|
1555
|
+
// `Table::try_new`: a hook failure is the scan's to record, not this
|
|
1556
|
+
// closure's to hide. The scan skips the file and reports it.
|
|
1557
|
+
let mut table = Table::try_new(
|
|
1555
1558
|
table_cfg.ddl.clone(),
|
|
1556
1559
|
table_cfg.glob.clone(),
|
|
1557
1560
|
move |abs_path: &str| run_on_file(&command, abs_path, &config_dir, &root, timeout),
|
|
@@ -1575,38 +1578,33 @@ fn build_tables_from_config(
|
|
|
1575
1578
|
/// config lives outside the index still resolves it. A template that omits a
|
|
1576
1579
|
/// placeholder simply never receives its value — nothing is appended.
|
|
1577
1580
|
///
|
|
1578
|
-
///
|
|
1579
|
-
///
|
|
1580
|
-
///
|
|
1581
|
-
///
|
|
1582
|
-
/// the table's configured value, or the shared
|
|
1581
|
+
/// Any failure — a spawn/exit/timeout error from [`command::run_command`], or
|
|
1582
|
+
/// output that is not a JSON array of objects — is returned to the caller. The
|
|
1583
|
+
/// scan turns it into a skipped file rather than a scan error, and needs the
|
|
1584
|
+
/// `Err` to tell a skip apart from a file that legitimately produced no rows.
|
|
1585
|
+
/// `timeout` bounds the run — the table's configured value, or the shared
|
|
1586
|
+
/// default.
|
|
1583
1587
|
fn run_on_file(
|
|
1584
1588
|
command: &str,
|
|
1585
1589
|
abs_path: &str,
|
|
1586
1590
|
config_dir: &Path,
|
|
1587
1591
|
root: &Path,
|
|
1588
1592
|
timeout: Duration,
|
|
1589
|
-
) -> Vec<Row> {
|
|
1593
|
+
) -> std::result::Result<Vec<Row>, BoxError> {
|
|
1590
1594
|
let placeholders = [
|
|
1591
1595
|
Placeholder::new("path", abs_path),
|
|
1592
1596
|
Placeholder::new("root", root.to_string_lossy().into_owned()),
|
|
1593
1597
|
];
|
|
1594
1598
|
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
},
|
|
1605
|
-
Err(error) => {
|
|
1606
|
-
eprintln!("dirsql: skipping `{abs_path}`: on-file command failed: {error}");
|
|
1607
|
-
Vec::new()
|
|
1608
|
-
}
|
|
1609
|
-
}
|
|
1599
|
+
// Errors travel to the caller rather than being logged and flattened to an
|
|
1600
|
+
// empty row set here. Swallowing them made a skip indistinguishable from a
|
|
1601
|
+
// file that legitimately produced no rows, so the scan could not count what
|
|
1602
|
+
// it dropped, cap the report, or exit differently.
|
|
1603
|
+
let output = command::run_command(command, &placeholders, config_dir, timeout, None)
|
|
1604
|
+
.map_err(|error| -> BoxError { format!("on-file command failed: {error}").into() })?;
|
|
1605
|
+
parse_command_rows(&output.payload).map_err(|message| -> BoxError {
|
|
1606
|
+
format!("on-file output was not a JSON array of rows: {message}").into()
|
|
1607
|
+
})
|
|
1610
1608
|
}
|
|
1611
1609
|
|
|
1612
1610
|
/// Parse an `on-file` command's stdout payload — a JSON array of row objects —
|
|
@@ -3206,7 +3204,8 @@ mod internal_tests {
|
|
|
3206
3204
|
dir.path(),
|
|
3207
3205
|
dir.path(),
|
|
3208
3206
|
command::DEFAULT_COMMAND_TIMEOUT,
|
|
3209
|
-
)
|
|
3207
|
+
)
|
|
3208
|
+
.expect("a well-formed payload parses");
|
|
3210
3209
|
assert_eq!(rows.len(), 1);
|
|
3211
3210
|
assert_eq!(rows[0]["n"], Value::Integer(1));
|
|
3212
3211
|
}
|
|
@@ -3225,7 +3224,8 @@ mod internal_tests {
|
|
|
3225
3224
|
dir.path(),
|
|
3226
3225
|
dir.path(),
|
|
3227
3226
|
command::DEFAULT_COMMAND_TIMEOUT,
|
|
3228
|
-
)
|
|
3227
|
+
)
|
|
3228
|
+
.expect("a well-formed payload parses");
|
|
3229
3229
|
assert_eq!(rows.len(), 1);
|
|
3230
3230
|
assert_eq!(rows[0]["q"], Value::Text("{abspath}".into()));
|
|
3231
3231
|
}
|
|
@@ -3245,7 +3245,8 @@ mod internal_tests {
|
|
|
3245
3245
|
dir.path(),
|
|
3246
3246
|
dir.path(),
|
|
3247
3247
|
command::DEFAULT_COMMAND_TIMEOUT,
|
|
3248
|
-
)
|
|
3248
|
+
)
|
|
3249
|
+
.expect("a well-formed payload parses");
|
|
3249
3250
|
assert_eq!(rows.len(), 1);
|
|
3250
3251
|
assert_eq!(
|
|
3251
3252
|
rows[0]["p"],
|
|
@@ -3253,31 +3254,41 @@ mod internal_tests {
|
|
|
3253
3254
|
);
|
|
3254
3255
|
}
|
|
3255
3256
|
|
|
3256
|
-
/// A command that cannot be spawned
|
|
3257
|
+
/// A command that cannot be spawned is an error the caller records, not an
|
|
3258
|
+
/// empty row set: a skip and a file that legitimately produced no rows must
|
|
3259
|
+
/// stay distinguishable.
|
|
3257
3260
|
#[test]
|
|
3258
|
-
fn
|
|
3261
|
+
fn run_on_file_errors_on_spawn_failure() {
|
|
3259
3262
|
let dir = TempDir::new().unwrap();
|
|
3260
|
-
let
|
|
3263
|
+
let error = run_on_file(
|
|
3261
3264
|
"definitely-not-a-real-binary-xyzzy",
|
|
3262
3265
|
"/outside/f.txt",
|
|
3263
3266
|
dir.path(),
|
|
3264
3267
|
dir.path(),
|
|
3265
3268
|
command::DEFAULT_COMMAND_TIMEOUT,
|
|
3269
|
+
)
|
|
3270
|
+
.expect_err("an unspawnable command is a failure");
|
|
3271
|
+
assert!(
|
|
3272
|
+
error.to_string().contains("on-file command failed"),
|
|
3273
|
+
"the error names the stage: {error}"
|
|
3266
3274
|
);
|
|
3267
|
-
assert!(rows.is_empty());
|
|
3268
3275
|
}
|
|
3269
3276
|
|
|
3270
3277
|
#[test]
|
|
3271
|
-
fn
|
|
3278
|
+
fn run_on_file_errors_on_non_json_output() {
|
|
3272
3279
|
let dir = TempDir::new().unwrap();
|
|
3273
|
-
let
|
|
3280
|
+
let error = run_on_file(
|
|
3274
3281
|
"echo not-json",
|
|
3275
3282
|
"/outside/f.txt",
|
|
3276
3283
|
dir.path(),
|
|
3277
3284
|
dir.path(),
|
|
3278
3285
|
command::DEFAULT_COMMAND_TIMEOUT,
|
|
3286
|
+
)
|
|
3287
|
+
.expect_err("a non-JSON payload is a failure");
|
|
3288
|
+
assert!(
|
|
3289
|
+
error.to_string().contains("not a JSON array of rows"),
|
|
3290
|
+
"the error names the stage: {error}"
|
|
3279
3291
|
);
|
|
3280
|
-
assert!(rows.is_empty());
|
|
3281
3292
|
}
|
|
3282
3293
|
|
|
3283
3294
|
#[tokio::test]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|