dirsql 0.4.40__tar.gz → 0.4.42__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 (232) hide show
  1. {dirsql-0.4.40 → dirsql-0.4.42}/Cargo.lock +2 -2
  2. {dirsql-0.4.40 → dirsql-0.4.42}/PKG-INFO +1 -1
  3. {dirsql-0.4.40 → dirsql-0.4.42}/dirsql/_dirsql.pyi +2 -0
  4. {dirsql-0.4.40 → dirsql-0.4.42}/dirsql/cli/resolve_config_extensions.py +1 -1
  5. {dirsql-0.4.40 → dirsql-0.4.42}/packages/python/Cargo.toml +1 -1
  6. dirsql-0.4.42/packages/python/changelog.d/2026-09-09-launcher-config-path-scan.md +3 -0
  7. dirsql-0.4.42/packages/python/e2e-attestations/claude-1078-argv-config-paths-core.json +7 -0
  8. dirsql-0.4.42/packages/python/e2e-attestations/claude-1080-share-vtab-scaffolding.json +7 -0
  9. dirsql-0.4.42/packages/python/e2e-attestations/claude-1109-watch-moved-out.json +7 -0
  10. {dirsql-0.4.40 → dirsql-0.4.42}/packages/python/src/lib.rs +16 -0
  11. {dirsql-0.4.40 → dirsql-0.4.42}/packages/rust/Cargo.toml +1 -1
  12. {dirsql-0.4.40 → dirsql-0.4.42}/packages/rust/src/db.rs +54 -2
  13. dirsql-0.4.42/packages/rust/src/launcher.rs +168 -0
  14. {dirsql-0.4.40 → dirsql-0.4.42}/packages/rust/src/lib.rs +179 -5
  15. {dirsql-0.4.40 → dirsql-0.4.42}/packages/rust/src/parsed_vtab.rs +35 -149
  16. {dirsql-0.4.40 → dirsql-0.4.42}/packages/rust/src/vtab.rs +41 -201
  17. dirsql-0.4.42/packages/rust/src/vtab_scaffold.rs +378 -0
  18. dirsql-0.4.40/dirsql/cli/config_paths_from_argv.py +0 -28
  19. {dirsql-0.4.40 → dirsql-0.4.42}/Cargo.toml +0 -0
  20. {dirsql-0.4.40 → dirsql-0.4.42}/README.md +0 -0
  21. {dirsql-0.4.40 → dirsql-0.4.42}/dirsql/__init__.py +0 -0
  22. {dirsql-0.4.40 → dirsql-0.4.42}/dirsql/_async.py +0 -0
  23. {dirsql-0.4.40 → dirsql-0.4.42}/dirsql/cli/__init__.py +0 -0
  24. {dirsql-0.4.40 → dirsql-0.4.42}/dirsql/cli/discover_plugins/__init__.py +0 -0
  25. {dirsql-0.4.40 → dirsql-0.4.42}/dirsql/cli/discover_plugins/discovered_fragments.py +0 -0
  26. {dirsql-0.4.40 → dirsql-0.4.42}/dirsql/cli/discover_plugins/discovery_disabled.py +0 -0
  27. {dirsql-0.4.40 → dirsql-0.4.42}/dirsql/cli/discover_plugins/fragment_path.py +0 -0
  28. {dirsql-0.4.40 → dirsql-0.4.42}/dirsql/cli/discover_plugins/user_passed_config.py +0 -0
  29. {dirsql-0.4.40 → dirsql-0.4.42}/dirsql/cli/discover_plugins/with_discovered_plugins.py +0 -0
  30. {dirsql-0.4.40 → dirsql-0.4.42}/dirsql/cli/main.py +0 -0
  31. {dirsql-0.4.40 → dirsql-0.4.42}/dirsql/has_bare_name.py +0 -0
  32. {dirsql-0.4.40 → dirsql-0.4.42}/dirsql/is_bare_name.py +0 -0
  33. {dirsql-0.4.40 → dirsql-0.4.42}/dirsql/load_extension_entries.py +0 -0
  34. {dirsql-0.4.40 → dirsql-0.4.42}/dirsql/platform_patterns.py +0 -0
  35. {dirsql-0.4.40 → dirsql-0.4.42}/dirsql/py.typed +0 -0
  36. {dirsql-0.4.40 → dirsql-0.4.42}/dirsql/resolve_config_extensions.py +0 -0
  37. {dirsql-0.4.40 → dirsql-0.4.42}/dirsql/resolve_configs_extension_specs.py +0 -0
  38. {dirsql-0.4.40 → dirsql-0.4.42}/dirsql/resolve_entries.py +0 -0
  39. {dirsql-0.4.40 → dirsql-0.4.42}/dirsql/resolve_extension.py +0 -0
  40. {dirsql-0.4.40 → dirsql-0.4.42}/dirsql/resolve_package.py +0 -0
  41. {dirsql-0.4.40 → dirsql-0.4.42}/dirsql/toml_module.py +0 -0
  42. {dirsql-0.4.40 → dirsql-0.4.42}/docs/.claude/CLAUDE.md +0 -0
  43. {dirsql-0.4.40 → dirsql-0.4.42}/docs/.vitepress/config.ts +0 -0
  44. {dirsql-0.4.40 → dirsql-0.4.42}/docs/.vitepress/theme/index.ts +0 -0
  45. {dirsql-0.4.40 → dirsql-0.4.42}/docs/.vitepress/theme/lang.ts +0 -0
  46. {dirsql-0.4.40 → dirsql-0.4.42}/docs/AGENTS.md +0 -0
  47. {dirsql-0.4.40 → dirsql-0.4.42}/docs/explanation.md +0 -0
  48. {dirsql-0.4.40 → dirsql-0.4.42}/docs/getting-started.md +0 -0
  49. {dirsql-0.4.40 → dirsql-0.4.42}/docs/howto/columns-from-paths.md +0 -0
  50. {dirsql-0.4.40 → dirsql-0.4.42}/docs/howto/define-tables.md +0 -0
  51. {dirsql-0.4.40 → dirsql-0.4.42}/docs/howto/embed.md +0 -0
  52. {dirsql-0.4.40 → dirsql-0.4.42}/docs/howto/extract-from-contents.md +0 -0
  53. {dirsql-0.4.40 → dirsql-0.4.42}/docs/howto/load-extension.md +0 -0
  54. {dirsql-0.4.40 → dirsql-0.4.42}/docs/howto/parse-files-into-columns.md +0 -0
  55. {dirsql-0.4.40 → dirsql-0.4.42}/docs/howto/persist.md +0 -0
  56. {dirsql-0.4.40 → dirsql-0.4.42}/docs/howto/query-json.md +0 -0
  57. {dirsql-0.4.40 → dirsql-0.4.42}/docs/howto/query-without-config.md +0 -0
  58. {dirsql-0.4.40 → dirsql-0.4.42}/docs/howto/react-to-changes.md +0 -0
  59. {dirsql-0.4.40 → dirsql-0.4.42}/docs/howto/search-by-meaning.md +0 -0
  60. {dirsql-0.4.40 → dirsql-0.4.42}/docs/howto/search-indexes.md +0 -0
  61. {dirsql-0.4.40 → dirsql-0.4.42}/docs/howto/skip-files.md +0 -0
  62. {dirsql-0.4.40 → dirsql-0.4.42}/docs/howto/write-a-plugin.md +0 -0
  63. {dirsql-0.4.40 → dirsql-0.4.42}/docs/index.md +0 -0
  64. {dirsql-0.4.40 → dirsql-0.4.42}/docs/package.json +0 -0
  65. {dirsql-0.4.40 → dirsql-0.4.42}/docs/plugins.md +0 -0
  66. {dirsql-0.4.40 → dirsql-0.4.42}/docs/pnpm-lock.yaml +0 -0
  67. {dirsql-0.4.40 → dirsql-0.4.42}/docs/pnpm-workspace.yaml +0 -0
  68. {dirsql-0.4.40 → dirsql-0.4.42}/docs/reference/cli.md +0 -0
  69. {dirsql-0.4.40 → dirsql-0.4.42}/docs/reference/columns.md +0 -0
  70. {dirsql-0.4.40 → dirsql-0.4.42}/docs/reference/config.md +0 -0
  71. {dirsql-0.4.40 → dirsql-0.4.42}/docs/reference/hooks.md +0 -0
  72. {dirsql-0.4.40 → dirsql-0.4.42}/docs/reference/http-api.md +0 -0
  73. {dirsql-0.4.40 → dirsql-0.4.42}/docs/reference/path-tables.md +0 -0
  74. {dirsql-0.4.40 → dirsql-0.4.42}/docs/reference/sdk.md +0 -0
  75. {dirsql-0.4.40 → dirsql-0.4.42}/packages/python/CHANGELOG.md +0 -0
  76. {dirsql-0.4.40 → dirsql-0.4.42}/packages/python/MIGRATIONS.md +0 -0
  77. {dirsql-0.4.40 → dirsql-0.4.42}/packages/python/README.md +0 -0
  78. {dirsql-0.4.40 → dirsql-0.4.42}/packages/python/changelog.d/2026-07-13-drop-cwd-config-auto-detect.md +0 -0
  79. {dirsql-0.4.40 → dirsql-0.4.42}/packages/python/changelog.d/2026-07-13-plugin-discovery.md +0 -0
  80. {dirsql-0.4.40 → dirsql-0.4.42}/packages/python/changelog.d/2026-07-13-rename-extract-to-on-file.md +0 -0
  81. {dirsql-0.4.40 → dirsql-0.4.42}/packages/python/changelog.d/2026-07-13-repeatable-config.md +0 -0
  82. {dirsql-0.4.40 → dirsql-0.4.42}/packages/python/changelog.d/2026-07-13-sdk-default-baked-in-config.md +0 -0
  83. {dirsql-0.4.40 → dirsql-0.4.42}/packages/python/changelog.d/2026-07-20-drop-implicit-files-table.md +0 -0
  84. {dirsql-0.4.40 → dirsql-0.4.42}/packages/python/changelog.d/2026-07-21-remove-glob-captures.md +0 -0
  85. {dirsql-0.4.40 → dirsql-0.4.42}/packages/python/changelog.d/2026-07-24-hookless-table-config-error.md +0 -0
  86. {dirsql-0.4.40 → dirsql-0.4.42}/packages/python/changelog.d/2026-07-24-remove-stat-fact-injection.md +0 -0
  87. {dirsql-0.4.40 → dirsql-0.4.42}/packages/python/changelog.d/2026-07-29-restore-python-310.md +0 -0
  88. {dirsql-0.4.40 → dirsql-0.4.42}/packages/python/changelog.d/2026-08-02-release-profile.md +0 -0
  89. {dirsql-0.4.40 → dirsql-0.4.42}/packages/python/changelog.d/2026-08-02-scan-failures.md +0 -0
  90. {dirsql-0.4.40 → dirsql-0.4.42}/packages/python/changelog.d/2026-08-03-discovery-ext-resolution.md +0 -0
  91. {dirsql-0.4.40 → dirsql-0.4.42}/packages/python/changelog.d/2026-08-03-manylinux-dynamic-binary.md +0 -0
  92. {dirsql-0.4.40 → dirsql-0.4.42}/packages/python/changelog.d/2026-08-03-python-no-ignore.md +0 -0
  93. {dirsql-0.4.40 → dirsql-0.4.42}/packages/python/changelog.d/2026-08-04-cast-lints.md +0 -0
  94. {dirsql-0.4.40 → dirsql-0.4.42}/packages/python/changelog.d/2026-08-04-cli-in-process.md +0 -0
  95. {dirsql-0.4.40 → dirsql-0.4.42}/packages/python/changelog.d/2026-08-20-curtaincall-dev-dependency.md +0 -0
  96. {dirsql-0.4.40 → dirsql-0.4.42}/packages/python/changelog.d/2026-08-20-declared-table-name.md +0 -0
  97. {dirsql-0.4.40 → dirsql-0.4.42}/packages/python/changelog.d/2026-09-04-config-paths-from-argv-module.md +0 -0
  98. {dirsql-0.4.40 → dirsql-0.4.42}/packages/python/changelog.d/2026-09-05-config-extension-loader-split.md +0 -0
  99. {dirsql-0.4.40 → dirsql-0.4.42}/packages/python/changelog.d/2026-09-05-resolve-config-extensions-split.md +0 -0
  100. {dirsql-0.4.40 → dirsql-0.4.42}/packages/python/changelog.d/2026-09-05-resolve-extension-split.md +0 -0
  101. {dirsql-0.4.40 → dirsql-0.4.42}/packages/python/changelog.d/README.md +0 -0
  102. {dirsql-0.4.40 → dirsql-0.4.42}/packages/python/conftest.py +0 -0
  103. {dirsql-0.4.40 → dirsql-0.4.42}/packages/python/docs/.claude/CLAUDE.md +0 -0
  104. {dirsql-0.4.40 → dirsql-0.4.42}/packages/python/docs/.vitepress/config.ts +0 -0
  105. {dirsql-0.4.40 → dirsql-0.4.42}/packages/python/docs/.vitepress/theme/index.ts +0 -0
  106. {dirsql-0.4.40 → dirsql-0.4.42}/packages/python/docs/.vitepress/theme/lang.ts +0 -0
  107. {dirsql-0.4.40 → dirsql-0.4.42}/packages/python/docs/AGENTS.md +0 -0
  108. {dirsql-0.4.40 → dirsql-0.4.42}/packages/python/docs/explanation.md +0 -0
  109. {dirsql-0.4.40 → dirsql-0.4.42}/packages/python/docs/getting-started.md +0 -0
  110. {dirsql-0.4.40 → dirsql-0.4.42}/packages/python/docs/howto/columns-from-paths.md +0 -0
  111. {dirsql-0.4.40 → dirsql-0.4.42}/packages/python/docs/howto/define-tables.md +0 -0
  112. {dirsql-0.4.40 → dirsql-0.4.42}/packages/python/docs/howto/embed.md +0 -0
  113. {dirsql-0.4.40 → dirsql-0.4.42}/packages/python/docs/howto/extract-from-contents.md +0 -0
  114. {dirsql-0.4.40 → dirsql-0.4.42}/packages/python/docs/howto/load-extension.md +0 -0
  115. {dirsql-0.4.40 → dirsql-0.4.42}/packages/python/docs/howto/parse-files-into-columns.md +0 -0
  116. {dirsql-0.4.40 → dirsql-0.4.42}/packages/python/docs/howto/persist.md +0 -0
  117. {dirsql-0.4.40 → dirsql-0.4.42}/packages/python/docs/howto/query-json.md +0 -0
  118. {dirsql-0.4.40 → dirsql-0.4.42}/packages/python/docs/howto/query-without-config.md +0 -0
  119. {dirsql-0.4.40 → dirsql-0.4.42}/packages/python/docs/howto/react-to-changes.md +0 -0
  120. {dirsql-0.4.40 → dirsql-0.4.42}/packages/python/docs/howto/search-by-meaning.md +0 -0
  121. {dirsql-0.4.40 → dirsql-0.4.42}/packages/python/docs/howto/search-indexes.md +0 -0
  122. {dirsql-0.4.40 → dirsql-0.4.42}/packages/python/docs/howto/skip-files.md +0 -0
  123. {dirsql-0.4.40 → dirsql-0.4.42}/packages/python/docs/howto/write-a-plugin.md +0 -0
  124. {dirsql-0.4.40 → dirsql-0.4.42}/packages/python/docs/index.md +0 -0
  125. {dirsql-0.4.40 → dirsql-0.4.42}/packages/python/docs/package.json +0 -0
  126. {dirsql-0.4.40 → dirsql-0.4.42}/packages/python/docs/plugins.md +0 -0
  127. {dirsql-0.4.40 → dirsql-0.4.42}/packages/python/docs/pnpm-lock.yaml +0 -0
  128. {dirsql-0.4.40 → dirsql-0.4.42}/packages/python/docs/pnpm-workspace.yaml +0 -0
  129. {dirsql-0.4.40 → dirsql-0.4.42}/packages/python/docs/reference/cli.md +0 -0
  130. {dirsql-0.4.40 → dirsql-0.4.42}/packages/python/docs/reference/columns.md +0 -0
  131. {dirsql-0.4.40 → dirsql-0.4.42}/packages/python/docs/reference/config.md +0 -0
  132. {dirsql-0.4.40 → dirsql-0.4.42}/packages/python/docs/reference/hooks.md +0 -0
  133. {dirsql-0.4.40 → dirsql-0.4.42}/packages/python/docs/reference/http-api.md +0 -0
  134. {dirsql-0.4.40 → dirsql-0.4.42}/packages/python/docs/reference/path-tables.md +0 -0
  135. {dirsql-0.4.40 → dirsql-0.4.42}/packages/python/docs/reference/sdk.md +0 -0
  136. {dirsql-0.4.40 → dirsql-0.4.42}/packages/python/e2e-attestations/cc-keen-knuth-f11gyq.json +0 -0
  137. {dirsql-0.4.40 → dirsql-0.4.42}/packages/python/e2e-attestations/claude-1056-py-ofpf9.json +0 -0
  138. {dirsql-0.4.40 → dirsql-0.4.42}/packages/python/e2e-attestations/claude-1066-p1-resolve-extension.json +0 -0
  139. {dirsql-0.4.40 → dirsql-0.4.42}/packages/python/e2e-attestations/claude-1079-p2-resolve-config-extensions.json +0 -0
  140. {dirsql-0.4.40 → dirsql-0.4.42}/packages/python/e2e-attestations/claude-1096-watch-moved-dir.json +0 -0
  141. {dirsql-0.4.40 → dirsql-0.4.42}/packages/python/e2e-attestations/claude-819-oneshot-timeout.json +0 -0
  142. {dirsql-0.4.40 → dirsql-0.4.42}/packages/python/e2e-attestations/claude-827-unquote-doubled-quotes.json +0 -0
  143. {dirsql-0.4.40 → dirsql-0.4.42}/packages/python/e2e-attestations/claude-epic-953-slice2-reedline.json +0 -0
  144. {dirsql-0.4.40 → dirsql-0.4.42}/packages/python/e2e-attestations/claude-epic-953-slice3-format.json +0 -0
  145. {dirsql-0.4.40 → dirsql-0.4.42}/packages/python/e2e-attestations/claude-epic-953-stacked-prs-x59wm3.json +0 -0
  146. {dirsql-0.4.40 → dirsql-0.4.42}/packages/python/e2e-attestations/claude-github-issue-825-e4mhhp.json +0 -0
  147. {dirsql-0.4.40 → dirsql-0.4.42}/packages/python/e2e-attestations/claude-issue-951-0eease.json +0 -0
  148. {dirsql-0.4.40 → dirsql-0.4.42}/packages/python/e2e-attestations/claude-issue-962-0jjrfv.json +0 -0
  149. {dirsql-0.4.40 → dirsql-0.4.42}/packages/python/e2e-attestations/claude-issue-986-red-test-e7sjrb.json +0 -0
  150. {dirsql-0.4.40 → dirsql-0.4.42}/packages/python/e2e-attestations/claude-tackle-1034-cachesplit.json +0 -0
  151. {dirsql-0.4.40 → dirsql-0.4.42}/packages/python/e2e-attestations/claude-tackle-1042-py12.json +0 -0
  152. {dirsql-0.4.40 → dirsql-0.4.42}/packages/python/e2e-attestations/claude-tackle-956-xzen74.json +0 -0
  153. {dirsql-0.4.40 → dirsql-0.4.42}/packages/python/e2e-attestations/claude-tackle-957-lrm0z6-worker-calls.json +0 -0
  154. {dirsql-0.4.40 → dirsql-0.4.42}/packages/python/e2e-attestations/claude-tackle-957-lrm0z6.json +0 -0
  155. {dirsql-0.4.40 → dirsql-0.4.42}/packages/python/e2e-attestations/claude-tackle-975-n8mc2h.json +0 -0
  156. {dirsql-0.4.40 → dirsql-0.4.42}/packages/python/migrations.d/2026-07-13-drop-cwd-config-auto-detect.md +0 -0
  157. {dirsql-0.4.40 → dirsql-0.4.42}/packages/python/migrations.d/2026-07-13-rename-extract-to-on-file.md +0 -0
  158. {dirsql-0.4.40 → dirsql-0.4.42}/packages/python/migrations.d/2026-07-13-sdk-default-baked-in-config.md +0 -0
  159. {dirsql-0.4.40 → dirsql-0.4.42}/packages/python/migrations.d/2026-07-20-drop-implicit-files-table.md +0 -0
  160. {dirsql-0.4.40 → dirsql-0.4.42}/packages/python/migrations.d/2026-07-21-remove-glob-captures.md +0 -0
  161. {dirsql-0.4.40 → dirsql-0.4.42}/packages/python/migrations.d/2026-07-24-hookless-table-config-error.md +0 -0
  162. {dirsql-0.4.40 → dirsql-0.4.42}/packages/python/migrations.d/2026-07-24-remove-stat-fact-injection.md +0 -0
  163. {dirsql-0.4.40 → dirsql-0.4.42}/packages/python/migrations.d/2026-07-29-restore-python-310.md +0 -0
  164. {dirsql-0.4.40 → dirsql-0.4.42}/packages/python/migrations.d/2026-08-04-cli-in-process.md +0 -0
  165. {dirsql-0.4.40 → dirsql-0.4.42}/packages/python/migrations.d/2026-08-20-declared-table-name.md +0 -0
  166. {dirsql-0.4.40 → dirsql-0.4.42}/packages/python/migrations.d/README.md +0 -0
  167. {dirsql-0.4.40 → dirsql-0.4.42}/packages/python/testing-conventions.toml +0 -0
  168. {dirsql-0.4.40 → dirsql-0.4.42}/packages/python/tests/__init__.py +0 -0
  169. {dirsql-0.4.40 → dirsql-0.4.42}/packages/python/tests/conftest.py +0 -0
  170. {dirsql-0.4.40 → dirsql-0.4.42}/packages/python/tests/e2e/__init__.py +0 -0
  171. {dirsql-0.4.40 → dirsql-0.4.42}/packages/python/tests/e2e/fixtures/dirsql_plugin_fixture/__init__.py +0 -0
  172. {dirsql-0.4.40 → dirsql-0.4.42}/packages/python/tests/e2e/fixtures/dirsql_plugin_fixture/dirsql.toml +0 -0
  173. {dirsql-0.4.40 → dirsql-0.4.42}/packages/python/tests/integration/__init__.py +0 -0
  174. {dirsql-0.4.40 → dirsql-0.4.42}/packages/python/tests/integration/binding/__init__.py +0 -0
  175. {dirsql-0.4.40 → dirsql-0.4.42}/packages/python/tests/integration/hermetic/__init__.py +0 -0
  176. {dirsql-0.4.40 → dirsql-0.4.42}/packages/rust/CHANGELOG.md +0 -0
  177. {dirsql-0.4.40 → dirsql-0.4.42}/packages/rust/MIGRATIONS.md +0 -0
  178. {dirsql-0.4.40 → dirsql-0.4.42}/packages/rust/README.md +0 -0
  179. {dirsql-0.4.40 → dirsql-0.4.42}/packages/rust/benches/db_bench.rs +0 -0
  180. {dirsql-0.4.40 → dirsql-0.4.42}/packages/rust/benches/differ_bench.rs +0 -0
  181. {dirsql-0.4.40 → dirsql-0.4.42}/packages/rust/benches/matcher_bench.rs +0 -0
  182. {dirsql-0.4.40 → dirsql-0.4.42}/packages/rust/benches/scanner_bench.rs +0 -0
  183. {dirsql-0.4.40 → dirsql-0.4.42}/packages/rust/docs/explanation.md +0 -0
  184. {dirsql-0.4.40 → dirsql-0.4.42}/packages/rust/docs/getting-started.md +0 -0
  185. {dirsql-0.4.40 → dirsql-0.4.42}/packages/rust/docs/howto/columns-from-paths.md +0 -0
  186. {dirsql-0.4.40 → dirsql-0.4.42}/packages/rust/docs/howto/define-tables.md +0 -0
  187. {dirsql-0.4.40 → dirsql-0.4.42}/packages/rust/docs/howto/embed.md +0 -0
  188. {dirsql-0.4.40 → dirsql-0.4.42}/packages/rust/docs/howto/extract-from-contents.md +0 -0
  189. {dirsql-0.4.40 → dirsql-0.4.42}/packages/rust/docs/howto/load-extension.md +0 -0
  190. {dirsql-0.4.40 → dirsql-0.4.42}/packages/rust/docs/howto/parse-files-into-columns.md +0 -0
  191. {dirsql-0.4.40 → dirsql-0.4.42}/packages/rust/docs/howto/persist.md +0 -0
  192. {dirsql-0.4.40 → dirsql-0.4.42}/packages/rust/docs/howto/query-json.md +0 -0
  193. {dirsql-0.4.40 → dirsql-0.4.42}/packages/rust/docs/howto/query-without-config.md +0 -0
  194. {dirsql-0.4.40 → dirsql-0.4.42}/packages/rust/docs/howto/react-to-changes.md +0 -0
  195. {dirsql-0.4.40 → dirsql-0.4.42}/packages/rust/docs/howto/search-by-meaning.md +0 -0
  196. {dirsql-0.4.40 → dirsql-0.4.42}/packages/rust/docs/howto/search-indexes.md +0 -0
  197. {dirsql-0.4.40 → dirsql-0.4.42}/packages/rust/docs/howto/skip-files.md +0 -0
  198. {dirsql-0.4.40 → dirsql-0.4.42}/packages/rust/docs/howto/write-a-plugin.md +0 -0
  199. {dirsql-0.4.40 → dirsql-0.4.42}/packages/rust/docs/index.md +0 -0
  200. {dirsql-0.4.40 → dirsql-0.4.42}/packages/rust/docs/plugins.md +0 -0
  201. {dirsql-0.4.40 → dirsql-0.4.42}/packages/rust/docs/reference/cli.md +0 -0
  202. {dirsql-0.4.40 → dirsql-0.4.42}/packages/rust/docs/reference/columns.md +0 -0
  203. {dirsql-0.4.40 → dirsql-0.4.42}/packages/rust/docs/reference/config.md +0 -0
  204. {dirsql-0.4.40 → dirsql-0.4.42}/packages/rust/docs/reference/hooks.md +0 -0
  205. {dirsql-0.4.40 → dirsql-0.4.42}/packages/rust/docs/reference/http-api.md +0 -0
  206. {dirsql-0.4.40 → dirsql-0.4.42}/packages/rust/docs/reference/path-tables.md +0 -0
  207. {dirsql-0.4.40 → dirsql-0.4.42}/packages/rust/docs/reference/sdk.md +0 -0
  208. {dirsql-0.4.40 → dirsql-0.4.42}/packages/rust/src/bin/dirsql.rs +0 -0
  209. {dirsql-0.4.40 → dirsql-0.4.42}/packages/rust/src/cli/execute.rs +0 -0
  210. {dirsql-0.4.40 → dirsql-0.4.42}/packages/rust/src/cli/init.rs +0 -0
  211. {dirsql-0.4.40 → dirsql-0.4.42}/packages/rust/src/cli/mod.rs +0 -0
  212. {dirsql-0.4.40 → dirsql-0.4.42}/packages/rust/src/cli/repl.rs +0 -0
  213. {dirsql-0.4.40 → dirsql-0.4.42}/packages/rust/src/cli/router.rs +0 -0
  214. {dirsql-0.4.40 → dirsql-0.4.42}/packages/rust/src/cli/run.rs +0 -0
  215. {dirsql-0.4.40 → dirsql-0.4.42}/packages/rust/src/cli/serialize.rs +0 -0
  216. {dirsql-0.4.40 → dirsql-0.4.42}/packages/rust/src/cli/server.rs +0 -0
  217. {dirsql-0.4.40 → dirsql-0.4.42}/packages/rust/src/cli/table.rs +0 -0
  218. {dirsql-0.4.40 → dirsql-0.4.42}/packages/rust/src/command.rs +0 -0
  219. {dirsql-0.4.40 → dirsql-0.4.42}/packages/rust/src/config.rs +0 -0
  220. {dirsql-0.4.40 → dirsql-0.4.42}/packages/rust/src/default_config.toml +0 -0
  221. {dirsql-0.4.40 → dirsql-0.4.42}/packages/rust/src/differ.rs +0 -0
  222. {dirsql-0.4.40 → dirsql-0.4.42}/packages/rust/src/functions.rs +0 -0
  223. {dirsql-0.4.40 → dirsql-0.4.42}/packages/rust/src/infer.rs +0 -0
  224. {dirsql-0.4.40 → dirsql-0.4.42}/packages/rust/src/matcher.rs +0 -0
  225. {dirsql-0.4.40 → dirsql-0.4.42}/packages/rust/src/parsed_cache.rs +0 -0
  226. {dirsql-0.4.40 → dirsql-0.4.42}/packages/rust/src/path_table.rs +0 -0
  227. {dirsql-0.4.40 → dirsql-0.4.42}/packages/rust/src/persist.rs +0 -0
  228. {dirsql-0.4.40 → dirsql-0.4.42}/packages/rust/src/progress.rs +0 -0
  229. {dirsql-0.4.40 → dirsql-0.4.42}/packages/rust/src/scanner.rs +0 -0
  230. {dirsql-0.4.40 → dirsql-0.4.42}/packages/rust/src/sql_literal.rs +0 -0
  231. {dirsql-0.4.40 → dirsql-0.4.42}/packages/rust/src/watcher.rs +0 -0
  232. {dirsql-0.4.40 → dirsql-0.4.42}/pyproject.toml +0 -0
@@ -540,7 +540,7 @@ checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8"
540
540
 
541
541
  [[package]]
542
542
  name = "dirsql"
543
- version = "0.4.40"
543
+ version = "0.4.42"
544
544
  dependencies = [
545
545
  "assert_cmd",
546
546
  "axum",
@@ -583,7 +583,7 @@ dependencies = [
583
583
 
584
584
  [[package]]
585
585
  name = "dirsql-py-ext"
586
- version = "0.4.40"
586
+ version = "0.4.42"
587
587
  dependencies = [
588
588
  "dirsql",
589
589
  "pyo3",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dirsql
3
- Version: 0.4.40
3
+ Version: 0.4.42
4
4
  Requires-Dist: tomli>=2 ; python_full_version < '3.11'
5
5
  Requires-Dist: bin-shim>=0.1
6
6
  Summary: Ephemeral SQL index over a local directory
@@ -77,3 +77,5 @@ class DirSQL:
77
77
  def query(self, sql: str) -> list[Row]: ...
78
78
  def _start_watcher(self) -> None: ...
79
79
  def _poll_events(self, timeout_ms: int) -> list[RowEvent]: ...
80
+
81
+ def config_paths_from_argv(argv: list[str]) -> list[str]: ...
@@ -15,8 +15,8 @@ carries resolved paths.
15
15
 
16
16
  from __future__ import annotations
17
17
 
18
+ from .._dirsql import config_paths_from_argv
18
19
  from ..resolve_configs_extension_specs import resolve_configs_extension_specs
19
- from .config_paths_from_argv import config_paths_from_argv
20
20
 
21
21
  # Config extensions the binary dispatches to `dirsql interpret`; never
22
22
  # pre-resolved here (that path resolves via the handshake).
@@ -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.40"
7
+ version = "0.4.42"
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,3 @@
1
+ **Changed**
2
+
3
+ - **The launcher's config-path scan now runs in the shared core.** Before clap sees argv, the pip launcher still walks it for every `--config` / `-c` value (all five spellings) so a bare package-name `[[dirsql.extension]]` entry can be resolved through `importlib` — but the walk itself is now `dirsql::launcher::config_paths_from_argv`, reached through the private `dirsql._dirsql.config_paths_from_argv` binding; the Python copy is deleted. No user-observable change: same flags, same paths, same default of `./.dirsql.toml`. (#1078, #1072)
@@ -0,0 +1,7 @@
1
+ {
2
+ "command": "just test-e2e",
3
+ "ran_at": 1788979577,
4
+ "exit_code": 0,
5
+ "commit": "ae4f94c0b871cd766d45be67204c8a523a58c707",
6
+ "branch": "claude/1078-argv-config-paths-core"
7
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "command": "just test-e2e",
3
+ "ran_at": 1789515494,
4
+ "exit_code": 0,
5
+ "commit": "51555d8d0ad35095d69c1568e4478f34feb36e09",
6
+ "branch": "claude/1080-share-vtab-scaffolding"
7
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "command": "just test-e2e",
3
+ "ran_at": 1789595962,
4
+ "exit_code": 0,
5
+ "commit": "c645cfe8052bdca311060ca475f6ee102246650b",
6
+ "branch": "claude/1109-watch-moved-out"
7
+ }
@@ -462,6 +462,13 @@ mod python {
462
462
  py.detach(|| dirsql::cli::run_cli(full))
463
463
  }
464
464
 
465
+ /// The config paths a launcher must inspect for package-name extensions;
466
+ /// see [`dirsql::launcher::config_paths_from_argv`].
467
+ #[pyfunction]
468
+ fn config_paths_from_argv(argv: Vec<String>) -> Vec<String> {
469
+ dirsql::launcher::config_paths_from_argv(&argv)
470
+ }
471
+
465
472
  #[pymodule]
466
473
  #[pyo3(name = "_dirsql")]
467
474
  fn py_dirsql_module(m: &Bound<'_, PyModule>) -> PyResult<()> {
@@ -471,6 +478,7 @@ mod python {
471
478
  m.add_class::<PyRowEvent>()?;
472
479
  m.add_class::<PyScanFailure>()?;
473
480
  m.add_function(wrap_pyfunction!(run_cli, m)?)?;
481
+ m.add_function(wrap_pyfunction!(config_paths_from_argv, m)?)?;
474
482
  Ok(())
475
483
  }
476
484
 
@@ -553,6 +561,14 @@ mod python {
553
561
  assert_eq!(OnFileError("bad".to_string()).to_string(), "bad");
554
562
  }
555
563
 
564
+ #[test]
565
+ fn config_paths_from_argv_forwards_to_the_core_scan() {
566
+ assert_eq!(
567
+ config_paths_from_argv(vec!["-c".into(), "a.toml".into()]),
568
+ ["a.toml"]
569
+ );
570
+ }
571
+
556
572
  #[test]
557
573
  fn int_overflow_message_names_the_value() {
558
574
  let m = int_overflow_message("9223372036854775808");
@@ -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.40"
9
+ version = "0.4.42"
10
10
  edition.workspace = true
11
11
  # Literal `license` (not `license.workspace = true`) because
12
12
  # putitoutthere's preflight check reads `[package].license` directly
@@ -645,6 +645,19 @@ impl Db {
645
645
  Ok(out)
646
646
  }
647
647
 
648
+ /// Every distinct `(table_name, file_path)` whose file sits beneath the
649
+ /// directory `dir`, ordered by table then path.
650
+ pub fn files_under(&self, dir: &str) -> Result<Vec<(String, String)>> {
651
+ let prefix = format!("{dir}{}", std::path::MAIN_SEPARATOR);
652
+ let mut stmt = self.conn.prepare(
653
+ "SELECT DISTINCT table_name, file_path FROM _dirsql_internal_rows \
654
+ WHERE substr(file_path, 1, length(?1)) = ?1 \
655
+ ORDER BY table_name, file_path",
656
+ )?;
657
+ let rows = stmt.query_map([prefix], |r| Ok((r.get(0)?, r.get(1)?)))?;
658
+ Ok(rows.collect::<rusqlite::Result<Vec<_>>>()?)
659
+ }
660
+
648
661
  /// Helper to execute delete_rows_by_file statements on any connection.
649
662
  /// Called from delete_rows_by_file() either inside a caller-supplied
650
663
  /// transaction or inside a transaction opened by delete_rows_by_file().
@@ -1700,6 +1713,45 @@ mod tests {
1700
1713
  assert_eq!(rows[0].2, 42, "rowid_ref must be the user-declared rowid");
1701
1714
  }
1702
1715
 
1716
+ #[test]
1717
+ fn files_under_lists_each_file_beneath_the_directory_once_across_tables() {
1718
+ let db = Db::new().unwrap();
1719
+ db.create_table("t1", "CREATE TABLE t1 (id TEXT)").unwrap();
1720
+ db.create_table("t2", "CREATE TABLE t2 (id TEXT)").unwrap();
1721
+ let row = HashMap::from([("id".into(), Value::Text("x".into()))]);
1722
+ for (table, file, idx) in [
1723
+ ("t1", "dir/a.jsonl", 0),
1724
+ ("t1", "dir/a.jsonl", 1),
1725
+ ("t1", "dir/sub/b.jsonl", 0),
1726
+ ("t2", "dir/c.jsonl", 0),
1727
+ ("t1", "dir2/x.jsonl", 0),
1728
+ ("t1", "other.jsonl", 0),
1729
+ ] {
1730
+ db.insert_row(table, &row, file, idx).unwrap();
1731
+ }
1732
+
1733
+ assert_eq!(
1734
+ db.files_under("dir").unwrap(),
1735
+ vec![
1736
+ ("t1".to_string(), "dir/a.jsonl".to_string()),
1737
+ ("t1".to_string(), "dir/sub/b.jsonl".to_string()),
1738
+ ("t2".to_string(), "dir/c.jsonl".to_string()),
1739
+ ],
1740
+ "a multi-row file is listed once; `dir2/` is not beneath `dir`"
1741
+ );
1742
+ assert!(db.files_under("nothing").unwrap().is_empty());
1743
+ }
1744
+
1745
+ #[test]
1746
+ fn files_under_surfaces_sql_failure() {
1747
+ let db = Db::new().unwrap();
1748
+ db.conn
1749
+ .execute("DROP TABLE _dirsql_internal_rows", [])
1750
+ .unwrap();
1751
+ let err = db.files_under("dir").unwrap_err();
1752
+ assert!(matches!(err, DbError::Sqlite(_)), "got: {err:?}");
1753
+ }
1754
+
1703
1755
  #[test]
1704
1756
  fn delete_rows_by_file_removes_mapping() {
1705
1757
  let db = Db::new().unwrap();
@@ -2340,7 +2392,7 @@ mod tests {
2340
2392
  let mut db = Db::new().unwrap();
2341
2393
  db.set_path_table_root(PathBuf::from("/nonexistent-dirsql-root"));
2342
2394
 
2343
- let rows = db.query("SELECT path FROM './'").unwrap();
2395
+ let rows = db.query("SELECT path FROM './' LIMIT 1").unwrap();
2344
2396
 
2345
2397
  assert!(rows.is_empty(), "an empty root yields no rows: {rows:?}");
2346
2398
  }
@@ -2361,7 +2413,7 @@ mod tests {
2361
2413
  db.set_path_table_root(PathBuf::from("/nonexistent-dirsql-root"));
2362
2414
 
2363
2415
  let rows = db
2364
- .query("SELECT path FROM '/nonexistent-dirsql-dir/*.log'")
2416
+ .query("SELECT path FROM '/nonexistent-dirsql-dir/*.log' LIMIT 1")
2365
2417
  .unwrap();
2366
2418
 
2367
2419
  assert!(
@@ -0,0 +1,168 @@
1
+ //! Argv scanning the language launchers share.
2
+ //!
3
+ //! The pip and npm launchers must know which config files an invocation names
4
+ //! *before* the core parses argv: a config's `[[dirsql.extension]]` entry may
5
+ //! name an extension by bare package name, and resolving that needs the
6
+ //! interpreter the launcher has and this crate does not. Everything about
7
+ //! locating those config paths is language-neutral, so it lives here and each
8
+ //! launcher reaches it through its binding rather than reimplementing it.
9
+
10
+ /// The config a launcher inspects when argv names none.
11
+ const DEFAULT_CONFIG_PATH: &str = "./.dirsql.toml";
12
+
13
+ /// Every config value in `argv`, in order (`--config X`, `--config=X`,
14
+ /// `-c X`, `-c=X`, `-cX`), or [`DEFAULT_CONFIG_PATH`] when argv names none.
15
+ ///
16
+ /// The scan is deliberately looser than clap: it runs before argv is parsed,
17
+ /// only to find files to read extensions out of, so a bare trailing `-c`
18
+ /// yields an empty string rather than an error. A malformed invocation is
19
+ /// clap's to reject once the launcher hands argv over.
20
+ pub fn config_paths_from_argv(argv: &[String]) -> Vec<String> {
21
+ let mut paths = Vec::new();
22
+ let mut args = argv.iter();
23
+ while let Some(arg) = args.next() {
24
+ if arg == "--config" || arg == "-c" {
25
+ paths.push(args.next().cloned().unwrap_or_default());
26
+ } else if let Some(value) = arg.strip_prefix("--config=") {
27
+ paths.push(value.to_string());
28
+ } else if let Some(value) = arg.strip_prefix("-c") {
29
+ paths.push(value.strip_prefix('=').unwrap_or(value).to_string());
30
+ }
31
+ }
32
+ if paths.is_empty() {
33
+ paths.push(DEFAULT_CONFIG_PATH.to_string());
34
+ }
35
+ paths
36
+ }
37
+
38
+ #[cfg(test)]
39
+ mod tests {
40
+ use super::config_paths_from_argv;
41
+
42
+ fn scan(argv: &[&str]) -> Vec<String> {
43
+ config_paths_from_argv(&argv.iter().map(|a| (*a).to_string()).collect::<Vec<_>>())
44
+ }
45
+
46
+ #[test]
47
+ fn reads_the_long_config_form() {
48
+ assert_eq!(
49
+ scan(&["--config", "/cfg/.dirsql.toml"]),
50
+ ["/cfg/.dirsql.toml"]
51
+ );
52
+ }
53
+
54
+ #[test]
55
+ fn reads_the_long_config_equals_form() {
56
+ assert_eq!(scan(&["--config=/c/.dirsql.toml"]), ["/c/.dirsql.toml"]);
57
+ }
58
+
59
+ #[test]
60
+ fn reads_the_short_c_form() {
61
+ assert_eq!(scan(&["-c", "/frag/dirsql.toml"]), ["/frag/dirsql.toml"]);
62
+ }
63
+
64
+ #[test]
65
+ fn reads_the_short_c_equals_form() {
66
+ assert_eq!(scan(&["-c=/frag/dirsql.toml"]), ["/frag/dirsql.toml"]);
67
+ }
68
+
69
+ #[test]
70
+ fn reads_the_short_c_attached_form() {
71
+ assert_eq!(scan(&["-c/frag/dirsql.toml"]), ["/frag/dirsql.toml"]);
72
+ }
73
+
74
+ #[test]
75
+ fn collects_every_config_flag_in_argv_order() {
76
+ assert_eq!(
77
+ scan(&[
78
+ "-c",
79
+ "a.toml",
80
+ "--config",
81
+ "b.toml",
82
+ "--config=c.toml",
83
+ "-cd.toml"
84
+ ]),
85
+ ["a.toml", "b.toml", "c.toml", "d.toml"]
86
+ );
87
+ }
88
+
89
+ #[test]
90
+ fn reads_the_config_value_at_any_argv_position() {
91
+ assert_eq!(scan(&["-v", "--config", "/x/y", "tail"]), ["/x/y"]);
92
+ }
93
+
94
+ #[test]
95
+ fn collects_a_discovery_injected_fragment_rather_than_the_default() {
96
+ assert_eq!(
97
+ scan(&[
98
+ "query",
99
+ "SELECT 1",
100
+ "--include-default",
101
+ "-c",
102
+ "/frag/dirsql.toml"
103
+ ]),
104
+ ["/frag/dirsql.toml"]
105
+ );
106
+ }
107
+
108
+ #[test]
109
+ fn consumes_a_config_value_that_looks_like_a_flag() {
110
+ assert_eq!(scan(&["--config", "-cx.toml"]), ["-cx.toml"]);
111
+ }
112
+
113
+ #[test]
114
+ fn consumes_a_config_value_that_is_itself_a_config_flag() {
115
+ assert_eq!(scan(&["-c", "-c", "x"]), ["-c"]);
116
+ }
117
+
118
+ #[test]
119
+ fn does_not_mistake_other_short_flags_for_config() {
120
+ assert_eq!(scan(&["-v", "-x", "val"]), ["./.dirsql.toml"]);
121
+ }
122
+
123
+ #[test]
124
+ fn does_not_mistake_other_long_flags_for_config() {
125
+ assert_eq!(scan(&["--a", "val"]), ["./.dirsql.toml"]);
126
+ }
127
+
128
+ #[test]
129
+ fn does_not_mistake_a_config_prefixed_long_flag_for_config() {
130
+ assert_eq!(scan(&["--configuration", "val"]), ["./.dirsql.toml"]);
131
+ }
132
+
133
+ #[test]
134
+ fn defaults_when_no_config_flag_is_given() {
135
+ assert_eq!(scan(&["--port", "9000"]), ["./.dirsql.toml"]);
136
+ }
137
+
138
+ #[test]
139
+ fn defaults_for_an_empty_argv() {
140
+ assert_eq!(scan(&[]), ["./.dirsql.toml"]);
141
+ }
142
+
143
+ #[test]
144
+ fn treats_a_bare_trailing_long_config_as_an_empty_path() {
145
+ assert_eq!(scan(&["--config"]), [""]);
146
+ }
147
+
148
+ #[test]
149
+ fn treats_a_bare_trailing_short_c_as_an_empty_path() {
150
+ assert_eq!(scan(&["-c"]), [""]);
151
+ }
152
+
153
+ #[test]
154
+ fn keeps_a_bare_trailing_short_c_after_an_earlier_config() {
155
+ assert_eq!(scan(&["--config", "a.toml", "-c"]), ["a.toml", ""]);
156
+ }
157
+
158
+ #[test]
159
+ fn reads_an_empty_value_from_the_equals_forms() {
160
+ assert_eq!(scan(&["--config="]), [""]);
161
+ assert_eq!(scan(&["-c="]), [""]);
162
+ }
163
+
164
+ #[test]
165
+ fn does_not_treat_a_double_dash_as_end_of_options() {
166
+ assert_eq!(scan(&["--", "-c", "a.toml"]), ["a.toml"]);
167
+ }
168
+ }
@@ -20,6 +20,8 @@ pub mod functions;
20
20
  #[doc(hidden)]
21
21
  pub mod infer;
22
22
  #[doc(hidden)]
23
+ pub mod launcher;
24
+ #[doc(hidden)]
23
25
  pub mod matcher;
24
26
  #[doc(hidden)]
25
27
  pub mod parsed_cache;
@@ -37,6 +39,7 @@ pub mod scanner;
37
39
  pub mod sql_literal;
38
40
  #[doc(hidden)]
39
41
  pub mod vtab;
42
+ mod vtab_scaffold;
40
43
  #[doc(hidden)]
41
44
  pub mod watcher;
42
45
 
@@ -555,9 +558,6 @@ impl DirSQL {
555
558
  // unspecified. An `on_file` failure produces an error event for that
556
559
  // table only; the other matching tables still process the event.
557
560
  let matches = self.inner.matcher.match_all(&rel_path_buf);
558
- if matches.is_empty() {
559
- return Vec::new();
560
- }
561
561
  let rel_path = rel_path_buf.to_string_lossy().to_string();
562
562
 
563
563
  let mut events = Vec::new();
@@ -571,6 +571,33 @@ impl DirSQL {
571
571
  }
572
572
  }
573
573
  }
574
+ // The mirror of the walk above: a directory that leaves the tree
575
+ // (`rm -r`, or a rename out of or within the root) arrives as one
576
+ // event naming the directory and none for the files beneath it. It is
577
+ // already gone, so the rows are the only record of what it held.
578
+ if matches!(event, FileEvent::Deleted(_)) {
579
+ events.extend(self.delete_subtree(&rel_path));
580
+ }
581
+ events
582
+ }
583
+
584
+ /// Delete the rows of every file recorded beneath `rel_dir`, in every
585
+ /// table, as if each file had its own delete event.
586
+ fn delete_subtree(&self, rel_dir: &str) -> Vec<RowEvent> {
587
+ let files = {
588
+ let db = match self.inner.db.lock() {
589
+ Ok(db) => db,
590
+ Err(e) => return vec![error_event(None, rel_dir, e.to_string())],
591
+ };
592
+ match db.files_under(rel_dir) {
593
+ Ok(files) => files,
594
+ Err(e) => return vec![error_event(None, rel_dir, e.to_string())],
595
+ }
596
+ };
597
+ let mut events = Vec::new();
598
+ for (table, file_path) in files {
599
+ events.extend(self.handle_delete(&table, &file_path));
600
+ }
574
601
  events
575
602
  }
576
603
 
@@ -2751,6 +2778,153 @@ mod internal_tests {
2751
2778
  );
2752
2779
  }
2753
2780
 
2781
+ /// A one-table db whose fake fs stats every file in `files`, each already
2782
+ /// upserted so the db holds a row per file.
2783
+ fn populated_db(root: &Path, glob: &str, files: &[&str]) -> DirSQL {
2784
+ let mut fake = FakeFs::default();
2785
+ for f in files {
2786
+ fake.stats.insert(root.join(f), fake_stat());
2787
+ }
2788
+ let db = DirSQL::with_ignore_and_fs(
2789
+ root,
2790
+ vec![Table::new(
2791
+ "items",
2792
+ "CREATE TABLE items (name TEXT)",
2793
+ glob,
2794
+ |path| {
2795
+ vec![Row::from_iter([(
2796
+ "name".to_string(),
2797
+ Value::Text(path.to_string()),
2798
+ )])]
2799
+ },
2800
+ )],
2801
+ Vec::<String>::new(),
2802
+ Arc::new(fake),
2803
+ )
2804
+ .unwrap();
2805
+ for f in files {
2806
+ let events = db.handle_upsert("items", &root.join(f), f);
2807
+ assert!(
2808
+ matches!(&events[..], [RowEvent::Insert { .. }]),
2809
+ "got: {events:?}"
2810
+ );
2811
+ }
2812
+ db
2813
+ }
2814
+
2815
+ fn row_names(db: &DirSQL) -> Vec<String> {
2816
+ let mut names: Vec<String> = db
2817
+ .query("SELECT name FROM items")
2818
+ .unwrap()
2819
+ .into_iter()
2820
+ .filter_map(|r| match r.get("name") {
2821
+ Some(Value::Text(n)) => Some(n.clone()),
2822
+ _ => None,
2823
+ })
2824
+ .collect();
2825
+ names.sort();
2826
+ names
2827
+ }
2828
+
2829
+ fn deleted_paths(events: &[RowEvent]) -> Vec<String> {
2830
+ assert!(
2831
+ events.iter().all(|e| matches!(e, RowEvent::Delete { .. })),
2832
+ "expected only Delete events: {events:?}"
2833
+ );
2834
+ let mut paths: Vec<String> = events
2835
+ .iter()
2836
+ .filter_map(|e| match e {
2837
+ RowEvent::Delete { file_path, .. } => Some(file_path.clone()),
2838
+ _ => None,
2839
+ })
2840
+ .collect();
2841
+ paths.sort();
2842
+ paths
2843
+ }
2844
+
2845
+ /// A deleted directory matches no file glob, yet every row recorded
2846
+ /// beneath it goes, with one Delete per file. A sibling whose name merely
2847
+ /// starts with the directory's (`moved2/`) is untouched.
2848
+ #[test]
2849
+ fn process_file_event_deleted_directory_removes_the_rows_beneath_it() {
2850
+ let dir = TempDir::new().unwrap();
2851
+ let root = dir.path().to_path_buf();
2852
+ let files = ["moved/a.txt", "moved/one/deep.txt", "moved2/x.txt"];
2853
+ let db = populated_db(&root, "**/*.txt", &files);
2854
+
2855
+ let events = db.process_file_event(FileEvent::Deleted(root.join("moved")));
2856
+
2857
+ assert_eq!(
2858
+ deleted_paths(&events),
2859
+ vec!["moved/a.txt".to_string(), "moved/one/deep.txt".to_string()]
2860
+ );
2861
+ assert_eq!(
2862
+ row_names(&db),
2863
+ vec![root.join("moved2/x.txt").to_string_lossy().to_string()]
2864
+ );
2865
+ }
2866
+
2867
+ /// With a glob that also matches the directory itself (`**/*`), the
2868
+ /// exact-path delete finds no row and the subtree delete still runs.
2869
+ #[test]
2870
+ fn process_file_event_deleted_directory_matching_a_glob_removes_the_rows_beneath_it() {
2871
+ let dir = TempDir::new().unwrap();
2872
+ let root = dir.path().to_path_buf();
2873
+ let db = populated_db(&root, "**/*", &["moved/a.txt", "keep.txt"]);
2874
+
2875
+ let events = db.process_file_event(FileEvent::Deleted(root.join("moved")));
2876
+
2877
+ assert_eq!(deleted_paths(&events), vec!["moved/a.txt".to_string()]);
2878
+ assert_eq!(
2879
+ row_names(&db),
2880
+ vec![root.join("keep.txt").to_string_lossy().to_string()]
2881
+ );
2882
+ }
2883
+
2884
+ /// Only a *deleted* directory empties its subtree; a metadata change on
2885
+ /// one says nothing about its files.
2886
+ #[test]
2887
+ fn process_file_event_keeps_the_rows_beneath_a_modified_directory() {
2888
+ let dir = TempDir::new().unwrap();
2889
+ let root = dir.path().to_path_buf();
2890
+ let db = populated_db(&root, "**/*.txt", &["moved/a.txt"]);
2891
+
2892
+ let events = db.process_file_event(FileEvent::Modified(root.join("moved")));
2893
+
2894
+ assert!(events.is_empty(), "got: {events:?}");
2895
+ assert_eq!(
2896
+ row_names(&db),
2897
+ vec![root.join("moved/a.txt").to_string_lossy().to_string()]
2898
+ );
2899
+ }
2900
+
2901
+ #[test]
2902
+ fn delete_subtree_surfaces_db_poison() {
2903
+ let (_dir, db, _abs, _rel) = upsert_fixture();
2904
+ poison(&db.inner.db);
2905
+ let events = db.delete_subtree("moved");
2906
+ assert_single_lock_error(&events);
2907
+ }
2908
+
2909
+ #[test]
2910
+ fn delete_subtree_surfaces_db_failure() {
2911
+ let (_dir, db, _abs, _rel) = upsert_fixture();
2912
+ db.inner
2913
+ .db
2914
+ .lock()
2915
+ .unwrap()
2916
+ .conn()
2917
+ .execute("DROP TABLE _dirsql_internal_rows", [])
2918
+ .unwrap();
2919
+ let events = db.delete_subtree("moved");
2920
+ assert_eq!(events.len(), 1, "expected one error event: {events:?}");
2921
+ assert!(
2922
+ matches!(&events[0], RowEvent::Error { table: None, file_path, error }
2923
+ if file_path == Path::new("moved") && error.contains("no such table")),
2924
+ "got: {events:?}"
2925
+ );
2926
+ }
2927
+
2754
2928
  /// Building with a **relative** root canonicalizes `watch_root` to an
2755
2929
  /// absolute path while leaving `root` exactly as the caller supplied it,
2756
2930
  /// so `notify` never sees `.`.
@@ -3536,7 +3710,7 @@ mod internal_tests {
3536
3710
  let db = DirSQL::builder().root(dir.path()).build().unwrap();
3537
3711
 
3538
3712
  assert!(
3539
- db.query("SELECT path FROM './'").is_ok(),
3713
+ db.query("SELECT path FROM './' LIMIT 1").is_ok(),
3540
3714
  "the path-table fallback must be armed on an ephemeral db"
3541
3715
  );
3542
3716
  }
@@ -3577,7 +3751,7 @@ mod internal_tests {
3577
3751
  .unwrap();
3578
3752
 
3579
3753
  assert!(
3580
- db.query("SELECT path FROM './'").is_ok(),
3754
+ db.query("SELECT path FROM './' LIMIT 1").is_ok(),
3581
3755
  "the persist branch must arm the fallback too"
3582
3756
  );
3583
3757
  }