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.
Files changed (412) hide show
  1. {dirsql-0.4.101 → dirsql-0.4.102}/Cargo.lock +2 -2
  2. {dirsql-0.4.101 → dirsql-0.4.102}/PKG-INFO +1 -1
  3. {dirsql-0.4.101/packages/rust → dirsql-0.4.102}/docs/reference/hooks.md +3 -1
  4. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/Cargo.toml +1 -1
  5. dirsql-0.4.102/packages/python/changelog.d/2026-10-07-parallel-hooks.md +1 -0
  6. {dirsql-0.4.101 → dirsql-0.4.102/packages/python}/docs/reference/hooks.md +3 -1
  7. dirsql-0.4.102/packages/python/e2e-attestations/bot-1429-parallel-hooks.json +7 -0
  8. dirsql-0.4.102/packages/python/migrations.d/2026-10-07-parallel-hooks.md +41 -0
  9. {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/Cargo.toml +1 -1
  10. {dirsql-0.4.101/packages/python → dirsql-0.4.102/packages/rust}/docs/reference/hooks.md +3 -1
  11. {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/src/command.rs +144 -11
  12. {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/src/db.rs +14 -0
  13. {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/src/infer.rs +13 -1
  14. {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/src/on_file.rs +141 -28
  15. {dirsql-0.4.101 → dirsql-0.4.102}/Cargo.toml +0 -0
  16. {dirsql-0.4.101 → dirsql-0.4.102}/README.md +0 -0
  17. {dirsql-0.4.101 → dirsql-0.4.102}/dirsql/__init__.py +0 -0
  18. {dirsql-0.4.101 → dirsql-0.4.102}/dirsql/_async.py +0 -0
  19. {dirsql-0.4.101 → dirsql-0.4.102}/dirsql/_dirsql.pyi +0 -0
  20. {dirsql-0.4.101 → dirsql-0.4.102}/dirsql/cli/__init__.py +0 -0
  21. {dirsql-0.4.101 → dirsql-0.4.102}/dirsql/cli/discover_plugins/__init__.py +0 -0
  22. {dirsql-0.4.101 → dirsql-0.4.102}/dirsql/cli/discover_plugins/discovered_fragments.py +0 -0
  23. {dirsql-0.4.101 → dirsql-0.4.102}/dirsql/cli/discover_plugins/discovery_disabled.py +0 -0
  24. {dirsql-0.4.101 → dirsql-0.4.102}/dirsql/cli/discover_plugins/fragment_path.py +0 -0
  25. {dirsql-0.4.101 → dirsql-0.4.102}/dirsql/cli/discover_plugins/with_discovered_plugins.py +0 -0
  26. {dirsql-0.4.101 → dirsql-0.4.102}/dirsql/cli/main.py +0 -0
  27. {dirsql-0.4.101 → dirsql-0.4.102}/dirsql/cli/resolve_config_extensions.py +0 -0
  28. {dirsql-0.4.101 → dirsql-0.4.102}/dirsql/plan_entry_path.py +0 -0
  29. {dirsql-0.4.101 → dirsql-0.4.102}/dirsql/py.typed +0 -0
  30. {dirsql-0.4.101 → dirsql-0.4.102}/dirsql/read_config.py +0 -0
  31. {dirsql-0.4.101 → dirsql-0.4.102}/dirsql/resolve_configs_extension_specs.py +0 -0
  32. {dirsql-0.4.101 → dirsql-0.4.102}/dirsql/resolve_extension.py +0 -0
  33. {dirsql-0.4.101 → dirsql-0.4.102}/dirsql/resolve_package.py +0 -0
  34. {dirsql-0.4.101 → dirsql-0.4.102}/docs/.claude/CLAUDE.md +0 -0
  35. {dirsql-0.4.101 → dirsql-0.4.102}/docs/.vitepress/config.ts +0 -0
  36. {dirsql-0.4.101 → dirsql-0.4.102}/docs/.vitepress/theme/index.ts +0 -0
  37. {dirsql-0.4.101 → dirsql-0.4.102}/docs/.vitepress/theme/lang.ts +0 -0
  38. {dirsql-0.4.101 → dirsql-0.4.102}/docs/AGENTS.md +0 -0
  39. {dirsql-0.4.101 → dirsql-0.4.102}/docs/context/.gitattributes +0 -0
  40. {dirsql-0.4.101 → dirsql-0.4.102}/docs/context/recipes.md +0 -0
  41. {dirsql-0.4.101 → dirsql-0.4.102}/docs/context/troubleshooting.md +0 -0
  42. {dirsql-0.4.101 → dirsql-0.4.102}/docs/context/usage.md +0 -0
  43. {dirsql-0.4.101 → dirsql-0.4.102}/docs/explanation.md +0 -0
  44. {dirsql-0.4.101 → dirsql-0.4.102}/docs/getting-started.md +0 -0
  45. {dirsql-0.4.101 → dirsql-0.4.102}/docs/howto/agent-skill.md +0 -0
  46. {dirsql-0.4.101 → dirsql-0.4.102}/docs/howto/columns-from-paths.md +0 -0
  47. {dirsql-0.4.101 → dirsql-0.4.102}/docs/howto/define-tables.md +0 -0
  48. {dirsql-0.4.101 → dirsql-0.4.102}/docs/howto/embed.md +0 -0
  49. {dirsql-0.4.101 → dirsql-0.4.102}/docs/howto/extract-from-contents.md +0 -0
  50. {dirsql-0.4.101 → dirsql-0.4.102}/docs/howto/load-extension.md +0 -0
  51. {dirsql-0.4.101 → dirsql-0.4.102}/docs/howto/parse-files-into-columns.md +0 -0
  52. {dirsql-0.4.101 → dirsql-0.4.102}/docs/howto/persist.md +0 -0
  53. {dirsql-0.4.101 → dirsql-0.4.102}/docs/howto/query-json.md +0 -0
  54. {dirsql-0.4.101 → dirsql-0.4.102}/docs/howto/query-without-config.md +0 -0
  55. {dirsql-0.4.101 → dirsql-0.4.102}/docs/howto/react-to-changes.md +0 -0
  56. {dirsql-0.4.101 → dirsql-0.4.102}/docs/howto/search-by-meaning.md +0 -0
  57. {dirsql-0.4.101 → dirsql-0.4.102}/docs/howto/search-indexes.md +0 -0
  58. {dirsql-0.4.101 → dirsql-0.4.102}/docs/howto/skip-files.md +0 -0
  59. {dirsql-0.4.101 → dirsql-0.4.102}/docs/howto/write-a-plugin.md +0 -0
  60. {dirsql-0.4.101 → dirsql-0.4.102}/docs/index.md +0 -0
  61. {dirsql-0.4.101 → dirsql-0.4.102}/docs/package.json +0 -0
  62. {dirsql-0.4.101 → dirsql-0.4.102}/docs/plugins.md +0 -0
  63. {dirsql-0.4.101 → dirsql-0.4.102}/docs/pnpm-lock.yaml +0 -0
  64. {dirsql-0.4.101 → dirsql-0.4.102}/docs/pnpm-workspace.yaml +0 -0
  65. {dirsql-0.4.101 → dirsql-0.4.102}/docs/reference/cli.md +0 -0
  66. {dirsql-0.4.101 → dirsql-0.4.102}/docs/reference/columns.md +0 -0
  67. {dirsql-0.4.101 → dirsql-0.4.102}/docs/reference/config.md +0 -0
  68. {dirsql-0.4.101 → dirsql-0.4.102}/docs/reference/glob.md +0 -0
  69. {dirsql-0.4.101 → dirsql-0.4.102}/docs/reference/http-api.md +0 -0
  70. {dirsql-0.4.101 → dirsql-0.4.102}/docs/reference/path-tables.md +0 -0
  71. {dirsql-0.4.101 → dirsql-0.4.102}/docs/reference/sdk.md +0 -0
  72. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/CHANGELOG.md +0 -0
  73. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/MIGRATIONS.md +0 -0
  74. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/README.md +0 -0
  75. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-07-13-drop-cwd-config-auto-detect.md +0 -0
  76. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-07-13-plugin-discovery.md +0 -0
  77. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-07-13-rename-extract-to-on-file.md +0 -0
  78. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-07-13-repeatable-config.md +0 -0
  79. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-07-13-sdk-default-baked-in-config.md +0 -0
  80. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-07-20-drop-implicit-files-table.md +0 -0
  81. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-07-21-remove-glob-captures.md +0 -0
  82. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-07-24-hookless-table-config-error.md +0 -0
  83. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-07-24-remove-stat-fact-injection.md +0 -0
  84. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-07-29-restore-python-310.md +0 -0
  85. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-08-02-release-profile.md +0 -0
  86. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-08-02-scan-failures.md +0 -0
  87. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-08-03-discovery-ext-resolution.md +0 -0
  88. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-08-03-manylinux-dynamic-binary.md +0 -0
  89. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-08-03-python-no-ignore.md +0 -0
  90. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-08-04-cast-lints.md +0 -0
  91. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-08-04-cli-in-process.md +0 -0
  92. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-08-20-curtaincall-dev-dependency.md +0 -0
  93. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-08-20-declared-table-name.md +0 -0
  94. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-09-04-config-paths-from-argv-module.md +0 -0
  95. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-09-05-config-extension-loader-split.md +0 -0
  96. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-09-05-resolve-config-extensions-split.md +0 -0
  97. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-09-05-resolve-extension-split.md +0 -0
  98. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-09-09-launcher-config-path-scan.md +0 -0
  99. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-09-17-extension-resolution-via-core.md +0 -0
  100. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-09-18-export-scan-failure.md +0 -0
  101. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-09-18-flatten-row-event-in-core.md +0 -0
  102. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-09-18-programmatic-extension-plan.md +0 -0
  103. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-09-20-sigint-ends-a-run-in-progress.md +0 -0
  104. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-09-24-query-rows-in-projection-order.md +0 -0
  105. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-10-02-readme-docs-host.md +0 -0
  106. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-10-03-one-glob-rule-for-config-globs.md +0 -0
  107. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-10-03-path-table-directory-one-level.md +0 -0
  108. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-10-04-path-table-dotfiles-hidden.md +0 -0
  109. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-10-05-brace-groups-follow-bash.md +0 -0
  110. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-10-05-cli-context.md +0 -0
  111. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-10-05-gitignore-requires-repo.md +0 -0
  112. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-10-05-path-table-node-modules-named.md +0 -0
  113. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-10-05-path-table-symlinks.md +0 -0
  114. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-10-05-path-table-trailing-slash.md +0 -0
  115. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-10-05-unbalanced-brackets-literal.md +0 -0
  116. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-10-06-config-braces.md +0 -0
  117. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-10-06-config-directory.md +0 -0
  118. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-10-06-config-dotnames.md +0 -0
  119. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-10-06-config-gitignore.md +0 -0
  120. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-10-06-config-globs-anchor-at-the-config.md +0 -0
  121. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-10-06-config-links.md +0 -0
  122. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-10-06-one-glob-compiler.md +0 -0
  123. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-10-06-plugin-no-default-records.md +0 -0
  124. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-10-06-watch-broadcast.md +0 -0
  125. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-10-06-zero-match-config-table-warning.md +0 -0
  126. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-10-07-bracket-slash-literal.md +0 -0
  127. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-10-07-dotdot-and-double-slashes.md +0 -0
  128. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-10-07-dotdot-as-written.md +0 -0
  129. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-10-07-glob-walks-node-modules.md +0 -0
  130. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-10-07-literal-gitignore.md +0 -0
  131. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-10-07-on-file-ndjson.md +0 -0
  132. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-10-07-persist-cache-outside-root.md +0 -0
  133. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/2026-10-07-posix-classes-unicode.md +0 -0
  134. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/changelog.d/README.md +0 -0
  135. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/conftest.py +0 -0
  136. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/docs/.claude/CLAUDE.md +0 -0
  137. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/docs/.vitepress/config.ts +0 -0
  138. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/docs/.vitepress/theme/index.ts +0 -0
  139. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/docs/.vitepress/theme/lang.ts +0 -0
  140. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/docs/AGENTS.md +0 -0
  141. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/docs/context/.gitattributes +0 -0
  142. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/docs/context/recipes.md +0 -0
  143. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/docs/context/troubleshooting.md +0 -0
  144. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/docs/context/usage.md +0 -0
  145. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/docs/explanation.md +0 -0
  146. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/docs/getting-started.md +0 -0
  147. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/docs/howto/agent-skill.md +0 -0
  148. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/docs/howto/columns-from-paths.md +0 -0
  149. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/docs/howto/define-tables.md +0 -0
  150. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/docs/howto/embed.md +0 -0
  151. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/docs/howto/extract-from-contents.md +0 -0
  152. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/docs/howto/load-extension.md +0 -0
  153. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/docs/howto/parse-files-into-columns.md +0 -0
  154. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/docs/howto/persist.md +0 -0
  155. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/docs/howto/query-json.md +0 -0
  156. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/docs/howto/query-without-config.md +0 -0
  157. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/docs/howto/react-to-changes.md +0 -0
  158. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/docs/howto/search-by-meaning.md +0 -0
  159. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/docs/howto/search-indexes.md +0 -0
  160. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/docs/howto/skip-files.md +0 -0
  161. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/docs/howto/write-a-plugin.md +0 -0
  162. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/docs/index.md +0 -0
  163. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/docs/package.json +0 -0
  164. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/docs/plugins.md +0 -0
  165. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/docs/pnpm-lock.yaml +0 -0
  166. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/docs/pnpm-workspace.yaml +0 -0
  167. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/docs/reference/cli.md +0 -0
  168. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/docs/reference/columns.md +0 -0
  169. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/docs/reference/config.md +0 -0
  170. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/docs/reference/glob.md +0 -0
  171. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/docs/reference/http-api.md +0 -0
  172. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/docs/reference/path-tables.md +0 -0
  173. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/docs/reference/sdk.md +0 -0
  174. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/bot-1334-listing-exemptions.json +0 -0
  175. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/bot-1397-watch-broadcast.json +0 -0
  176. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/bot-1398-inotify-error.json +0 -0
  177. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/bot-1399-server-cors.json +0 -0
  178. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/bot-1401-watch-scope.json +0 -0
  179. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/bot-1407-child-place-depth.json +0 -0
  180. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/bot-1414-stat-speed.json +0 -0
  181. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/bot-1415-server-speed.json +0 -0
  182. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/bot-1418-node-modules.json +0 -0
  183. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/bot-1419-dotdot-slashes.json +0 -0
  184. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/bot-1420-bracket-slash.json +0 -0
  185. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/bot-1421-posix-unicode.json +0 -0
  186. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/bot-1422-literal-gitignore.json +0 -0
  187. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/bot-1428-ndjson-hooks.json +0 -0
  188. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/bot-1430-dotdot-as-written.json +0 -0
  189. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/bot-1431-persist-cache-dir.json +0 -0
  190. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/bot-1437-test-cache-isolation.json +0 -0
  191. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/bot-1440-fix-main-build.json +0 -0
  192. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/bot-1448-wildcard-ignored-prefix.json +0 -0
  193. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/bot-1451-named-dir-never-hidden.json +0 -0
  194. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/bot-fix-main-place-depth-20261007.json +0 -0
  195. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/cc-keen-knuth-f11gyq.json +0 -0
  196. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1056-py-ofpf9.json +0 -0
  197. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1066-p1-resolve-extension.json +0 -0
  198. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1078-argv-config-paths-core.json +0 -0
  199. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1079-p2-resolve-config-extensions.json +0 -0
  200. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1080-share-vtab-scaffolding.json +0 -0
  201. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1081-indicatif-progress.json +0 -0
  202. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1096-watch-moved-dir.json +0 -0
  203. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1109-watch-moved-out.json +0 -0
  204. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1119-ext-plan.json +0 -0
  205. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1120-py-ext.json +0 -0
  206. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1122-ext-cleanup.json +0 -0
  207. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1130-codec-crates.json +0 -0
  208. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1131-flatten-row-event.json +0 -0
  209. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1139-sse-flatten.json +0 -0
  210. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1140-scan-failure-export.json +0 -0
  211. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1148-clippy-all-targets.json +0 -0
  212. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1164-sigint-fatal.json +0 -0
  213. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1170-projection-order.json +0 -0
  214. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1172-on-file-path.json +0 -0
  215. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1173-py-projection-order.json +0 -0
  216. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1184-win-rel-paths.json +0 -0
  217. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1185-win-abs-table-names.json +0 -0
  218. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1186-win-test-harness.json +0 -0
  219. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1196-join-index.json +0 -0
  220. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1199-glob-prefix.json +0 -0
  221. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1201-single-walk.json +0 -0
  222. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1204-embed-batch.json +0 -0
  223. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1207-win-argv-quote.json +0 -0
  224. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1208-win-nonverbatim-paths.json +0 -0
  225. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1215-on-file-batch.json +0 -0
  226. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1216-on-file-cleanup.json +0 -0
  227. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1223-config-glob-rule.json +0 -0
  228. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1223-dir-one-level.json +0 -0
  229. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1225-plugin-no-default.json +0 -0
  230. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1226-stale-docs.json +0 -0
  231. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1232-anchor-plumbing.json +0 -0
  232. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1232-config-anchor.json +0 -0
  233. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1232-multi-watch.json +0 -0
  234. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1232-zero-match.json +0 -0
  235. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1238-parallel-walk.json +0 -0
  236. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1241-content-prefetch.json +0 -0
  237. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1243-ingest-perf.json +0 -0
  238. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1249-dotfiles.json +0 -0
  239. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1256-symlinks.json +0 -0
  240. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1257-trailing-slash.json +0 -0
  241. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1258-config-braces.json +0 -0
  242. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1258-config-directory.json +0 -0
  243. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1258-config-dotnames.json +0 -0
  244. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1258-config-gitignore.json +0 -0
  245. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1258-config-links.json +0 -0
  246. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1258-one-pattern.json +0 -0
  247. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1262-brace-literal.json +0 -0
  248. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1264-context.json +0 -0
  249. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1267-posix-classes.json +0 -0
  250. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1268-unbalanced-brackets.json +0 -0
  251. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1269-utf8-chars.json +0 -0
  252. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1271-node-modules-named.json +0 -0
  253. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1272-ancestor-gitignore.json +0 -0
  254. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1273-walk-prune.json +0 -0
  255. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1285-ingest-fast.json +0 -0
  256. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1286-nested-repo.json +0 -0
  257. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1305-glob-speed.json +0 -0
  258. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1308-glob-speed.json +0 -0
  259. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1310-glob-speed.json +0 -0
  260. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1311-glob-speed.json +0 -0
  261. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1312-glob-speed.json +0 -0
  262. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1318-lazy-stat.json +0 -0
  263. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1320-walk-speed.json +0 -0
  264. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1322-walk-readdir.json +0 -0
  265. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1325-parallel-walk.json +0 -0
  266. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1326-stream-rows.json +0 -0
  267. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1330-python-dirsql-patch.json +0 -0
  268. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1330-python-extensions-patch.json +0 -0
  269. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1330-scanner-effects.json +0 -0
  270. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1330-scanner-gitignore.json +0 -0
  271. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1330-vtab-prefetch-flag.json +0 -0
  272. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1330-vtab-read.json +0 -0
  273. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-1351-dot-dirs.json +0 -0
  274. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-819-oneshot-timeout.json +0 -0
  275. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-827-unquote-doubled-quotes.json +0 -0
  276. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-epic-953-slice2-reedline.json +0 -0
  277. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-epic-953-slice3-format.json +0 -0
  278. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-epic-953-stacked-prs-x59wm3.json +0 -0
  279. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-github-issue-825-e4mhhp.json +0 -0
  280. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-issue-951-0eease.json +0 -0
  281. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-issue-962-0jjrfv.json +0 -0
  282. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-issue-986-red-test-e7sjrb.json +0 -0
  283. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-tackle-1034-cachesplit.json +0 -0
  284. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-tackle-1042-py12.json +0 -0
  285. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-tackle-956-xzen74.json +0 -0
  286. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-tackle-957-lrm0z6-worker-calls.json +0 -0
  287. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-tackle-957-lrm0z6.json +0 -0
  288. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/e2e-attestations/claude-tackle-975-n8mc2h.json +0 -0
  289. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/migrations.d/2026-07-13-drop-cwd-config-auto-detect.md +0 -0
  290. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/migrations.d/2026-07-13-rename-extract-to-on-file.md +0 -0
  291. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/migrations.d/2026-07-13-sdk-default-baked-in-config.md +0 -0
  292. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/migrations.d/2026-07-20-drop-implicit-files-table.md +0 -0
  293. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/migrations.d/2026-07-21-remove-glob-captures.md +0 -0
  294. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/migrations.d/2026-07-24-hookless-table-config-error.md +0 -0
  295. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/migrations.d/2026-07-24-remove-stat-fact-injection.md +0 -0
  296. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/migrations.d/2026-07-29-restore-python-310.md +0 -0
  297. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/migrations.d/2026-08-04-cli-in-process.md +0 -0
  298. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/migrations.d/2026-08-20-declared-table-name.md +0 -0
  299. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/migrations.d/2026-09-20-sigint-ends-a-run-in-progress.md +0 -0
  300. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/migrations.d/2026-09-24-query-rows-in-projection-order.md +0 -0
  301. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/migrations.d/2026-10-02-readme-docs-host.md +0 -0
  302. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/migrations.d/2026-10-03-one-glob-rule-for-config-globs.md +0 -0
  303. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/migrations.d/2026-10-03-path-table-directory-one-level.md +0 -0
  304. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/migrations.d/2026-10-04-path-table-dotfiles-hidden.md +0 -0
  305. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/migrations.d/2026-10-05-brace-groups-follow-bash.md +0 -0
  306. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/migrations.d/2026-10-05-cli-context.md +0 -0
  307. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/migrations.d/2026-10-05-gitignore-requires-repo.md +0 -0
  308. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/migrations.d/2026-10-05-path-table-node-modules-named.md +0 -0
  309. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/migrations.d/2026-10-05-path-table-symlinks.md +0 -0
  310. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/migrations.d/2026-10-05-path-table-trailing-slash.md +0 -0
  311. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/migrations.d/2026-10-05-unbalanced-brackets-literal.md +0 -0
  312. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/migrations.d/2026-10-06-config-braces.md +0 -0
  313. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/migrations.d/2026-10-06-config-directory.md +0 -0
  314. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/migrations.d/2026-10-06-config-dotnames.md +0 -0
  315. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/migrations.d/2026-10-06-config-gitignore.md +0 -0
  316. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/migrations.d/2026-10-06-config-globs-anchor-at-the-config.md +0 -0
  317. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/migrations.d/2026-10-06-config-links.md +0 -0
  318. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/migrations.d/2026-10-06-one-glob-compiler.md +0 -0
  319. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/migrations.d/2026-10-06-plugin-no-default-records.md +0 -0
  320. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/migrations.d/2026-10-06-watch-broadcast.md +0 -0
  321. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/migrations.d/2026-10-06-zero-match-config-table-warning.md +0 -0
  322. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/migrations.d/2026-10-07-bracket-slash-literal.md +0 -0
  323. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/migrations.d/2026-10-07-dotdot-and-double-slashes.md +0 -0
  324. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/migrations.d/2026-10-07-dotdot-as-written.md +0 -0
  325. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/migrations.d/2026-10-07-glob-walks-node-modules.md +0 -0
  326. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/migrations.d/2026-10-07-literal-gitignore.md +0 -0
  327. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/migrations.d/2026-10-07-on-file-ndjson.md +0 -0
  328. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/migrations.d/2026-10-07-persist-cache-outside-root.md +0 -0
  329. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/migrations.d/2026-10-07-posix-classes-unicode.md +0 -0
  330. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/migrations.d/README.md +0 -0
  331. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/src/lib.rs +0 -0
  332. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/testing-conventions.toml +0 -0
  333. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/tests/__init__.py +0 -0
  334. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/tests/conftest.py +0 -0
  335. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/tests/e2e/__init__.py +0 -0
  336. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/tests/e2e/fixtures/dirsql_plugin_fixture/__init__.py +0 -0
  337. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/tests/e2e/fixtures/dirsql_plugin_fixture/dirsql.toml +0 -0
  338. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/tests/e2e/speed_of_light.py +0 -0
  339. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/tests/integration/__init__.py +0 -0
  340. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/tests/integration/binding/__init__.py +0 -0
  341. {dirsql-0.4.101 → dirsql-0.4.102}/packages/python/tests/integration/hermetic/__init__.py +0 -0
  342. {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/CHANGELOG.md +0 -0
  343. {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/MIGRATIONS.md +0 -0
  344. {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/README.md +0 -0
  345. {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/benches/db_bench.rs +0 -0
  346. {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/benches/differ_bench.rs +0 -0
  347. {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/benches/matcher_bench.rs +0 -0
  348. {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/benches/scanner_bench.rs +0 -0
  349. {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/docs/context/.gitattributes +0 -0
  350. {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/docs/context/recipes.md +0 -0
  351. {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/docs/context/troubleshooting.md +0 -0
  352. {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/docs/context/usage.md +0 -0
  353. {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/docs/explanation.md +0 -0
  354. {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/docs/getting-started.md +0 -0
  355. {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/docs/howto/agent-skill.md +0 -0
  356. {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/docs/howto/columns-from-paths.md +0 -0
  357. {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/docs/howto/define-tables.md +0 -0
  358. {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/docs/howto/embed.md +0 -0
  359. {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/docs/howto/extract-from-contents.md +0 -0
  360. {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/docs/howto/load-extension.md +0 -0
  361. {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/docs/howto/parse-files-into-columns.md +0 -0
  362. {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/docs/howto/persist.md +0 -0
  363. {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/docs/howto/query-json.md +0 -0
  364. {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/docs/howto/query-without-config.md +0 -0
  365. {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/docs/howto/react-to-changes.md +0 -0
  366. {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/docs/howto/search-by-meaning.md +0 -0
  367. {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/docs/howto/search-indexes.md +0 -0
  368. {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/docs/howto/skip-files.md +0 -0
  369. {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/docs/howto/write-a-plugin.md +0 -0
  370. {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/docs/index.md +0 -0
  371. {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/docs/plugins.md +0 -0
  372. {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/docs/reference/cli.md +0 -0
  373. {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/docs/reference/columns.md +0 -0
  374. {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/docs/reference/config.md +0 -0
  375. {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/docs/reference/glob.md +0 -0
  376. {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/docs/reference/http-api.md +0 -0
  377. {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/docs/reference/path-tables.md +0 -0
  378. {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/docs/reference/sdk.md +0 -0
  379. {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/src/bin/dirsql.rs +0 -0
  380. {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/src/brace.rs +0 -0
  381. {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/src/cli/context.rs +0 -0
  382. {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/src/cli/execute.rs +0 -0
  383. {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/src/cli/init.rs +0 -0
  384. {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/src/cli/mod.rs +0 -0
  385. {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/src/cli/repl.rs +0 -0
  386. {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/src/cli/router.rs +0 -0
  387. {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/src/cli/run.rs +0 -0
  388. {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/src/cli/serialize.rs +0 -0
  389. {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/src/cli/server.rs +0 -0
  390. {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/src/cli/table.rs +0 -0
  391. {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/src/config.rs +0 -0
  392. {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/src/default_config.toml +0 -0
  393. {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/src/differ.rs +0 -0
  394. {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/src/extension_resolution.rs +0 -0
  395. {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/src/functions.rs +0 -0
  396. {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/src/launcher.rs +0 -0
  397. {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/src/lib.rs +0 -0
  398. {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/src/listing.rs +0 -0
  399. {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/src/matcher.rs +0 -0
  400. {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/src/parsed_vtab.rs +0 -0
  401. {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/src/path_table.rs +0 -0
  402. {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/src/persist.rs +0 -0
  403. {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/src/posix_class.rs +0 -0
  404. {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/src/progress.rs +0 -0
  405. {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/src/row_event_flat.rs +0 -0
  406. {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/src/scanner.rs +0 -0
  407. {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/src/sql_literal.rs +0 -0
  408. {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/src/tree_walk.rs +0 -0
  409. {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/src/vtab.rs +0 -0
  410. {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/src/vtab_scaffold.rs +0 -0
  411. {dirsql-0.4.101 → dirsql-0.4.102}/packages/rust/src/watcher.rs +0 -0
  412. {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.101"
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.101"
628
+ version = "0.4.102"
629
629
  dependencies = [
630
630
  "dirsql",
631
631
  "pyo3",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dirsql
3
- Version: 0.4.101
3
+ Version: 0.4.102
4
4
  Requires-Dist: bin-shim>=0.1
5
5
  Summary: Ephemeral SQL index over a local directory
6
6
  Keywords: sql,filesystem,directory,sqlite,index
@@ -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, in order. The command cannot tell: each run is an
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.101"
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, in order. The command cannot tell: each run is an
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.101"
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, in order. The command cannot tell: each run is an
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
- if !status.success() {
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, parse_rows};
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 each invocation's rows to `sink` as they parse. The
56
- /// invocations still run one after another; parsing one invocation's output
57
- /// overlaps the next invocation, the way a shell pipe would.
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 (payloads, received) = std::sync::mpsc::channel::<String>();
77
- let parser = scope.spawn(move || -> Result<(), String> {
78
- for payload in received {
79
- let rows = parse_rows(&payload).map_err(|message| {
80
- format!("on-file output was not one JSON object per line: {message}")
81
- })?;
82
- sink(rows);
83
- }
84
- Ok(())
85
- });
86
- let spawned = (|| -> Result<(), String> {
87
- for chunk in chunks(&args, ARG_BUDGET.saturating_sub(argv_bytes(&argv))) {
88
- let mut full = argv.clone();
89
- full.extend(chunk.iter().cloned());
90
- let output =
91
- command::run_argv_stdout(command, &full, cwd, None).map_err(spawn_failure)?;
92
- if payloads.send(output).is_err() {
93
- break;
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
- Ok(())
97
- })();
98
- drop(payloads);
99
- // An earlier invocation's bad output outranks a later one's failure.
100
- parser.join().expect("parser thread panicked")?;
101
- spawned
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