dirsql 0.4.104__tar.gz → 0.4.105__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 (419) hide show
  1. {dirsql-0.4.104 → dirsql-0.4.105}/Cargo.lock +2 -2
  2. {dirsql-0.4.104 → dirsql-0.4.105}/PKG-INFO +1 -1
  3. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/Cargo.toml +1 -1
  4. dirsql-0.4.105/packages/python/e2e-attestations/bot-1457-incremental-refresh-1008.json +7 -0
  5. {dirsql-0.4.104 → dirsql-0.4.105}/packages/rust/Cargo.toml +1 -1
  6. {dirsql-0.4.104 → dirsql-0.4.105}/packages/rust/src/db.rs +251 -12
  7. {dirsql-0.4.104 → dirsql-0.4.105}/packages/rust/src/lib.rs +191 -30
  8. {dirsql-0.4.104 → dirsql-0.4.105}/Cargo.toml +0 -0
  9. {dirsql-0.4.104 → dirsql-0.4.105}/README.md +0 -0
  10. {dirsql-0.4.104 → dirsql-0.4.105}/dirsql/__init__.py +0 -0
  11. {dirsql-0.4.104 → dirsql-0.4.105}/dirsql/_async.py +0 -0
  12. {dirsql-0.4.104 → dirsql-0.4.105}/dirsql/_dirsql.pyi +0 -0
  13. {dirsql-0.4.104 → dirsql-0.4.105}/dirsql/cli/__init__.py +0 -0
  14. {dirsql-0.4.104 → dirsql-0.4.105}/dirsql/cli/discover_plugins/__init__.py +0 -0
  15. {dirsql-0.4.104 → dirsql-0.4.105}/dirsql/cli/discover_plugins/discovered_fragments.py +0 -0
  16. {dirsql-0.4.104 → dirsql-0.4.105}/dirsql/cli/discover_plugins/discovery_disabled.py +0 -0
  17. {dirsql-0.4.104 → dirsql-0.4.105}/dirsql/cli/discover_plugins/fragment_path.py +0 -0
  18. {dirsql-0.4.104 → dirsql-0.4.105}/dirsql/cli/discover_plugins/with_discovered_plugins.py +0 -0
  19. {dirsql-0.4.104 → dirsql-0.4.105}/dirsql/cli/main.py +0 -0
  20. {dirsql-0.4.104 → dirsql-0.4.105}/dirsql/cli/resolve_config_extensions.py +0 -0
  21. {dirsql-0.4.104 → dirsql-0.4.105}/dirsql/plan_entry_path.py +0 -0
  22. {dirsql-0.4.104 → dirsql-0.4.105}/dirsql/py.typed +0 -0
  23. {dirsql-0.4.104 → dirsql-0.4.105}/dirsql/read_config.py +0 -0
  24. {dirsql-0.4.104 → dirsql-0.4.105}/dirsql/resolve_configs_extension_specs.py +0 -0
  25. {dirsql-0.4.104 → dirsql-0.4.105}/dirsql/resolve_extension.py +0 -0
  26. {dirsql-0.4.104 → dirsql-0.4.105}/dirsql/resolve_package.py +0 -0
  27. {dirsql-0.4.104 → dirsql-0.4.105}/docs/.claude/CLAUDE.md +0 -0
  28. {dirsql-0.4.104 → dirsql-0.4.105}/docs/.vitepress/config.ts +0 -0
  29. {dirsql-0.4.104 → dirsql-0.4.105}/docs/.vitepress/theme/index.ts +0 -0
  30. {dirsql-0.4.104 → dirsql-0.4.105}/docs/.vitepress/theme/lang.ts +0 -0
  31. {dirsql-0.4.104 → dirsql-0.4.105}/docs/AGENTS.md +0 -0
  32. {dirsql-0.4.104 → dirsql-0.4.105}/docs/context/.gitattributes +0 -0
  33. {dirsql-0.4.104 → dirsql-0.4.105}/docs/context/recipes.md +0 -0
  34. {dirsql-0.4.104 → dirsql-0.4.105}/docs/context/troubleshooting.md +0 -0
  35. {dirsql-0.4.104 → dirsql-0.4.105}/docs/context/usage.md +0 -0
  36. {dirsql-0.4.104 → dirsql-0.4.105}/docs/explanation.md +0 -0
  37. {dirsql-0.4.104 → dirsql-0.4.105}/docs/getting-started.md +0 -0
  38. {dirsql-0.4.104 → dirsql-0.4.105}/docs/howto/agent-skill.md +0 -0
  39. {dirsql-0.4.104 → dirsql-0.4.105}/docs/howto/columns-from-paths.md +0 -0
  40. {dirsql-0.4.104 → dirsql-0.4.105}/docs/howto/define-tables.md +0 -0
  41. {dirsql-0.4.104 → dirsql-0.4.105}/docs/howto/embed.md +0 -0
  42. {dirsql-0.4.104 → dirsql-0.4.105}/docs/howto/extract-from-contents.md +0 -0
  43. {dirsql-0.4.104 → dirsql-0.4.105}/docs/howto/load-extension.md +0 -0
  44. {dirsql-0.4.104 → dirsql-0.4.105}/docs/howto/parse-files-into-columns.md +0 -0
  45. {dirsql-0.4.104 → dirsql-0.4.105}/docs/howto/persist.md +0 -0
  46. {dirsql-0.4.104 → dirsql-0.4.105}/docs/howto/query-json.md +0 -0
  47. {dirsql-0.4.104 → dirsql-0.4.105}/docs/howto/query-without-config.md +0 -0
  48. {dirsql-0.4.104 → dirsql-0.4.105}/docs/howto/react-to-changes.md +0 -0
  49. {dirsql-0.4.104 → dirsql-0.4.105}/docs/howto/search-by-meaning.md +0 -0
  50. {dirsql-0.4.104 → dirsql-0.4.105}/docs/howto/search-indexes.md +0 -0
  51. {dirsql-0.4.104 → dirsql-0.4.105}/docs/howto/skip-files.md +0 -0
  52. {dirsql-0.4.104 → dirsql-0.4.105}/docs/howto/write-a-plugin.md +0 -0
  53. {dirsql-0.4.104 → dirsql-0.4.105}/docs/index.md +0 -0
  54. {dirsql-0.4.104 → dirsql-0.4.105}/docs/package.json +0 -0
  55. {dirsql-0.4.104 → dirsql-0.4.105}/docs/plugins.md +0 -0
  56. {dirsql-0.4.104 → dirsql-0.4.105}/docs/pnpm-lock.yaml +0 -0
  57. {dirsql-0.4.104 → dirsql-0.4.105}/docs/pnpm-workspace.yaml +0 -0
  58. {dirsql-0.4.104 → dirsql-0.4.105}/docs/reference/cli.md +0 -0
  59. {dirsql-0.4.104 → dirsql-0.4.105}/docs/reference/columns.md +0 -0
  60. {dirsql-0.4.104 → dirsql-0.4.105}/docs/reference/config.md +0 -0
  61. {dirsql-0.4.104 → dirsql-0.4.105}/docs/reference/glob.md +0 -0
  62. {dirsql-0.4.104 → dirsql-0.4.105}/docs/reference/hooks.md +0 -0
  63. {dirsql-0.4.104 → dirsql-0.4.105}/docs/reference/http-api.md +0 -0
  64. {dirsql-0.4.104 → dirsql-0.4.105}/docs/reference/path-tables.md +0 -0
  65. {dirsql-0.4.104 → dirsql-0.4.105}/docs/reference/sdk.md +0 -0
  66. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/CHANGELOG.md +0 -0
  67. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/MIGRATIONS.md +0 -0
  68. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/README.md +0 -0
  69. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/changelog.d/2026-07-13-drop-cwd-config-auto-detect.md +0 -0
  70. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/changelog.d/2026-07-13-plugin-discovery.md +0 -0
  71. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/changelog.d/2026-07-13-rename-extract-to-on-file.md +0 -0
  72. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/changelog.d/2026-07-13-repeatable-config.md +0 -0
  73. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/changelog.d/2026-07-13-sdk-default-baked-in-config.md +0 -0
  74. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/changelog.d/2026-07-20-drop-implicit-files-table.md +0 -0
  75. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/changelog.d/2026-07-21-remove-glob-captures.md +0 -0
  76. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/changelog.d/2026-07-24-hookless-table-config-error.md +0 -0
  77. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/changelog.d/2026-07-24-remove-stat-fact-injection.md +0 -0
  78. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/changelog.d/2026-07-29-restore-python-310.md +0 -0
  79. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/changelog.d/2026-08-02-release-profile.md +0 -0
  80. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/changelog.d/2026-08-02-scan-failures.md +0 -0
  81. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/changelog.d/2026-08-03-discovery-ext-resolution.md +0 -0
  82. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/changelog.d/2026-08-03-manylinux-dynamic-binary.md +0 -0
  83. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/changelog.d/2026-08-03-python-no-ignore.md +0 -0
  84. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/changelog.d/2026-08-04-cast-lints.md +0 -0
  85. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/changelog.d/2026-08-04-cli-in-process.md +0 -0
  86. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/changelog.d/2026-08-20-curtaincall-dev-dependency.md +0 -0
  87. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/changelog.d/2026-08-20-declared-table-name.md +0 -0
  88. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/changelog.d/2026-09-04-config-paths-from-argv-module.md +0 -0
  89. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/changelog.d/2026-09-05-config-extension-loader-split.md +0 -0
  90. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/changelog.d/2026-09-05-resolve-config-extensions-split.md +0 -0
  91. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/changelog.d/2026-09-05-resolve-extension-split.md +0 -0
  92. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/changelog.d/2026-09-09-launcher-config-path-scan.md +0 -0
  93. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/changelog.d/2026-09-17-extension-resolution-via-core.md +0 -0
  94. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/changelog.d/2026-09-18-export-scan-failure.md +0 -0
  95. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/changelog.d/2026-09-18-flatten-row-event-in-core.md +0 -0
  96. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/changelog.d/2026-09-18-programmatic-extension-plan.md +0 -0
  97. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/changelog.d/2026-09-20-sigint-ends-a-run-in-progress.md +0 -0
  98. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/changelog.d/2026-09-24-query-rows-in-projection-order.md +0 -0
  99. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/changelog.d/2026-10-02-readme-docs-host.md +0 -0
  100. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/changelog.d/2026-10-03-one-glob-rule-for-config-globs.md +0 -0
  101. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/changelog.d/2026-10-03-path-table-directory-one-level.md +0 -0
  102. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/changelog.d/2026-10-04-path-table-dotfiles-hidden.md +0 -0
  103. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/changelog.d/2026-10-05-brace-groups-follow-bash.md +0 -0
  104. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/changelog.d/2026-10-05-cli-context.md +0 -0
  105. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/changelog.d/2026-10-05-gitignore-requires-repo.md +0 -0
  106. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/changelog.d/2026-10-05-path-table-node-modules-named.md +0 -0
  107. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/changelog.d/2026-10-05-path-table-symlinks.md +0 -0
  108. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/changelog.d/2026-10-05-path-table-trailing-slash.md +0 -0
  109. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/changelog.d/2026-10-05-unbalanced-brackets-literal.md +0 -0
  110. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/changelog.d/2026-10-06-config-braces.md +0 -0
  111. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/changelog.d/2026-10-06-config-directory.md +0 -0
  112. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/changelog.d/2026-10-06-config-dotnames.md +0 -0
  113. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/changelog.d/2026-10-06-config-gitignore.md +0 -0
  114. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/changelog.d/2026-10-06-config-globs-anchor-at-the-config.md +0 -0
  115. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/changelog.d/2026-10-06-config-links.md +0 -0
  116. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/changelog.d/2026-10-06-one-glob-compiler.md +0 -0
  117. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/changelog.d/2026-10-06-plugin-no-default-records.md +0 -0
  118. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/changelog.d/2026-10-06-watch-broadcast.md +0 -0
  119. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/changelog.d/2026-10-06-zero-match-config-table-warning.md +0 -0
  120. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/changelog.d/2026-10-07-bracket-slash-literal.md +0 -0
  121. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/changelog.d/2026-10-07-dotdot-and-double-slashes.md +0 -0
  122. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/changelog.d/2026-10-07-dotdot-as-written.md +0 -0
  123. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/changelog.d/2026-10-07-glob-walks-node-modules.md +0 -0
  124. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/changelog.d/2026-10-07-literal-gitignore.md +0 -0
  125. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/changelog.d/2026-10-07-on-file-ndjson.md +0 -0
  126. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/changelog.d/2026-10-07-parallel-hooks.md +0 -0
  127. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/changelog.d/2026-10-07-persist-cache-outside-root.md +0 -0
  128. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/changelog.d/2026-10-07-posix-classes-unicode.md +0 -0
  129. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/changelog.d/2026-10-07-spread-few-large-files.md +0 -0
  130. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/changelog.d/2026-10-08-small-walks-stay-on-one-thread.md +0 -0
  131. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/changelog.d/README.md +0 -0
  132. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/conftest.py +0 -0
  133. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/docs/.claude/CLAUDE.md +0 -0
  134. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/docs/.vitepress/config.ts +0 -0
  135. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/docs/.vitepress/theme/index.ts +0 -0
  136. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/docs/.vitepress/theme/lang.ts +0 -0
  137. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/docs/AGENTS.md +0 -0
  138. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/docs/context/.gitattributes +0 -0
  139. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/docs/context/recipes.md +0 -0
  140. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/docs/context/troubleshooting.md +0 -0
  141. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/docs/context/usage.md +0 -0
  142. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/docs/explanation.md +0 -0
  143. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/docs/getting-started.md +0 -0
  144. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/docs/howto/agent-skill.md +0 -0
  145. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/docs/howto/columns-from-paths.md +0 -0
  146. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/docs/howto/define-tables.md +0 -0
  147. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/docs/howto/embed.md +0 -0
  148. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/docs/howto/extract-from-contents.md +0 -0
  149. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/docs/howto/load-extension.md +0 -0
  150. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/docs/howto/parse-files-into-columns.md +0 -0
  151. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/docs/howto/persist.md +0 -0
  152. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/docs/howto/query-json.md +0 -0
  153. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/docs/howto/query-without-config.md +0 -0
  154. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/docs/howto/react-to-changes.md +0 -0
  155. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/docs/howto/search-by-meaning.md +0 -0
  156. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/docs/howto/search-indexes.md +0 -0
  157. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/docs/howto/skip-files.md +0 -0
  158. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/docs/howto/write-a-plugin.md +0 -0
  159. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/docs/index.md +0 -0
  160. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/docs/package.json +0 -0
  161. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/docs/plugins.md +0 -0
  162. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/docs/pnpm-lock.yaml +0 -0
  163. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/docs/pnpm-workspace.yaml +0 -0
  164. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/docs/reference/cli.md +0 -0
  165. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/docs/reference/columns.md +0 -0
  166. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/docs/reference/config.md +0 -0
  167. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/docs/reference/glob.md +0 -0
  168. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/docs/reference/hooks.md +0 -0
  169. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/docs/reference/http-api.md +0 -0
  170. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/docs/reference/path-tables.md +0 -0
  171. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/docs/reference/sdk.md +0 -0
  172. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/bot-1334-listing-exemptions.json +0 -0
  173. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/bot-1397-watch-broadcast.json +0 -0
  174. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/bot-1398-inotify-error.json +0 -0
  175. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/bot-1399-server-cors.json +0 -0
  176. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/bot-1401-watch-scope.json +0 -0
  177. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/bot-1407-child-place-depth.json +0 -0
  178. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/bot-1414-stat-speed.json +0 -0
  179. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/bot-1415-server-speed.json +0 -0
  180. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/bot-1418-node-modules.json +0 -0
  181. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/bot-1419-dotdot-slashes.json +0 -0
  182. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/bot-1420-bracket-slash.json +0 -0
  183. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/bot-1421-posix-unicode.json +0 -0
  184. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/bot-1422-literal-gitignore.json +0 -0
  185. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/bot-1426-walk-spawn-threshold-1008.json +0 -0
  186. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/bot-1428-ndjson-hooks.json +0 -0
  187. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/bot-1429-parallel-hooks.json +0 -0
  188. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/bot-1430-dotdot-as-written.json +0 -0
  189. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/bot-1431-persist-cache-dir.json +0 -0
  190. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/bot-1437-test-cache-isolation.json +0 -0
  191. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/bot-1440-fix-main-build.json +0 -0
  192. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/bot-1448-wildcard-ignored-prefix.json +0 -0
  193. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/bot-1451-named-dir-never-hidden.json +0 -0
  194. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/bot-1454-split-few-files.json +0 -0
  195. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/bot-fix-main-place-depth-20261007.json +0 -0
  196. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/cc-keen-knuth-f11gyq.json +0 -0
  197. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/claude-1056-py-ofpf9.json +0 -0
  198. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/claude-1066-p1-resolve-extension.json +0 -0
  199. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/claude-1078-argv-config-paths-core.json +0 -0
  200. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/claude-1079-p2-resolve-config-extensions.json +0 -0
  201. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/claude-1080-share-vtab-scaffolding.json +0 -0
  202. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/claude-1081-indicatif-progress.json +0 -0
  203. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/claude-1096-watch-moved-dir.json +0 -0
  204. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/claude-1109-watch-moved-out.json +0 -0
  205. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/claude-1119-ext-plan.json +0 -0
  206. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/claude-1120-py-ext.json +0 -0
  207. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/claude-1122-ext-cleanup.json +0 -0
  208. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/claude-1130-codec-crates.json +0 -0
  209. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/claude-1131-flatten-row-event.json +0 -0
  210. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/claude-1139-sse-flatten.json +0 -0
  211. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/claude-1140-scan-failure-export.json +0 -0
  212. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/claude-1148-clippy-all-targets.json +0 -0
  213. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/claude-1164-sigint-fatal.json +0 -0
  214. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/claude-1170-projection-order.json +0 -0
  215. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/claude-1172-on-file-path.json +0 -0
  216. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/claude-1173-py-projection-order.json +0 -0
  217. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/claude-1184-win-rel-paths.json +0 -0
  218. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/claude-1185-win-abs-table-names.json +0 -0
  219. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/claude-1186-win-test-harness.json +0 -0
  220. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/claude-1196-join-index.json +0 -0
  221. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/claude-1199-glob-prefix.json +0 -0
  222. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/claude-1201-single-walk.json +0 -0
  223. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/claude-1204-embed-batch.json +0 -0
  224. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/claude-1207-win-argv-quote.json +0 -0
  225. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/claude-1208-win-nonverbatim-paths.json +0 -0
  226. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/claude-1215-on-file-batch.json +0 -0
  227. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/claude-1216-on-file-cleanup.json +0 -0
  228. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/claude-1223-config-glob-rule.json +0 -0
  229. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/claude-1223-dir-one-level.json +0 -0
  230. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/claude-1225-plugin-no-default.json +0 -0
  231. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/claude-1226-stale-docs.json +0 -0
  232. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/claude-1232-anchor-plumbing.json +0 -0
  233. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/claude-1232-config-anchor.json +0 -0
  234. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/claude-1232-multi-watch.json +0 -0
  235. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/claude-1232-zero-match.json +0 -0
  236. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/claude-1238-parallel-walk.json +0 -0
  237. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/claude-1241-content-prefetch.json +0 -0
  238. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/claude-1243-ingest-perf.json +0 -0
  239. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/claude-1249-dotfiles.json +0 -0
  240. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/claude-1256-symlinks.json +0 -0
  241. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/claude-1257-trailing-slash.json +0 -0
  242. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/claude-1258-config-braces.json +0 -0
  243. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/claude-1258-config-directory.json +0 -0
  244. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/claude-1258-config-dotnames.json +0 -0
  245. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/claude-1258-config-gitignore.json +0 -0
  246. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/claude-1258-config-links.json +0 -0
  247. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/claude-1258-one-pattern.json +0 -0
  248. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/claude-1262-brace-literal.json +0 -0
  249. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/claude-1264-context.json +0 -0
  250. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/claude-1267-posix-classes.json +0 -0
  251. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/claude-1268-unbalanced-brackets.json +0 -0
  252. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/claude-1269-utf8-chars.json +0 -0
  253. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/claude-1271-node-modules-named.json +0 -0
  254. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/claude-1272-ancestor-gitignore.json +0 -0
  255. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/claude-1273-walk-prune.json +0 -0
  256. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/claude-1285-ingest-fast.json +0 -0
  257. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/claude-1286-nested-repo.json +0 -0
  258. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/claude-1305-glob-speed.json +0 -0
  259. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/claude-1308-glob-speed.json +0 -0
  260. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/claude-1310-glob-speed.json +0 -0
  261. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/claude-1311-glob-speed.json +0 -0
  262. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/claude-1312-glob-speed.json +0 -0
  263. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/claude-1318-lazy-stat.json +0 -0
  264. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/claude-1320-walk-speed.json +0 -0
  265. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/claude-1322-walk-readdir.json +0 -0
  266. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/claude-1325-parallel-walk.json +0 -0
  267. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/claude-1326-stream-rows.json +0 -0
  268. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/claude-1330-python-dirsql-patch.json +0 -0
  269. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/claude-1330-python-extensions-patch.json +0 -0
  270. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/claude-1330-scanner-effects.json +0 -0
  271. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/claude-1330-scanner-gitignore.json +0 -0
  272. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/claude-1330-vtab-prefetch-flag.json +0 -0
  273. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/claude-1330-vtab-read.json +0 -0
  274. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/claude-1351-dot-dirs.json +0 -0
  275. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/claude-819-oneshot-timeout.json +0 -0
  276. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/claude-827-unquote-doubled-quotes.json +0 -0
  277. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/claude-epic-953-slice2-reedline.json +0 -0
  278. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/claude-epic-953-slice3-format.json +0 -0
  279. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/claude-epic-953-stacked-prs-x59wm3.json +0 -0
  280. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/claude-github-issue-825-e4mhhp.json +0 -0
  281. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/claude-issue-951-0eease.json +0 -0
  282. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/claude-issue-962-0jjrfv.json +0 -0
  283. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/claude-issue-986-red-test-e7sjrb.json +0 -0
  284. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/claude-tackle-1034-cachesplit.json +0 -0
  285. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/claude-tackle-1042-py12.json +0 -0
  286. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/claude-tackle-956-xzen74.json +0 -0
  287. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/claude-tackle-957-lrm0z6-worker-calls.json +0 -0
  288. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/claude-tackle-957-lrm0z6.json +0 -0
  289. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/e2e-attestations/claude-tackle-975-n8mc2h.json +0 -0
  290. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/migrations.d/2026-07-13-drop-cwd-config-auto-detect.md +0 -0
  291. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/migrations.d/2026-07-13-rename-extract-to-on-file.md +0 -0
  292. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/migrations.d/2026-07-13-sdk-default-baked-in-config.md +0 -0
  293. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/migrations.d/2026-07-20-drop-implicit-files-table.md +0 -0
  294. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/migrations.d/2026-07-21-remove-glob-captures.md +0 -0
  295. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/migrations.d/2026-07-24-hookless-table-config-error.md +0 -0
  296. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/migrations.d/2026-07-24-remove-stat-fact-injection.md +0 -0
  297. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/migrations.d/2026-07-29-restore-python-310.md +0 -0
  298. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/migrations.d/2026-08-04-cli-in-process.md +0 -0
  299. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/migrations.d/2026-08-20-declared-table-name.md +0 -0
  300. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/migrations.d/2026-09-20-sigint-ends-a-run-in-progress.md +0 -0
  301. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/migrations.d/2026-09-24-query-rows-in-projection-order.md +0 -0
  302. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/migrations.d/2026-10-02-readme-docs-host.md +0 -0
  303. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/migrations.d/2026-10-03-one-glob-rule-for-config-globs.md +0 -0
  304. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/migrations.d/2026-10-03-path-table-directory-one-level.md +0 -0
  305. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/migrations.d/2026-10-04-path-table-dotfiles-hidden.md +0 -0
  306. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/migrations.d/2026-10-05-brace-groups-follow-bash.md +0 -0
  307. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/migrations.d/2026-10-05-cli-context.md +0 -0
  308. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/migrations.d/2026-10-05-gitignore-requires-repo.md +0 -0
  309. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/migrations.d/2026-10-05-path-table-node-modules-named.md +0 -0
  310. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/migrations.d/2026-10-05-path-table-symlinks.md +0 -0
  311. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/migrations.d/2026-10-05-path-table-trailing-slash.md +0 -0
  312. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/migrations.d/2026-10-05-unbalanced-brackets-literal.md +0 -0
  313. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/migrations.d/2026-10-06-config-braces.md +0 -0
  314. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/migrations.d/2026-10-06-config-directory.md +0 -0
  315. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/migrations.d/2026-10-06-config-dotnames.md +0 -0
  316. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/migrations.d/2026-10-06-config-gitignore.md +0 -0
  317. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/migrations.d/2026-10-06-config-globs-anchor-at-the-config.md +0 -0
  318. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/migrations.d/2026-10-06-config-links.md +0 -0
  319. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/migrations.d/2026-10-06-one-glob-compiler.md +0 -0
  320. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/migrations.d/2026-10-06-plugin-no-default-records.md +0 -0
  321. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/migrations.d/2026-10-06-watch-broadcast.md +0 -0
  322. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/migrations.d/2026-10-06-zero-match-config-table-warning.md +0 -0
  323. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/migrations.d/2026-10-07-bracket-slash-literal.md +0 -0
  324. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/migrations.d/2026-10-07-dotdot-and-double-slashes.md +0 -0
  325. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/migrations.d/2026-10-07-dotdot-as-written.md +0 -0
  326. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/migrations.d/2026-10-07-glob-walks-node-modules.md +0 -0
  327. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/migrations.d/2026-10-07-literal-gitignore.md +0 -0
  328. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/migrations.d/2026-10-07-on-file-ndjson.md +0 -0
  329. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/migrations.d/2026-10-07-parallel-hooks.md +0 -0
  330. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/migrations.d/2026-10-07-persist-cache-outside-root.md +0 -0
  331. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/migrations.d/2026-10-07-posix-classes-unicode.md +0 -0
  332. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/migrations.d/2026-10-07-spread-few-large-files.md +0 -0
  333. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/migrations.d/2026-10-08-small-walks-stay-on-one-thread.md +0 -0
  334. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/migrations.d/README.md +0 -0
  335. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/src/lib.rs +0 -0
  336. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/testing-conventions.toml +0 -0
  337. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/tests/__init__.py +0 -0
  338. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/tests/conftest.py +0 -0
  339. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/tests/e2e/__init__.py +0 -0
  340. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/tests/e2e/fixtures/dirsql_plugin_fixture/__init__.py +0 -0
  341. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/tests/e2e/fixtures/dirsql_plugin_fixture/dirsql.toml +0 -0
  342. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/tests/e2e/speed_of_light.py +0 -0
  343. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/tests/integration/__init__.py +0 -0
  344. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/tests/integration/binding/__init__.py +0 -0
  345. {dirsql-0.4.104 → dirsql-0.4.105}/packages/python/tests/integration/hermetic/__init__.py +0 -0
  346. {dirsql-0.4.104 → dirsql-0.4.105}/packages/rust/CHANGELOG.md +0 -0
  347. {dirsql-0.4.104 → dirsql-0.4.105}/packages/rust/MIGRATIONS.md +0 -0
  348. {dirsql-0.4.104 → dirsql-0.4.105}/packages/rust/README.md +0 -0
  349. {dirsql-0.4.104 → dirsql-0.4.105}/packages/rust/benches/db_bench.rs +0 -0
  350. {dirsql-0.4.104 → dirsql-0.4.105}/packages/rust/benches/differ_bench.rs +0 -0
  351. {dirsql-0.4.104 → dirsql-0.4.105}/packages/rust/benches/matcher_bench.rs +0 -0
  352. {dirsql-0.4.104 → dirsql-0.4.105}/packages/rust/benches/scanner_bench.rs +0 -0
  353. {dirsql-0.4.104 → dirsql-0.4.105}/packages/rust/docs/context/.gitattributes +0 -0
  354. {dirsql-0.4.104 → dirsql-0.4.105}/packages/rust/docs/context/recipes.md +0 -0
  355. {dirsql-0.4.104 → dirsql-0.4.105}/packages/rust/docs/context/troubleshooting.md +0 -0
  356. {dirsql-0.4.104 → dirsql-0.4.105}/packages/rust/docs/context/usage.md +0 -0
  357. {dirsql-0.4.104 → dirsql-0.4.105}/packages/rust/docs/explanation.md +0 -0
  358. {dirsql-0.4.104 → dirsql-0.4.105}/packages/rust/docs/getting-started.md +0 -0
  359. {dirsql-0.4.104 → dirsql-0.4.105}/packages/rust/docs/howto/agent-skill.md +0 -0
  360. {dirsql-0.4.104 → dirsql-0.4.105}/packages/rust/docs/howto/columns-from-paths.md +0 -0
  361. {dirsql-0.4.104 → dirsql-0.4.105}/packages/rust/docs/howto/define-tables.md +0 -0
  362. {dirsql-0.4.104 → dirsql-0.4.105}/packages/rust/docs/howto/embed.md +0 -0
  363. {dirsql-0.4.104 → dirsql-0.4.105}/packages/rust/docs/howto/extract-from-contents.md +0 -0
  364. {dirsql-0.4.104 → dirsql-0.4.105}/packages/rust/docs/howto/load-extension.md +0 -0
  365. {dirsql-0.4.104 → dirsql-0.4.105}/packages/rust/docs/howto/parse-files-into-columns.md +0 -0
  366. {dirsql-0.4.104 → dirsql-0.4.105}/packages/rust/docs/howto/persist.md +0 -0
  367. {dirsql-0.4.104 → dirsql-0.4.105}/packages/rust/docs/howto/query-json.md +0 -0
  368. {dirsql-0.4.104 → dirsql-0.4.105}/packages/rust/docs/howto/query-without-config.md +0 -0
  369. {dirsql-0.4.104 → dirsql-0.4.105}/packages/rust/docs/howto/react-to-changes.md +0 -0
  370. {dirsql-0.4.104 → dirsql-0.4.105}/packages/rust/docs/howto/search-by-meaning.md +0 -0
  371. {dirsql-0.4.104 → dirsql-0.4.105}/packages/rust/docs/howto/search-indexes.md +0 -0
  372. {dirsql-0.4.104 → dirsql-0.4.105}/packages/rust/docs/howto/skip-files.md +0 -0
  373. {dirsql-0.4.104 → dirsql-0.4.105}/packages/rust/docs/howto/write-a-plugin.md +0 -0
  374. {dirsql-0.4.104 → dirsql-0.4.105}/packages/rust/docs/index.md +0 -0
  375. {dirsql-0.4.104 → dirsql-0.4.105}/packages/rust/docs/plugins.md +0 -0
  376. {dirsql-0.4.104 → dirsql-0.4.105}/packages/rust/docs/reference/cli.md +0 -0
  377. {dirsql-0.4.104 → dirsql-0.4.105}/packages/rust/docs/reference/columns.md +0 -0
  378. {dirsql-0.4.104 → dirsql-0.4.105}/packages/rust/docs/reference/config.md +0 -0
  379. {dirsql-0.4.104 → dirsql-0.4.105}/packages/rust/docs/reference/glob.md +0 -0
  380. {dirsql-0.4.104 → dirsql-0.4.105}/packages/rust/docs/reference/hooks.md +0 -0
  381. {dirsql-0.4.104 → dirsql-0.4.105}/packages/rust/docs/reference/http-api.md +0 -0
  382. {dirsql-0.4.104 → dirsql-0.4.105}/packages/rust/docs/reference/path-tables.md +0 -0
  383. {dirsql-0.4.104 → dirsql-0.4.105}/packages/rust/docs/reference/sdk.md +0 -0
  384. {dirsql-0.4.104 → dirsql-0.4.105}/packages/rust/src/bin/dirsql.rs +0 -0
  385. {dirsql-0.4.104 → dirsql-0.4.105}/packages/rust/src/brace.rs +0 -0
  386. {dirsql-0.4.104 → dirsql-0.4.105}/packages/rust/src/cli/context.rs +0 -0
  387. {dirsql-0.4.104 → dirsql-0.4.105}/packages/rust/src/cli/execute.rs +0 -0
  388. {dirsql-0.4.104 → dirsql-0.4.105}/packages/rust/src/cli/init.rs +0 -0
  389. {dirsql-0.4.104 → dirsql-0.4.105}/packages/rust/src/cli/mod.rs +0 -0
  390. {dirsql-0.4.104 → dirsql-0.4.105}/packages/rust/src/cli/repl.rs +0 -0
  391. {dirsql-0.4.104 → dirsql-0.4.105}/packages/rust/src/cli/router.rs +0 -0
  392. {dirsql-0.4.104 → dirsql-0.4.105}/packages/rust/src/cli/run.rs +0 -0
  393. {dirsql-0.4.104 → dirsql-0.4.105}/packages/rust/src/cli/serialize.rs +0 -0
  394. {dirsql-0.4.104 → dirsql-0.4.105}/packages/rust/src/cli/server.rs +0 -0
  395. {dirsql-0.4.104 → dirsql-0.4.105}/packages/rust/src/cli/table.rs +0 -0
  396. {dirsql-0.4.104 → dirsql-0.4.105}/packages/rust/src/command.rs +0 -0
  397. {dirsql-0.4.104 → dirsql-0.4.105}/packages/rust/src/config.rs +0 -0
  398. {dirsql-0.4.104 → dirsql-0.4.105}/packages/rust/src/default_config.toml +0 -0
  399. {dirsql-0.4.104 → dirsql-0.4.105}/packages/rust/src/differ.rs +0 -0
  400. {dirsql-0.4.104 → dirsql-0.4.105}/packages/rust/src/extension_resolution.rs +0 -0
  401. {dirsql-0.4.104 → dirsql-0.4.105}/packages/rust/src/functions.rs +0 -0
  402. {dirsql-0.4.104 → dirsql-0.4.105}/packages/rust/src/infer.rs +0 -0
  403. {dirsql-0.4.104 → dirsql-0.4.105}/packages/rust/src/launcher.rs +0 -0
  404. {dirsql-0.4.104 → dirsql-0.4.105}/packages/rust/src/listing.rs +0 -0
  405. {dirsql-0.4.104 → dirsql-0.4.105}/packages/rust/src/matcher.rs +0 -0
  406. {dirsql-0.4.104 → dirsql-0.4.105}/packages/rust/src/on_file.rs +0 -0
  407. {dirsql-0.4.104 → dirsql-0.4.105}/packages/rust/src/parsed_vtab.rs +0 -0
  408. {dirsql-0.4.104 → dirsql-0.4.105}/packages/rust/src/path_table.rs +0 -0
  409. {dirsql-0.4.104 → dirsql-0.4.105}/packages/rust/src/persist.rs +0 -0
  410. {dirsql-0.4.104 → dirsql-0.4.105}/packages/rust/src/posix_class.rs +0 -0
  411. {dirsql-0.4.104 → dirsql-0.4.105}/packages/rust/src/progress.rs +0 -0
  412. {dirsql-0.4.104 → dirsql-0.4.105}/packages/rust/src/row_event_flat.rs +0 -0
  413. {dirsql-0.4.104 → dirsql-0.4.105}/packages/rust/src/scanner.rs +0 -0
  414. {dirsql-0.4.104 → dirsql-0.4.105}/packages/rust/src/sql_literal.rs +0 -0
  415. {dirsql-0.4.104 → dirsql-0.4.105}/packages/rust/src/tree_walk.rs +0 -0
  416. {dirsql-0.4.104 → dirsql-0.4.105}/packages/rust/src/vtab.rs +0 -0
  417. {dirsql-0.4.104 → dirsql-0.4.105}/packages/rust/src/vtab_scaffold.rs +0 -0
  418. {dirsql-0.4.104 → dirsql-0.4.105}/packages/rust/src/watcher.rs +0 -0
  419. {dirsql-0.4.104 → dirsql-0.4.105}/pyproject.toml +0 -0
@@ -573,7 +573,7 @@ dependencies = [
573
573
 
574
574
  [[package]]
575
575
  name = "dirsql"
576
- version = "0.4.104"
576
+ version = "0.4.105"
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.104"
628
+ version = "0.4.105"
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.104
3
+ Version: 0.4.105
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
@@ -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.104"
7
+ version = "0.4.105"
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,7 @@
1
+ {
2
+ "command": "uv run python -m pytest tests/e2e/on_file_query_test.py tests/e2e/query_contract_test.py tests/e2e/use_case_transcripts_test.py -x -q",
3
+ "ran_at": 1791462122,
4
+ "exit_code": 0,
5
+ "commit": "e59418c942479166b7edd1a1587ed559574c60b7",
6
+ "branch": "bot/1457-incremental-refresh-1008"
7
+ }
@@ -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.104"
9
+ version = "0.4.105"
10
10
  edition.workspace = true
11
11
  # Literal `license` (not `license.workspace = true`) because
12
12
  # putitoutthere's preflight check reads `[package].license` directly
@@ -266,6 +266,28 @@ pub fn ensure_internal_rows_table(conn: &Connection) -> rusqlite::Result<()> {
266
266
  )
267
267
  }
268
268
 
269
+ fn insert_sql(table: &str, columns: &[String]) -> String {
270
+ if columns.is_empty() {
271
+ return format!("INSERT INTO \"{table}\" DEFAULT VALUES");
272
+ }
273
+ let names: Vec<String> = columns.iter().map(|c| format!("\"{c}\"")).collect();
274
+ let placeholders: Vec<String> = (1..=columns.len()).map(|i| format!("?{i}")).collect();
275
+ format!(
276
+ "INSERT INTO \"{table}\" ({}) VALUES ({})",
277
+ names.join(", "),
278
+ placeholders.join(", ")
279
+ )
280
+ }
281
+
282
+ /// The rows a [`Db::reconcile_batch_rows`] did not carry over: those only the
283
+ /// table held (`removed`) and those only the new rows hold (`added`), each in
284
+ /// its original order.
285
+ #[derive(Debug, Default)]
286
+ pub struct RowChanges {
287
+ pub removed: Vec<HashMap<String, Value>>,
288
+ pub added: Vec<HashMap<String, Value>>,
289
+ }
290
+
269
291
  /// A batch of rows laid out in a table's DDL column order, so each row binds
270
292
  /// positionally with no per-cell lookup.
271
293
  #[derive(Debug, Clone, PartialEq)]
@@ -700,18 +722,7 @@ impl Db {
700
722
  /// interleave batches of different owners.
701
723
  pub fn insert_rows(&self, table: &str, rows: &ShapedRows, file_path: &str) -> Result<()> {
702
724
  validate_identifier(table)?;
703
- let columns = &rows.columns;
704
- let sql = if columns.is_empty() {
705
- format!("INSERT INTO \"{table}\" DEFAULT VALUES")
706
- } else {
707
- let names: Vec<String> = columns.iter().map(|c| format!("\"{c}\"")).collect();
708
- let placeholders: Vec<String> = (1..=columns.len()).map(|i| format!("?{i}")).collect();
709
- format!(
710
- "INSERT INTO \"{table}\" ({}) VALUES ({})",
711
- names.join(", "),
712
- placeholders.join(", ")
713
- )
714
- };
725
+ let sql = insert_sql(table, &rows.columns);
715
726
  let tx = if self.conn.is_autocommit() {
716
727
  Some(self.conn.unchecked_transaction()?)
717
728
  } else {
@@ -743,6 +754,99 @@ impl Db {
743
754
  Ok(())
744
755
  }
745
756
 
757
+ /// Make the table's rows, all owned by the batch `owner`, equal `rows`,
758
+ /// writing only the difference. The rows are compared as a multiset (a
759
+ /// batch has no row identity): an equal row already stored is left in
760
+ /// place, a stored row with no match is deleted, and a new row with no
761
+ /// match is inserted. The owner's rowid range is then re-fitted to the
762
+ /// table. Run it inside the caller's transaction.
763
+ pub fn reconcile_batch_rows(
764
+ &self,
765
+ table: &str,
766
+ owner: &str,
767
+ rows: &ShapedRows,
768
+ ) -> Result<RowChanges> {
769
+ validate_identifier(table)?;
770
+ let columns = &rows.columns;
771
+ let select = if columns.is_empty() {
772
+ format!("SELECT rowid FROM \"{table}\" ORDER BY rowid")
773
+ } else {
774
+ let names: Vec<String> = columns.iter().map(|c| format!("\"{c}\"")).collect();
775
+ format!(
776
+ "SELECT rowid, {} FROM \"{table}\" ORDER BY rowid",
777
+ names.join(", ")
778
+ )
779
+ };
780
+ let mut stored: Vec<(i64, Vec<Value>)> = Vec::new();
781
+ {
782
+ let mut stmt = self.conn.prepare(&select)?;
783
+ let mut cursor = stmt.query([])?;
784
+ while let Some(row) = cursor.next()? {
785
+ let cells = (0..columns.len())
786
+ .map(|i| row.get::<_, rusqlite::types::Value>(i + 1).map(Value::from))
787
+ .collect::<rusqlite::Result<Vec<_>>>()?;
788
+ stored.push((row.get(0)?, cells));
789
+ }
790
+ }
791
+ let mut unclaimed: HashMap<String, Vec<usize>> = HashMap::new();
792
+ for (index, (_, cells)) in stored.iter().enumerate().rev() {
793
+ unclaimed
794
+ .entry(format!("{cells:?}"))
795
+ .or_default()
796
+ .push(index);
797
+ }
798
+ let mut added = Vec::new();
799
+ for (index, cells) in rows.cells.iter().enumerate() {
800
+ match unclaimed.get_mut(&format!("{cells:?}")) {
801
+ Some(indexes) if !indexes.is_empty() => {
802
+ indexes.pop();
803
+ }
804
+ _ => added.push(index),
805
+ }
806
+ }
807
+ let mut removed: Vec<usize> = unclaimed.into_values().flatten().collect();
808
+ removed.sort_unstable();
809
+
810
+ let to_map = |cells: &[Value]| -> HashMap<String, Value> {
811
+ columns.iter().cloned().zip(cells.iter().cloned()).collect()
812
+ };
813
+ let changes = RowChanges {
814
+ removed: removed.iter().map(|&i| to_map(&stored[i].1)).collect(),
815
+ added: added.iter().map(|&i| to_map(&rows.cells[i])).collect(),
816
+ };
817
+ if removed.is_empty() && added.is_empty() {
818
+ return Ok(changes);
819
+ }
820
+ let mut delete = self
821
+ .conn
822
+ .prepare_cached(&format!("DELETE FROM \"{table}\" WHERE rowid = ?1"))?;
823
+ for &index in &removed {
824
+ delete.execute([stored[index].0])?;
825
+ }
826
+ let mut insert = self.conn.prepare_cached(&insert_sql(table, columns))?;
827
+ for &index in &added {
828
+ for (i, value) in rows.cells[index].iter().enumerate() {
829
+ insert.raw_bind_parameter(i + 1, value)?;
830
+ }
831
+ insert.raw_execute()?;
832
+ }
833
+ self.conn.execute(
834
+ "DELETE FROM _dirsql_internal_ranges WHERE table_name = ?1 AND file_path = ?2",
835
+ rusqlite::params![table, owner],
836
+ )?;
837
+ let (first, last): (Option<i64>, Option<i64>) = self.conn.query_row(
838
+ &format!("SELECT min(rowid), max(rowid) FROM \"{table}\""),
839
+ [],
840
+ |r| Ok((r.get(0)?, r.get(1)?)),
841
+ )?;
842
+ if let (Some(first), Some(last)) = (first, last) {
843
+ self.conn
844
+ .prepare_cached(RANGE_INSERT)?
845
+ .execute(rusqlite::params![table, owner, first, last])?;
846
+ }
847
+ Ok(changes)
848
+ }
849
+
746
850
  /// Both the table name and every user-provided column name are validated
747
851
  /// as safe SQL identifiers before being interpolated. A column key with
748
852
  /// SQL syntax (e.g. `id); DROP TABLE t; --`) produces a clean
@@ -3023,6 +3127,141 @@ mod tests {
3023
3127
  assert!(range_rows(&db, "t").is_empty());
3024
3128
  }
3025
3129
 
3130
+ fn table_rows(db: &Db, table: &str) -> Vec<(i64, String)> {
3131
+ db.conn()
3132
+ .prepare(&format!("SELECT rowid, id FROM {table} ORDER BY rowid"))
3133
+ .unwrap()
3134
+ .query_map([], |r| Ok((r.get(0)?, r.get(1)?)))
3135
+ .unwrap()
3136
+ .collect::<rusqlite::Result<_>>()
3137
+ .unwrap()
3138
+ }
3139
+
3140
+ fn ids(changes: &[HashMap<String, Value>]) -> Vec<Value> {
3141
+ changes.iter().map(|row| row["id"].clone()).collect()
3142
+ }
3143
+
3144
+ fn batch_db(ids: serde_json::Value) -> Db {
3145
+ let db = Db::new().unwrap();
3146
+ db.create_table("t", "CREATE TABLE t (id TEXT)").unwrap();
3147
+ db.insert_rows("t", &shaped(&db, "t", ids), "").unwrap();
3148
+ db
3149
+ }
3150
+
3151
+ #[test]
3152
+ fn reconcile_batch_rows_leaves_equal_rows_in_place_and_writes_the_rest() {
3153
+ let db = batch_db(serde_json::json!([{"id":"a"},{"id":"b"},{"id":"c"}]));
3154
+ let next = shaped(
3155
+ &db,
3156
+ "t",
3157
+ serde_json::json!([{"id":"a"},{"id":"x"},{"id":"c"}]),
3158
+ );
3159
+ let before = db.conn().total_changes();
3160
+
3161
+ let changes = db.reconcile_batch_rows("t", "", &next).unwrap();
3162
+
3163
+ assert_eq!(
3164
+ table_rows(&db, "t"),
3165
+ vec![(1, "a".into()), (3, "c".into()), (4, "x".into())]
3166
+ );
3167
+ assert_eq!(ids(&changes.removed), vec![Value::Text("b".into())]);
3168
+ assert_eq!(ids(&changes.added), vec![Value::Text("x".into())]);
3169
+ assert_eq!(db.conn().total_changes() - before, 4);
3170
+ }
3171
+
3172
+ #[test]
3173
+ fn reconcile_batch_rows_compares_rows_as_a_multiset() {
3174
+ let db = batch_db(serde_json::json!([{"id":"a"},{"id":"a"},{"id":"a"}]));
3175
+ let next = shaped(&db, "t", serde_json::json!([{"id":"a"},{"id":"a"}]));
3176
+
3177
+ let changes = db.reconcile_batch_rows("t", "", &next).unwrap();
3178
+
3179
+ assert_eq!(table_rows(&db, "t"), vec![(1, "a".into()), (2, "a".into())]);
3180
+ assert_eq!(ids(&changes.removed), vec![Value::Text("a".into())]);
3181
+ assert!(changes.added.is_empty());
3182
+ }
3183
+
3184
+ #[test]
3185
+ fn reconcile_batch_rows_inserts_each_copy_the_table_holds_too_few_of() {
3186
+ let db = batch_db(serde_json::json!([{"id":"a"}]));
3187
+ let next = shaped(
3188
+ &db,
3189
+ "t",
3190
+ serde_json::json!([{"id":"a"},{"id":"a"},{"id":"a"}]),
3191
+ );
3192
+
3193
+ let changes = db.reconcile_batch_rows("t", "", &next).unwrap();
3194
+
3195
+ assert_eq!(
3196
+ table_rows(&db, "t"),
3197
+ vec![(1, "a".into()), (2, "a".into()), (3, "a".into())]
3198
+ );
3199
+ assert_eq!(ids(&changes.added).len(), 2);
3200
+ assert!(changes.removed.is_empty());
3201
+ }
3202
+
3203
+ #[test]
3204
+ fn reconcile_batch_rows_writes_nothing_when_no_row_changed() {
3205
+ let db = batch_db(serde_json::json!([{"id":"a"},{"id":"b"}]));
3206
+ let same = shaped(&db, "t", serde_json::json!([{"id":"b"},{"id":"a"}]));
3207
+ let before = db.conn().total_changes();
3208
+
3209
+ let changes = db.reconcile_batch_rows("t", "", &same).unwrap();
3210
+
3211
+ assert!(changes.removed.is_empty() && changes.added.is_empty());
3212
+ assert_eq!(db.conn().total_changes(), before);
3213
+ }
3214
+
3215
+ #[test]
3216
+ fn reconcile_batch_rows_refits_the_owners_range_to_the_table() {
3217
+ let db = batch_db(serde_json::json!([{"id":"a"},{"id":"b"},{"id":"c"}]));
3218
+ let next = shaped(&db, "t", serde_json::json!([{"id":"b"},{"id":"d"}]));
3219
+ db.reconcile_batch_rows("t", "", &next).unwrap();
3220
+
3221
+ assert_eq!(range_rows(&db, "t"), vec![(String::new(), 2, 3)]);
3222
+ let read: Vec<Value> = db
3223
+ .get_rows_by_file("t", "")
3224
+ .unwrap()
3225
+ .into_iter()
3226
+ .map(|r| r["id"].clone())
3227
+ .collect();
3228
+ assert_eq!(read, vec![Value::Text("b".into()), Value::Text("d".into())]);
3229
+
3230
+ let empty = shaped(&db, "t", serde_json::json!([]));
3231
+ db.reconcile_batch_rows("t", "", &empty).unwrap();
3232
+ assert!(range_rows(&db, "t").is_empty());
3233
+ assert!(table_rows(&db, "t").is_empty());
3234
+ }
3235
+
3236
+ #[test]
3237
+ fn reconcile_batch_rows_binds_an_empty_column_list_by_default_values() {
3238
+ let db = Db::new().unwrap();
3239
+ db.create_table("t", "CREATE TABLE t (id TEXT)").unwrap();
3240
+ let rows = ShapedRows {
3241
+ columns: vec![],
3242
+ cells: vec![vec![]],
3243
+ };
3244
+ db.reconcile_batch_rows("t", "", &rows).unwrap();
3245
+ assert_eq!(table_rows_count(&db), 1);
3246
+ }
3247
+
3248
+ fn table_rows_count(db: &Db) -> i64 {
3249
+ db.conn()
3250
+ .query_row("SELECT count(*) FROM t", [], |r| r.get(0))
3251
+ .unwrap()
3252
+ }
3253
+
3254
+ #[test]
3255
+ fn reconcile_batch_rows_rejects_an_unsafe_table_name() {
3256
+ let db = Db::new().unwrap();
3257
+ let rows = ShapedRows {
3258
+ columns: vec![],
3259
+ cells: vec![],
3260
+ };
3261
+ let err = db.reconcile_batch_rows("bad name", "", &rows).unwrap_err();
3262
+ assert!(matches!(err, DbError::InvalidIdentifier(_)), "got: {err}");
3263
+ }
3264
+
3026
3265
  #[test]
3027
3266
  fn insert_rows_rejects_an_unsafe_table_name() {
3028
3267
  let db = Db::new().unwrap();
@@ -755,6 +755,8 @@ impl DirSQL {
755
755
 
756
756
  for m in matches {
757
757
  if self.is_per_table(&m.table_name) {
758
+ let deleted = matches!(event, FileEvent::Deleted(_));
759
+ self.track_batch_file(&m.table_name, &abs_path, &rel_path, deleted);
758
760
  pending.mark(&m.table_name, &rel_path);
759
761
  continue;
760
762
  }
@@ -832,22 +834,20 @@ impl DirSQL {
832
834
  else {
833
835
  return Vec::new();
834
836
  };
835
- let root = &group.anchor;
836
- let mut rel_paths = Vec::new();
837
- let mut abs_paths = Vec::new();
838
- for (path, matched) in self.inner.fs.scan_subtree(root, root, &group.matcher) {
839
- if matched == table {
840
- rel_paths.push(relative_path(root, &path));
841
- abs_paths.push(path);
842
- }
843
- }
837
+ let abs_paths: Vec<PathBuf> = match self.inner.batch_files.lock() {
838
+ Ok(files) => files
839
+ .get(table)
840
+ .map(|rel_paths| rel_paths.iter().map(|rel| group.anchor.join(rel)).collect())
841
+ .unwrap_or_default(),
842
+ Err(e) => return vec![error_event(Some(table), trigger, e.to_string())],
843
+ };
844
844
  let raw_rows = match collect_table_rows(hook.as_ref(), &abs_paths) {
845
845
  Ok(rows) => rows,
846
846
  Err(e) => return vec![error_event(Some(table), trigger, e.to_string())],
847
847
  };
848
848
  let strict = *self.inner.strict_map.get(table).unwrap_or(&false);
849
849
 
850
- let (old_rows, new_rows) = {
850
+ let changes = {
851
851
  let db = match self.inner.db.lock() {
852
852
  Ok(g) => g,
853
853
  Err(e) => return vec![error_event(Some(table), trigger, e.to_string())],
@@ -856,27 +856,41 @@ impl DirSQL {
856
856
  Ok(rows) => rows,
857
857
  Err(message) => return vec![error_event(Some(table), trigger, message)],
858
858
  };
859
- let old_rows = match db.get_rows_by_file(table, BATCH_OWNER) {
860
- Ok(rows) => rows,
861
- Err(e) => return vec![error_event(Some(table), trigger, e.to_string())],
862
- };
863
859
  let _tx = match db.conn().unchecked_transaction() {
864
860
  Ok(tx) => tx,
865
861
  Err(e) => return vec![error_event(Some(table), trigger, e.to_string())],
866
862
  };
867
- if let Err(e) = replace_table_rows(&db, table, &new_rows) {
868
- return vec![error_event(Some(table), trigger, e.to_string())];
869
- }
863
+ let changes = match db.reconcile_batch_rows(table, BATCH_OWNER, &new_rows) {
864
+ Ok(changes) => changes,
865
+ Err(e) => return vec![error_event(Some(table), trigger, e.to_string())],
866
+ };
870
867
  if let Err(e) = _tx.commit() {
871
868
  return vec![error_event(Some(table), trigger, e.to_string())];
872
869
  }
873
- (old_rows, new_rows.to_maps())
870
+ changes
874
871
  };
875
- if let Ok(mut files) = self.inner.batch_files.lock() {
876
- files.insert(table.to_string(), rel_paths);
877
- }
878
872
 
879
- differ::diff_unordered(table, &old_rows, &new_rows, trigger)
873
+ differ::diff_unordered(table, &changes.removed, &changes.added, trigger)
874
+ }
875
+
876
+ /// Record in the table's file set whether `abs_path` is, now, one of the
877
+ /// files its per-table hook runs over. The set stays in walk order
878
+ /// (component-wise byte order), so the hook sees the same argument list a
879
+ /// walk of the tree would give it.
880
+ fn track_batch_file(&self, table: &str, abs_path: &Path, rel_path: &str, deleted: bool) {
881
+ let present = !deleted && self.inner.fs.is_file(abs_path).unwrap_or(false);
882
+ let Ok(mut batch_files) = self.inner.batch_files.lock() else {
883
+ return;
884
+ };
885
+ let files = batch_files.entry(table.to_string()).or_default();
886
+ let at = files.binary_search_by(|file| Path::new(file).cmp(Path::new(rel_path)));
887
+ match (present, at) {
888
+ (true, Err(at)) => files.insert(at, rel_path.to_string()),
889
+ (false, Ok(at)) => {
890
+ files.remove(at);
891
+ }
892
+ _ => {}
893
+ }
880
894
  }
881
895
 
882
896
  /// Delete the rows of every file recorded beneath `rel_dir`, in every
@@ -889,9 +903,10 @@ impl DirSQL {
889
903
  ) -> Vec<RowEvent> {
890
904
  let prefix = format!("{rel_dir}/");
891
905
  let in_group = |table: &str| group.tables.iter().any(|name| name == table);
892
- if let Ok(batch_files) = self.inner.batch_files.lock() {
893
- for (table, files) in batch_files.iter() {
906
+ if let Ok(mut batch_files) = self.inner.batch_files.lock() {
907
+ for (table, files) in batch_files.iter_mut() {
894
908
  if in_group(table) && files.iter().any(|file| file.starts_with(&prefix)) {
909
+ files.retain(|file| !file.starts_with(&prefix));
895
910
  pending.mark(table, rel_dir);
896
911
  }
897
912
  }
@@ -934,6 +949,7 @@ impl DirSQL {
934
949
  for (path, table) in self.inner.fs.scan_subtree(base, dir, &group.matcher) {
935
950
  let rel_path = scanner::to_slash(path.strip_prefix(base).unwrap_or(&path));
936
951
  if self.is_per_table(&table) {
952
+ self.track_batch_file(&table, &path, &rel_path, false);
937
953
  pending.mark(&table, &rel_path);
938
954
  continue;
939
955
  }
@@ -2344,12 +2360,6 @@ fn stream_table_rows(
2344
2360
  })
2345
2361
  }
2346
2362
 
2347
- /// Replace a per-table hook's rows wholesale under [`BATCH_OWNER`].
2348
- fn replace_table_rows(db: &Db, table: &str, rows: &ShapedRows) -> db::Result<()> {
2349
- db.delete_rows_by_file(table, BATCH_OWNER)?;
2350
- db.insert_rows(table, rows, BATCH_OWNER)
2351
- }
2352
-
2353
2363
  fn error_event(table: Option<&str>, rel_path: &str, error: String) -> RowEvent {
2354
2364
  RowEvent::Error {
2355
2365
  table: table.map(str::to_string),
@@ -5402,6 +5412,157 @@ mod internal_tests {
5402
5412
  assert_eq!(row_names(&db), vec!["a.txt", "b.txt"]);
5403
5413
  }
5404
5414
 
5415
+ #[test]
5416
+ fn apply_file_events_refreshes_a_per_table_hook_without_walking_the_tree() {
5417
+ let dir = TempDir::new().unwrap();
5418
+ let root = dir.path().to_path_buf();
5419
+ let a = root.join("a.txt");
5420
+ let b = root.join("b.txt");
5421
+ let fake = Arc::new(FakeFs::default().with_subtree(
5422
+ root.clone(),
5423
+ vec![(a.clone(), "items".into()), (b.clone(), "items".into())],
5424
+ ));
5425
+ let (items, calls) = recording_per_table("items", "**/*.txt");
5426
+ let db = DirSQL::with_ignore_and_fs(&root, vec![items], Vec::<String>::new(), fake.clone())
5427
+ .unwrap();
5428
+
5429
+ db.apply_file_events(vec![
5430
+ FileEvent::Created(b.clone()),
5431
+ FileEvent::Created(a.clone()),
5432
+ ]);
5433
+ db.apply_file_events(vec![FileEvent::Modified(a.clone())]);
5434
+ db.apply_file_events(vec![FileEvent::Deleted(a.clone())]);
5435
+
5436
+ assert_eq!(
5437
+ fake.subtree_walks(),
5438
+ Vec::<PathBuf>::new(),
5439
+ "a file event must not re-walk the tree"
5440
+ );
5441
+ assert_eq!(
5442
+ *calls.lock().unwrap(),
5443
+ vec![vec![a.clone(), b.clone()], vec![a, b.clone()], vec![b]]
5444
+ );
5445
+ }
5446
+
5447
+ #[test]
5448
+ fn apply_file_events_writes_only_the_rows_a_per_table_refresh_changed() {
5449
+ let dir = TempDir::new().unwrap();
5450
+ let root = dir.path().to_path_buf();
5451
+ let paths: Vec<PathBuf> = (0..20).map(|i| root.join(format!("f{i:02}.txt"))).collect();
5452
+ let found = paths.iter().map(|p| (p.clone(), "items".into())).collect();
5453
+ let fake = Arc::new(FakeFs::default().with_subtree(root.clone(), found));
5454
+ let runs = Arc::new(std::sync::atomic::AtomicUsize::new(0));
5455
+ let counted = Arc::clone(&runs);
5456
+ let items = Table::per_table(
5457
+ "items",
5458
+ "CREATE TABLE items (name TEXT)",
5459
+ "**/*.txt",
5460
+ move |paths: &[PathBuf]| {
5461
+ let run = counted.fetch_add(1, Ordering::SeqCst);
5462
+ Ok(paths
5463
+ .iter()
5464
+ .map(|p| {
5465
+ let name = p.file_name().unwrap().to_string_lossy().into_owned();
5466
+ name_json_row(&if name == "f00.txt" {
5467
+ format!("{name}#{run}")
5468
+ } else {
5469
+ name
5470
+ })
5471
+ })
5472
+ .collect())
5473
+ },
5474
+ );
5475
+ let db =
5476
+ DirSQL::with_ignore_and_fs(&root, vec![items], Vec::<String>::new(), fake).unwrap();
5477
+ db.apply_file_events(paths.iter().cloned().map(FileEvent::Created).collect());
5478
+ let written = || db.inner.db.lock().unwrap().conn().total_changes();
5479
+ let before = written();
5480
+
5481
+ let events = db.apply_file_events(vec![FileEvent::Modified(paths[0].clone())]);
5482
+
5483
+ assert_eq!(events.len(), 1, "{events:?}");
5484
+ let delta = written() - before;
5485
+ assert!(delta <= 6, "one changed row rewrote {delta} rows");
5486
+ assert_eq!(row_names(&db).len(), 20);
5487
+ }
5488
+
5489
+ fn walked_paths(root: &Path, glob: &str) -> Vec<PathBuf> {
5490
+ let matcher = TableMatcher::new(&[(glob, "items")], &[]).unwrap();
5491
+ scanner::scan_subtree(root, root, &matcher)
5492
+ .into_iter()
5493
+ .map(|(path, _)| path)
5494
+ .collect()
5495
+ }
5496
+
5497
+ #[test]
5498
+ fn a_per_table_hook_gets_the_argument_list_a_walk_of_the_tree_gives_it() {
5499
+ let dir = TempDir::new().unwrap();
5500
+ let root = dir.path().canonicalize().unwrap();
5501
+ let write = |rel: &str| {
5502
+ let path = root.join(rel);
5503
+ std::fs::create_dir_all(path.parent().unwrap()).unwrap();
5504
+ std::fs::write(&path, "x").unwrap();
5505
+ path
5506
+ };
5507
+ let (items, calls) = recording_per_table("items", "**/*.txt");
5508
+ let db =
5509
+ DirSQL::with_ignore_and_fs(&root, vec![items], Vec::<String>::new(), Arc::new(RealFs))
5510
+ .unwrap();
5511
+ let last_args = || calls.lock().unwrap().last().cloned().unwrap();
5512
+
5513
+ let events: Vec<FileEvent> = [
5514
+ "z.txt",
5515
+ "a-b.txt",
5516
+ "a/c.txt",
5517
+ ".hid.txt",
5518
+ "a/b/d.txt",
5519
+ "n.md",
5520
+ ]
5521
+ .into_iter()
5522
+ .map(|rel| FileEvent::Created(write(rel)))
5523
+ .collect();
5524
+ db.apply_file_events(events);
5525
+ assert_eq!(last_args(), walked_paths(&root, "**/*.txt"));
5526
+
5527
+ write("a/c.txt");
5528
+ db.apply_file_events(vec![FileEvent::Modified(root.join("a/c.txt"))]);
5529
+ assert_eq!(last_args(), walked_paths(&root, "**/*.txt"));
5530
+
5531
+ write("m/one.txt");
5532
+ write("m/deep/two.txt");
5533
+ db.apply_file_events(vec![FileEvent::Created(root.join("m"))]);
5534
+ assert_eq!(last_args(), walked_paths(&root, "**/*.txt"));
5535
+
5536
+ std::fs::remove_file(root.join("a-b.txt")).unwrap();
5537
+ db.apply_file_events(vec![FileEvent::Deleted(root.join("a-b.txt"))]);
5538
+ assert_eq!(last_args(), walked_paths(&root, "**/*.txt"));
5539
+
5540
+ std::fs::remove_dir_all(root.join("m")).unwrap();
5541
+ db.apply_file_events(vec![FileEvent::Deleted(root.join("m"))]);
5542
+ assert_eq!(last_args(), walked_paths(&root, "**/*.txt"));
5543
+ }
5544
+
5545
+ #[test]
5546
+ fn a_vanished_file_is_dropped_from_a_per_table_hooks_arguments() {
5547
+ let dir = TempDir::new().unwrap();
5548
+ let root = dir.path().canonicalize().unwrap();
5549
+ let (items, calls) = recording_per_table("items", "*.txt");
5550
+ let db =
5551
+ DirSQL::with_ignore_and_fs(&root, vec![items], Vec::<String>::new(), Arc::new(RealFs))
5552
+ .unwrap();
5553
+ std::fs::write(root.join("a.txt"), "x").unwrap();
5554
+ db.apply_file_events(vec![FileEvent::Created(root.join("a.txt"))]);
5555
+ std::fs::remove_file(root.join("a.txt")).unwrap();
5556
+
5557
+ db.apply_file_events(vec![FileEvent::Modified(root.join("a.txt"))]);
5558
+
5559
+ assert_eq!(
5560
+ calls.lock().unwrap().last().unwrap(),
5561
+ &Vec::<PathBuf>::new()
5562
+ );
5563
+ assert_eq!(row_names(&db), Vec::<String>::new());
5564
+ }
5565
+
5405
5566
  fn anchored_table(name: &str, glob: &str, anchor: &str) -> Table {
5406
5567
  Table::new(
5407
5568
  name,
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes