dirsql 0.3.118__tar.gz → 0.3.119__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 (192) hide show
  1. {dirsql-0.3.118 → dirsql-0.3.119}/Cargo.lock +1 -1
  2. {dirsql-0.3.118 → dirsql-0.3.119}/PKG-INFO +1 -1
  3. {dirsql-0.3.118/packages/rust → dirsql-0.3.119}/docs/reference/cli.md +19 -0
  4. {dirsql-0.3.118 → dirsql-0.3.119}/docs/reference/path-tables.md +33 -0
  5. {dirsql-0.3.118 → dirsql-0.3.119}/packages/python/Cargo.toml +1 -1
  6. {dirsql-0.3.118 → dirsql-0.3.119}/packages/python/docs/reference/cli.md +19 -0
  7. {dirsql-0.3.118 → dirsql-0.3.119}/packages/python/docs/reference/path-tables.md +33 -0
  8. dirsql-0.3.119/packages/python/e2e-attestations/feat-631-on-file-flag.json +7 -0
  9. dirsql-0.3.119/packages/rust/changelog.d/2026-07-21-on-file-flag.md +14 -0
  10. {dirsql-0.3.118 → dirsql-0.3.119/packages/rust}/docs/reference/cli.md +19 -0
  11. {dirsql-0.3.118 → dirsql-0.3.119}/packages/rust/docs/reference/path-tables.md +33 -0
  12. {dirsql-0.3.118 → dirsql-0.3.119}/packages/rust/src/bin/dirsql.rs +131 -6
  13. {dirsql-0.3.118 → dirsql-0.3.119}/packages/rust/src/db.rs +103 -12
  14. {dirsql-0.3.118 → dirsql-0.3.119}/packages/rust/src/lib.rs +69 -0
  15. {dirsql-0.3.118 → dirsql-0.3.119}/packages/rust/src/parsed_vtab.rs +139 -63
  16. dirsql-0.3.118/packages/python/e2e-attestations/feat-628-path-table-glob-semantics.json +0 -7
  17. {dirsql-0.3.118 → dirsql-0.3.119}/Cargo.toml +0 -0
  18. {dirsql-0.3.118 → dirsql-0.3.119}/README.md +0 -0
  19. {dirsql-0.3.118 → dirsql-0.3.119}/dirsql/__init__.py +0 -0
  20. {dirsql-0.3.118 → dirsql-0.3.119}/dirsql/_async.py +0 -0
  21. {dirsql-0.3.118 → dirsql-0.3.119}/dirsql/_dirsql.pyi +0 -0
  22. {dirsql-0.3.118 → dirsql-0.3.119}/dirsql/cli/__init__.py +0 -0
  23. {dirsql-0.3.118 → dirsql-0.3.119}/dirsql/cli/binary_path.py +0 -0
  24. {dirsql-0.3.118 → dirsql-0.3.119}/dirsql/cli/discover_plugins/__init__.py +0 -0
  25. {dirsql-0.3.118 → dirsql-0.3.119}/dirsql/cli/discover_plugins/discovered_fragments.py +0 -0
  26. {dirsql-0.3.118 → dirsql-0.3.119}/dirsql/cli/discover_plugins/discovery_disabled.py +0 -0
  27. {dirsql-0.3.118 → dirsql-0.3.119}/dirsql/cli/discover_plugins/fragment_path.py +0 -0
  28. {dirsql-0.3.118 → dirsql-0.3.119}/dirsql/cli/discover_plugins/user_passed_config.py +0 -0
  29. {dirsql-0.3.118 → dirsql-0.3.119}/dirsql/cli/discover_plugins/with_discovered_plugins.py +0 -0
  30. {dirsql-0.3.118 → dirsql-0.3.119}/dirsql/cli/is_windows.py +0 -0
  31. {dirsql-0.3.118 → dirsql-0.3.119}/dirsql/cli/main.py +0 -0
  32. {dirsql-0.3.118 → dirsql-0.3.119}/dirsql/cli/resolve_config_extensions.py +0 -0
  33. {dirsql-0.3.118 → dirsql-0.3.119}/dirsql/py.typed +0 -0
  34. {dirsql-0.3.118 → dirsql-0.3.119}/dirsql/resolve_config_extensions.py +0 -0
  35. {dirsql-0.3.118 → dirsql-0.3.119}/dirsql/resolve_extension.py +0 -0
  36. {dirsql-0.3.118 → dirsql-0.3.119}/docs/.claude/CLAUDE.md +0 -0
  37. {dirsql-0.3.118 → dirsql-0.3.119}/docs/.vitepress/config.ts +0 -0
  38. {dirsql-0.3.118 → dirsql-0.3.119}/docs/.vitepress/theme/index.ts +0 -0
  39. {dirsql-0.3.118 → dirsql-0.3.119}/docs/.vitepress/theme/lang.ts +0 -0
  40. {dirsql-0.3.118 → dirsql-0.3.119}/docs/AGENTS.md +0 -0
  41. {dirsql-0.3.118 → dirsql-0.3.119}/docs/explanation.md +0 -0
  42. {dirsql-0.3.118 → dirsql-0.3.119}/docs/getting-started.md +0 -0
  43. {dirsql-0.3.118 → dirsql-0.3.119}/docs/howto/columns-from-paths.md +0 -0
  44. {dirsql-0.3.118 → dirsql-0.3.119}/docs/howto/define-tables.md +0 -0
  45. {dirsql-0.3.118 → dirsql-0.3.119}/docs/howto/embed.md +0 -0
  46. {dirsql-0.3.118 → dirsql-0.3.119}/docs/howto/extract-from-contents.md +0 -0
  47. {dirsql-0.3.118 → dirsql-0.3.119}/docs/howto/load-extension.md +0 -0
  48. {dirsql-0.3.118 → dirsql-0.3.119}/docs/howto/persist.md +0 -0
  49. {dirsql-0.3.118 → dirsql-0.3.119}/docs/howto/query-without-config.md +0 -0
  50. {dirsql-0.3.118 → dirsql-0.3.119}/docs/howto/react-to-changes.md +0 -0
  51. {dirsql-0.3.118 → dirsql-0.3.119}/docs/howto/search-by-meaning.md +0 -0
  52. {dirsql-0.3.118 → dirsql-0.3.119}/docs/howto/skip-files.md +0 -0
  53. {dirsql-0.3.118 → dirsql-0.3.119}/docs/howto/write-a-plugin.md +0 -0
  54. {dirsql-0.3.118 → dirsql-0.3.119}/docs/index.md +0 -0
  55. {dirsql-0.3.118 → dirsql-0.3.119}/docs/migrations.md +0 -0
  56. {dirsql-0.3.118 → dirsql-0.3.119}/docs/package.json +0 -0
  57. {dirsql-0.3.118 → dirsql-0.3.119}/docs/pnpm-lock.yaml +0 -0
  58. {dirsql-0.3.118 → dirsql-0.3.119}/docs/pnpm-workspace.yaml +0 -0
  59. {dirsql-0.3.118 → dirsql-0.3.119}/docs/reference/columns.md +0 -0
  60. {dirsql-0.3.118 → dirsql-0.3.119}/docs/reference/config.md +0 -0
  61. {dirsql-0.3.118 → dirsql-0.3.119}/docs/reference/hooks.md +0 -0
  62. {dirsql-0.3.118 → dirsql-0.3.119}/docs/reference/http-api.md +0 -0
  63. {dirsql-0.3.118 → dirsql-0.3.119}/docs/reference/sdk.md +0 -0
  64. {dirsql-0.3.118 → dirsql-0.3.119}/packages/python/CHANGELOG.md +0 -0
  65. {dirsql-0.3.118 → dirsql-0.3.119}/packages/python/MIGRATIONS.md +0 -0
  66. {dirsql-0.3.118 → dirsql-0.3.119}/packages/python/README.md +0 -0
  67. {dirsql-0.3.118 → dirsql-0.3.119}/packages/python/changelog.d/2026-07-13-drop-cwd-config-auto-detect.md +0 -0
  68. {dirsql-0.3.118 → dirsql-0.3.119}/packages/python/changelog.d/2026-07-13-plugin-discovery.md +0 -0
  69. {dirsql-0.3.118 → dirsql-0.3.119}/packages/python/changelog.d/2026-07-13-rename-extract-to-on-file.md +0 -0
  70. {dirsql-0.3.118 → dirsql-0.3.119}/packages/python/changelog.d/2026-07-13-repeatable-config.md +0 -0
  71. {dirsql-0.3.118 → dirsql-0.3.119}/packages/python/changelog.d/2026-07-13-sdk-default-baked-in-config.md +0 -0
  72. {dirsql-0.3.118 → dirsql-0.3.119}/packages/python/changelog.d/2026-07-20-drop-implicit-files-table.md +0 -0
  73. {dirsql-0.3.118 → dirsql-0.3.119}/packages/python/changelog.d/2026-07-21-remove-glob-captures.md +0 -0
  74. {dirsql-0.3.118 → dirsql-0.3.119}/packages/python/changelog.d/README.md +0 -0
  75. {dirsql-0.3.118 → dirsql-0.3.119}/packages/python/conftest.py +0 -0
  76. {dirsql-0.3.118 → dirsql-0.3.119}/packages/python/docs/.claude/CLAUDE.md +0 -0
  77. {dirsql-0.3.118 → dirsql-0.3.119}/packages/python/docs/.vitepress/config.ts +0 -0
  78. {dirsql-0.3.118 → dirsql-0.3.119}/packages/python/docs/.vitepress/theme/index.ts +0 -0
  79. {dirsql-0.3.118 → dirsql-0.3.119}/packages/python/docs/.vitepress/theme/lang.ts +0 -0
  80. {dirsql-0.3.118 → dirsql-0.3.119}/packages/python/docs/AGENTS.md +0 -0
  81. {dirsql-0.3.118 → dirsql-0.3.119}/packages/python/docs/explanation.md +0 -0
  82. {dirsql-0.3.118 → dirsql-0.3.119}/packages/python/docs/getting-started.md +0 -0
  83. {dirsql-0.3.118 → dirsql-0.3.119}/packages/python/docs/howto/columns-from-paths.md +0 -0
  84. {dirsql-0.3.118 → dirsql-0.3.119}/packages/python/docs/howto/define-tables.md +0 -0
  85. {dirsql-0.3.118 → dirsql-0.3.119}/packages/python/docs/howto/embed.md +0 -0
  86. {dirsql-0.3.118 → dirsql-0.3.119}/packages/python/docs/howto/extract-from-contents.md +0 -0
  87. {dirsql-0.3.118 → dirsql-0.3.119}/packages/python/docs/howto/load-extension.md +0 -0
  88. {dirsql-0.3.118 → dirsql-0.3.119}/packages/python/docs/howto/persist.md +0 -0
  89. {dirsql-0.3.118 → dirsql-0.3.119}/packages/python/docs/howto/query-without-config.md +0 -0
  90. {dirsql-0.3.118 → dirsql-0.3.119}/packages/python/docs/howto/react-to-changes.md +0 -0
  91. {dirsql-0.3.118 → dirsql-0.3.119}/packages/python/docs/howto/search-by-meaning.md +0 -0
  92. {dirsql-0.3.118 → dirsql-0.3.119}/packages/python/docs/howto/skip-files.md +0 -0
  93. {dirsql-0.3.118 → dirsql-0.3.119}/packages/python/docs/howto/write-a-plugin.md +0 -0
  94. {dirsql-0.3.118 → dirsql-0.3.119}/packages/python/docs/index.md +0 -0
  95. {dirsql-0.3.118 → dirsql-0.3.119}/packages/python/docs/migrations.md +0 -0
  96. {dirsql-0.3.118 → dirsql-0.3.119}/packages/python/docs/package.json +0 -0
  97. {dirsql-0.3.118 → dirsql-0.3.119}/packages/python/docs/pnpm-lock.yaml +0 -0
  98. {dirsql-0.3.118 → dirsql-0.3.119}/packages/python/docs/pnpm-workspace.yaml +0 -0
  99. {dirsql-0.3.118 → dirsql-0.3.119}/packages/python/docs/reference/columns.md +0 -0
  100. {dirsql-0.3.118 → dirsql-0.3.119}/packages/python/docs/reference/config.md +0 -0
  101. {dirsql-0.3.118 → dirsql-0.3.119}/packages/python/docs/reference/hooks.md +0 -0
  102. {dirsql-0.3.118 → dirsql-0.3.119}/packages/python/docs/reference/http-api.md +0 -0
  103. {dirsql-0.3.118 → dirsql-0.3.119}/packages/python/docs/reference/sdk.md +0 -0
  104. {dirsql-0.3.118 → dirsql-0.3.119}/packages/python/e2e-attestations/feat-655-drop-glob-captures.json +0 -0
  105. {dirsql-0.3.118 → dirsql-0.3.119}/packages/python/migrations.d/2026-07-13-drop-cwd-config-auto-detect.md +0 -0
  106. {dirsql-0.3.118 → dirsql-0.3.119}/packages/python/migrations.d/2026-07-13-rename-extract-to-on-file.md +0 -0
  107. {dirsql-0.3.118 → dirsql-0.3.119}/packages/python/migrations.d/2026-07-13-sdk-default-baked-in-config.md +0 -0
  108. {dirsql-0.3.118 → dirsql-0.3.119}/packages/python/migrations.d/2026-07-20-drop-implicit-files-table.md +0 -0
  109. {dirsql-0.3.118 → dirsql-0.3.119}/packages/python/migrations.d/2026-07-21-remove-glob-captures.md +0 -0
  110. {dirsql-0.3.118 → dirsql-0.3.119}/packages/python/migrations.d/README.md +0 -0
  111. {dirsql-0.3.118 → dirsql-0.3.119}/packages/python/src/lib.rs +0 -0
  112. {dirsql-0.3.118 → dirsql-0.3.119}/packages/python/tests/__init__.py +0 -0
  113. {dirsql-0.3.118 → dirsql-0.3.119}/packages/python/tests/conftest.py +0 -0
  114. {dirsql-0.3.118 → dirsql-0.3.119}/packages/python/tests/e2e/__init__.py +0 -0
  115. {dirsql-0.3.118 → dirsql-0.3.119}/packages/python/tests/e2e/fixtures/dirsql_plugin_fixture/__init__.py +0 -0
  116. {dirsql-0.3.118 → dirsql-0.3.119}/packages/python/tests/e2e/fixtures/dirsql_plugin_fixture/dirsql.toml +0 -0
  117. {dirsql-0.3.118 → dirsql-0.3.119}/packages/python/tests/integration/__init__.py +0 -0
  118. {dirsql-0.3.118 → dirsql-0.3.119}/packages/python/tests/integration/binding/__init__.py +0 -0
  119. {dirsql-0.3.118 → dirsql-0.3.119}/packages/python/tests/integration/hermetic/__init__.py +0 -0
  120. {dirsql-0.3.118 → dirsql-0.3.119}/packages/rust/CHANGELOG.md +0 -0
  121. {dirsql-0.3.118 → dirsql-0.3.119}/packages/rust/Cargo.toml +0 -0
  122. {dirsql-0.3.118 → dirsql-0.3.119}/packages/rust/MIGRATIONS.md +0 -0
  123. {dirsql-0.3.118 → dirsql-0.3.119}/packages/rust/README.md +0 -0
  124. {dirsql-0.3.118 → dirsql-0.3.119}/packages/rust/benches/db_bench.rs +0 -0
  125. {dirsql-0.3.118 → dirsql-0.3.119}/packages/rust/benches/differ_bench.rs +0 -0
  126. {dirsql-0.3.118 → dirsql-0.3.119}/packages/rust/benches/matcher_bench.rs +0 -0
  127. {dirsql-0.3.118 → dirsql-0.3.119}/packages/rust/benches/scanner_bench.rs +0 -0
  128. {dirsql-0.3.118 → dirsql-0.3.119}/packages/rust/changelog.d/2026-07-13-batch-ingest-transaction.md +0 -0
  129. {dirsql-0.3.118 → dirsql-0.3.119}/packages/rust/changelog.d/2026-07-13-chained-pre-post-query-hooks.md +0 -0
  130. {dirsql-0.3.118 → dirsql-0.3.119}/packages/rust/changelog.d/2026-07-13-drop-cwd-config-auto-detect.md +0 -0
  131. {dirsql-0.3.118 → dirsql-0.3.119}/packages/rust/changelog.d/2026-07-13-fan-out-file-table-matching.md +0 -0
  132. {dirsql-0.3.118 → dirsql-0.3.119}/packages/rust/changelog.d/2026-07-13-persist-wal.md +0 -0
  133. {dirsql-0.3.118 → dirsql-0.3.119}/packages/rust/changelog.d/2026-07-13-rename-extract-to-on-file.md +0 -0
  134. {dirsql-0.3.118 → dirsql-0.3.119}/packages/rust/changelog.d/2026-07-13-repeatable-builder-config.md +0 -0
  135. {dirsql-0.3.118 → dirsql-0.3.119}/packages/rust/changelog.d/2026-07-13-repeatable-cli-config.md +0 -0
  136. {dirsql-0.3.118 → dirsql-0.3.119}/packages/rust/changelog.d/2026-07-13-sdk-default-baked-in-config.md +0 -0
  137. {dirsql-0.3.118 → dirsql-0.3.119}/packages/rust/changelog.d/2026-07-13-subcommand-local-config-flags.md +0 -0
  138. {dirsql-0.3.118 → dirsql-0.3.119}/packages/rust/changelog.d/2026-07-19-path-table-query-fallback.md +0 -0
  139. {dirsql-0.3.118 → dirsql-0.3.119}/packages/rust/changelog.d/2026-07-19-path-table-vtab.md +0 -0
  140. {dirsql-0.3.118 → dirsql-0.3.119}/packages/rust/changelog.d/2026-07-20-drop-implicit-files-table.md +0 -0
  141. {dirsql-0.3.118 → dirsql-0.3.119}/packages/rust/changelog.d/2026-07-20-path-table-glob-semantics.md +0 -0
  142. {dirsql-0.3.118 → dirsql-0.3.119}/packages/rust/changelog.d/2026-07-20-schema-inference.md +0 -0
  143. {dirsql-0.3.118 → dirsql-0.3.119}/packages/rust/changelog.d/2026-07-21-remove-glob-captures.md +0 -0
  144. {dirsql-0.3.118 → dirsql-0.3.119}/packages/rust/changelog.d/README.md +0 -0
  145. {dirsql-0.3.118 → dirsql-0.3.119}/packages/rust/docs/explanation.md +0 -0
  146. {dirsql-0.3.118 → dirsql-0.3.119}/packages/rust/docs/getting-started.md +0 -0
  147. {dirsql-0.3.118 → dirsql-0.3.119}/packages/rust/docs/howto/columns-from-paths.md +0 -0
  148. {dirsql-0.3.118 → dirsql-0.3.119}/packages/rust/docs/howto/define-tables.md +0 -0
  149. {dirsql-0.3.118 → dirsql-0.3.119}/packages/rust/docs/howto/embed.md +0 -0
  150. {dirsql-0.3.118 → dirsql-0.3.119}/packages/rust/docs/howto/extract-from-contents.md +0 -0
  151. {dirsql-0.3.118 → dirsql-0.3.119}/packages/rust/docs/howto/load-extension.md +0 -0
  152. {dirsql-0.3.118 → dirsql-0.3.119}/packages/rust/docs/howto/persist.md +0 -0
  153. {dirsql-0.3.118 → dirsql-0.3.119}/packages/rust/docs/howto/query-without-config.md +0 -0
  154. {dirsql-0.3.118 → dirsql-0.3.119}/packages/rust/docs/howto/react-to-changes.md +0 -0
  155. {dirsql-0.3.118 → dirsql-0.3.119}/packages/rust/docs/howto/search-by-meaning.md +0 -0
  156. {dirsql-0.3.118 → dirsql-0.3.119}/packages/rust/docs/howto/skip-files.md +0 -0
  157. {dirsql-0.3.118 → dirsql-0.3.119}/packages/rust/docs/howto/write-a-plugin.md +0 -0
  158. {dirsql-0.3.118 → dirsql-0.3.119}/packages/rust/docs/index.md +0 -0
  159. {dirsql-0.3.118 → dirsql-0.3.119}/packages/rust/docs/migrations.md +0 -0
  160. {dirsql-0.3.118 → dirsql-0.3.119}/packages/rust/docs/reference/columns.md +0 -0
  161. {dirsql-0.3.118 → dirsql-0.3.119}/packages/rust/docs/reference/config.md +0 -0
  162. {dirsql-0.3.118 → dirsql-0.3.119}/packages/rust/docs/reference/hooks.md +0 -0
  163. {dirsql-0.3.118 → dirsql-0.3.119}/packages/rust/docs/reference/http-api.md +0 -0
  164. {dirsql-0.3.118 → dirsql-0.3.119}/packages/rust/docs/reference/sdk.md +0 -0
  165. {dirsql-0.3.118 → dirsql-0.3.119}/packages/rust/migrations.d/2026-07-13-drop-cwd-config-auto-detect.md +0 -0
  166. {dirsql-0.3.118 → dirsql-0.3.119}/packages/rust/migrations.d/2026-07-13-fan-out-file-table-matching.md +0 -0
  167. {dirsql-0.3.118 → dirsql-0.3.119}/packages/rust/migrations.d/2026-07-13-persist-wal.md +0 -0
  168. {dirsql-0.3.118 → dirsql-0.3.119}/packages/rust/migrations.d/2026-07-13-rename-extract-to-on-file.md +0 -0
  169. {dirsql-0.3.118 → dirsql-0.3.119}/packages/rust/migrations.d/2026-07-13-sdk-default-baked-in-config.md +0 -0
  170. {dirsql-0.3.118 → dirsql-0.3.119}/packages/rust/migrations.d/2026-07-13-subcommand-local-config-flags.md +0 -0
  171. {dirsql-0.3.118 → dirsql-0.3.119}/packages/rust/migrations.d/2026-07-20-drop-implicit-files-table.md +0 -0
  172. {dirsql-0.3.118 → dirsql-0.3.119}/packages/rust/migrations.d/2026-07-20-path-table-glob-semantics.md +0 -0
  173. {dirsql-0.3.118 → dirsql-0.3.119}/packages/rust/migrations.d/2026-07-21-remove-glob-captures.md +0 -0
  174. {dirsql-0.3.118 → dirsql-0.3.119}/packages/rust/migrations.d/README.md +0 -0
  175. {dirsql-0.3.118 → dirsql-0.3.119}/packages/rust/src/cli/execute.rs +0 -0
  176. {dirsql-0.3.118 → dirsql-0.3.119}/packages/rust/src/cli/init.rs +0 -0
  177. {dirsql-0.3.118 → dirsql-0.3.119}/packages/rust/src/cli/mod.rs +0 -0
  178. {dirsql-0.3.118 → dirsql-0.3.119}/packages/rust/src/cli/router.rs +0 -0
  179. {dirsql-0.3.118 → dirsql-0.3.119}/packages/rust/src/cli/serialize.rs +0 -0
  180. {dirsql-0.3.118 → dirsql-0.3.119}/packages/rust/src/cli/server.rs +0 -0
  181. {dirsql-0.3.118 → dirsql-0.3.119}/packages/rust/src/command.rs +0 -0
  182. {dirsql-0.3.118 → dirsql-0.3.119}/packages/rust/src/config.rs +0 -0
  183. {dirsql-0.3.118 → dirsql-0.3.119}/packages/rust/src/default_config.toml +0 -0
  184. {dirsql-0.3.118 → dirsql-0.3.119}/packages/rust/src/differ.rs +0 -0
  185. {dirsql-0.3.118 → dirsql-0.3.119}/packages/rust/src/infer.rs +0 -0
  186. {dirsql-0.3.118 → dirsql-0.3.119}/packages/rust/src/matcher.rs +0 -0
  187. {dirsql-0.3.118 → dirsql-0.3.119}/packages/rust/src/path_table.rs +0 -0
  188. {dirsql-0.3.118 → dirsql-0.3.119}/packages/rust/src/persist.rs +0 -0
  189. {dirsql-0.3.118 → dirsql-0.3.119}/packages/rust/src/scanner.rs +0 -0
  190. {dirsql-0.3.118 → dirsql-0.3.119}/packages/rust/src/vtab.rs +0 -0
  191. {dirsql-0.3.118 → dirsql-0.3.119}/packages/rust/src/watcher.rs +0 -0
  192. {dirsql-0.3.118 → dirsql-0.3.119}/pyproject.toml +0 -0
@@ -500,7 +500,7 @@ dependencies = [
500
500
 
501
501
  [[package]]
502
502
  name = "dirsql-py-ext"
503
- version = "0.3.118"
503
+ version = "0.3.119"
504
504
  dependencies = [
505
505
  "dirsql",
506
506
  "pyo3",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dirsql
3
- Version: 0.3.118
3
+ Version: 0.3.119
4
4
  Summary: Ephemeral SQL index over a local directory
5
5
  Keywords: sql,filesystem,directory,sqlite,index
6
6
  Author: Kevin Scott
@@ -151,6 +151,25 @@ uses**, so behavior is identical to `POST /query` by construction:
151
151
  statements; dirsql separately never modifies the files it indexes — see
152
152
  [Read-only by design](../explanation#read-only-by-design).
153
153
 
154
+ #### `--on-file <command>`
155
+
156
+ Attach a parser to every [path-table](./path-tables.md#parsing-rows-with-on-file)
157
+ in the query, so each matched file yields the rows the command prints (a JSON
158
+ array of row objects) instead of the stat columns:
159
+
160
+ ```sh
161
+ dirsql query "SELECT title, author FROM './posts/*.md'" \
162
+ --on-file 'extract.py {path}'
163
+ ```
164
+
165
+ The command follows the [`on-file` hook contract](./hooks.md#on-file) — argv
166
+ splitting, `{path}`/`{root}` placeholders, per-file failure isolation, and the
167
+ timeout. The parser's output is the whole schema; the stat columns are not
168
+ reachable on a parsed path-table. `--on-file` may be given **at most once** (a
169
+ repeat is an error pointing at config files) and never touches config-declared
170
+ tables. It is a `query`-only flag — server mode rejects it as an unknown
171
+ argument.
172
+
154
173
  Errors print the same diagnostic the HTTP `{"error": …}` body carries —
155
174
  config failures, SQL errors, rejected reads, hook failures, timeouts — to
156
175
  stderr, with exit code `1`.
@@ -114,6 +114,39 @@ SELECT path FROM './docs/*.md' WHERE content LIKE '%deprecated%';
114
114
  A file that cannot be read, or is not valid UTF-8, yields `NULL` content rather
115
115
  than failing the query.
116
116
 
117
+ ## Parsing rows with `--on-file`
118
+
119
+ By default a path-table's columns are the stat columns above — one row per
120
+ file. When you want *one row per record inside* each file, attach a parser with
121
+ the `dirsql query` flag [`--on-file`](/reference/cli):
122
+
123
+ ```sh
124
+ dirsql query "SELECT title, author FROM './posts/*.md'" \
125
+ --on-file 'extract.py {path}'
126
+ ```
127
+
128
+ The command runs once per matched file and prints a JSON array of row objects,
129
+ exactly like a declared table's [`on-file` hook](/reference/hooks) — same argv
130
+ splitting, same `{path}`/`{root}` placeholders, same timeout. Its output *is*
131
+ the table:
132
+
133
+ - **The parser supplies the whole schema.** Columns are inferred from the keys
134
+ across the emitted rows. The stat columns (`path`, `size`, …) are **not**
135
+ reachable on a parsed path-table — a parser that wants the path emits it (it
136
+ has `{path}`). The two modes stay cleanly separate.
137
+ - **Failures are isolated per file.** A file whose parser fails (spawn, non-zero
138
+ exit, timeout, or no output) or whose output is not a JSON array of rows
139
+ contributes no rows; a one-line warning naming the file goes to stderr and the
140
+ scan continues. The schema is inferred from the files that did parse.
141
+ - **The skip rules still apply.** A parsed scan honors the same `node_modules`
142
+ /`.git`/`ignore` rules a stat scan does (see below).
143
+
144
+ `--on-file` applies to **every** path-table in the query and may be given **at
145
+ most once**. For different parsers per file set, define named tables in a
146
+ `.dirsql.toml` with their own `on-file` keys and pass it with `-c` — the flag
147
+ never touches config-declared tables. It is a `query`-subcommand flag; server
148
+ mode does not accept it.
149
+
117
150
  ## Freshness and scope
118
151
 
119
152
  A path-table is scanned when the statement runs, so it always reflects the
@@ -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.3.118"
7
+ version = "0.3.119"
8
8
  # abi3 note: the `abi3-py311` pyo3 feature (below) builds ONE stable-ABI
9
9
  # `cp311-abi3` wheel per platform that loads on every CPython >= 3.11
10
10
  # (matching `pyproject.toml`'s `requires-python = ">=3.11"`), instead of
@@ -151,6 +151,25 @@ uses**, so behavior is identical to `POST /query` by construction:
151
151
  statements; dirsql separately never modifies the files it indexes — see
152
152
  [Read-only by design](../explanation#read-only-by-design).
153
153
 
154
+ #### `--on-file <command>`
155
+
156
+ Attach a parser to every [path-table](./path-tables.md#parsing-rows-with-on-file)
157
+ in the query, so each matched file yields the rows the command prints (a JSON
158
+ array of row objects) instead of the stat columns:
159
+
160
+ ```sh
161
+ dirsql query "SELECT title, author FROM './posts/*.md'" \
162
+ --on-file 'extract.py {path}'
163
+ ```
164
+
165
+ The command follows the [`on-file` hook contract](./hooks.md#on-file) — argv
166
+ splitting, `{path}`/`{root}` placeholders, per-file failure isolation, and the
167
+ timeout. The parser's output is the whole schema; the stat columns are not
168
+ reachable on a parsed path-table. `--on-file` may be given **at most once** (a
169
+ repeat is an error pointing at config files) and never touches config-declared
170
+ tables. It is a `query`-only flag — server mode rejects it as an unknown
171
+ argument.
172
+
154
173
  Errors print the same diagnostic the HTTP `{"error": …}` body carries —
155
174
  config failures, SQL errors, rejected reads, hook failures, timeouts — to
156
175
  stderr, with exit code `1`.
@@ -114,6 +114,39 @@ SELECT path FROM './docs/*.md' WHERE content LIKE '%deprecated%';
114
114
  A file that cannot be read, or is not valid UTF-8, yields `NULL` content rather
115
115
  than failing the query.
116
116
 
117
+ ## Parsing rows with `--on-file`
118
+
119
+ By default a path-table's columns are the stat columns above — one row per
120
+ file. When you want *one row per record inside* each file, attach a parser with
121
+ the `dirsql query` flag [`--on-file`](/reference/cli):
122
+
123
+ ```sh
124
+ dirsql query "SELECT title, author FROM './posts/*.md'" \
125
+ --on-file 'extract.py {path}'
126
+ ```
127
+
128
+ The command runs once per matched file and prints a JSON array of row objects,
129
+ exactly like a declared table's [`on-file` hook](/reference/hooks) — same argv
130
+ splitting, same `{path}`/`{root}` placeholders, same timeout. Its output *is*
131
+ the table:
132
+
133
+ - **The parser supplies the whole schema.** Columns are inferred from the keys
134
+ across the emitted rows. The stat columns (`path`, `size`, …) are **not**
135
+ reachable on a parsed path-table — a parser that wants the path emits it (it
136
+ has `{path}`). The two modes stay cleanly separate.
137
+ - **Failures are isolated per file.** A file whose parser fails (spawn, non-zero
138
+ exit, timeout, or no output) or whose output is not a JSON array of rows
139
+ contributes no rows; a one-line warning naming the file goes to stderr and the
140
+ scan continues. The schema is inferred from the files that did parse.
141
+ - **The skip rules still apply.** A parsed scan honors the same `node_modules`
142
+ /`.git`/`ignore` rules a stat scan does (see below).
143
+
144
+ `--on-file` applies to **every** path-table in the query and may be given **at
145
+ most once**. For different parsers per file set, define named tables in a
146
+ `.dirsql.toml` with their own `on-file` keys and pass it with `-c` — the flag
147
+ never touches config-declared tables. It is a `query`-subcommand flag; server
148
+ mode does not accept it.
149
+
117
150
  ## Freshness and scope
118
151
 
119
152
  A path-table is scanned when the statement runs, so it always reflects the
@@ -0,0 +1,7 @@
1
+ {
2
+ "command": "UV_NO_SYNC=1 uv run python -m pytest tests/e2e/ -x -q",
3
+ "ran_at": 1784652812,
4
+ "exit_code": 0,
5
+ "commit": "c59b0667084ba1d9542031a60cd55ae0bc3c7db6",
6
+ "branch": "feat/631-on-file-flag"
7
+ }
@@ -0,0 +1,14 @@
1
+ **Added**
2
+
3
+ `dirsql query "<sql>" --on-file '<command>'` attaches a parser to every
4
+ [path-table](https://thekevinscott.github.io/dirsql/reference/path-tables) in
5
+ the query. With the flag set, a path-table's rows and schema come from the
6
+ command's JSON output (the `on-file` hook contract — argv splitting,
7
+ `{path}`/`{root}` placeholders, timeout) instead of the stat columns. The
8
+ parser's output is the whole schema; stat columns are not reachable on a parsed
9
+ path-table. Failures are isolated per file — a file whose parser fails or whose
10
+ output does not parse contributes no rows and a stderr warning, and the scan
11
+ continues. Parsed scans honor the same `node_modules`/`.git`/`ignore` skip
12
+ rules stat scans do. The flag may be given at most once (a repeat errors,
13
+ pointing at config files), applies only to path-tables (config `[[table]]`
14
+ `on-file` hooks are untouched), and is `query`-only (server mode rejects it).
@@ -151,6 +151,25 @@ uses**, so behavior is identical to `POST /query` by construction:
151
151
  statements; dirsql separately never modifies the files it indexes — see
152
152
  [Read-only by design](../explanation#read-only-by-design).
153
153
 
154
+ #### `--on-file <command>`
155
+
156
+ Attach a parser to every [path-table](./path-tables.md#parsing-rows-with-on-file)
157
+ in the query, so each matched file yields the rows the command prints (a JSON
158
+ array of row objects) instead of the stat columns:
159
+
160
+ ```sh
161
+ dirsql query "SELECT title, author FROM './posts/*.md'" \
162
+ --on-file 'extract.py {path}'
163
+ ```
164
+
165
+ The command follows the [`on-file` hook contract](./hooks.md#on-file) — argv
166
+ splitting, `{path}`/`{root}` placeholders, per-file failure isolation, and the
167
+ timeout. The parser's output is the whole schema; the stat columns are not
168
+ reachable on a parsed path-table. `--on-file` may be given **at most once** (a
169
+ repeat is an error pointing at config files) and never touches config-declared
170
+ tables. It is a `query`-only flag — server mode rejects it as an unknown
171
+ argument.
172
+
154
173
  Errors print the same diagnostic the HTTP `{"error": …}` body carries —
155
174
  config failures, SQL errors, rejected reads, hook failures, timeouts — to
156
175
  stderr, with exit code `1`.
@@ -114,6 +114,39 @@ SELECT path FROM './docs/*.md' WHERE content LIKE '%deprecated%';
114
114
  A file that cannot be read, or is not valid UTF-8, yields `NULL` content rather
115
115
  than failing the query.
116
116
 
117
+ ## Parsing rows with `--on-file`
118
+
119
+ By default a path-table's columns are the stat columns above — one row per
120
+ file. When you want *one row per record inside* each file, attach a parser with
121
+ the `dirsql query` flag [`--on-file`](/reference/cli):
122
+
123
+ ```sh
124
+ dirsql query "SELECT title, author FROM './posts/*.md'" \
125
+ --on-file 'extract.py {path}'
126
+ ```
127
+
128
+ The command runs once per matched file and prints a JSON array of row objects,
129
+ exactly like a declared table's [`on-file` hook](/reference/hooks) — same argv
130
+ splitting, same `{path}`/`{root}` placeholders, same timeout. Its output *is*
131
+ the table:
132
+
133
+ - **The parser supplies the whole schema.** Columns are inferred from the keys
134
+ across the emitted rows. The stat columns (`path`, `size`, …) are **not**
135
+ reachable on a parsed path-table — a parser that wants the path emits it (it
136
+ has `{path}`). The two modes stay cleanly separate.
137
+ - **Failures are isolated per file.** A file whose parser fails (spawn, non-zero
138
+ exit, timeout, or no output) or whose output is not a JSON array of rows
139
+ contributes no rows; a one-line warning naming the file goes to stderr and the
140
+ scan continues. The schema is inferred from the files that did parse.
141
+ - **The skip rules still apply.** A parsed scan honors the same `node_modules`
142
+ /`.git`/`ignore` rules a stat scan does (see below).
143
+
144
+ `--on-file` applies to **every** path-table in the query and may be given **at
145
+ most once**. For different parsers per file set, define named tables in a
146
+ `.dirsql.toml` with their own `on-file` keys and pass it with `-c` — the flag
147
+ never touches config-declared tables. It is a `query`-subcommand flag; server
148
+ mode does not accept it.
149
+
117
150
  ## Freshness and scope
118
151
 
119
152
  A path-table is scanned when the statement runs, so it always reflects the
@@ -146,10 +146,42 @@ struct QueryArgs {
146
146
  /// The SQL to run (a single read-only statement).
147
147
  sql: String,
148
148
 
149
+ /// Attach a parser to every path-table in the query. The command follows
150
+ /// the `on-file` hook contract (`docs/reference/hooks.md`): argv splitting,
151
+ /// `{path}`/`{root}` placeholders, a JSON array of row objects on stdout,
152
+ /// per-file failure isolation, and the hook timeout. With it set, a
153
+ /// path-table's rows and schema come from the parser instead of the stat
154
+ /// columns. One `--on-file` max; for multiple tables use a config file.
155
+ #[arg(long = "on-file")]
156
+ on_file: Vec<String>,
157
+
149
158
  #[command(flatten)]
150
159
  common: ConfigArgs,
151
160
  }
152
161
 
162
+ /// Reduce the repeatable `--on-file` occurrences to at most one parser command.
163
+ ///
164
+ /// `clap` collects repeats into a `Vec` so the error can name config files
165
+ /// (its default "cannot be used multiple times" cannot). Empty → no parser;
166
+ /// exactly one → that command; more than one → an error pointing at config
167
+ /// files, where per-table parsers belong. A whitespace-only command is rejected
168
+ /// up front (the hook contract forbids an empty command).
169
+ fn resolve_on_file(occurrences: &[String]) -> std::result::Result<Option<String>, String> {
170
+ match occurrences {
171
+ [] => Ok(None),
172
+ [command] if command.trim().is_empty() => {
173
+ Err("--on-file needs a non-empty command".to_string())
174
+ }
175
+ [command] => Ok(Some(command.clone())),
176
+ _ => Err(
177
+ "--on-file may be given at most once; it applies to every path-table in the \
178
+ query. For per-table parsers, define tables in a config file with an \
179
+ `on-file` key and pass it with -c."
180
+ .to_string(),
181
+ ),
182
+ }
183
+ }
184
+
153
185
  #[derive(Debug, Args)]
154
186
  struct InitArgs {
155
187
  /// Directory the default `--output` path is resolved against (default:
@@ -185,7 +217,14 @@ async fn main() -> ExitCode {
185
217
  /// message — the same string the HTTP `{"error": …}` body carries — to
186
218
  /// stderr with a non-zero exit.
187
219
  async fn run_query(args: QueryArgs) -> ExitCode {
188
- let state = load_state(&args.common);
220
+ let parser = match resolve_on_file(&args.on_file) {
221
+ Ok(parser) => parser,
222
+ Err(message) => {
223
+ eprintln!("dirsql query: {message}");
224
+ return ExitCode::from(1);
225
+ }
226
+ };
227
+ let state = load_state(&args.common, parser);
189
228
  let pre_query = load_pre_queries(&args.common);
190
229
  let post_query = load_post_queries(&args.common);
191
230
  // Same default the server binds with; the pipeline enforces it.
@@ -246,7 +285,9 @@ fn run_init(args: InitArgs) -> ExitCode {
246
285
  }
247
286
 
248
287
  async fn run_server(cli: Cli) -> ExitCode {
249
- let state = load_state(&cli.common);
288
+ // The server has no `--on-file`: clap rejects it as an unknown flag before
289
+ // reaching here. Path-tables served over HTTP keep their stat columns.
290
+ let state = load_state(&cli.common, None);
250
291
  let mut server_config = ServerConfig::bind(cli.host.clone(), cli.port);
251
292
  for pre_query in load_pre_queries(&cli.common) {
252
293
  server_config = server_config.with_pre_query(pre_query);
@@ -278,12 +319,12 @@ async fn run_server(cli: Cli) -> ExitCode {
278
319
  ExitCode::SUCCESS
279
320
  }
280
321
 
281
- fn load_state(cfg: &ConfigArgs) -> AppState {
322
+ fn load_state(cfg: &ConfigArgs, path_table_parser: Option<String>) -> AppState {
282
323
  // Neither a `-c` nor the launcher's `--include-default` -> index the
283
324
  // invocation directory with no named tables. A `./.dirsql.toml` on disk is
284
325
  // NOT consulted (#602); pass it explicitly with `-c` to use it.
285
326
  if cfg.config.is_empty() && !cfg.include_default {
286
- return load_configless_state(cfg);
327
+ return load_configless_state(cfg, path_table_parser);
287
328
  }
288
329
 
289
330
  let mut builder = DirSQL::builder();
@@ -324,6 +365,12 @@ fn load_state(cfg: &ConfigArgs) -> AppState {
324
365
  .suppress_config_extensions(true);
325
366
  }
326
367
  builder = cfg.apply_persist(builder);
368
+ // `--on-file` touches path-tables only: config `[[table]]` definitions keep
369
+ // their own `on-file` hooks; a path-table named in the query gets this
370
+ // parser regardless of any `-c`.
371
+ if let Some(command) = path_table_parser {
372
+ builder = builder.path_table_parser(command);
373
+ }
327
374
  match builder.build() {
328
375
  Ok(db) => AppState::Ready(db),
329
376
  Err(err) => AppState::Unavailable(format!("failed to load config: {err}")),
@@ -420,7 +467,7 @@ fn load_post_queries(cfg: &ConfigArgs) -> Vec<PostQuery> {
420
467
  /// tables: filesystem queries go through path-tables (`SELECT * FROM './'`),
421
468
  /// and a `files` query fails with a hint pointing at that form (#636). A
422
469
  /// `./.dirsql.toml` in the cwd is not consulted (#602).
423
- fn load_configless_state(cfg: &ConfigArgs) -> AppState {
470
+ fn load_configless_state(cfg: &ConfigArgs, path_table_parser: Option<String>) -> AppState {
424
471
  // Canonicalize for the same reason `load_state` does: `notify` misbehaves
425
472
  // when watching relative paths.
426
473
  let root = match PathBuf::from(".").canonicalize() {
@@ -430,7 +477,10 @@ fn load_configless_state(cfg: &ConfigArgs) -> AppState {
430
477
  }
431
478
  };
432
479
 
433
- let builder = cfg.apply_persist(DirSQL::builder().root(root));
480
+ let mut builder = cfg.apply_persist(DirSQL::builder().root(root));
481
+ if let Some(command) = path_table_parser {
482
+ builder = builder.path_table_parser(command);
483
+ }
434
484
  match builder.build() {
435
485
  Ok(db) => AppState::Ready(db),
436
486
  Err(err) => AppState::Unavailable(format!("failed to build the index: {err}")),
@@ -606,6 +656,81 @@ mod tests {
606
656
  );
607
657
  }
608
658
 
659
+ /// The `on_file` occurrences parsed from a `query` invocation.
660
+ fn query_on_file(argv: &[&str]) -> Vec<String> {
661
+ match Cli::parse_from(argv).command {
662
+ Some(Command::Query(args)) => args.on_file,
663
+ other => panic!("expected a query subcommand, got {other:?}"),
664
+ }
665
+ }
666
+
667
+ #[test]
668
+ fn on_file_absent_leaves_no_occurrences() {
669
+ assert!(query_on_file(&["dirsql", "query", "SELECT 1"]).is_empty());
670
+ }
671
+
672
+ #[test]
673
+ fn on_file_parses_after_the_query_subcommand() {
674
+ assert_eq!(
675
+ query_on_file(&["dirsql", "query", "SELECT 1", "--on-file", "cat {path}"]),
676
+ vec!["cat {path}".to_string()]
677
+ );
678
+ }
679
+
680
+ #[test]
681
+ fn on_file_collects_every_repeat_for_the_arity_check() {
682
+ // clap collects repeats; `resolve_on_file` turns >1 into the pointed
683
+ // error rather than clap's generic "cannot be used multiple times".
684
+ assert_eq!(
685
+ query_on_file(&[
686
+ "dirsql",
687
+ "query",
688
+ "SELECT 1",
689
+ "--on-file",
690
+ "a",
691
+ "--on-file",
692
+ "b"
693
+ ]),
694
+ vec!["a".to_string(), "b".to_string()]
695
+ );
696
+ }
697
+
698
+ #[test]
699
+ fn on_file_is_rejected_before_a_query_subcommand() {
700
+ // Like the other subcommand-local flags, `--on-file` ahead of the
701
+ // subcommand is not a server flag: clap rejects it.
702
+ assert!(Cli::try_parse_from(["dirsql", "--on-file", "cat", "query", "SELECT 1"]).is_err());
703
+ }
704
+
705
+ #[test]
706
+ fn resolve_on_file_is_none_without_the_flag() {
707
+ assert_eq!(resolve_on_file(&[]), Ok(None));
708
+ }
709
+
710
+ #[test]
711
+ fn resolve_on_file_returns_the_single_command() {
712
+ assert_eq!(
713
+ resolve_on_file(&["cat {path}".to_string()]),
714
+ Ok(Some("cat {path}".to_string()))
715
+ );
716
+ }
717
+
718
+ #[test]
719
+ fn resolve_on_file_rejects_a_blank_command() {
720
+ let err = resolve_on_file(&[" ".to_string()]).unwrap_err();
721
+ assert!(err.contains("non-empty"), "got: {err}");
722
+ }
723
+
724
+ #[test]
725
+ fn resolve_on_file_rejects_a_repeat_and_points_at_config_files() {
726
+ let err = resolve_on_file(&["a".to_string(), "b".to_string()]).unwrap_err();
727
+ assert!(err.contains("at most once"), "got: {err}");
728
+ assert!(
729
+ err.contains("config file"),
730
+ "the error must point at config files, got: {err}"
731
+ );
732
+ }
733
+
609
734
  #[test]
610
735
  fn parse_extension_specs_handles_bare_path_and_entrypoint() {
611
736
  let specs = vec![
@@ -3,6 +3,7 @@ use std::collections::{HashMap, HashSet};
3
3
  use std::path::{Path, PathBuf};
4
4
  use thiserror::Error;
5
5
 
6
+ use crate::parsed_vtab;
6
7
  use crate::path_table::{self, PathTable, Resolution};
7
8
  use crate::vtab;
8
9
 
@@ -107,18 +108,41 @@ fn quote_identifier(s: &str) -> String {
107
108
  /// persistent cache file — so a path-table can never leak into a persisted
108
109
  /// `sqlite_master`. `IF NOT EXISTS` makes a repeat reference a no-op, which is
109
110
  /// why no cross-call registry is needed.
110
- fn path_table_ddl(name: &str, table: &PathTable, ignore: &[String]) -> String {
111
- let mut args = vec![
112
- quote_literal(&table.root.to_string_lossy()),
113
- quote_literal(&table.glob),
114
- quote_literal(&table.path_prefix),
115
- ];
111
+ ///
112
+ /// With a `parser` present (the CLI's `--on-file`), the table is minted over
113
+ /// the parsed module instead: its rows and schema come from the parser, not
114
+ /// the stat columns, and the `path_prefix` is irrelevant (a parser wanting a
115
+ /// path emits it). Both forms carry the same ignore rules.
116
+ fn path_table_ddl(
117
+ name: &str,
118
+ table: &PathTable,
119
+ ignore: &[String],
120
+ parser: Option<&str>,
121
+ ) -> String {
122
+ let (module, mut args) = match parser {
123
+ Some(command) => (
124
+ parsed_vtab::MODULE_NAME,
125
+ vec![
126
+ quote_literal(&table.root.to_string_lossy()),
127
+ quote_literal(&table.glob),
128
+ quote_literal(command),
129
+ ],
130
+ ),
131
+ None => (
132
+ vtab::MODULE_NAME,
133
+ vec![
134
+ quote_literal(&table.root.to_string_lossy()),
135
+ quote_literal(&table.glob),
136
+ quote_literal(&table.path_prefix),
137
+ ],
138
+ ),
139
+ };
116
140
  args.extend(ignore.iter().map(|p| quote_literal(p)));
117
141
 
118
142
  format!(
119
143
  "CREATE VIRTUAL TABLE IF NOT EXISTS temp.{} USING {}({})",
120
144
  quote_identifier(name),
121
- vtab::MODULE_NAME,
145
+ module,
122
146
  args.join(", "),
123
147
  )
124
148
  }
@@ -184,6 +208,10 @@ pub struct Db {
184
208
  hint_legacy_files_table: bool,
185
209
  /// Skip rules a path-table scan applies, seeded with the built-in defaults.
186
210
  path_table_ignore: Vec<String>,
211
+ /// When set (the CLI's `--on-file`), every path-table is minted over the
212
+ /// parser instead of the stat columns: its rows and schema come from the
213
+ /// command's output. `None` keeps the stat path-table behavior.
214
+ path_table_parser: Option<String>,
187
215
  }
188
216
 
189
217
  /// The skip rules a fresh `Db` starts with.
@@ -210,11 +238,13 @@ impl Db {
210
238
  let conn = Connection::open("")?;
211
239
  ensure_internal_rows_table(&conn)?;
212
240
  vtab::load_module(&conn)?;
241
+ parsed_vtab::load_module(&conn)?;
213
242
  Ok(Self {
214
243
  conn,
215
244
  path_table_root: None,
216
245
  hint_legacy_files_table: false,
217
246
  path_table_ignore: default_path_table_ignore(),
247
+ path_table_parser: None,
218
248
  })
219
249
  }
220
250
 
@@ -228,11 +258,13 @@ impl Db {
228
258
  conn.pragma_update(None, "synchronous", "NORMAL")?;
229
259
  ensure_internal_rows_table(&conn)?;
230
260
  vtab::load_module(&conn)?;
261
+ parsed_vtab::load_module(&conn)?;
231
262
  Ok(Self {
232
263
  conn,
233
264
  path_table_root: None,
234
265
  hint_legacy_files_table: false,
235
266
  path_table_ignore: default_path_table_ignore(),
267
+ path_table_parser: None,
236
268
  })
237
269
  }
238
270
 
@@ -255,6 +287,13 @@ impl Db {
255
287
  self.path_table_ignore.extend(patterns);
256
288
  }
257
289
 
290
+ /// Attach a parser command to every path-table minted on this connection
291
+ /// (the CLI's `--on-file`). With it set, a path-table's rows and schema
292
+ /// come from the command's output instead of the stat columns.
293
+ pub fn set_path_table_parser(&mut self, command: String) {
294
+ self.path_table_parser = Some(command);
295
+ }
296
+
258
297
  /// Borrow the underlying SQLite connection. Internal use only — exposed
259
298
  /// to the `persist` module so it can manage the sidecar tables.
260
299
  #[doc(hidden)]
@@ -710,8 +749,12 @@ impl Db {
710
749
  /// straight on the connection rather than through [`query`](Self::query),
711
750
  /// which classifies `CREATE VIRTUAL TABLE` as a write and would reject it.
712
751
  fn create_path_table(&self, name: &str, table: &PathTable) -> Result<()> {
713
- self.conn
714
- .execute_batch(&path_table_ddl(name, table, &self.path_table_ignore))?;
752
+ self.conn.execute_batch(&path_table_ddl(
753
+ name,
754
+ table,
755
+ &self.path_table_ignore,
756
+ self.path_table_parser.as_deref(),
757
+ ))?;
715
758
  Ok(())
716
759
  }
717
760
  }
@@ -2095,7 +2138,7 @@ mod tests {
2095
2138
 
2096
2139
  #[test]
2097
2140
  fn path_table_ddl_creates_in_temp_if_not_exists() {
2098
- let ddl = path_table_ddl("./docs/*.md", &docs_path_table(), &[]);
2141
+ let ddl = path_table_ddl("./docs/*.md", &docs_path_table(), &[], None);
2099
2142
  assert_eq!(
2100
2143
  ddl,
2101
2144
  "CREATE VIRTUAL TABLE IF NOT EXISTS temp.\"./docs/*.md\" \
@@ -2111,10 +2154,10 @@ mod tests {
2111
2154
  path_prefix: "/var/log".to_string(),
2112
2155
  };
2113
2156
  assert!(
2114
- path_table_ddl("/var/log/*.log", &table, &[])
2157
+ path_table_ddl("/var/log/*.log", &table, &[], None)
2115
2158
  .ends_with("'/var/log', '*.log', '/var/log')"),
2116
2159
  "got: {}",
2117
- path_table_ddl("/var/log/*.log", &table, &[])
2160
+ path_table_ddl("/var/log/*.log", &table, &[], None)
2118
2161
  );
2119
2162
  }
2120
2163
 
@@ -2124,6 +2167,7 @@ mod tests {
2124
2167
  "./",
2125
2168
  &docs_path_table(),
2126
2169
  &["node_modules/**".to_string(), "*.tmp".to_string()],
2170
+ None,
2127
2171
  );
2128
2172
  assert!(
2129
2173
  ddl.ends_with("'', 'node_modules/**', '*.tmp')"),
@@ -2131,6 +2175,53 @@ mod tests {
2131
2175
  );
2132
2176
  }
2133
2177
 
2178
+ #[test]
2179
+ fn path_table_ddl_uses_the_parsed_module_when_a_parser_is_set() {
2180
+ let ddl = path_table_ddl("./docs/*.md", &docs_path_table(), &[], Some("cat {path}"));
2181
+ assert_eq!(
2182
+ ddl,
2183
+ "CREATE VIRTUAL TABLE IF NOT EXISTS temp.\"./docs/*.md\" \
2184
+ USING dirsql_parsed('/root', 'docs/*.md', 'cat {path}')"
2185
+ );
2186
+ }
2187
+
2188
+ #[test]
2189
+ fn path_table_ddl_parser_form_omits_the_path_prefix_and_carries_ignore() {
2190
+ let table = PathTable {
2191
+ root: PathBuf::from("/var/log"),
2192
+ glob: "*.log".to_string(),
2193
+ path_prefix: "/var/log".to_string(),
2194
+ };
2195
+ let ddl = path_table_ddl(
2196
+ "/var/log/*.log",
2197
+ &table,
2198
+ &["node_modules/**".to_string()],
2199
+ Some("parse.py {path}"),
2200
+ );
2201
+ assert!(
2202
+ ddl.ends_with(
2203
+ "USING dirsql_parsed('/var/log', '*.log', 'parse.py {path}', 'node_modules/**')"
2204
+ ),
2205
+ "the parser form drops the path prefix and keeps ignore rules, got: {ddl}"
2206
+ );
2207
+ }
2208
+
2209
+ #[test]
2210
+ fn path_table_ddl_parser_form_quotes_a_command_with_a_quote() {
2211
+ let ddl = path_table_ddl("./", &docs_path_table(), &[], Some("sh -c 'echo hi'"));
2212
+ assert!(
2213
+ ddl.contains("'sh -c ''echo hi'''"),
2214
+ "an embedded quote must be doubled, got: {ddl}"
2215
+ );
2216
+ }
2217
+
2218
+ #[test]
2219
+ fn set_path_table_parser_arms_the_parsed_module() {
2220
+ let mut db = Db::new().unwrap();
2221
+ db.set_path_table_parser("cat {path}".to_string());
2222
+ assert_eq!(db.path_table_parser.as_deref(), Some("cat {path}"));
2223
+ }
2224
+
2134
2225
  #[test]
2135
2226
  fn query_leaves_a_path_name_alone_without_a_root() {
2136
2227
  let db = Db::new().unwrap();