dirsql 0.4.101__tar.gz → 0.4.102__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.101 → dirsql-0.4.102}/Cargo.lock +2 -2
- {dirsql-0.4.101 → dirsql-0.4.102}/PKG-INFO +1 -1
- {dirsql-0.4.101/packages/rust → dirsql-0.4.102}/docs/reference/hooks.md +3 -1
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/Cargo.toml +1 -1
- dirsql-0.4.102/packages/python/changelog.d/2026-10-07-parallel-hooks.md +1 -0
- {dirsql-0.4.101 → dirsql-0.4.102/packages/python}/docs/reference/hooks.md +3 -1
- dirsql-0.4.102/packages/python/e2e-attestations/bot-1429-parallel-hooks.json +7 -0
- dirsql-0.4.102/packages/python/migrations.d/2026-10-07-parallel-hooks.md +41 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/Cargo.toml +1 -1
- {dirsql-0.4.101/packages/python → dirsql-0.4.102/packages/rust}/docs/reference/hooks.md +3 -1
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/src/command.rs +144 -11
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/src/db.rs +14 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/src/infer.rs +13 -1
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/src/on_file.rs +141 -28
- {dirsql-0.4.101 → dirsql-0.4.102}/Cargo.toml +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/README.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/dirsql/__init__.py +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/dirsql/_async.py +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/dirsql/_dirsql.pyi +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/dirsql/cli/__init__.py +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/dirsql/cli/discover_plugins/__init__.py +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/dirsql/cli/discover_plugins/discovered_fragments.py +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/dirsql/cli/discover_plugins/discovery_disabled.py +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/dirsql/cli/discover_plugins/fragment_path.py +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/dirsql/cli/discover_plugins/with_discovered_plugins.py +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/dirsql/cli/main.py +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/dirsql/cli/resolve_config_extensions.py +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/dirsql/plan_entry_path.py +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/dirsql/py.typed +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/dirsql/read_config.py +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/dirsql/resolve_configs_extension_specs.py +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/dirsql/resolve_extension.py +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/dirsql/resolve_package.py +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/docs/.claude/CLAUDE.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/docs/.vitepress/config.ts +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/docs/.vitepress/theme/index.ts +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/docs/.vitepress/theme/lang.ts +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/docs/AGENTS.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/docs/context/.gitattributes +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/docs/context/recipes.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/docs/context/troubleshooting.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/docs/context/usage.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/docs/explanation.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/docs/getting-started.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/docs/howto/agent-skill.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/docs/howto/columns-from-paths.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/docs/howto/define-tables.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/docs/howto/embed.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/docs/howto/extract-from-contents.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/docs/howto/load-extension.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/docs/howto/parse-files-into-columns.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/docs/howto/persist.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/docs/howto/query-json.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/docs/howto/query-without-config.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/docs/howto/react-to-changes.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/docs/howto/search-by-meaning.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/docs/howto/search-indexes.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/docs/howto/skip-files.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/docs/howto/write-a-plugin.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/docs/index.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/docs/package.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/docs/plugins.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/docs/pnpm-lock.yaml +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/docs/pnpm-workspace.yaml +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/docs/reference/cli.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/docs/reference/columns.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/docs/reference/config.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/docs/reference/glob.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/docs/reference/http-api.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/docs/reference/path-tables.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/docs/reference/sdk.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/CHANGELOG.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/MIGRATIONS.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/README.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-07-13-drop-cwd-config-auto-detect.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-07-13-plugin-discovery.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-07-13-rename-extract-to-on-file.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-07-13-repeatable-config.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-07-13-sdk-default-baked-in-config.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-07-20-drop-implicit-files-table.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-07-21-remove-glob-captures.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-07-24-hookless-table-config-error.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-07-24-remove-stat-fact-injection.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-07-29-restore-python-310.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-08-02-release-profile.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-08-02-scan-failures.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-08-03-discovery-ext-resolution.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-08-03-manylinux-dynamic-binary.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-08-03-python-no-ignore.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-08-04-cast-lints.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-08-04-cli-in-process.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-08-20-curtaincall-dev-dependency.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-08-20-declared-table-name.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-09-04-config-paths-from-argv-module.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-09-05-config-extension-loader-split.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-09-05-resolve-config-extensions-split.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-09-05-resolve-extension-split.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-09-09-launcher-config-path-scan.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-09-17-extension-resolution-via-core.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-09-18-export-scan-failure.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-09-18-flatten-row-event-in-core.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-09-18-programmatic-extension-plan.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-09-20-sigint-ends-a-run-in-progress.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-09-24-query-rows-in-projection-order.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-10-02-readme-docs-host.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-10-03-one-glob-rule-for-config-globs.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-10-03-path-table-directory-one-level.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-10-04-path-table-dotfiles-hidden.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-10-05-brace-groups-follow-bash.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-10-05-cli-context.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-10-05-gitignore-requires-repo.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-10-05-path-table-node-modules-named.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-10-05-path-table-symlinks.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-10-05-path-table-trailing-slash.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-10-05-unbalanced-brackets-literal.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-10-06-config-braces.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-10-06-config-directory.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-10-06-config-dotnames.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-10-06-config-gitignore.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-10-06-config-globs-anchor-at-the-config.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-10-06-config-links.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-10-06-one-glob-compiler.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-10-06-plugin-no-default-records.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-10-06-watch-broadcast.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-10-06-zero-match-config-table-warning.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-10-07-bracket-slash-literal.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-10-07-dotdot-and-double-slashes.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-10-07-dotdot-as-written.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-10-07-glob-walks-node-modules.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-10-07-literal-gitignore.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-10-07-on-file-ndjson.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-10-07-persist-cache-outside-root.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-10-07-posix-classes-unicode.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/README.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/conftest.py +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/docs/.claude/CLAUDE.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/docs/.vitepress/config.ts +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/docs/.vitepress/theme/index.ts +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/docs/.vitepress/theme/lang.ts +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/docs/AGENTS.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/docs/context/.gitattributes +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/docs/context/recipes.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/docs/context/troubleshooting.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/docs/context/usage.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/docs/explanation.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/docs/getting-started.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/docs/howto/agent-skill.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/docs/howto/columns-from-paths.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/docs/howto/define-tables.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/docs/howto/embed.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/docs/howto/extract-from-contents.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/docs/howto/load-extension.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/docs/howto/parse-files-into-columns.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/docs/howto/persist.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/docs/howto/query-json.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/docs/howto/query-without-config.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/docs/howto/react-to-changes.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/docs/howto/search-by-meaning.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/docs/howto/search-indexes.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/docs/howto/skip-files.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/docs/howto/write-a-plugin.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/docs/index.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/docs/package.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/docs/plugins.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/docs/pnpm-lock.yaml +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/docs/pnpm-workspace.yaml +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/docs/reference/cli.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/docs/reference/columns.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/docs/reference/config.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/docs/reference/glob.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/docs/reference/http-api.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/docs/reference/path-tables.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/docs/reference/sdk.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/bot-1334-listing-exemptions.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/bot-1397-watch-broadcast.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/bot-1398-inotify-error.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/bot-1399-server-cors.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/bot-1401-watch-scope.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/bot-1407-child-place-depth.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/bot-1414-stat-speed.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/bot-1415-server-speed.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/bot-1418-node-modules.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/bot-1419-dotdot-slashes.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/bot-1420-bracket-slash.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/bot-1421-posix-unicode.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/bot-1422-literal-gitignore.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/bot-1428-ndjson-hooks.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/bot-1430-dotdot-as-written.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/bot-1431-persist-cache-dir.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/bot-1437-test-cache-isolation.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/bot-1440-fix-main-build.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/bot-1448-wildcard-ignored-prefix.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/bot-1451-named-dir-never-hidden.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/bot-fix-main-place-depth-20261007.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/cc-keen-knuth-f11gyq.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1056-py-ofpf9.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1066-p1-resolve-extension.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1078-argv-config-paths-core.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1079-p2-resolve-config-extensions.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1080-share-vtab-scaffolding.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1081-indicatif-progress.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1096-watch-moved-dir.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1109-watch-moved-out.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1119-ext-plan.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1120-py-ext.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1122-ext-cleanup.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1130-codec-crates.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1131-flatten-row-event.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1139-sse-flatten.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1140-scan-failure-export.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1148-clippy-all-targets.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1164-sigint-fatal.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1170-projection-order.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1172-on-file-path.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1173-py-projection-order.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1184-win-rel-paths.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1185-win-abs-table-names.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1186-win-test-harness.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1196-join-index.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1199-glob-prefix.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1201-single-walk.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1204-embed-batch.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1207-win-argv-quote.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1208-win-nonverbatim-paths.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1215-on-file-batch.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1216-on-file-cleanup.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1223-config-glob-rule.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1223-dir-one-level.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1225-plugin-no-default.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1226-stale-docs.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1232-anchor-plumbing.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1232-config-anchor.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1232-multi-watch.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1232-zero-match.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1238-parallel-walk.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1241-content-prefetch.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1243-ingest-perf.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1249-dotfiles.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1256-symlinks.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1257-trailing-slash.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1258-config-braces.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1258-config-directory.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1258-config-dotnames.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1258-config-gitignore.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1258-config-links.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1258-one-pattern.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1262-brace-literal.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1264-context.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1267-posix-classes.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1268-unbalanced-brackets.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1269-utf8-chars.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1271-node-modules-named.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1272-ancestor-gitignore.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1273-walk-prune.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1285-ingest-fast.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1286-nested-repo.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1305-glob-speed.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1308-glob-speed.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1310-glob-speed.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1311-glob-speed.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1312-glob-speed.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1318-lazy-stat.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1320-walk-speed.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1322-walk-readdir.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1325-parallel-walk.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1326-stream-rows.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1330-python-dirsql-patch.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1330-python-extensions-patch.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1330-scanner-effects.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1330-scanner-gitignore.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1330-vtab-prefetch-flag.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1330-vtab-read.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1351-dot-dirs.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-819-oneshot-timeout.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-827-unquote-doubled-quotes.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-epic-953-slice2-reedline.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-epic-953-slice3-format.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-epic-953-stacked-prs-x59wm3.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-github-issue-825-e4mhhp.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-issue-951-0eease.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-issue-962-0jjrfv.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-issue-986-red-test-e7sjrb.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-tackle-1034-cachesplit.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-tackle-1042-py12.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-tackle-956-xzen74.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-tackle-957-lrm0z6-worker-calls.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-tackle-957-lrm0z6.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-tackle-975-n8mc2h.json +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/migrations.d/2026-07-13-drop-cwd-config-auto-detect.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/migrations.d/2026-07-13-rename-extract-to-on-file.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/migrations.d/2026-07-13-sdk-default-baked-in-config.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/migrations.d/2026-07-20-drop-implicit-files-table.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/migrations.d/2026-07-21-remove-glob-captures.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/migrations.d/2026-07-24-hookless-table-config-error.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/migrations.d/2026-07-24-remove-stat-fact-injection.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/migrations.d/2026-07-29-restore-python-310.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/migrations.d/2026-08-04-cli-in-process.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/migrations.d/2026-08-20-declared-table-name.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/migrations.d/2026-09-20-sigint-ends-a-run-in-progress.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/migrations.d/2026-09-24-query-rows-in-projection-order.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/migrations.d/2026-10-02-readme-docs-host.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/migrations.d/2026-10-03-one-glob-rule-for-config-globs.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/migrations.d/2026-10-03-path-table-directory-one-level.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/migrations.d/2026-10-04-path-table-dotfiles-hidden.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/migrations.d/2026-10-05-brace-groups-follow-bash.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/migrations.d/2026-10-05-cli-context.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/migrations.d/2026-10-05-gitignore-requires-repo.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/migrations.d/2026-10-05-path-table-node-modules-named.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/migrations.d/2026-10-05-path-table-symlinks.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/migrations.d/2026-10-05-path-table-trailing-slash.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/migrations.d/2026-10-05-unbalanced-brackets-literal.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/migrations.d/2026-10-06-config-braces.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/migrations.d/2026-10-06-config-directory.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/migrations.d/2026-10-06-config-dotnames.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/migrations.d/2026-10-06-config-gitignore.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/migrations.d/2026-10-06-config-globs-anchor-at-the-config.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/migrations.d/2026-10-06-config-links.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/migrations.d/2026-10-06-one-glob-compiler.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/migrations.d/2026-10-06-plugin-no-default-records.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/migrations.d/2026-10-06-watch-broadcast.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/migrations.d/2026-10-06-zero-match-config-table-warning.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/migrations.d/2026-10-07-bracket-slash-literal.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/migrations.d/2026-10-07-dotdot-and-double-slashes.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/migrations.d/2026-10-07-dotdot-as-written.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/migrations.d/2026-10-07-glob-walks-node-modules.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/migrations.d/2026-10-07-literal-gitignore.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/migrations.d/2026-10-07-on-file-ndjson.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/migrations.d/2026-10-07-persist-cache-outside-root.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/migrations.d/2026-10-07-posix-classes-unicode.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/migrations.d/README.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/src/lib.rs +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/testing-conventions.toml +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/tests/__init__.py +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/tests/conftest.py +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/tests/e2e/__init__.py +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/tests/e2e/fixtures/dirsql_plugin_fixture/__init__.py +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/tests/e2e/fixtures/dirsql_plugin_fixture/dirsql.toml +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/tests/e2e/speed_of_light.py +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/tests/integration/__init__.py +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/tests/integration/binding/__init__.py +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/tests/integration/hermetic/__init__.py +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/CHANGELOG.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/MIGRATIONS.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/README.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/benches/db_bench.rs +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/benches/differ_bench.rs +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/benches/matcher_bench.rs +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/benches/scanner_bench.rs +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/docs/context/.gitattributes +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/docs/context/recipes.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/docs/context/troubleshooting.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/docs/context/usage.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/docs/explanation.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/docs/getting-started.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/docs/howto/agent-skill.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/docs/howto/columns-from-paths.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/docs/howto/define-tables.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/docs/howto/embed.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/docs/howto/extract-from-contents.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/docs/howto/load-extension.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/docs/howto/parse-files-into-columns.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/docs/howto/persist.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/docs/howto/query-json.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/docs/howto/query-without-config.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/docs/howto/react-to-changes.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/docs/howto/search-by-meaning.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/docs/howto/search-indexes.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/docs/howto/skip-files.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/docs/howto/write-a-plugin.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/docs/index.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/docs/plugins.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/docs/reference/cli.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/docs/reference/columns.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/docs/reference/config.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/docs/reference/glob.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/docs/reference/http-api.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/docs/reference/path-tables.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/docs/reference/sdk.md +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/src/bin/dirsql.rs +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/src/brace.rs +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/src/cli/context.rs +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/src/cli/execute.rs +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/src/cli/init.rs +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/src/cli/mod.rs +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/src/cli/repl.rs +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/src/cli/router.rs +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/src/cli/run.rs +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/src/cli/serialize.rs +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/src/cli/server.rs +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/src/cli/table.rs +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/src/config.rs +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/src/default_config.toml +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/src/differ.rs +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/src/extension_resolution.rs +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/src/functions.rs +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/src/launcher.rs +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/src/lib.rs +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/src/listing.rs +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/src/matcher.rs +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/src/parsed_vtab.rs +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/src/path_table.rs +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/src/persist.rs +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/src/posix_class.rs +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/src/progress.rs +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/src/row_event_flat.rs +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/src/scanner.rs +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/src/sql_literal.rs +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/src/tree_walk.rs +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/src/vtab.rs +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/src/vtab_scaffold.rs +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/src/watcher.rs +0 -0
- {dirsql-0.4.101 → dirsql-0.4.102}/pyproject.toml +0 -0
|
@@ -573,7 +573,7 @@ dependencies = [
|
|
|
573
573
|
|
|
574
574
|
[[package]]
|
|
575
575
|
name = "dirsql"
|
|
576
|
-
version = "0.4.
|
|
576
|
+
version = "0.4.102"
|
|
577
577
|
dependencies = [
|
|
578
578
|
"assert_cmd",
|
|
579
579
|
"axum",
|
|
@@ -625,7 +625,7 @@ dependencies = [
|
|
|
625
625
|
|
|
626
626
|
[[package]]
|
|
627
627
|
name = "dirsql-py-ext"
|
|
628
|
-
version = "0.4.
|
|
628
|
+
version = "0.4.102"
|
|
629
629
|
dependencies = [
|
|
630
630
|
"dirsql",
|
|
631
631
|
"pyo3",
|
|
@@ -161,7 +161,9 @@ on-file command `python3 extract.py {path}` uses `{path}`, but an on-file comman
|
|
|
161
161
|
Every platform caps the bytes one process may receive as arguments. When a
|
|
162
162
|
table's paths outgrow that cap, dirsql splits them into the fewest
|
|
163
163
|
consecutive runs that fit, runs the command once per run, and concatenates
|
|
164
|
-
the rows it prints
|
|
164
|
+
the rows it prints. Runs execute concurrently, one per CPU at most, like
|
|
165
|
+
`xargs -P`, so rows keep their order within a run but not across runs; SQL
|
|
166
|
+
never promised row order, so `ORDER BY` what you need. The command cannot tell: each run is an
|
|
165
167
|
ordinary invocation with a subset of the paths. A command must therefore not
|
|
166
168
|
assume one invocation sees every path — a count, a cross-file join, or a
|
|
167
169
|
dedupe over `sys.argv[1:]` is per run, not per table. Do that work in SQL.
|
|
@@ -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.102"
|
|
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
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
**Changed** An `on-file` command's runs now execute concurrently, one per CPU at most, when a table's paths outgrow one argument list, and each run's output is parsed as it is written instead of after it exits. Rows keep their order within a run but not across runs. A 2M-row JSONL ingest through `jq` runs within 5% of the equivalent `jq | awk` pipeline, and a frontmatter parse over thousands of files runs in a quarter of the time of `find -exec awk +`. The scratch database no longer syncs to disk.
|
|
@@ -161,7 +161,9 @@ on-file command `python3 extract.py {path}` uses `{path}`, but an on-file comman
|
|
|
161
161
|
Every platform caps the bytes one process may receive as arguments. When a
|
|
162
162
|
table's paths outgrow that cap, dirsql splits them into the fewest
|
|
163
163
|
consecutive runs that fit, runs the command once per run, and concatenates
|
|
164
|
-
the rows it prints
|
|
164
|
+
the rows it prints. Runs execute concurrently, one per CPU at most, like
|
|
165
|
+
`xargs -P`, so rows keep their order within a run but not across runs; SQL
|
|
166
|
+
never promised row order, so `ORDER BY` what you need. The command cannot tell: each run is an
|
|
165
167
|
ordinary invocation with a subset of the paths. A command must therefore not
|
|
166
168
|
assume one invocation sees every path — a count, a cross-file join, or a
|
|
167
169
|
dedupe over `sys.argv[1:]` is per run, not per table. Do that work in SQL.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"command": "uv run python -m pytest tests/e2e/declared_table_name_test.py tests/e2e/fanout_test.py tests/e2e/extension_package_test.py tests/e2e/frontmatter_parse_speed_test.py tests/e2e/jsonl_parse_speed_test.py tests/e2e/plugin_extension_discovery_test.py tests/e2e/on_file_query_test.py tests/e2e/sigint_interrupt_test.py tests/e2e/use_case_notebook_test.py tests/e2e/server_speed_test.py tests/e2e/query_contract_test.py tests/e2e/plugin_discovery_test.py tests/e2e/use_case_transcripts_test.py -q",
|
|
3
|
+
"ran_at": 1791407652,
|
|
4
|
+
"exit_code": 0,
|
|
5
|
+
"commit": "8d6801314e86177dc46926aedb94bf63e47e87bb",
|
|
6
|
+
"branch": "bot/1429-parallel-hooks"
|
|
7
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
### `on-file` runs execute concurrently
|
|
2
|
+
|
|
3
|
+
#### Summary
|
|
4
|
+
|
|
5
|
+
When a table's matched paths outgrow one argument list, dirsql splits them
|
|
6
|
+
into runs and now executes the runs concurrently, one per CPU at most. Rows
|
|
7
|
+
keep their order within a run, but not across runs.
|
|
8
|
+
|
|
9
|
+
#### Required changes
|
|
10
|
+
|
|
11
|
+
_None._
|
|
12
|
+
|
|
13
|
+
#### Deprecations removed
|
|
14
|
+
|
|
15
|
+
_None._
|
|
16
|
+
|
|
17
|
+
#### Behavior changes without code changes
|
|
18
|
+
|
|
19
|
+
- A query that relied on rows coming back in path order across runs must add
|
|
20
|
+
an `ORDER BY`. A table small enough for one run is unchanged.
|
|
21
|
+
- A hook that holds memory or a GPU may now have several copies running at
|
|
22
|
+
once on a very large table.
|
|
23
|
+
|
|
24
|
+
#### Verification
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
cd "$(mktemp -d)"
|
|
28
|
+
for i in $(seq 1 5000); do : > "a-long-file-name-to-fill-the-argument-list-$i.txt"; done
|
|
29
|
+
cat > count.sh <<'SH'
|
|
30
|
+
echo "{\"n\":$#}"
|
|
31
|
+
SH
|
|
32
|
+
cat > .dirsql.toml <<'TOML'
|
|
33
|
+
[[table]]
|
|
34
|
+
name = "t"
|
|
35
|
+
ddl = "CREATE TABLE t (n INTEGER)"
|
|
36
|
+
glob = "*.txt"
|
|
37
|
+
on-file = "sh count.sh"
|
|
38
|
+
TOML
|
|
39
|
+
dirsql query "SELECT count(*) AS runs, sum(n) AS paths FROM t" -c .dirsql.toml
|
|
40
|
+
# expected: one row whose paths is 5000 and whose runs is greater than 1
|
|
41
|
+
```
|
|
@@ -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.102"
|
|
10
10
|
edition.workspace = true
|
|
11
11
|
# Literal `license` (not `license.workspace = true`) because
|
|
12
12
|
# putitoutthere's preflight check reads `[package].license` directly
|
|
@@ -161,7 +161,9 @@ on-file command `python3 extract.py {path}` uses `{path}`, but an on-file comman
|
|
|
161
161
|
Every platform caps the bytes one process may receive as arguments. When a
|
|
162
162
|
table's paths outgrow that cap, dirsql splits them into the fewest
|
|
163
163
|
consecutive runs that fit, runs the command once per run, and concatenates
|
|
164
|
-
the rows it prints
|
|
164
|
+
the rows it prints. Runs execute concurrently, one per CPU at most, like
|
|
165
|
+
`xargs -P`, so rows keep their order within a run but not across runs; SQL
|
|
166
|
+
never promised row order, so `ORDER BY` what you need. The command cannot tell: each run is an
|
|
165
167
|
ordinary invocation with a subset of the paths. A command must therefore not
|
|
166
168
|
assume one invocation sees every path — a count, a cross-file join, or a
|
|
167
169
|
dedupe over `sys.argv[1:]` is per run, not per table. Do that work in SQL.
|
|
@@ -256,21 +256,85 @@ pub(crate) fn run_argv_stdout(
|
|
|
256
256
|
let stdout = stdout_thread.join().unwrap_or_default();
|
|
257
257
|
let stderr = stderr_thread.join().unwrap_or_default();
|
|
258
258
|
|
|
259
|
-
|
|
260
|
-
let code = status
|
|
261
|
-
.code()
|
|
262
|
-
.map(|c| c.to_string())
|
|
263
|
-
.unwrap_or_else(|| "signal".to_string());
|
|
264
|
-
return Err(CommandError::NonZeroExit {
|
|
265
|
-
command: command.to_string(),
|
|
266
|
-
code,
|
|
267
|
-
stderr_tail: stderr_tail(&stderr),
|
|
268
|
-
});
|
|
269
|
-
}
|
|
259
|
+
check_exit(command, status, &stderr)?;
|
|
270
260
|
|
|
271
261
|
Ok(String::from_utf8_lossy(&stdout).into_owned())
|
|
272
262
|
}
|
|
273
263
|
|
|
264
|
+
fn check_exit(
|
|
265
|
+
command: &str,
|
|
266
|
+
status: std::process::ExitStatus,
|
|
267
|
+
stderr: &[u8],
|
|
268
|
+
) -> Result<(), CommandError> {
|
|
269
|
+
if status.success() {
|
|
270
|
+
return Ok(());
|
|
271
|
+
}
|
|
272
|
+
let code = status
|
|
273
|
+
.code()
|
|
274
|
+
.map(|c| c.to_string())
|
|
275
|
+
.unwrap_or_else(|| "signal".to_string());
|
|
276
|
+
Err(CommandError::NonZeroExit {
|
|
277
|
+
command: command.to_string(),
|
|
278
|
+
code,
|
|
279
|
+
stderr_tail: stderr_tail(stderr),
|
|
280
|
+
})
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/// Bytes read from a child's stdout per `read` call.
|
|
284
|
+
const READ_BYTES: usize = 262_144;
|
|
285
|
+
|
|
286
|
+
/// Run an already-built `argv` and hand its stdout to `on_block` as it
|
|
287
|
+
/// arrives, in blocks that each hold whole lines (the last block may lack its
|
|
288
|
+
/// trailing newline). The child runs with stdin closed. A non-zero exit is an
|
|
289
|
+
/// error after every block has been delivered.
|
|
290
|
+
pub(crate) fn run_argv_blocks(
|
|
291
|
+
command: &str,
|
|
292
|
+
argv: &[String],
|
|
293
|
+
cwd: &Path,
|
|
294
|
+
on_block: &mut dyn FnMut(&str),
|
|
295
|
+
) -> Result<(), CommandError> {
|
|
296
|
+
let mut cmd = Command::new(&argv[0]);
|
|
297
|
+
push_args(&mut cmd, &argv[1..]);
|
|
298
|
+
cmd.current_dir(cwd)
|
|
299
|
+
.stdout(Stdio::piped())
|
|
300
|
+
.stderr(Stdio::piped())
|
|
301
|
+
.stdin(Stdio::null());
|
|
302
|
+
let mut child = cmd
|
|
303
|
+
.spawn()
|
|
304
|
+
.map_err(|source| spawn_error(&argv[0], source, cwd))?;
|
|
305
|
+
|
|
306
|
+
let mut stderr_pipe = child.stderr.take().expect("stderr piped");
|
|
307
|
+
let stderr_thread = std::thread::spawn(move || {
|
|
308
|
+
let mut buf = Vec::new();
|
|
309
|
+
let _ = stderr_pipe.read_to_end(&mut buf);
|
|
310
|
+
buf
|
|
311
|
+
});
|
|
312
|
+
let mut stdout_pipe = child.stdout.take().expect("stdout piped");
|
|
313
|
+
let mut pending: Vec<u8> = Vec::new();
|
|
314
|
+
let mut buf = vec![0u8; READ_BYTES];
|
|
315
|
+
loop {
|
|
316
|
+
let n = match stdout_pipe.read(&mut buf) {
|
|
317
|
+
Ok(0) | Err(_) => break,
|
|
318
|
+
Ok(n) => n,
|
|
319
|
+
};
|
|
320
|
+
pending.extend_from_slice(&buf[..n]);
|
|
321
|
+
if let Some(end) = pending.iter().rposition(|&b| b == b'\n') {
|
|
322
|
+
on_block(&String::from_utf8_lossy(&pending[..=end]));
|
|
323
|
+
pending.drain(..=end);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
if !pending.is_empty() {
|
|
327
|
+
on_block(&String::from_utf8_lossy(&pending));
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
let status = child.wait().map_err(|source| CommandError::Io {
|
|
331
|
+
command: command.to_string(),
|
|
332
|
+
source,
|
|
333
|
+
})?;
|
|
334
|
+
let stderr = stderr_thread.join().unwrap_or_default();
|
|
335
|
+
check_exit(command, status, &stderr)
|
|
336
|
+
}
|
|
337
|
+
|
|
274
338
|
// One fn with cfg blocks rather than two cfg'd fns: cargo-mutants mutates
|
|
275
339
|
// the uncompiled twin too, and no test on a Linux gate can kill that mutant.
|
|
276
340
|
fn push_args(cmd: &mut Command, args: &[String]) {
|
|
@@ -736,6 +800,75 @@ mod tests {
|
|
|
736
800
|
);
|
|
737
801
|
}
|
|
738
802
|
|
|
803
|
+
#[test]
|
|
804
|
+
fn run_argv_blocks_delivers_whole_lines_and_the_unterminated_tail() {
|
|
805
|
+
let argv = vec![
|
|
806
|
+
"sh".to_string(),
|
|
807
|
+
"-c".to_string(),
|
|
808
|
+
"printf 'a\\nb\\nc'".to_string(),
|
|
809
|
+
];
|
|
810
|
+
let mut seen = String::new();
|
|
811
|
+
let mut blocks = 0;
|
|
812
|
+
run_argv_blocks("t", &argv, &cwd(), &mut |block| {
|
|
813
|
+
seen.push_str(block);
|
|
814
|
+
blocks += 1;
|
|
815
|
+
})
|
|
816
|
+
.unwrap();
|
|
817
|
+
assert_eq!(seen, "a\nb\nc");
|
|
818
|
+
assert!(blocks >= 1);
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
#[test]
|
|
822
|
+
fn run_argv_blocks_cuts_every_block_at_a_line_boundary() {
|
|
823
|
+
let argv = vec![
|
|
824
|
+
"sh".to_string(),
|
|
825
|
+
"-c".to_string(),
|
|
826
|
+
"yes abcdefghijklmnopqrstuvwxyz | head -n 100000".to_string(),
|
|
827
|
+
];
|
|
828
|
+
let mut lines = 0;
|
|
829
|
+
let mut blocks = 0;
|
|
830
|
+
run_argv_blocks("t", &argv, &cwd(), &mut |block| {
|
|
831
|
+
blocks += 1;
|
|
832
|
+
assert!(block.ends_with('\n'), "block cut mid-line");
|
|
833
|
+
lines += block.lines().count();
|
|
834
|
+
})
|
|
835
|
+
.unwrap();
|
|
836
|
+
assert_eq!(lines, 100_000);
|
|
837
|
+
assert!(blocks > 1, "2.7 MB arrives in more than one read");
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
#[test]
|
|
841
|
+
fn run_argv_blocks_reports_a_non_zero_exit_after_delivering_output() {
|
|
842
|
+
let argv = vec![
|
|
843
|
+
"sh".to_string(),
|
|
844
|
+
"-c".to_string(),
|
|
845
|
+
"echo partial; echo oops >&2; exit 3".to_string(),
|
|
846
|
+
];
|
|
847
|
+
let mut seen = String::new();
|
|
848
|
+
let err =
|
|
849
|
+
run_argv_blocks("t", &argv, &cwd(), &mut |block| seen.push_str(block)).unwrap_err();
|
|
850
|
+
assert_eq!(seen, "partial\n");
|
|
851
|
+
match err {
|
|
852
|
+
CommandError::NonZeroExit {
|
|
853
|
+
code, stderr_tail, ..
|
|
854
|
+
} => {
|
|
855
|
+
assert_eq!(code, "3");
|
|
856
|
+
assert!(stderr_tail.contains("oops"));
|
|
857
|
+
}
|
|
858
|
+
other => panic!("expected NonZeroExit, got {other:?}"),
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
#[test]
|
|
863
|
+
fn run_argv_blocks_names_a_missing_program() {
|
|
864
|
+
let argv = vec!["definitely-not-a-real-binary-xyzzy".to_string()];
|
|
865
|
+
let err = run_argv_blocks("t", &argv, &cwd(), &mut |_| {}).unwrap_err();
|
|
866
|
+
assert!(
|
|
867
|
+
matches!(err, CommandError::NotOnPath { .. }),
|
|
868
|
+
"got: {err:?}"
|
|
869
|
+
);
|
|
870
|
+
}
|
|
871
|
+
|
|
739
872
|
#[test]
|
|
740
873
|
fn run_command_writes_stdin_payload_to_the_child() {
|
|
741
874
|
let out = run_command("cat", &[], &cwd(), Some(b"hello-stdin")).unwrap();
|
|
@@ -368,6 +368,9 @@ impl Db {
|
|
|
368
368
|
/// tmpfs mount.
|
|
369
369
|
pub fn new() -> Result<Self> {
|
|
370
370
|
let conn = Connection::open("")?;
|
|
371
|
+
// The database is a private temp file that dies with the process, so
|
|
372
|
+
// durability buys nothing.
|
|
373
|
+
conn.pragma_update(None, "synchronous", "OFF")?;
|
|
371
374
|
ensure_internal_rows_table(&conn)?;
|
|
372
375
|
let scope = StatementScope::new();
|
|
373
376
|
vtab::load_module(&conn, Arc::clone(&scope))?;
|
|
@@ -2066,6 +2069,17 @@ mod tests {
|
|
|
2066
2069
|
assert_eq!(mode, "delete");
|
|
2067
2070
|
}
|
|
2068
2071
|
|
|
2072
|
+
#[test]
|
|
2073
|
+
fn new_skips_fsync() {
|
|
2074
|
+
let db = Db::new().unwrap();
|
|
2075
|
+
let pragma = |name: &str| -> i64 {
|
|
2076
|
+
db.conn()
|
|
2077
|
+
.query_row(&format!("PRAGMA {name}"), [], |row| row.get(0))
|
|
2078
|
+
.unwrap()
|
|
2079
|
+
};
|
|
2080
|
+
assert_eq!(pragma("synchronous"), 0);
|
|
2081
|
+
}
|
|
2082
|
+
|
|
2069
2083
|
#[test]
|
|
2070
2084
|
fn get_rows_by_file_returns_rows_in_row_index_order() {
|
|
2071
2085
|
let db = Db::new().unwrap();
|
|
@@ -110,13 +110,19 @@ impl<'de> serde::Deserialize<'de> for JsonRow {
|
|
|
110
110
|
/// Parse a parser command's output — one JSON object per line (NDJSON), blank
|
|
111
111
|
/// lines skipped — into rows that remember their key order.
|
|
112
112
|
pub fn parse_rows(output: &str) -> Result<Vec<JsonRow>, String> {
|
|
113
|
+
parse_rows_at(output, 0)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/// [`parse_rows`] for a block that begins after `skipped` earlier lines of the
|
|
117
|
+
/// same output, so an error names its line in the whole output.
|
|
118
|
+
pub(crate) fn parse_rows_at(output: &str, skipped: usize) -> Result<Vec<JsonRow>, String> {
|
|
113
119
|
let mut rows = Vec::new();
|
|
114
120
|
for (index, line) in output.lines().enumerate() {
|
|
115
121
|
let line = line.trim();
|
|
116
122
|
if line.is_empty() {
|
|
117
123
|
continue;
|
|
118
124
|
}
|
|
119
|
-
let number = index + 1;
|
|
125
|
+
let number = skipped + index + 1;
|
|
120
126
|
if line.starts_with('[') {
|
|
121
127
|
return Err(format!(
|
|
122
128
|
"line {number} is a JSON array; print one JSON object per line instead"
|
|
@@ -255,6 +261,12 @@ mod tests {
|
|
|
255
261
|
assert_eq!(SqlType::Real.as_str(), "REAL");
|
|
256
262
|
}
|
|
257
263
|
|
|
264
|
+
#[test]
|
|
265
|
+
fn parse_rows_at_numbers_lines_after_the_skipped_ones() {
|
|
266
|
+
let err = parse_rows_at("{\"a\":1}\nnope", 40).unwrap_err();
|
|
267
|
+
assert!(err.starts_with("line 42 "), "got: {err}");
|
|
268
|
+
}
|
|
269
|
+
|
|
258
270
|
#[test]
|
|
259
271
|
fn parse_rows_preserves_key_order_rather_than_sorting() {
|
|
260
272
|
let parsed = parse_rows(r#"{"zeta":1,"alpha":2}"#).unwrap();
|
|
@@ -9,9 +9,11 @@
|
|
|
9
9
|
//! fails the whole table.
|
|
10
10
|
|
|
11
11
|
use std::path::{Path, PathBuf};
|
|
12
|
+
use std::sync::Mutex;
|
|
13
|
+
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
|
|
12
14
|
|
|
13
15
|
use crate::command::{self, Placeholder};
|
|
14
|
-
use crate::infer::{JsonRow,
|
|
16
|
+
use crate::infer::{JsonRow, parse_rows_at};
|
|
15
17
|
|
|
16
18
|
/// Bytes of trailing arguments one invocation may carry: GNU xargs's ceiling,
|
|
17
19
|
/// comfortably under every platform's argv limit with the environment and the
|
|
@@ -52,9 +54,9 @@ pub(crate) fn run(
|
|
|
52
54
|
Ok(rows)
|
|
53
55
|
}
|
|
54
56
|
|
|
55
|
-
/// [`run`], handing
|
|
56
|
-
///
|
|
57
|
-
///
|
|
57
|
+
/// [`run`], handing rows to `sink` as they parse. Invocations run
|
|
58
|
+
/// concurrently, like `xargs -P`, each decoding its own output as it arrives;
|
|
59
|
+
/// rows keep their order within an invocation but not across invocations.
|
|
58
60
|
pub(crate) fn run_streaming(
|
|
59
61
|
command: &str,
|
|
60
62
|
cwd: &Path,
|
|
@@ -71,35 +73,89 @@ pub(crate) fn run_streaming(
|
|
|
71
73
|
.iter()
|
|
72
74
|
.map(|path| command::non_verbatim(&path.to_string_lossy()))
|
|
73
75
|
.collect();
|
|
76
|
+
let runs = chunks(&args, ARG_BUDGET.saturating_sub(argv_bytes(&argv)));
|
|
77
|
+
let workers = std::thread::available_parallelism()
|
|
78
|
+
.map_or(1, std::num::NonZero::get)
|
|
79
|
+
.min(runs.len());
|
|
74
80
|
|
|
81
|
+
let next = AtomicUsize::new(0);
|
|
82
|
+
let aborted = AtomicBool::new(false);
|
|
83
|
+
let failures = Mutex::new(Vec::<(usize, String)>::new());
|
|
75
84
|
std::thread::scope(|scope| {
|
|
76
|
-
let (
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
85
|
+
let (rows_tx, rows_rx) = std::sync::mpsc::sync_channel::<Vec<JsonRow>>(16);
|
|
86
|
+
for _ in 0..workers {
|
|
87
|
+
let rows_tx = rows_tx.clone();
|
|
88
|
+
let (argv, runs) = (&argv, &runs);
|
|
89
|
+
let (next, aborted, failures) = (&next, &aborted, &failures);
|
|
90
|
+
scope.spawn(move || {
|
|
91
|
+
loop {
|
|
92
|
+
let index = next.fetch_add(1, Ordering::SeqCst);
|
|
93
|
+
if index >= runs.len() || aborted.load(Ordering::SeqCst) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
let mut full = argv.clone();
|
|
97
|
+
full.extend(runs[index].iter().cloned());
|
|
98
|
+
let outcome = run_invocation(command, &full, cwd, &|rows| {
|
|
99
|
+
let _ = rows_tx.send(rows);
|
|
100
|
+
});
|
|
101
|
+
if let Err(message) = outcome {
|
|
102
|
+
aborted.store(true, Ordering::SeqCst);
|
|
103
|
+
failures
|
|
104
|
+
.lock()
|
|
105
|
+
.expect("failures lock")
|
|
106
|
+
.push((index, message));
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
drop(rows_tx);
|
|
112
|
+
for rows in rows_rx {
|
|
113
|
+
sink(rows);
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
// The earliest invocation's failure outranks a later one's.
|
|
117
|
+
match failures
|
|
118
|
+
.into_inner()
|
|
119
|
+
.expect("failures lock")
|
|
120
|
+
.into_iter()
|
|
121
|
+
.min()
|
|
122
|
+
{
|
|
123
|
+
Some((_, message)) => Err(message),
|
|
124
|
+
None => Ok(()),
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/// One invocation: stream its stdout through the parser, `send`ing each
|
|
129
|
+
/// block's rows. A non-zero exit outranks bad output, and bad output is
|
|
130
|
+
/// drained rather than cut short so the child never dies on a broken pipe.
|
|
131
|
+
fn run_invocation(
|
|
132
|
+
command: &str,
|
|
133
|
+
full: &[String],
|
|
134
|
+
cwd: &Path,
|
|
135
|
+
send: &dyn Fn(Vec<JsonRow>),
|
|
136
|
+
) -> Result<(), String> {
|
|
137
|
+
let mut bad_output: Option<String> = None;
|
|
138
|
+
let mut lines_seen = 0usize;
|
|
139
|
+
command::run_argv_blocks(command, full, cwd, &mut |block| {
|
|
140
|
+
if bad_output.is_some() {
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
match parse_rows_at(block, lines_seen) {
|
|
144
|
+
Ok(rows) => {
|
|
145
|
+
lines_seen += block.bytes().filter(|&b| b == b'\n').count();
|
|
146
|
+
if !rows.is_empty() {
|
|
147
|
+
send(rows);
|
|
94
148
|
}
|
|
95
149
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
150
|
+
Err(message) => {
|
|
151
|
+
bad_output = Some(format!(
|
|
152
|
+
"on-file output was not one JSON object per line: {message}"
|
|
153
|
+
));
|
|
154
|
+
}
|
|
155
|
+
}
|
|
102
156
|
})
|
|
157
|
+
.map_err(spawn_failure)?;
|
|
158
|
+
bad_output.map_or(Ok(()), Err)
|
|
103
159
|
}
|
|
104
160
|
|
|
105
161
|
fn spawn_failure(error: command::CommandError) -> String {
|
|
@@ -256,6 +312,63 @@ mod tests {
|
|
|
256
312
|
assert_eq!(rows[0].get("n").unwrap().as_i64().unwrap(), 300);
|
|
257
313
|
}
|
|
258
314
|
|
|
315
|
+
fn long_paths(count: usize) -> Vec<PathBuf> {
|
|
316
|
+
(0..count)
|
|
317
|
+
.map(|i| {
|
|
318
|
+
PathBuf::from(format!(
|
|
319
|
+
"/some/long/directory/name/for/the/budget/f-{i:06}.json"
|
|
320
|
+
))
|
|
321
|
+
})
|
|
322
|
+
.collect()
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
#[test]
|
|
326
|
+
fn run_gives_every_path_to_some_invocation_when_the_table_needs_several() {
|
|
327
|
+
let paths = long_paths(6000);
|
|
328
|
+
let rows = run(
|
|
329
|
+
r#"sh -c 'echo "{\"n\":$#}"' sh"#,
|
|
330
|
+
Path::new("."),
|
|
331
|
+
Path::new("."),
|
|
332
|
+
&paths,
|
|
333
|
+
)
|
|
334
|
+
.unwrap();
|
|
335
|
+
assert!(
|
|
336
|
+
rows.len() > 1,
|
|
337
|
+
"{} paths outgrow one argument list",
|
|
338
|
+
paths.len()
|
|
339
|
+
);
|
|
340
|
+
let total: i64 = rows
|
|
341
|
+
.iter()
|
|
342
|
+
.map(|r| r.get("n").unwrap().as_i64().unwrap())
|
|
343
|
+
.sum();
|
|
344
|
+
assert_eq!(total, 6000);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
#[test]
|
|
348
|
+
fn run_names_the_line_of_bad_output_after_earlier_blocks() {
|
|
349
|
+
let err = run(
|
|
350
|
+
r#"sh -c 'yes "{\"a\":1}" | head -n 100000; echo nope' sh"#,
|
|
351
|
+
Path::new("."),
|
|
352
|
+
Path::new("."),
|
|
353
|
+
&[PathBuf::from("/x")],
|
|
354
|
+
)
|
|
355
|
+
.unwrap_err();
|
|
356
|
+
assert!(err.contains("line 100001 "), "got: {err}");
|
|
357
|
+
assert!(err.contains("one JSON object per line"), "got: {err}");
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
#[test]
|
|
361
|
+
fn run_prefers_a_non_zero_exit_to_the_bad_output_before_it() {
|
|
362
|
+
let err = run(
|
|
363
|
+
"sh -c 'echo nope; echo boom >&2; exit 4' sh",
|
|
364
|
+
Path::new("."),
|
|
365
|
+
Path::new("."),
|
|
366
|
+
&[PathBuf::from("/x")],
|
|
367
|
+
)
|
|
368
|
+
.unwrap_err();
|
|
369
|
+
assert!(err.contains("boom"), "got: {err}");
|
|
370
|
+
}
|
|
371
|
+
|
|
259
372
|
#[test]
|
|
260
373
|
fn run_over_no_paths_spawns_nothing() {
|
|
261
374
|
let rows = run(
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|