dirsql 0.4.1__tar.gz → 0.4.3__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.1 → dirsql-0.4.3}/Cargo.lock +1 -1
- {dirsql-0.4.1 → dirsql-0.4.3}/PKG-INFO +1 -1
- {dirsql-0.4.1/packages/rust → dirsql-0.4.3}/docs/reference/hooks.md +1 -1
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/python/Cargo.toml +1 -1
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/python/docs/reference/hooks.md +1 -1
- dirsql-0.4.3/packages/python/e2e-attestations/claude-710-accumulate-onfile.json +7 -0
- dirsql-0.4.3/packages/python/e2e-attestations/claude-714-unify-file-failures.json +7 -0
- dirsql-0.4.3/packages/rust/changelog.d/2026-07-29-on-file-failures-accumulate.md +8 -0
- dirsql-0.4.3/packages/rust/changelog.d/2026-08-02-per-file-scan-failures.md +35 -0
- {dirsql-0.4.1 → dirsql-0.4.3/packages/rust}/docs/reference/hooks.md +1 -1
- dirsql-0.4.3/packages/rust/migrations.d/2026-08-02-per-file-scan-failures.md +100 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/rust/src/bin/dirsql.rs +42 -1
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/rust/src/lib.rs +112 -46
- dirsql-0.4.1/packages/python/e2e-attestations/claude-tackle-698-bz6ere.json +0 -7
- {dirsql-0.4.1 → dirsql-0.4.3}/Cargo.toml +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/README.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/dirsql/__init__.py +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/dirsql/_async.py +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/dirsql/_dirsql.pyi +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/dirsql/cli/__init__.py +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/dirsql/cli/binary_path.py +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/dirsql/cli/discover_plugins/__init__.py +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/dirsql/cli/discover_plugins/discovered_fragments.py +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/dirsql/cli/discover_plugins/discovery_disabled.py +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/dirsql/cli/discover_plugins/fragment_path.py +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/dirsql/cli/discover_plugins/user_passed_config.py +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/dirsql/cli/discover_plugins/with_discovered_plugins.py +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/dirsql/cli/is_windows.py +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/dirsql/cli/main.py +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/dirsql/cli/resolve_config_extensions.py +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/dirsql/py.typed +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/dirsql/resolve_config_extensions.py +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/dirsql/resolve_extension.py +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/docs/.claude/CLAUDE.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/docs/.vitepress/config.ts +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/docs/.vitepress/theme/index.ts +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/docs/.vitepress/theme/lang.ts +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/docs/AGENTS.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/docs/explanation.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/docs/getting-started.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/docs/howto/columns-from-paths.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/docs/howto/define-tables.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/docs/howto/embed.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/docs/howto/extract-from-contents.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/docs/howto/load-extension.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/docs/howto/parse-files-into-columns.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/docs/howto/persist.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/docs/howto/query-without-config.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/docs/howto/react-to-changes.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/docs/howto/search-by-meaning.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/docs/howto/skip-files.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/docs/howto/write-a-plugin.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/docs/index.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/docs/migrations.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/docs/package.json +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/docs/pnpm-lock.yaml +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/docs/pnpm-workspace.yaml +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/docs/reference/cli.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/docs/reference/columns.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/docs/reference/config.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/docs/reference/http-api.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/docs/reference/path-tables.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/docs/reference/sdk.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/python/CHANGELOG.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/python/MIGRATIONS.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/python/README.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/python/changelog.d/2026-07-13-drop-cwd-config-auto-detect.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/python/changelog.d/2026-07-13-plugin-discovery.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/python/changelog.d/2026-07-13-rename-extract-to-on-file.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/python/changelog.d/2026-07-13-repeatable-config.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/python/changelog.d/2026-07-13-sdk-default-baked-in-config.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/python/changelog.d/2026-07-20-drop-implicit-files-table.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/python/changelog.d/2026-07-21-remove-glob-captures.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/python/changelog.d/2026-07-24-hookless-table-config-error.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/python/changelog.d/2026-07-24-remove-stat-fact-injection.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/python/changelog.d/2026-07-29-restore-python-310.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/python/changelog.d/README.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/python/conftest.py +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/python/docs/.claude/CLAUDE.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/python/docs/.vitepress/config.ts +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/python/docs/.vitepress/theme/index.ts +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/python/docs/.vitepress/theme/lang.ts +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/python/docs/AGENTS.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/python/docs/explanation.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/python/docs/getting-started.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/python/docs/howto/columns-from-paths.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/python/docs/howto/define-tables.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/python/docs/howto/embed.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/python/docs/howto/extract-from-contents.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/python/docs/howto/load-extension.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/python/docs/howto/parse-files-into-columns.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/python/docs/howto/persist.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/python/docs/howto/query-without-config.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/python/docs/howto/react-to-changes.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/python/docs/howto/search-by-meaning.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/python/docs/howto/skip-files.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/python/docs/howto/write-a-plugin.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/python/docs/index.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/python/docs/migrations.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/python/docs/package.json +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/python/docs/pnpm-lock.yaml +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/python/docs/pnpm-workspace.yaml +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/python/docs/reference/cli.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/python/docs/reference/columns.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/python/docs/reference/config.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/python/docs/reference/http-api.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/python/docs/reference/path-tables.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/python/docs/reference/sdk.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/python/migrations.d/2026-07-13-drop-cwd-config-auto-detect.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/python/migrations.d/2026-07-13-rename-extract-to-on-file.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/python/migrations.d/2026-07-13-sdk-default-baked-in-config.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/python/migrations.d/2026-07-20-drop-implicit-files-table.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/python/migrations.d/2026-07-21-remove-glob-captures.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/python/migrations.d/2026-07-24-hookless-table-config-error.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/python/migrations.d/2026-07-24-remove-stat-fact-injection.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/python/migrations.d/2026-07-29-restore-python-310.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/python/migrations.d/README.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/python/src/lib.rs +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/python/tests/__init__.py +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/python/tests/conftest.py +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/python/tests/e2e/__init__.py +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/python/tests/e2e/fixtures/dirsql_plugin_fixture/__init__.py +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/python/tests/e2e/fixtures/dirsql_plugin_fixture/dirsql.toml +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/python/tests/integration/__init__.py +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/python/tests/integration/binding/__init__.py +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/python/tests/integration/hermetic/__init__.py +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/rust/CHANGELOG.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/rust/Cargo.toml +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/rust/MIGRATIONS.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/rust/README.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/rust/benches/db_bench.rs +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/rust/benches/differ_bench.rs +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/rust/benches/matcher_bench.rs +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/rust/benches/scanner_bench.rs +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/rust/changelog.d/2026-07-13-batch-ingest-transaction.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/rust/changelog.d/2026-07-13-chained-pre-post-query-hooks.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/rust/changelog.d/2026-07-13-drop-cwd-config-auto-detect.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/rust/changelog.d/2026-07-13-fan-out-file-table-matching.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/rust/changelog.d/2026-07-13-persist-wal.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/rust/changelog.d/2026-07-13-rename-extract-to-on-file.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/rust/changelog.d/2026-07-13-repeatable-builder-config.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/rust/changelog.d/2026-07-13-repeatable-cli-config.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/rust/changelog.d/2026-07-13-sdk-default-baked-in-config.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/rust/changelog.d/2026-07-13-subcommand-local-config-flags.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/rust/changelog.d/2026-07-19-path-table-query-fallback.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/rust/changelog.d/2026-07-19-path-table-vtab.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/rust/changelog.d/2026-07-20-drop-implicit-files-table.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/rust/changelog.d/2026-07-20-path-table-glob-semantics.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/rust/changelog.d/2026-07-20-schema-inference.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/rust/changelog.d/2026-07-21-on-file-flag.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/rust/changelog.d/2026-07-21-remove-glob-captures.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/rust/changelog.d/2026-07-23-init-escalation-example.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/rust/changelog.d/2026-07-23-query-default-cli.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/rust/changelog.d/2026-07-24-hookless-table-config-error.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/rust/changelog.d/2026-07-24-remove-stat-fact-injection.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/rust/changelog.d/README.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/rust/docs/explanation.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/rust/docs/getting-started.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/rust/docs/howto/columns-from-paths.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/rust/docs/howto/define-tables.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/rust/docs/howto/embed.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/rust/docs/howto/extract-from-contents.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/rust/docs/howto/load-extension.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/rust/docs/howto/parse-files-into-columns.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/rust/docs/howto/persist.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/rust/docs/howto/query-without-config.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/rust/docs/howto/react-to-changes.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/rust/docs/howto/search-by-meaning.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/rust/docs/howto/skip-files.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/rust/docs/howto/write-a-plugin.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/rust/docs/index.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/rust/docs/migrations.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/rust/docs/reference/cli.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/rust/docs/reference/columns.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/rust/docs/reference/config.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/rust/docs/reference/http-api.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/rust/docs/reference/path-tables.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/rust/docs/reference/sdk.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/rust/migrations.d/2026-07-13-drop-cwd-config-auto-detect.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/rust/migrations.d/2026-07-13-fan-out-file-table-matching.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/rust/migrations.d/2026-07-13-persist-wal.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/rust/migrations.d/2026-07-13-rename-extract-to-on-file.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/rust/migrations.d/2026-07-13-sdk-default-baked-in-config.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/rust/migrations.d/2026-07-13-subcommand-local-config-flags.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/rust/migrations.d/2026-07-20-drop-implicit-files-table.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/rust/migrations.d/2026-07-20-path-table-glob-semantics.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/rust/migrations.d/2026-07-21-remove-glob-captures.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/rust/migrations.d/2026-07-23-init-escalation-example.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/rust/migrations.d/2026-07-23-query-default-cli.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/rust/migrations.d/2026-07-24-hookless-table-config-error.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/rust/migrations.d/2026-07-24-remove-stat-fact-injection.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/rust/migrations.d/README.md +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/rust/src/cli/execute.rs +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/rust/src/cli/init.rs +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/rust/src/cli/mod.rs +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/rust/src/cli/router.rs +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/rust/src/cli/serialize.rs +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/rust/src/cli/server.rs +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/rust/src/command.rs +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/rust/src/config.rs +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/rust/src/db.rs +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/rust/src/default_config.toml +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/rust/src/differ.rs +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/rust/src/infer.rs +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/rust/src/matcher.rs +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/rust/src/parsed_vtab.rs +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/rust/src/path_table.rs +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/rust/src/persist.rs +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/rust/src/scanner.rs +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/rust/src/vtab.rs +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/packages/rust/src/watcher.rs +0 -0
- {dirsql-0.4.1 → dirsql-0.4.3}/pyproject.toml +0 -0
|
@@ -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.3"
|
|
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,8 @@
|
|
|
1
|
+
**Changed** — a scan now attempts every matched file instead of stopping at the
|
|
2
|
+
first whose `on-file` hook fails, and reports every failure rather than only the
|
|
3
|
+
first. A single failure still raises `DirSqlError::OnFile` unchanged; several
|
|
4
|
+
now raise a new `DirSqlError::OnFileMany` carrying one `OnFileFailure { path,
|
|
5
|
+
message }` per file.
|
|
6
|
+
|
|
7
|
+
A scan with failures still fails and still leaves the cache exactly as it was —
|
|
8
|
+
only the reporting improved. Committing a partial index is tracked in #697.
|
|
@@ -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.
|
|
@@ -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.
|
|
@@ -78,6 +78,16 @@ pub const DEFAULT_CONFIG_TOML: &str = include_str!("default_config.toml");
|
|
|
78
78
|
type BoxError = Box<dyn StdError + Send + Sync + 'static>;
|
|
79
79
|
type OnFileFn = dyn Fn(&str) -> std::result::Result<Vec<Row>, BoxError> + Send + Sync + 'static;
|
|
80
80
|
|
|
81
|
+
/// One file whose on-file hook failed, carried so a scan can report every
|
|
82
|
+
/// failure rather than only whichever came first.
|
|
83
|
+
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
84
|
+
pub struct OnFileFailure {
|
|
85
|
+
/// Path relative to the scan root.
|
|
86
|
+
pub path: String,
|
|
87
|
+
/// The hook's error, as rendered by its `Display`.
|
|
88
|
+
pub message: String,
|
|
89
|
+
}
|
|
90
|
+
|
|
81
91
|
#[derive(Debug, Error)]
|
|
82
92
|
pub enum DirSqlError {
|
|
83
93
|
#[error(transparent)]
|
|
@@ -119,9 +129,6 @@ pub enum DirSqlError {
|
|
|
119
129
|
#[error("duplicate table name: {0}")]
|
|
120
130
|
DuplicateTable(String),
|
|
121
131
|
|
|
122
|
-
#[error("on-file error for {path}: {message}")]
|
|
123
|
-
OnFile { path: String, message: String },
|
|
124
|
-
|
|
125
132
|
#[error("config error: {message}")]
|
|
126
133
|
Config {
|
|
127
134
|
message: String,
|
|
@@ -249,6 +256,8 @@ struct DirSqlInner {
|
|
|
249
256
|
matcher: TableMatcher,
|
|
250
257
|
on_file_map: HashMap<String, Arc<OnFileFn>>,
|
|
251
258
|
strict_map: HashMap<String, bool>,
|
|
259
|
+
/// Files the initial scan could not index. Empty for a clean scan.
|
|
260
|
+
scan_failures: Vec<OnFileFailure>,
|
|
252
261
|
watcher: Mutex<Option<Watcher>>,
|
|
253
262
|
/// Locks out [`DirSQL::watch`] once [`DirSQL::poll_events`] has run:
|
|
254
263
|
/// both would drain the same underlying watcher.
|
|
@@ -711,6 +720,18 @@ impl DirSQL {
|
|
|
711
720
|
})
|
|
712
721
|
}
|
|
713
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
|
+
|
|
714
735
|
/// Split-phase construction — part 2. Consumes the intermediate state from
|
|
715
736
|
/// [`prepare_resolved`](Self::prepare_resolved): creates the SQLite
|
|
716
737
|
/// database (or wires up the persistent on-disk one), runs each table's
|
|
@@ -816,6 +837,7 @@ impl DirSQL {
|
|
|
816
837
|
}
|
|
817
838
|
|
|
818
839
|
let snapshot_ns = now_ns();
|
|
840
|
+
let mut on_file_failures: Vec<OnFileFailure> = Vec::new();
|
|
819
841
|
for ScannedFile {
|
|
820
842
|
rel_path,
|
|
821
843
|
table_name,
|
|
@@ -827,11 +849,20 @@ impl DirSQL {
|
|
|
827
849
|
})?;
|
|
828
850
|
let strict = *strict_map.get(&table_name).unwrap_or(&false);
|
|
829
851
|
let abs_path = root.join(&rel_path);
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
852
|
+
// A hook failure is this file's problem, not the scan's: record it
|
|
853
|
+
// and keep going, so one unreadable file cannot hide the state of
|
|
854
|
+
// every file after it. The collected failures still fail the build
|
|
855
|
+
// below -- whether a partial index commits is dirsql#697.
|
|
856
|
+
let raw_rows = match on_file(&abs_path.to_string_lossy()) {
|
|
857
|
+
Ok(rows) => rows,
|
|
858
|
+
Err(e) => {
|
|
859
|
+
on_file_failures.push(OnFileFailure {
|
|
860
|
+
path: rel_path.clone(),
|
|
861
|
+
message: e.to_string(),
|
|
862
|
+
});
|
|
863
|
+
continue;
|
|
864
|
+
}
|
|
865
|
+
};
|
|
835
866
|
|
|
836
867
|
// When updating an existing file in the persistent cache, drop
|
|
837
868
|
// its old rows before inserting the new ones.
|
|
@@ -839,9 +870,29 @@ impl DirSQL {
|
|
|
839
870
|
db.delete_rows_by_file(&table_name, &rel_path)
|
|
840
871
|
.map_err(map_db_error)?;
|
|
841
872
|
}
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
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)
|
|
845
896
|
.map_err(map_db_error)?;
|
|
846
897
|
}
|
|
847
898
|
|
|
@@ -870,6 +921,13 @@ impl DirSQL {
|
|
|
870
921
|
write_meta(db.conn(), meta).map_err(DirSqlError::sqlite)?;
|
|
871
922
|
}
|
|
872
923
|
|
|
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.
|
|
930
|
+
|
|
873
931
|
// Commit the ingest transaction.
|
|
874
932
|
_tx.commit().map_err(DirSqlError::sqlite)?;
|
|
875
933
|
|
|
@@ -885,6 +943,7 @@ impl DirSQL {
|
|
|
885
943
|
matcher,
|
|
886
944
|
on_file_map,
|
|
887
945
|
strict_map,
|
|
946
|
+
scan_failures: on_file_failures,
|
|
888
947
|
watcher: Mutex::new(None),
|
|
889
948
|
poll_used: AtomicBool::new(false),
|
|
890
949
|
watch_thread_started: AtomicBool::new(false),
|
|
@@ -1493,10 +1552,9 @@ fn build_tables_from_config(
|
|
|
1493
1552
|
let command = table_cfg.on_file.clone();
|
|
1494
1553
|
let config_dir = config_dir.to_path_buf();
|
|
1495
1554
|
let root = root.to_path_buf();
|
|
1496
|
-
// `Table::
|
|
1497
|
-
//
|
|
1498
|
-
|
|
1499
|
-
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(
|
|
1500
1558
|
table_cfg.ddl.clone(),
|
|
1501
1559
|
table_cfg.glob.clone(),
|
|
1502
1560
|
move |abs_path: &str| run_on_file(&command, abs_path, &config_dir, &root, timeout),
|
|
@@ -1520,38 +1578,33 @@ fn build_tables_from_config(
|
|
|
1520
1578
|
/// config lives outside the index still resolves it. A template that omits a
|
|
1521
1579
|
/// placeholder simply never receives its value — nothing is appended.
|
|
1522
1580
|
///
|
|
1523
|
-
///
|
|
1524
|
-
///
|
|
1525
|
-
///
|
|
1526
|
-
///
|
|
1527
|
-
/// 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.
|
|
1528
1587
|
fn run_on_file(
|
|
1529
1588
|
command: &str,
|
|
1530
1589
|
abs_path: &str,
|
|
1531
1590
|
config_dir: &Path,
|
|
1532
1591
|
root: &Path,
|
|
1533
1592
|
timeout: Duration,
|
|
1534
|
-
) -> Vec<Row> {
|
|
1593
|
+
) -> std::result::Result<Vec<Row>, BoxError> {
|
|
1535
1594
|
let placeholders = [
|
|
1536
1595
|
Placeholder::new("path", abs_path),
|
|
1537
1596
|
Placeholder::new("root", root.to_string_lossy().into_owned()),
|
|
1538
1597
|
];
|
|
1539
1598
|
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
},
|
|
1550
|
-
Err(error) => {
|
|
1551
|
-
eprintln!("dirsql: skipping `{abs_path}`: on-file command failed: {error}");
|
|
1552
|
-
Vec::new()
|
|
1553
|
-
}
|
|
1554
|
-
}
|
|
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
|
+
})
|
|
1555
1608
|
}
|
|
1556
1609
|
|
|
1557
1610
|
/// Parse an `on-file` command's stdout payload — a JSON array of row objects —
|
|
@@ -3151,7 +3204,8 @@ mod internal_tests {
|
|
|
3151
3204
|
dir.path(),
|
|
3152
3205
|
dir.path(),
|
|
3153
3206
|
command::DEFAULT_COMMAND_TIMEOUT,
|
|
3154
|
-
)
|
|
3207
|
+
)
|
|
3208
|
+
.expect("a well-formed payload parses");
|
|
3155
3209
|
assert_eq!(rows.len(), 1);
|
|
3156
3210
|
assert_eq!(rows[0]["n"], Value::Integer(1));
|
|
3157
3211
|
}
|
|
@@ -3170,7 +3224,8 @@ mod internal_tests {
|
|
|
3170
3224
|
dir.path(),
|
|
3171
3225
|
dir.path(),
|
|
3172
3226
|
command::DEFAULT_COMMAND_TIMEOUT,
|
|
3173
|
-
)
|
|
3227
|
+
)
|
|
3228
|
+
.expect("a well-formed payload parses");
|
|
3174
3229
|
assert_eq!(rows.len(), 1);
|
|
3175
3230
|
assert_eq!(rows[0]["q"], Value::Text("{abspath}".into()));
|
|
3176
3231
|
}
|
|
@@ -3190,7 +3245,8 @@ mod internal_tests {
|
|
|
3190
3245
|
dir.path(),
|
|
3191
3246
|
dir.path(),
|
|
3192
3247
|
command::DEFAULT_COMMAND_TIMEOUT,
|
|
3193
|
-
)
|
|
3248
|
+
)
|
|
3249
|
+
.expect("a well-formed payload parses");
|
|
3194
3250
|
assert_eq!(rows.len(), 1);
|
|
3195
3251
|
assert_eq!(
|
|
3196
3252
|
rows[0]["p"],
|
|
@@ -3198,31 +3254,41 @@ mod internal_tests {
|
|
|
3198
3254
|
);
|
|
3199
3255
|
}
|
|
3200
3256
|
|
|
3201
|
-
/// 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.
|
|
3202
3260
|
#[test]
|
|
3203
|
-
fn
|
|
3261
|
+
fn run_on_file_errors_on_spawn_failure() {
|
|
3204
3262
|
let dir = TempDir::new().unwrap();
|
|
3205
|
-
let
|
|
3263
|
+
let error = run_on_file(
|
|
3206
3264
|
"definitely-not-a-real-binary-xyzzy",
|
|
3207
3265
|
"/outside/f.txt",
|
|
3208
3266
|
dir.path(),
|
|
3209
3267
|
dir.path(),
|
|
3210
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}"
|
|
3211
3274
|
);
|
|
3212
|
-
assert!(rows.is_empty());
|
|
3213
3275
|
}
|
|
3214
3276
|
|
|
3215
3277
|
#[test]
|
|
3216
|
-
fn
|
|
3278
|
+
fn run_on_file_errors_on_non_json_output() {
|
|
3217
3279
|
let dir = TempDir::new().unwrap();
|
|
3218
|
-
let
|
|
3280
|
+
let error = run_on_file(
|
|
3219
3281
|
"echo not-json",
|
|
3220
3282
|
"/outside/f.txt",
|
|
3221
3283
|
dir.path(),
|
|
3222
3284
|
dir.path(),
|
|
3223
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}"
|
|
3224
3291
|
);
|
|
3225
|
-
assert!(rows.is_empty());
|
|
3226
3292
|
}
|
|
3227
3293
|
|
|
3228
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
|