dirsql 0.3.126__tar.gz → 0.4.0__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 (215) hide show
  1. {dirsql-0.3.126 → dirsql-0.4.0}/Cargo.lock +1 -1
  2. {dirsql-0.3.126 → dirsql-0.4.0}/PKG-INFO +1 -1
  3. {dirsql-0.3.126 → dirsql-0.4.0}/docs/.vitepress/config.ts +12 -1
  4. {dirsql-0.3.126 → dirsql-0.4.0}/docs/AGENTS.md +2 -1
  5. {dirsql-0.3.126/packages/rust → dirsql-0.4.0}/docs/getting-started.md +47 -30
  6. dirsql-0.4.0/docs/howto/columns-from-paths.md +77 -0
  7. dirsql-0.4.0/docs/howto/define-tables.md +76 -0
  8. {dirsql-0.3.126/packages/python → dirsql-0.4.0}/docs/howto/embed.md +20 -8
  9. {dirsql-0.3.126/packages/rust → dirsql-0.4.0}/docs/howto/extract-from-contents.md +7 -8
  10. {dirsql-0.3.126/packages/rust → dirsql-0.4.0}/docs/howto/parse-files-into-columns.md +3 -4
  11. {dirsql-0.3.126/packages/python → dirsql-0.4.0}/docs/howto/react-to-changes.md +7 -4
  12. {dirsql-0.3.126 → dirsql-0.4.0}/docs/howto/search-by-meaning.md +9 -5
  13. {dirsql-0.3.126/packages/rust → dirsql-0.4.0}/docs/howto/skip-files.md +5 -4
  14. {dirsql-0.3.126 → dirsql-0.4.0}/docs/howto/write-a-plugin.md +10 -5
  15. dirsql-0.4.0/docs/reference/columns.md +70 -0
  16. {dirsql-0.3.126/packages/python → dirsql-0.4.0}/docs/reference/config.md +33 -25
  17. {dirsql-0.3.126/packages/python → dirsql-0.4.0}/docs/reference/hooks.md +4 -4
  18. {dirsql-0.3.126/packages/rust → dirsql-0.4.0}/docs/reference/sdk.md +9 -9
  19. {dirsql-0.3.126 → dirsql-0.4.0}/packages/python/Cargo.toml +1 -1
  20. {dirsql-0.3.126 → dirsql-0.4.0}/packages/python/docs/.vitepress/config.ts +12 -1
  21. {dirsql-0.3.126 → dirsql-0.4.0}/packages/python/docs/AGENTS.md +2 -1
  22. {dirsql-0.3.126 → dirsql-0.4.0/packages/python}/docs/getting-started.md +47 -30
  23. dirsql-0.4.0/packages/python/docs/howto/columns-from-paths.md +77 -0
  24. dirsql-0.4.0/packages/python/docs/howto/define-tables.md +76 -0
  25. {dirsql-0.3.126 → dirsql-0.4.0/packages/python}/docs/howto/embed.md +20 -8
  26. {dirsql-0.3.126 → dirsql-0.4.0}/packages/python/docs/howto/extract-from-contents.md +7 -8
  27. {dirsql-0.3.126 → dirsql-0.4.0}/packages/python/docs/howto/parse-files-into-columns.md +3 -4
  28. {dirsql-0.3.126/packages/rust → dirsql-0.4.0/packages/python}/docs/howto/react-to-changes.md +7 -4
  29. {dirsql-0.3.126/packages/rust → dirsql-0.4.0/packages/python}/docs/howto/search-by-meaning.md +9 -5
  30. {dirsql-0.3.126 → dirsql-0.4.0/packages/python}/docs/howto/skip-files.md +5 -4
  31. {dirsql-0.3.126/packages/rust → dirsql-0.4.0/packages/python}/docs/howto/write-a-plugin.md +10 -5
  32. dirsql-0.4.0/packages/python/docs/reference/columns.md +70 -0
  33. {dirsql-0.3.126 → dirsql-0.4.0/packages/python}/docs/reference/config.md +33 -25
  34. {dirsql-0.3.126/packages/rust → dirsql-0.4.0/packages/python}/docs/reference/hooks.md +4 -4
  35. {dirsql-0.3.126 → dirsql-0.4.0}/packages/python/docs/reference/sdk.md +9 -9
  36. dirsql-0.4.0/packages/python/e2e-attestations/chore-681-ruff-016.json +7 -0
  37. {dirsql-0.3.126/packages/python → dirsql-0.4.0/packages/rust}/docs/getting-started.md +47 -30
  38. dirsql-0.4.0/packages/rust/docs/howto/columns-from-paths.md +77 -0
  39. dirsql-0.4.0/packages/rust/docs/howto/define-tables.md +76 -0
  40. {dirsql-0.3.126 → dirsql-0.4.0}/packages/rust/docs/howto/embed.md +20 -8
  41. {dirsql-0.3.126 → dirsql-0.4.0/packages/rust}/docs/howto/extract-from-contents.md +7 -8
  42. {dirsql-0.3.126 → dirsql-0.4.0/packages/rust}/docs/howto/parse-files-into-columns.md +3 -4
  43. {dirsql-0.3.126 → dirsql-0.4.0/packages/rust}/docs/howto/react-to-changes.md +7 -4
  44. {dirsql-0.3.126/packages/python → dirsql-0.4.0/packages/rust}/docs/howto/search-by-meaning.md +9 -5
  45. {dirsql-0.3.126/packages/python → dirsql-0.4.0/packages/rust}/docs/howto/skip-files.md +5 -4
  46. {dirsql-0.3.126/packages/python → dirsql-0.4.0/packages/rust}/docs/howto/write-a-plugin.md +10 -5
  47. dirsql-0.4.0/packages/rust/docs/reference/columns.md +70 -0
  48. {dirsql-0.3.126 → dirsql-0.4.0}/packages/rust/docs/reference/config.md +33 -25
  49. {dirsql-0.3.126 → dirsql-0.4.0/packages/rust}/docs/reference/hooks.md +4 -4
  50. {dirsql-0.3.126 → dirsql-0.4.0/packages/rust}/docs/reference/sdk.md +9 -9
  51. {dirsql-0.3.126 → dirsql-0.4.0}/pyproject.toml +43 -1
  52. dirsql-0.3.126/docs/howto/columns-from-paths.md +0 -60
  53. dirsql-0.3.126/docs/howto/define-tables.md +0 -58
  54. dirsql-0.3.126/docs/reference/columns.md +0 -80
  55. dirsql-0.3.126/packages/python/docs/howto/columns-from-paths.md +0 -60
  56. dirsql-0.3.126/packages/python/docs/howto/define-tables.md +0 -58
  57. dirsql-0.3.126/packages/python/docs/reference/columns.md +0 -80
  58. dirsql-0.3.126/packages/python/e2e-attestations/feat-634-slice4-hookless-error.json +0 -7
  59. dirsql-0.3.126/packages/rust/docs/howto/columns-from-paths.md +0 -60
  60. dirsql-0.3.126/packages/rust/docs/howto/define-tables.md +0 -58
  61. dirsql-0.3.126/packages/rust/docs/reference/columns.md +0 -80
  62. {dirsql-0.3.126 → dirsql-0.4.0}/Cargo.toml +0 -0
  63. {dirsql-0.3.126/packages/python → dirsql-0.4.0}/README.md +0 -0
  64. {dirsql-0.3.126 → dirsql-0.4.0}/dirsql/__init__.py +0 -0
  65. {dirsql-0.3.126 → dirsql-0.4.0}/dirsql/_async.py +0 -0
  66. {dirsql-0.3.126 → dirsql-0.4.0}/dirsql/_dirsql.pyi +0 -0
  67. {dirsql-0.3.126/packages/python/tests/integration/binding → dirsql-0.4.0/dirsql/cli}/__init__.py +0 -0
  68. {dirsql-0.3.126 → dirsql-0.4.0}/dirsql/cli/binary_path.py +0 -0
  69. {dirsql-0.3.126/packages/python/tests/e2e/fixtures/dirsql_plugin_fixture → dirsql-0.4.0/dirsql/cli/discover_plugins}/__init__.py +0 -0
  70. {dirsql-0.3.126 → dirsql-0.4.0}/dirsql/cli/discover_plugins/discovered_fragments.py +0 -0
  71. {dirsql-0.3.126 → dirsql-0.4.0}/dirsql/cli/discover_plugins/discovery_disabled.py +0 -0
  72. {dirsql-0.3.126 → dirsql-0.4.0}/dirsql/cli/discover_plugins/fragment_path.py +0 -0
  73. {dirsql-0.3.126 → dirsql-0.4.0}/dirsql/cli/discover_plugins/user_passed_config.py +0 -0
  74. {dirsql-0.3.126 → dirsql-0.4.0}/dirsql/cli/discover_plugins/with_discovered_plugins.py +0 -0
  75. {dirsql-0.3.126 → dirsql-0.4.0}/dirsql/cli/is_windows.py +0 -0
  76. {dirsql-0.3.126 → dirsql-0.4.0}/dirsql/cli/main.py +0 -0
  77. {dirsql-0.3.126 → dirsql-0.4.0}/dirsql/cli/resolve_config_extensions.py +0 -0
  78. {dirsql-0.3.126 → dirsql-0.4.0}/dirsql/py.typed +0 -0
  79. {dirsql-0.3.126 → dirsql-0.4.0}/dirsql/resolve_config_extensions.py +0 -0
  80. {dirsql-0.3.126 → dirsql-0.4.0}/dirsql/resolve_extension.py +0 -0
  81. {dirsql-0.3.126/packages/python → dirsql-0.4.0}/docs/.claude/CLAUDE.md +0 -0
  82. {dirsql-0.3.126/packages/python → dirsql-0.4.0}/docs/.vitepress/theme/index.ts +0 -0
  83. {dirsql-0.3.126/packages/python → dirsql-0.4.0}/docs/.vitepress/theme/lang.ts +0 -0
  84. {dirsql-0.3.126/packages/rust → dirsql-0.4.0}/docs/explanation.md +0 -0
  85. {dirsql-0.3.126/packages/rust → dirsql-0.4.0}/docs/howto/load-extension.md +0 -0
  86. {dirsql-0.3.126/packages/rust → dirsql-0.4.0}/docs/howto/persist.md +0 -0
  87. {dirsql-0.3.126/packages/rust → dirsql-0.4.0}/docs/howto/query-without-config.md +0 -0
  88. {dirsql-0.3.126/packages/rust → dirsql-0.4.0}/docs/index.md +0 -0
  89. {dirsql-0.3.126/packages/rust → dirsql-0.4.0}/docs/migrations.md +0 -0
  90. {dirsql-0.3.126/packages/python → dirsql-0.4.0}/docs/package.json +0 -0
  91. {dirsql-0.3.126/packages/python → dirsql-0.4.0}/docs/pnpm-lock.yaml +0 -0
  92. {dirsql-0.3.126/packages/python → dirsql-0.4.0}/docs/pnpm-workspace.yaml +0 -0
  93. {dirsql-0.3.126/packages/rust → dirsql-0.4.0}/docs/reference/cli.md +0 -0
  94. {dirsql-0.3.126/packages/rust → dirsql-0.4.0}/docs/reference/http-api.md +0 -0
  95. {dirsql-0.3.126/packages/rust → dirsql-0.4.0}/docs/reference/path-tables.md +0 -0
  96. {dirsql-0.3.126 → dirsql-0.4.0}/packages/python/CHANGELOG.md +0 -0
  97. {dirsql-0.3.126 → dirsql-0.4.0}/packages/python/MIGRATIONS.md +0 -0
  98. {dirsql-0.3.126 → dirsql-0.4.0/packages/python}/README.md +0 -0
  99. {dirsql-0.3.126 → dirsql-0.4.0}/packages/python/changelog.d/2026-07-13-drop-cwd-config-auto-detect.md +0 -0
  100. {dirsql-0.3.126 → dirsql-0.4.0}/packages/python/changelog.d/2026-07-13-plugin-discovery.md +0 -0
  101. {dirsql-0.3.126/packages/rust → dirsql-0.4.0/packages/python}/changelog.d/2026-07-13-rename-extract-to-on-file.md +0 -0
  102. {dirsql-0.3.126 → dirsql-0.4.0}/packages/python/changelog.d/2026-07-13-repeatable-config.md +0 -0
  103. {dirsql-0.3.126 → dirsql-0.4.0}/packages/python/changelog.d/2026-07-13-sdk-default-baked-in-config.md +0 -0
  104. {dirsql-0.3.126/packages/rust → dirsql-0.4.0/packages/python}/changelog.d/2026-07-20-drop-implicit-files-table.md +0 -0
  105. {dirsql-0.3.126 → dirsql-0.4.0}/packages/python/changelog.d/2026-07-21-remove-glob-captures.md +0 -0
  106. {dirsql-0.3.126 → dirsql-0.4.0}/packages/python/changelog.d/2026-07-24-hookless-table-config-error.md +0 -0
  107. {dirsql-0.3.126 → dirsql-0.4.0}/packages/python/changelog.d/2026-07-24-remove-stat-fact-injection.md +0 -0
  108. {dirsql-0.3.126 → dirsql-0.4.0}/packages/python/changelog.d/README.md +0 -0
  109. {dirsql-0.3.126 → dirsql-0.4.0}/packages/python/conftest.py +0 -0
  110. {dirsql-0.3.126 → dirsql-0.4.0/packages/python}/docs/.claude/CLAUDE.md +0 -0
  111. {dirsql-0.3.126 → dirsql-0.4.0/packages/python}/docs/.vitepress/theme/index.ts +0 -0
  112. {dirsql-0.3.126 → dirsql-0.4.0/packages/python}/docs/.vitepress/theme/lang.ts +0 -0
  113. {dirsql-0.3.126 → dirsql-0.4.0}/packages/python/docs/explanation.md +0 -0
  114. {dirsql-0.3.126 → dirsql-0.4.0}/packages/python/docs/howto/load-extension.md +0 -0
  115. {dirsql-0.3.126 → dirsql-0.4.0}/packages/python/docs/howto/persist.md +0 -0
  116. {dirsql-0.3.126 → dirsql-0.4.0}/packages/python/docs/howto/query-without-config.md +0 -0
  117. {dirsql-0.3.126 → dirsql-0.4.0}/packages/python/docs/index.md +0 -0
  118. {dirsql-0.3.126 → dirsql-0.4.0}/packages/python/docs/migrations.md +0 -0
  119. {dirsql-0.3.126 → dirsql-0.4.0/packages/python}/docs/package.json +0 -0
  120. {dirsql-0.3.126 → dirsql-0.4.0/packages/python}/docs/pnpm-lock.yaml +0 -0
  121. {dirsql-0.3.126 → dirsql-0.4.0/packages/python}/docs/pnpm-workspace.yaml +0 -0
  122. {dirsql-0.3.126 → dirsql-0.4.0}/packages/python/docs/reference/cli.md +0 -0
  123. {dirsql-0.3.126 → dirsql-0.4.0}/packages/python/docs/reference/http-api.md +0 -0
  124. {dirsql-0.3.126 → dirsql-0.4.0}/packages/python/docs/reference/path-tables.md +0 -0
  125. {dirsql-0.3.126 → dirsql-0.4.0}/packages/python/migrations.d/2026-07-13-drop-cwd-config-auto-detect.md +0 -0
  126. {dirsql-0.3.126/packages/rust → dirsql-0.4.0/packages/python}/migrations.d/2026-07-13-rename-extract-to-on-file.md +0 -0
  127. {dirsql-0.3.126 → dirsql-0.4.0}/packages/python/migrations.d/2026-07-13-sdk-default-baked-in-config.md +0 -0
  128. {dirsql-0.3.126/packages/rust → dirsql-0.4.0/packages/python}/migrations.d/2026-07-20-drop-implicit-files-table.md +0 -0
  129. {dirsql-0.3.126 → dirsql-0.4.0}/packages/python/migrations.d/2026-07-21-remove-glob-captures.md +0 -0
  130. {dirsql-0.3.126 → dirsql-0.4.0}/packages/python/migrations.d/2026-07-24-hookless-table-config-error.md +0 -0
  131. {dirsql-0.3.126 → dirsql-0.4.0}/packages/python/migrations.d/2026-07-24-remove-stat-fact-injection.md +0 -0
  132. {dirsql-0.3.126 → dirsql-0.4.0}/packages/python/migrations.d/README.md +0 -0
  133. {dirsql-0.3.126 → dirsql-0.4.0}/packages/python/src/lib.rs +0 -0
  134. {dirsql-0.3.126/packages/python/tests/integration/hermetic → dirsql-0.4.0/packages/python/tests}/__init__.py +0 -0
  135. {dirsql-0.3.126 → dirsql-0.4.0}/packages/python/tests/conftest.py +0 -0
  136. {dirsql-0.3.126 → dirsql-0.4.0}/packages/python/tests/e2e/__init__.py +0 -0
  137. {dirsql-0.3.126/dirsql/cli/discover_plugins → dirsql-0.4.0/packages/python/tests/e2e/fixtures/dirsql_plugin_fixture}/__init__.py +0 -0
  138. {dirsql-0.3.126 → dirsql-0.4.0}/packages/python/tests/e2e/fixtures/dirsql_plugin_fixture/dirsql.toml +0 -0
  139. {dirsql-0.3.126 → dirsql-0.4.0}/packages/python/tests/integration/__init__.py +0 -0
  140. {dirsql-0.3.126/dirsql/cli → dirsql-0.4.0/packages/python/tests/integration/binding}/__init__.py +0 -0
  141. {dirsql-0.3.126/packages/python/tests → dirsql-0.4.0/packages/python/tests/integration/hermetic}/__init__.py +0 -0
  142. {dirsql-0.3.126 → dirsql-0.4.0}/packages/rust/CHANGELOG.md +0 -0
  143. {dirsql-0.3.126 → dirsql-0.4.0}/packages/rust/Cargo.toml +0 -0
  144. {dirsql-0.3.126 → dirsql-0.4.0}/packages/rust/MIGRATIONS.md +0 -0
  145. {dirsql-0.3.126 → dirsql-0.4.0}/packages/rust/README.md +0 -0
  146. {dirsql-0.3.126 → dirsql-0.4.0}/packages/rust/benches/db_bench.rs +0 -0
  147. {dirsql-0.3.126 → dirsql-0.4.0}/packages/rust/benches/differ_bench.rs +0 -0
  148. {dirsql-0.3.126 → dirsql-0.4.0}/packages/rust/benches/matcher_bench.rs +0 -0
  149. {dirsql-0.3.126 → dirsql-0.4.0}/packages/rust/benches/scanner_bench.rs +0 -0
  150. {dirsql-0.3.126 → dirsql-0.4.0}/packages/rust/changelog.d/2026-07-13-batch-ingest-transaction.md +0 -0
  151. {dirsql-0.3.126 → dirsql-0.4.0}/packages/rust/changelog.d/2026-07-13-chained-pre-post-query-hooks.md +0 -0
  152. {dirsql-0.3.126 → dirsql-0.4.0}/packages/rust/changelog.d/2026-07-13-drop-cwd-config-auto-detect.md +0 -0
  153. {dirsql-0.3.126 → dirsql-0.4.0}/packages/rust/changelog.d/2026-07-13-fan-out-file-table-matching.md +0 -0
  154. {dirsql-0.3.126 → dirsql-0.4.0}/packages/rust/changelog.d/2026-07-13-persist-wal.md +0 -0
  155. {dirsql-0.3.126/packages/python → dirsql-0.4.0/packages/rust}/changelog.d/2026-07-13-rename-extract-to-on-file.md +0 -0
  156. {dirsql-0.3.126 → dirsql-0.4.0}/packages/rust/changelog.d/2026-07-13-repeatable-builder-config.md +0 -0
  157. {dirsql-0.3.126 → dirsql-0.4.0}/packages/rust/changelog.d/2026-07-13-repeatable-cli-config.md +0 -0
  158. {dirsql-0.3.126 → dirsql-0.4.0}/packages/rust/changelog.d/2026-07-13-sdk-default-baked-in-config.md +0 -0
  159. {dirsql-0.3.126 → dirsql-0.4.0}/packages/rust/changelog.d/2026-07-13-subcommand-local-config-flags.md +0 -0
  160. {dirsql-0.3.126 → dirsql-0.4.0}/packages/rust/changelog.d/2026-07-19-path-table-query-fallback.md +0 -0
  161. {dirsql-0.3.126 → dirsql-0.4.0}/packages/rust/changelog.d/2026-07-19-path-table-vtab.md +0 -0
  162. {dirsql-0.3.126/packages/python → dirsql-0.4.0/packages/rust}/changelog.d/2026-07-20-drop-implicit-files-table.md +0 -0
  163. {dirsql-0.3.126 → dirsql-0.4.0}/packages/rust/changelog.d/2026-07-20-path-table-glob-semantics.md +0 -0
  164. {dirsql-0.3.126 → dirsql-0.4.0}/packages/rust/changelog.d/2026-07-20-schema-inference.md +0 -0
  165. {dirsql-0.3.126 → dirsql-0.4.0}/packages/rust/changelog.d/2026-07-21-on-file-flag.md +0 -0
  166. {dirsql-0.3.126 → dirsql-0.4.0}/packages/rust/changelog.d/2026-07-21-remove-glob-captures.md +0 -0
  167. {dirsql-0.3.126 → dirsql-0.4.0}/packages/rust/changelog.d/2026-07-23-init-escalation-example.md +0 -0
  168. {dirsql-0.3.126 → dirsql-0.4.0}/packages/rust/changelog.d/2026-07-23-query-default-cli.md +0 -0
  169. {dirsql-0.3.126 → dirsql-0.4.0}/packages/rust/changelog.d/2026-07-24-hookless-table-config-error.md +0 -0
  170. {dirsql-0.3.126 → dirsql-0.4.0}/packages/rust/changelog.d/2026-07-24-remove-stat-fact-injection.md +0 -0
  171. {dirsql-0.3.126 → dirsql-0.4.0}/packages/rust/changelog.d/README.md +0 -0
  172. {dirsql-0.3.126 → dirsql-0.4.0/packages/rust}/docs/explanation.md +0 -0
  173. {dirsql-0.3.126 → dirsql-0.4.0/packages/rust}/docs/howto/load-extension.md +0 -0
  174. {dirsql-0.3.126 → dirsql-0.4.0/packages/rust}/docs/howto/persist.md +0 -0
  175. {dirsql-0.3.126 → dirsql-0.4.0/packages/rust}/docs/howto/query-without-config.md +0 -0
  176. {dirsql-0.3.126 → dirsql-0.4.0/packages/rust}/docs/index.md +0 -0
  177. {dirsql-0.3.126 → dirsql-0.4.0/packages/rust}/docs/migrations.md +0 -0
  178. {dirsql-0.3.126 → dirsql-0.4.0/packages/rust}/docs/reference/cli.md +0 -0
  179. {dirsql-0.3.126 → dirsql-0.4.0/packages/rust}/docs/reference/http-api.md +0 -0
  180. {dirsql-0.3.126 → dirsql-0.4.0/packages/rust}/docs/reference/path-tables.md +0 -0
  181. {dirsql-0.3.126 → dirsql-0.4.0}/packages/rust/migrations.d/2026-07-13-drop-cwd-config-auto-detect.md +0 -0
  182. {dirsql-0.3.126 → dirsql-0.4.0}/packages/rust/migrations.d/2026-07-13-fan-out-file-table-matching.md +0 -0
  183. {dirsql-0.3.126 → dirsql-0.4.0}/packages/rust/migrations.d/2026-07-13-persist-wal.md +0 -0
  184. {dirsql-0.3.126/packages/python → dirsql-0.4.0/packages/rust}/migrations.d/2026-07-13-rename-extract-to-on-file.md +0 -0
  185. {dirsql-0.3.126 → dirsql-0.4.0}/packages/rust/migrations.d/2026-07-13-sdk-default-baked-in-config.md +0 -0
  186. {dirsql-0.3.126 → dirsql-0.4.0}/packages/rust/migrations.d/2026-07-13-subcommand-local-config-flags.md +0 -0
  187. {dirsql-0.3.126/packages/python → dirsql-0.4.0/packages/rust}/migrations.d/2026-07-20-drop-implicit-files-table.md +0 -0
  188. {dirsql-0.3.126 → dirsql-0.4.0}/packages/rust/migrations.d/2026-07-20-path-table-glob-semantics.md +0 -0
  189. {dirsql-0.3.126 → dirsql-0.4.0}/packages/rust/migrations.d/2026-07-21-remove-glob-captures.md +0 -0
  190. {dirsql-0.3.126 → dirsql-0.4.0}/packages/rust/migrations.d/2026-07-23-init-escalation-example.md +0 -0
  191. {dirsql-0.3.126 → dirsql-0.4.0}/packages/rust/migrations.d/2026-07-23-query-default-cli.md +0 -0
  192. {dirsql-0.3.126 → dirsql-0.4.0}/packages/rust/migrations.d/2026-07-24-hookless-table-config-error.md +0 -0
  193. {dirsql-0.3.126 → dirsql-0.4.0}/packages/rust/migrations.d/2026-07-24-remove-stat-fact-injection.md +0 -0
  194. {dirsql-0.3.126 → dirsql-0.4.0}/packages/rust/migrations.d/README.md +0 -0
  195. {dirsql-0.3.126 → dirsql-0.4.0}/packages/rust/src/bin/dirsql.rs +0 -0
  196. {dirsql-0.3.126 → dirsql-0.4.0}/packages/rust/src/cli/execute.rs +0 -0
  197. {dirsql-0.3.126 → dirsql-0.4.0}/packages/rust/src/cli/init.rs +0 -0
  198. {dirsql-0.3.126 → dirsql-0.4.0}/packages/rust/src/cli/mod.rs +0 -0
  199. {dirsql-0.3.126 → dirsql-0.4.0}/packages/rust/src/cli/router.rs +0 -0
  200. {dirsql-0.3.126 → dirsql-0.4.0}/packages/rust/src/cli/serialize.rs +0 -0
  201. {dirsql-0.3.126 → dirsql-0.4.0}/packages/rust/src/cli/server.rs +0 -0
  202. {dirsql-0.3.126 → dirsql-0.4.0}/packages/rust/src/command.rs +0 -0
  203. {dirsql-0.3.126 → dirsql-0.4.0}/packages/rust/src/config.rs +0 -0
  204. {dirsql-0.3.126 → dirsql-0.4.0}/packages/rust/src/db.rs +0 -0
  205. {dirsql-0.3.126 → dirsql-0.4.0}/packages/rust/src/default_config.toml +0 -0
  206. {dirsql-0.3.126 → dirsql-0.4.0}/packages/rust/src/differ.rs +0 -0
  207. {dirsql-0.3.126 → dirsql-0.4.0}/packages/rust/src/infer.rs +0 -0
  208. {dirsql-0.3.126 → dirsql-0.4.0}/packages/rust/src/lib.rs +0 -0
  209. {dirsql-0.3.126 → dirsql-0.4.0}/packages/rust/src/matcher.rs +0 -0
  210. {dirsql-0.3.126 → dirsql-0.4.0}/packages/rust/src/parsed_vtab.rs +0 -0
  211. {dirsql-0.3.126 → dirsql-0.4.0}/packages/rust/src/path_table.rs +0 -0
  212. {dirsql-0.3.126 → dirsql-0.4.0}/packages/rust/src/persist.rs +0 -0
  213. {dirsql-0.3.126 → dirsql-0.4.0}/packages/rust/src/scanner.rs +0 -0
  214. {dirsql-0.3.126 → dirsql-0.4.0}/packages/rust/src/vtab.rs +0 -0
  215. {dirsql-0.3.126 → dirsql-0.4.0}/packages/rust/src/watcher.rs +0 -0
@@ -500,7 +500,7 @@ dependencies = [
500
500
 
501
501
  [[package]]
502
502
  name = "dirsql-py-ext"
503
- version = "0.3.126"
503
+ version = "0.4.0"
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.126
3
+ Version: 0.4.0
4
4
  Summary: Ephemeral SQL index over a local directory
5
5
  Keywords: sql,filesystem,directory,sqlite,index
6
6
  Author: Kevin Scott
@@ -5,6 +5,17 @@ export default defineConfig({
5
5
  description: 'Ephemeral SQL index over a local directory. Watches a filesystem, ingests structured files into an ephemeral SQLite database, and exposes a SQL query interface.',
6
6
  base: '/dirsql/',
7
7
 
8
+ // Vite blocks dev-server requests whose Host header isn't allowlisted, so
9
+ // `pnpm dev --host` reached by a network hostname (e.g. a Tailscale
10
+ // `*.ts.net` name) 400s. Allow extra hosts from the environment so a
11
+ // personal hostname never has to be committed:
12
+ // VITEPRESS_ALLOWED_HOSTS=my.host.ts.net pnpm dev --host
13
+ vite: {
14
+ server: {
15
+ allowedHosts: process.env.VITEPRESS_ALLOWED_HOSTS?.split(',') ?? []
16
+ }
17
+ },
18
+
8
19
  themeConfig: {
9
20
  search: {
10
21
  provider: 'local'
@@ -57,7 +68,7 @@ export default defineConfig({
57
68
  { text: 'CLI', link: '/reference/cli' },
58
69
  { text: 'Configuration File', link: '/reference/config' },
59
70
  { text: 'Command Hooks', link: '/reference/hooks' },
60
- { text: 'Virtual Columns & Glob Captures', link: '/reference/columns' },
71
+ { text: 'Columns', link: '/reference/columns' },
61
72
  { text: 'Path-tables', link: '/reference/path-tables' },
62
73
  { text: 'HTTP API', link: '/reference/http-api' },
63
74
  { text: 'SDK', link: '/reference/sdk' },
@@ -57,7 +57,8 @@ migrated -- a page survives only if a slot wants its content):
57
57
  `dirsql` in an application.
58
58
  - **Reference** (`reference/`) -- CLI flags and defaults; the complete
59
59
  `.dirsql.toml` schema; the command hook contract (placeholders, stdout
60
- protocol, exit codes, timeouts); stat columns and glob captures; the
60
+ protocol, exit codes, timeouts); the columns model (named tables get their
61
+ hook's output; path-tables get stat columns); the
61
62
  HTTP API; the SDK page (`reference/sdk.md`, one page with
62
63
  Python/TypeScript/Rust code-groups -- the sole SDK home); plus the
63
64
  Migrations include (`migrations.md`).
@@ -1,12 +1,12 @@
1
1
  # Your first dirsql database
2
2
 
3
3
  In this tutorial you will turn a directory of three tiny markdown files into
4
- a SQL database — with a single command, and without writing any code. You
5
- will:
4
+ a SQL database. You will:
6
5
 
7
6
  1. Create the directory and files.
8
- 2. Query them straight away with zero configuration.
9
- 3. Declare your own table to name and reuse a shape, and query it.
7
+ 2. Query them straight away with zero configuration and no code.
8
+ 3. Declare your own named table with a tiny parser that pulls a column out
9
+ of each file — and query it.
10
10
 
11
11
  It takes about five minutes.
12
12
 
@@ -140,25 +140,44 @@ A declared table fixes a shape once: you give it a name, scope it to exactly
140
140
  the files you care about, and then query it by name instead of repeating a
141
141
  path in every question. It is also the on-ramp to everything a path-table
142
142
  can't do — a named table can be kept live by the watcher, persisted across
143
- restarts, and given a parser that reads inside your files.
143
+ restarts, and given a parser that reads *inside* your files.
144
144
 
145
- Still inside `my-notes`, create a `.dirsql.toml`:
145
+ That parser is the point: a named table's columns are exactly what its
146
+ `on-file` command emits. `dirsql` adds nothing on its own — so this is where
147
+ you pull a value out of each file. Still inside `my-notes`, write a tiny
148
+ parser that reads a note's title line (the `# Heading`) and its author (the
149
+ folder name), and prints them as a JSON row:
150
+
151
+ ```bash
152
+ cat > note.sh <<'EOF'
153
+ #!/usr/bin/env sh
154
+ title=$(sed -n 's/^# //p' "$1" | head -n1)
155
+ author=$(basename "$(dirname "$1")")
156
+ printf '[{"title":%s,"author":%s}]' \
157
+ "$(jq -Rn --arg t "$title" '$t')" \
158
+ "$(jq -Rn --arg a "$author" '$a')"
159
+ EOF
160
+ ```
161
+
162
+ Now create a `.dirsql.toml` that points a table at it:
146
163
 
147
164
  ```bash
148
165
  cat > .dirsql.toml <<'EOF'
149
166
  [[table]]
150
- ddl = "CREATE TABLE notes (dir TEXT, basename TEXT, size INTEGER)"
151
- glob = "notes/**/*.md"
167
+ ddl = "CREATE TABLE notes (title TEXT, author TEXT)"
168
+ glob = "notes/**/*.md"
169
+ on-file = "sh note.sh {path}"
152
170
  EOF
153
171
  ```
154
172
 
155
- Two keys define the table:
173
+ Three keys define the table:
156
174
 
157
175
  - `glob` selects which files feed the table — every `.md` at any depth under
158
176
  `notes/`, relative to the directory the config sits in.
159
177
  - `ddl` is ordinary `CREATE TABLE` SQL naming the columns you want to keep.
160
- Each is a [stat column](./reference/columns.md#stat-columns) `dirsql`
161
- computes for every file.
178
+ - `on-file` is the command run once per matched file; `{path}` is the file's
179
+ path, and its printed JSON row becomes the file's row. The columns are
180
+ exactly what it emits — `title` from the heading, `author` from the folder.
162
181
 
163
182
  ## 5. Query the table
164
183
 
@@ -168,11 +187,11 @@ pass it explicitly with `-c`, **after** the SQL:
168
187
  ::: code-group
169
188
 
170
189
  ```bash [npm]
171
- npx dirsql "SELECT dir, basename, size FROM notes ORDER BY dir, basename" -c .dirsql.toml | jq
190
+ npx dirsql "SELECT title, author FROM notes ORDER BY author, title" -c .dirsql.toml | jq
172
191
  ```
173
192
 
174
193
  ```bash [PyPI]
175
- uvx dirsql "SELECT dir, basename, size FROM notes ORDER BY dir, basename" -c .dirsql.toml | jq
194
+ uvx dirsql "SELECT title, author FROM notes ORDER BY author, title" -c .dirsql.toml | jq
176
195
  ```
177
196
 
178
197
  :::
@@ -180,35 +199,33 @@ uvx dirsql "SELECT dir, basename, size FROM notes ORDER BY dir, basename" -c .di
180
199
  ```json
181
200
  [
182
201
  {
183
- "basename": "ideas.md",
184
- "dir": "notes/alice",
185
- "size": 52
202
+ "author": "alice",
203
+ "title": "Ideas"
186
204
  },
187
205
  {
188
- "basename": "welcome.md",
189
- "dir": "notes/alice",
190
- "size": 66
206
+ "author": "alice",
207
+ "title": "Welcome"
191
208
  },
192
209
  {
193
- "basename": "reading-list.md",
194
- "dir": "notes/bob",
195
- "size": 41
210
+ "author": "bob",
211
+ "title": "Reading list"
196
212
  }
197
213
  ]
198
214
  ```
199
215
 
200
- You queried `FROM notes` by name — no path, no glob to repeat. And because
201
- `dir` is a real SQL column, you can aggregate on it. Count each author's
202
- notes by their folder:
216
+ You queried `FROM notes` by name — no path, no glob to repeat and `title`
217
+ came from *inside* each file, something a path-table can't reach. Because
218
+ `author` is a real SQL column, you can aggregate on it. Count each author's
219
+ notes:
203
220
 
204
221
  ::: code-group
205
222
 
206
223
  ```bash [npm]
207
- npx dirsql query "SELECT dir, COUNT(*) AS notes FROM notes GROUP BY dir ORDER BY dir" -c .dirsql.toml | jq
224
+ npx dirsql query "SELECT author, COUNT(*) AS notes FROM notes GROUP BY author ORDER BY author" -c .dirsql.toml | jq
208
225
  ```
209
226
 
210
227
  ```bash [PyPI]
211
- uvx dirsql query "SELECT dir, COUNT(*) AS notes FROM notes GROUP BY dir ORDER BY dir" -c .dirsql.toml | jq
228
+ uvx dirsql query "SELECT author, COUNT(*) AS notes FROM notes GROUP BY author ORDER BY author" -c .dirsql.toml | jq
212
229
  ```
213
230
 
214
231
  :::
@@ -216,11 +233,11 @@ uvx dirsql query "SELECT dir, COUNT(*) AS notes FROM notes GROUP BY dir ORDER BY
216
233
  ```json
217
234
  [
218
235
  {
219
- "dir": "notes/alice",
236
+ "author": "alice",
220
237
  "notes": 2
221
238
  },
222
239
  {
223
- "dir": "notes/bob",
240
+ "author": "bob",
224
241
  "notes": 1
225
242
  }
226
243
  ]
@@ -234,7 +251,7 @@ configuration, and a declared table when you want a named shape to reuse.
234
251
  - [Query files without a config](./howto/query-without-config.md) — more
235
252
  path-table questions you can ask with no setup at all.
236
253
  - [Define tables for your files](./howto/define-tables.md) — the full
237
- `[[table]]` recipe: multiple tables, ignore patterns.
254
+ `[[table]]` recipe: multiple tables, each with its own `on-file` parser.
238
255
  - [Extract rows from file contents](./howto/extract-from-contents.md) —
239
256
  pull columns out of *inside* your files with an `on-file` parser.
240
257
  - [CLI](./reference/cli.md) — every flag, plus running `dirsql` as a
@@ -0,0 +1,77 @@
1
+ # Derive columns from file paths
2
+
3
+ Directory layouts often encode real data — an author, a year, a thread ID —
4
+ as path segments. An [`on-file`](../reference/config.md#table) hook receives
5
+ each file's path and can split it into columns, so a query can group and
6
+ filter on those segments.
7
+
8
+ ## 1. Split the path in a hook
9
+
10
+ Suppose photos are filed by year and month:
11
+
12
+ ```
13
+ photos/2024/05/beach.jpg
14
+ photos/2024/11/hike.jpg
15
+ photos/2025/01/snow.jpg
16
+ ```
17
+
18
+ Write a small parser, `pathcols.py`, that turns the path into a row. It
19
+ receives the file's absolute path as its argument and prints a JSON array of
20
+ row objects:
21
+
22
+ ```python
23
+ #!/usr/bin/env python3
24
+ import json, os, sys
25
+
26
+ parts = sys.argv[1].split(os.sep)
27
+ # .../photos/<year>/<month>/<file>
28
+ print(json.dumps([{"year": parts[-3], "month": parts[-2],
29
+ "basename": os.path.basename(sys.argv[1])}]))
30
+ ```
31
+
32
+ Point a table at it in `.dirsql.toml`:
33
+
34
+ ```toml
35
+ [[table]]
36
+ ddl = "CREATE TABLE photos (year TEXT, month TEXT, basename TEXT)"
37
+ glob = "photos/*/*/*.jpg"
38
+ on-file = "python3 pathcols.py {path}"
39
+ ```
40
+
41
+ The hook emits every column the table has — dirsql injects nothing. `{path}`
42
+ is the matched file's absolute path, one of the placeholders in the
43
+ [command hook contract](../reference/hooks.md#on-file). A column appears only
44
+ because the DDL declares it *and* the hook emits it.
45
+
46
+ ## 2. Query the derived columns
47
+
48
+ Pass the config with [`-c`](../reference/cli.md#flags) (`dirsql` does not
49
+ auto-load a `.dirsql.toml` from the current directory):
50
+
51
+ ```bash
52
+ dirsql query "SELECT year, month, basename FROM photos ORDER BY year, month" -c ./.dirsql.toml
53
+ ```
54
+
55
+ ```json
56
+ [{"basename":"beach.jpg","month":"05","year":"2024"},{"basename":"hike.jpg","month":"11","year":"2024"},{"basename":"snow.jpg","month":"01","year":"2025"}]
57
+ ```
58
+
59
+ They are real SQL columns, so aggregation works:
60
+
61
+ ```bash
62
+ dirsql query "SELECT year, COUNT(*) AS photos FROM photos GROUP BY year" -c ./.dirsql.toml
63
+ ```
64
+
65
+ ```json
66
+ [{"photos":2,"year":"2024"},{"photos":1,"year":"2025"}]
67
+ ```
68
+
69
+ ## Going further
70
+
71
+ - Only need the plain filesystem stat columns (`path`, `basename`, `dir`,
72
+ `ext`, `size`, …) with no code? Query the path directly — a
73
+ [path-table](../reference/path-tables.md) gives them for free, no config.
74
+ - The [tutorial](../getting-started.md) walks the same idea, deriving an
75
+ author from the folder name, starting from zero.
76
+ - When the value you need lives inside the file rather than in its path,
77
+ see [Extract rows from file contents](./extract-from-contents.md).
@@ -0,0 +1,76 @@
1
+ # Define tables for your files
2
+
3
+ Map a glob of files to a named SQL table so you query exactly the files you
4
+ care about, by name — a shape you can reuse, keep live with the watcher, and
5
+ persist across restarts, instead of repeating an ad-hoc
6
+ [path-table](../reference/path-tables.md) path in every query.
7
+
8
+ ## 1. Create a config with a table
9
+
10
+ Suppose your blog posts live under `posts/`, one markdown file each. A named
11
+ table needs three keys: a `glob` that selects the files, a `ddl` that names
12
+ the columns, and an [`on-file`](../reference/config.md#table) hook that emits
13
+ each file's rows. Put a small parser next to the config — `extract.py`, which
14
+ reads a post's title line and prints a JSON array of row objects:
15
+
16
+ ```python
17
+ #!/usr/bin/env python3
18
+ import json, os, sys
19
+
20
+ text = open(sys.argv[1], encoding="utf-8").read()
21
+ title = next((l[2:].strip() for l in text.splitlines() if l.startswith("# ")), None)
22
+ print(json.dumps([{"title": title, "slug": os.path.basename(sys.argv[1])[:-3]}]))
23
+ ```
24
+
25
+ Then declare the table in `.dirsql.toml`:
26
+
27
+ ```toml
28
+ [[table]]
29
+ ddl = "CREATE TABLE posts (title TEXT, slug TEXT)"
30
+ glob = "posts/**/*.md"
31
+ on-file = "python3 extract.py {path}"
32
+ ```
33
+
34
+ - `glob` selects the files: every `.md` under `posts/`, at any depth, relative
35
+ to the directory containing the config.
36
+ - `ddl` is a plain SQLite `CREATE TABLE` naming the columns you want to keep.
37
+ - `on-file` is **required** — it is where the table's rows come from. dirsql
38
+ injects nothing; the hook emits every column, reading the file (it has
39
+ `{path}`) and deriving whatever it needs. A `[[table]]` with no `on-file` is
40
+ a [config error](../reference/config.md#parse-errors). For plain stat
41
+ columns with no code, query the path directly with a path-table instead.
42
+
43
+ ## 2. Query the table
44
+
45
+ Pass the config with [`-c`](../reference/cli.md#flags) — `dirsql` does not
46
+ auto-load a `.dirsql.toml` from the current directory. Each matched file is
47
+ one row:
48
+
49
+ ```bash
50
+ dirsql query "SELECT title, slug FROM posts ORDER BY slug" -c ./.dirsql.toml
51
+ ```
52
+
53
+ ```json
54
+ [{"slug":"again","title":"On Recursion"},{"slug":"hello","title":"Hello World"}]
55
+ ```
56
+
57
+ Files that don't match the glob (a `README.txt` next to `posts/`, say) are
58
+ simply not in the table. Only the tables you define are served.
59
+
60
+ ## Multiple tables
61
+
62
+ Add one `[[table]]` entry per table — each with its own `glob`, `ddl`, and
63
+ `on-file`. When a file matches several globs, it populates every matching
64
+ table — each table is an independent view. See
65
+ [`[[table]]`](../reference/config.md#table) for the remaining key, `strict`.
66
+
67
+ ## Going further
68
+
69
+ - The parser mechanics — placeholders, stdout protocol, per-file failure
70
+ isolation — are the [`on-file` hook contract](../reference/hooks.md#on-file);
71
+ [Extract rows from file contents](./extract-from-contents.md) is the fuller
72
+ recipe.
73
+ - Your directory layout encodes data (authors, dates, IDs)? Split the path in
74
+ the hook — [Derive columns from file paths](./columns-from-paths.md).
75
+ - Why one row per file, rebuilt from disk? See
76
+ [how `dirsql` thinks](../explanation.md).
@@ -34,22 +34,25 @@ comments/t2/c1.json # {"body": "following up", "author": "alice"}
34
34
  ```
35
35
 
36
36
  Unlike a config-file table, a programmatic table takes an `on_file`
37
- callback — your code reads each matched file and returns its rows, with
38
- [glob captures and stat columns](../reference/columns.md) merged on
39
- automatically (here, `{thread}` from the path):
37
+ callback — your code reads each matched file and returns its rows. The row's
38
+ columns are exactly what the callback returns; `dirsql` merges nothing on top
39
+ (see [Columns](../reference/columns.md)), so the callback derives the `thread`
40
+ from the file's path itself:
40
41
 
41
42
  ::: code-group
42
43
 
43
44
  ```python [Python]
44
45
  import asyncio
45
46
  import json
47
+ import os
46
48
 
47
49
  from dirsql import DirSQL, Table
48
50
 
49
51
 
50
52
  def on_file(path: str) -> list[dict]:
53
+ thread = os.path.basename(os.path.dirname(path))
51
54
  with open(path, encoding="utf-8") as f:
52
- return [json.load(f)]
55
+ return [{**json.load(f), "thread": thread}]
53
56
 
54
57
 
55
58
  async def main() -> None:
@@ -58,7 +61,7 @@ async def main() -> None:
58
61
  tables=[
59
62
  Table(
60
63
  ddl="CREATE TABLE comments (thread TEXT, author TEXT, body TEXT)",
61
- glob="comments/{thread}/*.json",
64
+ glob="comments/*/*.json",
62
65
  on_file=on_file,
63
66
  )
64
67
  ],
@@ -73,6 +76,7 @@ asyncio.run(main())
73
76
 
74
77
  ```typescript [TypeScript]
75
78
  import { readFileSync } from "node:fs";
79
+ import { basename, dirname } from "node:path";
76
80
  import { DirSQL } from "dirsql";
77
81
 
78
82
  const db = new DirSQL({
@@ -80,8 +84,10 @@ const db = new DirSQL({
80
84
  tables: [
81
85
  {
82
86
  ddl: "CREATE TABLE comments (thread TEXT, author TEXT, body TEXT)",
83
- glob: "comments/{thread}/*.json",
84
- onFile: (path) => [JSON.parse(readFileSync(path, "utf8"))],
87
+ glob: "comments/*/*.json",
88
+ onFile: (path) => [
89
+ { ...JSON.parse(readFileSync(path, "utf8")), thread: basename(dirname(path)) },
90
+ ],
85
91
  },
86
92
  ],
87
93
  });
@@ -110,6 +116,12 @@ fn on_file(path: &str) -> Vec<HashMap<String, Value>> {
110
116
  row.insert(key, Value::Text(s));
111
117
  }
112
118
  }
119
+ let thread = std::path::Path::new(path)
120
+ .parent()
121
+ .and_then(|p| p.file_name())
122
+ .and_then(|n| n.to_str())
123
+ .unwrap_or_default();
124
+ row.insert("thread".into(), Value::Text(thread.into()));
113
125
  vec![row]
114
126
  }
115
127
 
@@ -118,7 +130,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
118
130
  .root("./comments-root")
119
131
  .table(Table::new(
120
132
  "CREATE TABLE comments (thread TEXT, author TEXT, body TEXT)",
121
- "comments/{thread}/*.json",
133
+ "comments/*/*.json",
122
134
  on_file,
123
135
  ))
124
136
  .build()?;
@@ -18,7 +18,7 @@ stdout works. With [`jq`](https://jqlang.org/):
18
18
 
19
19
  ```toml
20
20
  [[table]]
21
- ddl = "CREATE TABLE books (title TEXT, author TEXT, year INTEGER, path TEXT)"
21
+ ddl = "CREATE TABLE books (title TEXT, author TEXT, year INTEGER)"
22
22
  glob = "books/*.json"
23
23
  on-file = "jq -c '[{title, author, year}]' {path}"
24
24
  ```
@@ -35,17 +35,16 @@ Pass the config with [`-c`](../reference/cli.md#flags) (`dirsql` does not
35
35
  auto-load a `.dirsql.toml` from the current directory):
36
36
 
37
37
  ```bash
38
- dirsql query "SELECT title, author, year, path FROM books ORDER BY year" -c ./.dirsql.toml
38
+ dirsql query "SELECT title, author, year FROM books ORDER BY year" -c ./.dirsql.toml
39
39
  ```
40
40
 
41
41
  ```json
42
- [{"path":"books/bleak-house.json","author":"Charles Dickens","title":"Bleak House","year":1852},{"path":"books/middlemarch.json","author":"George Eliot","title":"Middlemarch","year":1871}]
42
+ [{"author":"Charles Dickens","title":"Bleak House","year":1852},{"author":"George Eliot","title":"Middlemarch","year":1871}]
43
43
  ```
44
44
 
45
- Filesystem facts are still merged onto every row `path` above comes from
46
- `dirsql`, not from `jq`. When the command emits a key that collides with a
47
- fact, the command wins
48
- ([precedence](../reference/columns.md#precedence)).
45
+ The table's columns are exactly what the command emits, narrowed to the DDL
46
+ `dirsql` adds nothing. To include the file's `path`, have the command emit it
47
+ (it has `{path}`); dirsql will not merge it in for you.
49
48
 
50
49
  ## Multiple rows per file
51
50
 
@@ -54,7 +53,7 @@ row per line, slurp it:
54
53
 
55
54
  ```toml
56
55
  [[table]]
57
- ddl = "CREATE TABLE events (event TEXT, user TEXT, path TEXT)"
56
+ ddl = "CREATE TABLE events (event TEXT, user TEXT)"
58
57
  glob = "logs/*.jsonl"
59
58
  on-file = "jq -c -s '.' {path}"
60
59
  ```
@@ -106,10 +106,9 @@ Same command, same rows. The difference is what a declared table brings: it is
106
106
  indexed on build, kept fresh by the watcher, survives restarts with
107
107
  [`--persist`](./persist.md), and a config can declare
108
108
  [many tables](./define-tables.md) — each with its own `on-file` — where the flag
109
- gives every path-table one parser. A declared table also merges the filesystem
110
- facts back onto each row, so a `path TEXT` column in the DDL is populated by
111
- `dirsql` even though the parser did not emit it
112
- ([precedence](../reference/columns.md#precedence)).
109
+ gives every path-table one parser. In both spellings the table's columns are
110
+ exactly what the parser emits: `dirsql` merges no filesystem facts back on. A
111
+ row that needs the file's `path` emits it (the parser has `{path}`).
113
112
 
114
113
  ## Going further
115
114
 
@@ -7,13 +7,16 @@ side.
7
7
 
8
8
  Row events are emitted for **named tables**, so this flow needs a config —
9
9
  [path-tables](../reference/path-tables.md) are scanned per query and are not
10
- watched. Define one next to your files:
10
+ watched. Define one next to your files. A named table's columns are whatever
11
+ its [`on-file`](../reference/config.md#table) hook emits; here a minimal hook
12
+ prints each file's basename:
11
13
 
12
14
  ```toml
13
15
  # .dirsql.toml
14
16
  [[table]]
15
- ddl = "CREATE TABLE files (path TEXT, basename TEXT, dir TEXT, ext TEXT, size INTEGER, mtime INTEGER, ctime INTEGER)"
16
- glob = "**/*"
17
+ ddl = "CREATE TABLE files (basename TEXT)"
18
+ glob = "**/*"
19
+ on-file = '''sh -c 'printf "[{\"basename\":\"%s\"}]" "${1##*/}"' sh {path}'''
17
20
  ```
18
21
 
19
22
  ## 1. Open the stream
@@ -44,7 +47,7 @@ The stream delivers the resulting row change:
44
47
 
45
48
  ```
46
49
  event: row
47
- data: {"action":"insert","file_path":"inbox/two.txt","old_row":null,"row":{"basename":"two.txt","ctime":1783170226,"dir":"inbox","ext":"txt","mtime":1783170226,"path":"inbox/two.txt","size":7},"table":"files"}
50
+ data: {"action":"insert","file_path":"inbox/two.txt","old_row":null,"row":{"basename":"two.txt"},"table":"files"}
48
51
  ```
49
52
 
50
53
  Edits arrive as `update` events carrying both the old and new row;
@@ -41,21 +41,25 @@ notes/tomatoes.md # planting tomato seedlings after the last frost
41
41
  ```
42
42
 
43
43
  Next to them, `embed.py` turns one file into one row carrying its text and
44
- its embedding (a JSON array, stored as TEXT — `sqlite-vec` accepts JSON
45
- vectors directly):
44
+ its `path`, text, and embedding (a JSON array, stored as TEXT — `sqlite-vec`
45
+ accepts JSON vectors directly). dirsql injects no columns, so the script emits
46
+ the path itself, deriving it from the `{path}`/`{root}` the hook passes in:
46
47
 
47
48
  ```python
48
49
  """Embed one file's text; print a dirsql row array on stdout."""
49
50
  import json
51
+ import os
50
52
  import sys
51
53
 
52
54
  from model2vec import StaticModel
53
55
 
54
- path = sys.argv[1]
56
+ path, root = sys.argv[1], sys.argv[2]
55
57
  text = open(path, encoding="utf-8").read()
56
58
  model = StaticModel.from_pretrained("minishlab/potion-base-8M")
57
59
  vector = model.encode([text])[0]
58
- print(json.dumps([{"text": text, "embedding": json.dumps([round(float(x), 6) for x in vector])}]))
60
+ row = {"path": os.path.relpath(path, root), "text": text,
61
+ "embedding": json.dumps([round(float(x), 6) for x in vector])}
62
+ print(json.dumps([row]))
59
63
  ```
60
64
 
61
65
  And `search.py` turns a `{"q": "..."}` request body into SQL, embedding the
@@ -98,7 +102,7 @@ entrypoint = "sqlite3_vec_init"
98
102
  [[table]]
99
103
  ddl = "CREATE TABLE notes (path TEXT, text TEXT, embedding TEXT)"
100
104
  glob = "notes/*.md"
101
- on-file = "uv run --with model2vec python embed.py {path}"
105
+ on-file = "uv run --with model2vec python embed.py {path} {root}"
102
106
  ```
103
107
 
104
108
  The extension is named by package: the Python launcher resolves the
@@ -22,8 +22,9 @@ Exclude the noise in `.dirsql.toml`:
22
22
  ignore = ["notes/drafts/**", "**/*.tmp"]
23
23
 
24
24
  [[table]]
25
- ddl = "CREATE TABLE notes (path TEXT)"
26
- glob = "notes/**/*"
25
+ ddl = "CREATE TABLE notes (basename TEXT)"
26
+ glob = "notes/**/*"
27
+ on-file = '''sh -c 'printf "[{\"basename\":\"%s\"}]" "${1##*/}"' sh {path}'''
27
28
  ```
28
29
 
29
30
  Patterns match against root-relative paths, the same way table globs do. An
@@ -35,11 +36,11 @@ Pass the config with [`-c`](../reference/cli.md#flags) (`dirsql` does not
35
36
  auto-load a `.dirsql.toml` from the current directory):
36
37
 
37
38
  ```bash
38
- dirsql query "SELECT path FROM notes ORDER BY path" -c ./.dirsql.toml
39
+ dirsql query "SELECT basename FROM notes ORDER BY basename" -c ./.dirsql.toml
39
40
  ```
40
41
 
41
42
  ```json
42
- [{"path":"notes/final.md"}]
43
+ [{"basename":"final.md"}]
43
44
  ```
44
45
 
45
46
  ## Notes
@@ -72,7 +72,7 @@ Both hook command styles from the [hook contract](../reference/hooks.md) work
72
72
  in a plugin fragment:
73
73
 
74
74
  - **Console scripts** — a `bin`-style entry point your package installs on
75
- `PATH` (`embed-file {path}`). Recommended for published plugins: the command
75
+ `PATH` (`embed-file {path} {root}`). Recommended for published plugins: the command
76
76
  is bound to your package's interpreter and dependencies, and it is
77
77
  language-neutral (the fragment names a command, not a Python file).
78
78
  - **Relative scripts** — a path resolved against the fragment's own directory
@@ -116,23 +116,28 @@ entrypoint = "sqlite3_vec_init"
116
116
  [[table]]
117
117
  ddl = "CREATE TABLE notes (path TEXT, text TEXT, embedding TEXT)"
118
118
  glob = "notes/*.md"
119
- on-file = "uv run --with model2vec python embed.py {path}"
119
+ on-file = "uv run --with model2vec python embed.py {path} {root}"
120
120
  ```
121
121
 
122
- `embed.py` turns one file into one row carrying its text and its embedding:
122
+ `embed.py` turns one file into one row carrying its path, text, and embedding.
123
+ dirsql injects no columns, so the script emits the path itself, from the
124
+ `{path}`/`{root}` the hook passes in:
123
125
 
124
126
  ```python
125
127
  """Embed one file's text; print a dirsql row array on stdout."""
126
128
  import json
129
+ import os
127
130
  import sys
128
131
 
129
132
  from model2vec import StaticModel
130
133
 
131
- path = sys.argv[1]
134
+ path, root = sys.argv[1], sys.argv[2]
132
135
  text = open(path, encoding="utf-8").read()
133
136
  model = StaticModel.from_pretrained("minishlab/potion-base-8M")
134
137
  vector = model.encode([text])[0]
135
- print(json.dumps([{"text": text, "embedding": json.dumps([round(float(x), 6) for x in vector])}]))
138
+ row = {"path": os.path.relpath(path, root), "text": text,
139
+ "embedding": json.dumps([round(float(x), 6) for x in vector])}
140
+ print(json.dumps([row]))
136
141
  ```
137
142
 
138
143
  `search.py` turns a `{"q": "..."}` request body into nearest-neighbor SQL: