dirsql 0.4.18__tar.gz → 0.4.20__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 (244) hide show
  1. {dirsql-0.4.18 → dirsql-0.4.20}/Cargo.lock +1 -1
  2. {dirsql-0.4.18 → dirsql-0.4.20}/PKG-INFO +1 -1
  3. {dirsql-0.4.18/packages/rust → dirsql-0.4.20}/docs/howto/persist.md +13 -0
  4. {dirsql-0.4.18/packages/rust → dirsql-0.4.20}/docs/reference/path-tables.md +14 -6
  5. {dirsql-0.4.18 → dirsql-0.4.20}/packages/python/Cargo.toml +1 -1
  6. {dirsql-0.4.18 → dirsql-0.4.20}/packages/python/docs/howto/persist.md +13 -0
  7. {dirsql-0.4.18 → dirsql-0.4.20}/packages/python/docs/reference/path-tables.md +14 -6
  8. dirsql-0.4.20/packages/python/e2e-attestations/claude-827-unquote-doubled-quotes.json +7 -0
  9. dirsql-0.4.20/packages/python/e2e-attestations/claude-github-issue-825-e4mhhp.json +7 -0
  10. dirsql-0.4.20/packages/rust/changelog.d/2026-08-11-persist-parsed-path-tables.md +4 -0
  11. dirsql-0.4.20/packages/rust/changelog.d/2026-08-12-unquote-inverts-quote-literal.md +3 -0
  12. {dirsql-0.4.18 → dirsql-0.4.20/packages/rust}/docs/howto/persist.md +13 -0
  13. {dirsql-0.4.18 → dirsql-0.4.20/packages/rust}/docs/reference/path-tables.md +14 -6
  14. dirsql-0.4.20/packages/rust/migrations.d/2026-08-11-persist-parsed-path-tables.md +58 -0
  15. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/src/db.rs +88 -26
  16. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/src/lib.rs +52 -20
  17. dirsql-0.4.20/packages/rust/src/parsed_cache.rs +454 -0
  18. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/src/parsed_vtab.rs +548 -61
  19. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/src/persist.rs +76 -31
  20. dirsql-0.4.20/packages/rust/src/sql_literal.rs +127 -0
  21. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/src/vtab.rs +1 -42
  22. {dirsql-0.4.18 → dirsql-0.4.20}/Cargo.toml +0 -0
  23. {dirsql-0.4.18 → dirsql-0.4.20}/README.md +0 -0
  24. {dirsql-0.4.18 → dirsql-0.4.20}/dirsql/__init__.py +0 -0
  25. {dirsql-0.4.18 → dirsql-0.4.20}/dirsql/_async.py +0 -0
  26. {dirsql-0.4.18 → dirsql-0.4.20}/dirsql/_dirsql.pyi +0 -0
  27. {dirsql-0.4.18 → dirsql-0.4.20}/dirsql/cli/__init__.py +0 -0
  28. {dirsql-0.4.18 → dirsql-0.4.20}/dirsql/cli/discover_plugins/__init__.py +0 -0
  29. {dirsql-0.4.18 → dirsql-0.4.20}/dirsql/cli/discover_plugins/discovered_fragments.py +0 -0
  30. {dirsql-0.4.18 → dirsql-0.4.20}/dirsql/cli/discover_plugins/discovery_disabled.py +0 -0
  31. {dirsql-0.4.18 → dirsql-0.4.20}/dirsql/cli/discover_plugins/fragment_path.py +0 -0
  32. {dirsql-0.4.18 → dirsql-0.4.20}/dirsql/cli/discover_plugins/user_passed_config.py +0 -0
  33. {dirsql-0.4.18 → dirsql-0.4.20}/dirsql/cli/discover_plugins/with_discovered_plugins.py +0 -0
  34. {dirsql-0.4.18 → dirsql-0.4.20}/dirsql/cli/main.py +0 -0
  35. {dirsql-0.4.18 → dirsql-0.4.20}/dirsql/cli/resolve_config_extensions.py +0 -0
  36. {dirsql-0.4.18 → dirsql-0.4.20}/dirsql/py.typed +0 -0
  37. {dirsql-0.4.18 → dirsql-0.4.20}/dirsql/resolve_config_extensions.py +0 -0
  38. {dirsql-0.4.18 → dirsql-0.4.20}/dirsql/resolve_extension.py +0 -0
  39. {dirsql-0.4.18 → dirsql-0.4.20}/docs/.claude/CLAUDE.md +0 -0
  40. {dirsql-0.4.18 → dirsql-0.4.20}/docs/.vitepress/config.ts +0 -0
  41. {dirsql-0.4.18 → dirsql-0.4.20}/docs/.vitepress/theme/index.ts +0 -0
  42. {dirsql-0.4.18 → dirsql-0.4.20}/docs/.vitepress/theme/lang.ts +0 -0
  43. {dirsql-0.4.18 → dirsql-0.4.20}/docs/AGENTS.md +0 -0
  44. {dirsql-0.4.18 → dirsql-0.4.20}/docs/explanation.md +0 -0
  45. {dirsql-0.4.18 → dirsql-0.4.20}/docs/getting-started.md +0 -0
  46. {dirsql-0.4.18 → dirsql-0.4.20}/docs/howto/columns-from-paths.md +0 -0
  47. {dirsql-0.4.18 → dirsql-0.4.20}/docs/howto/define-tables.md +0 -0
  48. {dirsql-0.4.18 → dirsql-0.4.20}/docs/howto/embed.md +0 -0
  49. {dirsql-0.4.18 → dirsql-0.4.20}/docs/howto/extract-from-contents.md +0 -0
  50. {dirsql-0.4.18 → dirsql-0.4.20}/docs/howto/load-extension.md +0 -0
  51. {dirsql-0.4.18 → dirsql-0.4.20}/docs/howto/parse-files-into-columns.md +0 -0
  52. {dirsql-0.4.18 → dirsql-0.4.20}/docs/howto/query-without-config.md +0 -0
  53. {dirsql-0.4.18 → dirsql-0.4.20}/docs/howto/react-to-changes.md +0 -0
  54. {dirsql-0.4.18 → dirsql-0.4.20}/docs/howto/search-by-meaning.md +0 -0
  55. {dirsql-0.4.18 → dirsql-0.4.20}/docs/howto/skip-files.md +0 -0
  56. {dirsql-0.4.18 → dirsql-0.4.20}/docs/howto/write-a-plugin.md +0 -0
  57. {dirsql-0.4.18 → dirsql-0.4.20}/docs/index.md +0 -0
  58. {dirsql-0.4.18 → dirsql-0.4.20}/docs/migrations.md +0 -0
  59. {dirsql-0.4.18 → dirsql-0.4.20}/docs/package.json +0 -0
  60. {dirsql-0.4.18 → dirsql-0.4.20}/docs/plugins.md +0 -0
  61. {dirsql-0.4.18 → dirsql-0.4.20}/docs/pnpm-lock.yaml +0 -0
  62. {dirsql-0.4.18 → dirsql-0.4.20}/docs/pnpm-workspace.yaml +0 -0
  63. {dirsql-0.4.18 → dirsql-0.4.20}/docs/reference/cli.md +0 -0
  64. {dirsql-0.4.18 → dirsql-0.4.20}/docs/reference/columns.md +0 -0
  65. {dirsql-0.4.18 → dirsql-0.4.20}/docs/reference/config.md +0 -0
  66. {dirsql-0.4.18 → dirsql-0.4.20}/docs/reference/hooks.md +0 -0
  67. {dirsql-0.4.18 → dirsql-0.4.20}/docs/reference/http-api.md +0 -0
  68. {dirsql-0.4.18 → dirsql-0.4.20}/docs/reference/sdk.md +0 -0
  69. {dirsql-0.4.18 → dirsql-0.4.20}/packages/python/CHANGELOG.md +0 -0
  70. {dirsql-0.4.18 → dirsql-0.4.20}/packages/python/MIGRATIONS.md +0 -0
  71. {dirsql-0.4.18 → dirsql-0.4.20}/packages/python/README.md +0 -0
  72. {dirsql-0.4.18 → dirsql-0.4.20}/packages/python/changelog.d/2026-07-13-drop-cwd-config-auto-detect.md +0 -0
  73. {dirsql-0.4.18 → dirsql-0.4.20}/packages/python/changelog.d/2026-07-13-plugin-discovery.md +0 -0
  74. {dirsql-0.4.18 → dirsql-0.4.20}/packages/python/changelog.d/2026-07-13-rename-extract-to-on-file.md +0 -0
  75. {dirsql-0.4.18 → dirsql-0.4.20}/packages/python/changelog.d/2026-07-13-repeatable-config.md +0 -0
  76. {dirsql-0.4.18 → dirsql-0.4.20}/packages/python/changelog.d/2026-07-13-sdk-default-baked-in-config.md +0 -0
  77. {dirsql-0.4.18 → dirsql-0.4.20}/packages/python/changelog.d/2026-07-20-drop-implicit-files-table.md +0 -0
  78. {dirsql-0.4.18 → dirsql-0.4.20}/packages/python/changelog.d/2026-07-21-remove-glob-captures.md +0 -0
  79. {dirsql-0.4.18 → dirsql-0.4.20}/packages/python/changelog.d/2026-07-24-hookless-table-config-error.md +0 -0
  80. {dirsql-0.4.18 → dirsql-0.4.20}/packages/python/changelog.d/2026-07-24-remove-stat-fact-injection.md +0 -0
  81. {dirsql-0.4.18 → dirsql-0.4.20}/packages/python/changelog.d/2026-07-29-restore-python-310.md +0 -0
  82. {dirsql-0.4.18 → dirsql-0.4.20}/packages/python/changelog.d/2026-08-02-release-profile.md +0 -0
  83. {dirsql-0.4.18 → dirsql-0.4.20}/packages/python/changelog.d/2026-08-02-scan-failures.md +0 -0
  84. {dirsql-0.4.18 → dirsql-0.4.20}/packages/python/changelog.d/2026-08-03-discovery-ext-resolution.md +0 -0
  85. {dirsql-0.4.18 → dirsql-0.4.20}/packages/python/changelog.d/2026-08-03-manylinux-dynamic-binary.md +0 -0
  86. {dirsql-0.4.18 → dirsql-0.4.20}/packages/python/changelog.d/2026-08-03-python-no-ignore.md +0 -0
  87. {dirsql-0.4.18 → dirsql-0.4.20}/packages/python/changelog.d/2026-08-04-cast-lints.md +0 -0
  88. {dirsql-0.4.18 → dirsql-0.4.20}/packages/python/changelog.d/2026-08-04-cli-in-process.md +0 -0
  89. {dirsql-0.4.18 → dirsql-0.4.20}/packages/python/changelog.d/README.md +0 -0
  90. {dirsql-0.4.18 → dirsql-0.4.20}/packages/python/conftest.py +0 -0
  91. {dirsql-0.4.18 → dirsql-0.4.20}/packages/python/docs/.claude/CLAUDE.md +0 -0
  92. {dirsql-0.4.18 → dirsql-0.4.20}/packages/python/docs/.vitepress/config.ts +0 -0
  93. {dirsql-0.4.18 → dirsql-0.4.20}/packages/python/docs/.vitepress/theme/index.ts +0 -0
  94. {dirsql-0.4.18 → dirsql-0.4.20}/packages/python/docs/.vitepress/theme/lang.ts +0 -0
  95. {dirsql-0.4.18 → dirsql-0.4.20}/packages/python/docs/AGENTS.md +0 -0
  96. {dirsql-0.4.18 → dirsql-0.4.20}/packages/python/docs/explanation.md +0 -0
  97. {dirsql-0.4.18 → dirsql-0.4.20}/packages/python/docs/getting-started.md +0 -0
  98. {dirsql-0.4.18 → dirsql-0.4.20}/packages/python/docs/howto/columns-from-paths.md +0 -0
  99. {dirsql-0.4.18 → dirsql-0.4.20}/packages/python/docs/howto/define-tables.md +0 -0
  100. {dirsql-0.4.18 → dirsql-0.4.20}/packages/python/docs/howto/embed.md +0 -0
  101. {dirsql-0.4.18 → dirsql-0.4.20}/packages/python/docs/howto/extract-from-contents.md +0 -0
  102. {dirsql-0.4.18 → dirsql-0.4.20}/packages/python/docs/howto/load-extension.md +0 -0
  103. {dirsql-0.4.18 → dirsql-0.4.20}/packages/python/docs/howto/parse-files-into-columns.md +0 -0
  104. {dirsql-0.4.18 → dirsql-0.4.20}/packages/python/docs/howto/query-without-config.md +0 -0
  105. {dirsql-0.4.18 → dirsql-0.4.20}/packages/python/docs/howto/react-to-changes.md +0 -0
  106. {dirsql-0.4.18 → dirsql-0.4.20}/packages/python/docs/howto/search-by-meaning.md +0 -0
  107. {dirsql-0.4.18 → dirsql-0.4.20}/packages/python/docs/howto/skip-files.md +0 -0
  108. {dirsql-0.4.18 → dirsql-0.4.20}/packages/python/docs/howto/write-a-plugin.md +0 -0
  109. {dirsql-0.4.18 → dirsql-0.4.20}/packages/python/docs/index.md +0 -0
  110. {dirsql-0.4.18 → dirsql-0.4.20}/packages/python/docs/migrations.md +0 -0
  111. {dirsql-0.4.18 → dirsql-0.4.20}/packages/python/docs/package.json +0 -0
  112. {dirsql-0.4.18 → dirsql-0.4.20}/packages/python/docs/plugins.md +0 -0
  113. {dirsql-0.4.18 → dirsql-0.4.20}/packages/python/docs/pnpm-lock.yaml +0 -0
  114. {dirsql-0.4.18 → dirsql-0.4.20}/packages/python/docs/pnpm-workspace.yaml +0 -0
  115. {dirsql-0.4.18 → dirsql-0.4.20}/packages/python/docs/reference/cli.md +0 -0
  116. {dirsql-0.4.18 → dirsql-0.4.20}/packages/python/docs/reference/columns.md +0 -0
  117. {dirsql-0.4.18 → dirsql-0.4.20}/packages/python/docs/reference/config.md +0 -0
  118. {dirsql-0.4.18 → dirsql-0.4.20}/packages/python/docs/reference/hooks.md +0 -0
  119. {dirsql-0.4.18 → dirsql-0.4.20}/packages/python/docs/reference/http-api.md +0 -0
  120. {dirsql-0.4.18 → dirsql-0.4.20}/packages/python/docs/reference/sdk.md +0 -0
  121. {dirsql-0.4.18 → dirsql-0.4.20}/packages/python/e2e-attestations/claude-819-oneshot-timeout.json +0 -0
  122. {dirsql-0.4.18 → dirsql-0.4.20}/packages/python/migrations.d/2026-07-13-drop-cwd-config-auto-detect.md +0 -0
  123. {dirsql-0.4.18 → dirsql-0.4.20}/packages/python/migrations.d/2026-07-13-rename-extract-to-on-file.md +0 -0
  124. {dirsql-0.4.18 → dirsql-0.4.20}/packages/python/migrations.d/2026-07-13-sdk-default-baked-in-config.md +0 -0
  125. {dirsql-0.4.18 → dirsql-0.4.20}/packages/python/migrations.d/2026-07-20-drop-implicit-files-table.md +0 -0
  126. {dirsql-0.4.18 → dirsql-0.4.20}/packages/python/migrations.d/2026-07-21-remove-glob-captures.md +0 -0
  127. {dirsql-0.4.18 → dirsql-0.4.20}/packages/python/migrations.d/2026-07-24-hookless-table-config-error.md +0 -0
  128. {dirsql-0.4.18 → dirsql-0.4.20}/packages/python/migrations.d/2026-07-24-remove-stat-fact-injection.md +0 -0
  129. {dirsql-0.4.18 → dirsql-0.4.20}/packages/python/migrations.d/2026-07-29-restore-python-310.md +0 -0
  130. {dirsql-0.4.18 → dirsql-0.4.20}/packages/python/migrations.d/2026-08-04-cli-in-process.md +0 -0
  131. {dirsql-0.4.18 → dirsql-0.4.20}/packages/python/migrations.d/README.md +0 -0
  132. {dirsql-0.4.18 → dirsql-0.4.20}/packages/python/src/lib.rs +0 -0
  133. {dirsql-0.4.18 → dirsql-0.4.20}/packages/python/tests/__init__.py +0 -0
  134. {dirsql-0.4.18 → dirsql-0.4.20}/packages/python/tests/conftest.py +0 -0
  135. {dirsql-0.4.18 → dirsql-0.4.20}/packages/python/tests/e2e/__init__.py +0 -0
  136. {dirsql-0.4.18 → dirsql-0.4.20}/packages/python/tests/e2e/fixtures/dirsql_plugin_fixture/__init__.py +0 -0
  137. {dirsql-0.4.18 → dirsql-0.4.20}/packages/python/tests/e2e/fixtures/dirsql_plugin_fixture/dirsql.toml +0 -0
  138. {dirsql-0.4.18 → dirsql-0.4.20}/packages/python/tests/integration/__init__.py +0 -0
  139. {dirsql-0.4.18 → dirsql-0.4.20}/packages/python/tests/integration/binding/__init__.py +0 -0
  140. {dirsql-0.4.18 → dirsql-0.4.20}/packages/python/tests/integration/hermetic/__init__.py +0 -0
  141. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/CHANGELOG.md +0 -0
  142. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/Cargo.toml +0 -0
  143. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/MIGRATIONS.md +0 -0
  144. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/README.md +0 -0
  145. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/benches/db_bench.rs +0 -0
  146. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/benches/differ_bench.rs +0 -0
  147. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/benches/matcher_bench.rs +0 -0
  148. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/benches/scanner_bench.rs +0 -0
  149. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/changelog.d/2026-07-13-batch-ingest-transaction.md +0 -0
  150. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/changelog.d/2026-07-13-chained-pre-post-query-hooks.md +0 -0
  151. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/changelog.d/2026-07-13-drop-cwd-config-auto-detect.md +0 -0
  152. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/changelog.d/2026-07-13-fan-out-file-table-matching.md +0 -0
  153. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/changelog.d/2026-07-13-persist-wal.md +0 -0
  154. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/changelog.d/2026-07-13-rename-extract-to-on-file.md +0 -0
  155. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/changelog.d/2026-07-13-repeatable-builder-config.md +0 -0
  156. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/changelog.d/2026-07-13-repeatable-cli-config.md +0 -0
  157. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/changelog.d/2026-07-13-sdk-default-baked-in-config.md +0 -0
  158. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/changelog.d/2026-07-13-subcommand-local-config-flags.md +0 -0
  159. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/changelog.d/2026-07-19-path-table-query-fallback.md +0 -0
  160. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/changelog.d/2026-07-19-path-table-vtab.md +0 -0
  161. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/changelog.d/2026-07-20-drop-implicit-files-table.md +0 -0
  162. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/changelog.d/2026-07-20-path-table-glob-semantics.md +0 -0
  163. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/changelog.d/2026-07-20-schema-inference.md +0 -0
  164. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/changelog.d/2026-07-21-on-file-flag.md +0 -0
  165. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/changelog.d/2026-07-21-remove-glob-captures.md +0 -0
  166. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/changelog.d/2026-07-23-init-escalation-example.md +0 -0
  167. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/changelog.d/2026-07-23-query-default-cli.md +0 -0
  168. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/changelog.d/2026-07-24-hookless-table-config-error.md +0 -0
  169. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/changelog.d/2026-07-24-remove-stat-fact-injection.md +0 -0
  170. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/changelog.d/2026-07-29-on-file-failures-accumulate.md +0 -0
  171. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/changelog.d/2026-08-02-drop-regex.md +0 -0
  172. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/changelog.d/2026-08-02-per-file-scan-failures.md +0 -0
  173. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/changelog.d/2026-08-02-release-profile.md +0 -0
  174. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/changelog.d/2026-08-03-gitignore-by-default.md +0 -0
  175. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/changelog.d/2026-08-03-nested-default-ignores.md +0 -0
  176. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/changelog.d/2026-08-04-cast-lints.md +0 -0
  177. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/changelog.d/2026-08-04-cli-run-shim.md +0 -0
  178. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/changelog.d/2026-08-04-configless-extension.md +0 -0
  179. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/changelog.d/2026-08-10-dirsql-function.md +0 -0
  180. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/changelog.d/2026-08-10-remove-query-hooks.md +0 -0
  181. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/changelog.d/2026-08-11-oneshot-query-no-timeout.md +0 -0
  182. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/changelog.d/2026-08-11-retire-hook-timeout.md +0 -0
  183. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/changelog.d/README.md +0 -0
  184. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/docs/explanation.md +0 -0
  185. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/docs/getting-started.md +0 -0
  186. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/docs/howto/columns-from-paths.md +0 -0
  187. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/docs/howto/define-tables.md +0 -0
  188. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/docs/howto/embed.md +0 -0
  189. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/docs/howto/extract-from-contents.md +0 -0
  190. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/docs/howto/load-extension.md +0 -0
  191. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/docs/howto/parse-files-into-columns.md +0 -0
  192. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/docs/howto/query-without-config.md +0 -0
  193. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/docs/howto/react-to-changes.md +0 -0
  194. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/docs/howto/search-by-meaning.md +0 -0
  195. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/docs/howto/skip-files.md +0 -0
  196. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/docs/howto/write-a-plugin.md +0 -0
  197. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/docs/index.md +0 -0
  198. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/docs/migrations.md +0 -0
  199. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/docs/plugins.md +0 -0
  200. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/docs/reference/cli.md +0 -0
  201. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/docs/reference/columns.md +0 -0
  202. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/docs/reference/config.md +0 -0
  203. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/docs/reference/hooks.md +0 -0
  204. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/docs/reference/http-api.md +0 -0
  205. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/docs/reference/sdk.md +0 -0
  206. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/migrations.d/2026-07-13-drop-cwd-config-auto-detect.md +0 -0
  207. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/migrations.d/2026-07-13-fan-out-file-table-matching.md +0 -0
  208. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/migrations.d/2026-07-13-persist-wal.md +0 -0
  209. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/migrations.d/2026-07-13-rename-extract-to-on-file.md +0 -0
  210. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/migrations.d/2026-07-13-sdk-default-baked-in-config.md +0 -0
  211. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/migrations.d/2026-07-13-subcommand-local-config-flags.md +0 -0
  212. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/migrations.d/2026-07-20-drop-implicit-files-table.md +0 -0
  213. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/migrations.d/2026-07-20-path-table-glob-semantics.md +0 -0
  214. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/migrations.d/2026-07-21-remove-glob-captures.md +0 -0
  215. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/migrations.d/2026-07-23-init-escalation-example.md +0 -0
  216. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/migrations.d/2026-07-23-query-default-cli.md +0 -0
  217. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/migrations.d/2026-07-24-hookless-table-config-error.md +0 -0
  218. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/migrations.d/2026-07-24-remove-stat-fact-injection.md +0 -0
  219. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/migrations.d/2026-08-02-per-file-scan-failures.md +0 -0
  220. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/migrations.d/2026-08-03-gitignore-by-default.md +0 -0
  221. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/migrations.d/2026-08-03-nested-default-ignores.md +0 -0
  222. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/migrations.d/2026-08-10-remove-query-hooks.md +0 -0
  223. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/migrations.d/2026-08-11-oneshot-query-no-timeout.md +0 -0
  224. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/migrations.d/2026-08-11-retire-hook-timeout.md +0 -0
  225. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/migrations.d/README.md +0 -0
  226. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/src/bin/dirsql.rs +0 -0
  227. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/src/cli/execute.rs +0 -0
  228. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/src/cli/init.rs +0 -0
  229. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/src/cli/mod.rs +0 -0
  230. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/src/cli/router.rs +0 -0
  231. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/src/cli/run.rs +0 -0
  232. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/src/cli/serialize.rs +0 -0
  233. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/src/cli/server.rs +0 -0
  234. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/src/command.rs +0 -0
  235. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/src/config.rs +0 -0
  236. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/src/default_config.toml +0 -0
  237. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/src/differ.rs +0 -0
  238. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/src/functions.rs +0 -0
  239. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/src/infer.rs +0 -0
  240. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/src/matcher.rs +0 -0
  241. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/src/path_table.rs +0 -0
  242. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/src/scanner.rs +0 -0
  243. {dirsql-0.4.18 → dirsql-0.4.20}/packages/rust/src/watcher.rs +0 -0
  244. {dirsql-0.4.18 → dirsql-0.4.20}/pyproject.toml +0 -0
@@ -499,7 +499,7 @@ dependencies = [
499
499
 
500
500
  [[package]]
501
501
  name = "dirsql-py-ext"
502
- version = "0.4.18"
502
+ version = "0.4.20"
503
503
  dependencies = [
504
504
  "dirsql",
505
505
  "pyo3",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dirsql
3
- Version: 0.4.18
3
+ Version: 0.4.20
4
4
  Requires-Dist: tomli>=2 ; python_full_version < '3.11'
5
5
  Requires-Dist: bin-shim>=0.1
6
6
  Summary: Ephemeral SQL index over a local directory
@@ -55,6 +55,19 @@ can't be trusted at all — the table/ignore configuration changed, or the
55
55
  automatically. You never need to delete it by hand; a full rebuild costs
56
56
  exactly what a non-persistent startup does.
57
57
 
58
+ A run that changes nothing writes nothing: the cache file is read and left
59
+ exactly as it was.
60
+
61
+ This covers both kinds of table. Declared tables keep their indexed rows, and
62
+ a [path-table](../reference/path-tables.md) parsed with `--on-file` keeps each
63
+ file's parser output — so the second run over an unchanged tree spawns no
64
+ parser process at all. A path-table's cached rows are keyed by the tree, the
65
+ glob, the parser command and the `dirsql` version together, so changing any of
66
+ them parses afresh rather than serving rows the current command never
67
+ produced. A *stat* path-table caches nothing, because there is nothing to
68
+ save: its columns are the metadata the scan already collected, and `content`
69
+ is read live by design.
70
+
58
71
  Persistence is a startup-time optimization, not a change in meaning: the
59
72
  database remains a derived view of your files, and queries return the same
60
73
  rows either way ([how `dirsql` thinks](../explanation.md)).
@@ -140,6 +140,12 @@ the table:
140
140
  scan continues. The schema is inferred from the files that did parse.
141
141
  - **The skip rules still apply.** A parsed scan honors the same `node_modules`
142
142
  /`.git`/`ignore` rules a stat scan does (see below).
143
+ - **`--persist` skips the parser for unchanged files.** With a
144
+ [persistent cache](/howto/persist), each file's parser output is stored
145
+ against its stat metadata, so a later run over an unchanged tree serves the
146
+ rows from the cache and spawns no process. Change the file, the glob, the
147
+ parser command, or the dirsql version and that file (or that whole table) is
148
+ parsed again.
143
149
 
144
150
  `--on-file` applies to **every** path-table in the query and may be given **at
145
151
  most once**. For different parsers per file set, define named tables in a
@@ -160,15 +166,17 @@ deleted *after* the scan finds it but *before* its `content` is read yields
160
166
  than failing the query. This is an accepted consequence of reading live, not a
161
167
  bug to design around.
162
168
 
163
- Path-tables are per-connection and are never written to a persistent cache, so
164
- they cannot leak into `sqlite_master` or survive a restart. The reserved
165
- top-level `.dirsql/` directory is excluded from the scan, as everywhere else.
169
+ The table itself is per-connection: it lives in `temp`, so it cannot leak into
170
+ `sqlite_master` or survive a restart. Under `--persist` a *parsed* table's rows
171
+ outlive the connection in the cache (above), but the table is still minted
172
+ fresh each run and the scan still decides what exists. The reserved top-level
173
+ `.dirsql/` directory is excluded from the scan, as everywhere else.
166
174
 
167
175
  ### When to promote to a declared table
168
176
 
169
- Every query re-scans the filesystem: a path-table has no index, no watcher, and
170
- no persistent cache. That is the right trade for a hundreds-of-files, run-it-once
171
- question. When the same tree is queried repeatedly, or is large, declare a
177
+ Every query re-scans the filesystem: a path-table has no index and no watcher.
178
+ That is the right trade for a hundreds-of-files, run-it-once question. When the
179
+ same tree is queried repeatedly, or is large, declare a
172
180
  [table](/reference/config) for it instead — a declared table is indexed on
173
181
  build, kept fresh by the watcher, and (with `--persist`) survives restarts, so
174
182
  its rows are read from SQLite rather than re-walked each time.
@@ -4,7 +4,7 @@ name = "dirsql-py-ext"
4
4
  # pypi/maturin handler can rewrite it via `write-version` before
5
5
  # `maturin build`. `pyproject.toml` declares `dynamic = ["version"]`
6
6
  # and maturin reads this field. Mirrors `packages/rust/Cargo.toml`.
7
- version = "0.4.18"
7
+ version = "0.4.20"
8
8
  # abi3 note: the `abi3-py310` pyo3 feature (below) builds ONE stable-ABI
9
9
  # `cp310-abi3` wheel per platform that loads on every CPython >= 3.10
10
10
  # (matching `pyproject.toml`'s `requires-python = ">=3.10"`), instead of
@@ -55,6 +55,19 @@ can't be trusted at all — the table/ignore configuration changed, or the
55
55
  automatically. You never need to delete it by hand; a full rebuild costs
56
56
  exactly what a non-persistent startup does.
57
57
 
58
+ A run that changes nothing writes nothing: the cache file is read and left
59
+ exactly as it was.
60
+
61
+ This covers both kinds of table. Declared tables keep their indexed rows, and
62
+ a [path-table](../reference/path-tables.md) parsed with `--on-file` keeps each
63
+ file's parser output — so the second run over an unchanged tree spawns no
64
+ parser process at all. A path-table's cached rows are keyed by the tree, the
65
+ glob, the parser command and the `dirsql` version together, so changing any of
66
+ them parses afresh rather than serving rows the current command never
67
+ produced. A *stat* path-table caches nothing, because there is nothing to
68
+ save: its columns are the metadata the scan already collected, and `content`
69
+ is read live by design.
70
+
58
71
  Persistence is a startup-time optimization, not a change in meaning: the
59
72
  database remains a derived view of your files, and queries return the same
60
73
  rows either way ([how `dirsql` thinks](../explanation.md)).
@@ -140,6 +140,12 @@ the table:
140
140
  scan continues. The schema is inferred from the files that did parse.
141
141
  - **The skip rules still apply.** A parsed scan honors the same `node_modules`
142
142
  /`.git`/`ignore` rules a stat scan does (see below).
143
+ - **`--persist` skips the parser for unchanged files.** With a
144
+ [persistent cache](/howto/persist), each file's parser output is stored
145
+ against its stat metadata, so a later run over an unchanged tree serves the
146
+ rows from the cache and spawns no process. Change the file, the glob, the
147
+ parser command, or the dirsql version and that file (or that whole table) is
148
+ parsed again.
143
149
 
144
150
  `--on-file` applies to **every** path-table in the query and may be given **at
145
151
  most once**. For different parsers per file set, define named tables in a
@@ -160,15 +166,17 @@ deleted *after* the scan finds it but *before* its `content` is read yields
160
166
  than failing the query. This is an accepted consequence of reading live, not a
161
167
  bug to design around.
162
168
 
163
- Path-tables are per-connection and are never written to a persistent cache, so
164
- they cannot leak into `sqlite_master` or survive a restart. The reserved
165
- top-level `.dirsql/` directory is excluded from the scan, as everywhere else.
169
+ The table itself is per-connection: it lives in `temp`, so it cannot leak into
170
+ `sqlite_master` or survive a restart. Under `--persist` a *parsed* table's rows
171
+ outlive the connection in the cache (above), but the table is still minted
172
+ fresh each run and the scan still decides what exists. The reserved top-level
173
+ `.dirsql/` directory is excluded from the scan, as everywhere else.
166
174
 
167
175
  ### When to promote to a declared table
168
176
 
169
- Every query re-scans the filesystem: a path-table has no index, no watcher, and
170
- no persistent cache. That is the right trade for a hundreds-of-files, run-it-once
171
- question. When the same tree is queried repeatedly, or is large, declare a
177
+ Every query re-scans the filesystem: a path-table has no index and no watcher.
178
+ That is the right trade for a hundreds-of-files, run-it-once question. When the
179
+ same tree is queried repeatedly, or is large, declare a
172
180
  [table](/reference/config) for it instead — a declared table is indexed on
173
181
  build, kept fresh by the watcher, and (with `--persist`) survives restarts, so
174
182
  its rows are read from SQLite rather than re-walked each time.
@@ -0,0 +1,7 @@
1
+ {
2
+ "command": "uv run python -m pytest tests/e2e/ -x -q",
3
+ "ran_at": 1786553201,
4
+ "exit_code": 0,
5
+ "commit": "859a062ab1c795d43af357130dc1587ef9e77db0",
6
+ "branch": "claude/827-unquote-doubled-quotes"
7
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "command": "uv run python -m pytest tests/e2e/ -x -q",
3
+ "ran_at": 1786485396,
4
+ "exit_code": 0,
5
+ "commit": "31831d69029df1e191afbab33cb50818e43dc7df",
6
+ "branch": "claude/github-issue-825-e4mhhp"
7
+ }
@@ -0,0 +1,4 @@
1
+ **Fixed**
2
+
3
+ - **`--persist` now caches a parsed path-table's rows, so a second run over an unchanged tree re-runs the `on-file` parser for no file.** Previously the persistent cache only covered declared `[[table]]` entries: a path-table (`SELECT ... FROM './**/*.json' --on-file '<command>'`) re-scanned, re-read and re-parsed every file on every run, which made `--persist` pure overhead on the surface a configless `dirsql --persist` actually queries — measured 2.7s cold and 2.7s warm over a 2,000-file tree, versus 2.7s → 0.04s now. Each file's parser output is stored in the cache against its stat tuple (`_dirsql_parsed_rows`, reconciled by the same racy-stat rule declared tables use), keyed by the tree, glob, parser command and `dirsql` version together, so changed files re-parse and a changed command or glob starts fresh. A cache that cannot be updated costs the run its speedup, not its rows: the failure is a one-line stderr warning and the next run re-parses. A *stat* path-table is unaffected either way: its columns are the scan's own metadata and `content` stays a live read. (#825)
4
+ - **A run that changes nothing no longer rewrites the cache file.** The `_dirsql_meta` block was rewritten on every startup, so `cache.db` changed even when the tree had not; it is now written only when the meta actually differs, leaving an unchanged warm start byte-for-byte identical. (#825)
@@ -0,0 +1,3 @@
1
+ **Fixed**
2
+
3
+ - **A path-table argument containing a single quote now reaches the module as written.** `dirsql --on-file "sh -c 'jq . \"$1\"' sh {path}"` — `sh -c '…'` being the documented way to ask for a shell — arrived at the parser as `sh -c ''…''` and ran a different script. Minting a path-table writes its arguments as SQL literals, where an embedded quote is escaped by doubling; the modules stripped the surrounding quotes without undoing the doubling. The same corruption applied to any glob or ignore pattern carrying a quote. `quote_literal`, `quote_identifier` and `unquote` now live together in one module with a round-trip property test, so the encoder and decoder cannot drift apart again. (#827)
@@ -55,6 +55,19 @@ can't be trusted at all — the table/ignore configuration changed, or the
55
55
  automatically. You never need to delete it by hand; a full rebuild costs
56
56
  exactly what a non-persistent startup does.
57
57
 
58
+ A run that changes nothing writes nothing: the cache file is read and left
59
+ exactly as it was.
60
+
61
+ This covers both kinds of table. Declared tables keep their indexed rows, and
62
+ a [path-table](../reference/path-tables.md) parsed with `--on-file` keeps each
63
+ file's parser output — so the second run over an unchanged tree spawns no
64
+ parser process at all. A path-table's cached rows are keyed by the tree, the
65
+ glob, the parser command and the `dirsql` version together, so changing any of
66
+ them parses afresh rather than serving rows the current command never
67
+ produced. A *stat* path-table caches nothing, because there is nothing to
68
+ save: its columns are the metadata the scan already collected, and `content`
69
+ is read live by design.
70
+
58
71
  Persistence is a startup-time optimization, not a change in meaning: the
59
72
  database remains a derived view of your files, and queries return the same
60
73
  rows either way ([how `dirsql` thinks](../explanation.md)).
@@ -140,6 +140,12 @@ the table:
140
140
  scan continues. The schema is inferred from the files that did parse.
141
141
  - **The skip rules still apply.** A parsed scan honors the same `node_modules`
142
142
  /`.git`/`ignore` rules a stat scan does (see below).
143
+ - **`--persist` skips the parser for unchanged files.** With a
144
+ [persistent cache](/howto/persist), each file's parser output is stored
145
+ against its stat metadata, so a later run over an unchanged tree serves the
146
+ rows from the cache and spawns no process. Change the file, the glob, the
147
+ parser command, or the dirsql version and that file (or that whole table) is
148
+ parsed again.
143
149
 
144
150
  `--on-file` applies to **every** path-table in the query and may be given **at
145
151
  most once**. For different parsers per file set, define named tables in a
@@ -160,15 +166,17 @@ deleted *after* the scan finds it but *before* its `content` is read yields
160
166
  than failing the query. This is an accepted consequence of reading live, not a
161
167
  bug to design around.
162
168
 
163
- Path-tables are per-connection and are never written to a persistent cache, so
164
- they cannot leak into `sqlite_master` or survive a restart. The reserved
165
- top-level `.dirsql/` directory is excluded from the scan, as everywhere else.
169
+ The table itself is per-connection: it lives in `temp`, so it cannot leak into
170
+ `sqlite_master` or survive a restart. Under `--persist` a *parsed* table's rows
171
+ outlive the connection in the cache (above), but the table is still minted
172
+ fresh each run and the scan still decides what exists. The reserved top-level
173
+ `.dirsql/` directory is excluded from the scan, as everywhere else.
166
174
 
167
175
  ### When to promote to a declared table
168
176
 
169
- Every query re-scans the filesystem: a path-table has no index, no watcher, and
170
- no persistent cache. That is the right trade for a hundreds-of-files, run-it-once
171
- question. When the same tree is queried repeatedly, or is large, declare a
177
+ Every query re-scans the filesystem: a path-table has no index and no watcher.
178
+ That is the right trade for a hundreds-of-files, run-it-once question. When the
179
+ same tree is queried repeatedly, or is large, declare a
172
180
  [table](/reference/config) for it instead — a declared table is indexed on
173
181
  build, kept fresh by the watcher, and (with `--persist`) survives restarts, so
174
182
  its rows are read from SQLite rather than re-walked each time.
@@ -0,0 +1,58 @@
1
+ ### Persisted path-tables reuse a parser's output across runs
2
+
3
+ #### Summary
4
+
5
+ Under `--persist`, a path-table parsed with `--on-file` now serves an unchanged
6
+ file's rows from the cache instead of re-running the parser (dirsql#825). No
7
+ API changed and no call site moves; what changes is how often your parser
8
+ command runs. A parser whose output is a function of the file it is handed —
9
+ the documented `on-file` contract, and what declared `[[table]]` hooks have
10
+ always assumed under `--persist` — sees no difference beyond speed. A parser
11
+ that returns something different for the same unchanged file (it reads the
12
+ clock, a network service, or an environment variable) will now be observed
13
+ returning the *previous* run's answer until the file itself changes.
14
+
15
+ #### Required changes
16
+
17
+ _None._ Nothing to update: the fix restores the behavior
18
+ `docs/howto/persist.md` already documented.
19
+
20
+ | Surface | Before | After |
21
+ | ------- | ------ | ----- |
22
+ | `dirsql query "… FROM './**/*.json'" --on-file '<cmd>' --persist` | the parser ran once per file on every run | the parser runs only for files whose stat metadata moved |
23
+
24
+ #### Deprecations removed
25
+
26
+ _None._
27
+
28
+ #### Behavior changes without code changes
29
+
30
+ - **Parsed path-tables under `--persist`**: previously the parser ran once per
31
+ matched file on every run; now a file whose size/mtime/ctime/inode/device
32
+ tuple matches the cache serves its cached rows and the process is not
33
+ spawned. Cached rows are keyed by the scan root, the glob, the parser command
34
+ and the `dirsql` version together, so changing any of them re-parses. A
35
+ parser that is not a pure function of its input file should either be run
36
+ without `--persist` or made to vary with the file.
37
+ - **The cache file on an unchanged run**: previously every startup rewrote the
38
+ `_dirsql_meta` block, so `cache.db` changed even when nothing about the tree
39
+ had; now a run that changes nothing writes nothing and the file is left
40
+ byte-for-byte identical. Tooling that watched the cache's mtime as a
41
+ "dirsql ran" signal needs a different signal.
42
+ - **The internal `dirsql_parsed` vtab module** takes a fifth fixed argument
43
+ (the cache path, empty for none) between the gitignore switch and the ignore
44
+ patterns. It is `#[doc(hidden)]` plumbing that `dirsql` mints for itself; a
45
+ hand-written `CREATE VIRTUAL TABLE … USING dirsql_parsed(…)` must add the
46
+ argument.
47
+
48
+ #### Verification
49
+
50
+ ```bash
51
+ mkdir -p /tmp/dirsql-825 && cd /tmp/dirsql-825
52
+ printf '[{"id":1}]' > a.json
53
+ dirsql query "SELECT id FROM './*.json'" --on-file 'cat {path}' --persist
54
+ md5sum .dirsql/cache.db
55
+ time dirsql query "SELECT id FROM './*.json'" --on-file 'cat {path}' --persist
56
+ md5sum .dirsql/cache.db
57
+ # expected: the same rows, the second run near-instant, the same md5 both times
58
+ ```
@@ -6,6 +6,7 @@ use thiserror::Error;
6
6
  use crate::parsed_vtab;
7
7
  use crate::path_table::{self, PathTable, Resolution};
8
8
  use crate::scanner;
9
+ use crate::sql_literal::{quote_identifier, quote_literal};
9
10
  use crate::vtab;
10
11
 
11
12
  /// The user's home directory, if the platform reports one. Injected here so
@@ -94,15 +95,6 @@ fn no_home_path_table(name: &str) -> String {
94
95
  }
95
96
 
96
97
  /// Wrap `s` as a SQL string literal.
97
- fn quote_literal(s: &str) -> String {
98
- format!("'{}'", s.replace('\'', "''"))
99
- }
100
-
101
- /// Wrap `s` as a double-quoted SQL identifier.
102
- fn quote_identifier(s: &str) -> String {
103
- format!("\"{}\"", s.replace('"', "\"\""))
104
- }
105
-
106
98
  /// The DDL that mints a path-table.
107
99
  ///
108
100
  /// It lands in `temp`, which is per-connection and never written to the
@@ -114,12 +106,18 @@ fn quote_identifier(s: &str) -> String {
114
106
  /// the parsed module instead: its rows and schema come from the parser, not
115
107
  /// the stat columns, and the `path_prefix` is irrelevant (a parser wanting a
116
108
  /// path emits it). Both forms carry the same ignore rules.
109
+ ///
110
+ /// A parsed table also carries `cache` — the persistent cache path when the
111
+ /// index has one — so it can serve an unchanged file's rows without re-running
112
+ /// the parser. The stat module takes no such argument: its columns are the
113
+ /// stat tuple the scan already has, so there is nothing to save.
117
114
  fn path_table_ddl(
118
115
  name: &str,
119
116
  table: &PathTable,
120
117
  ignore: &[String],
121
118
  gitignore: bool,
122
119
  parser: Option<&str>,
120
+ cache: Option<&Path>,
123
121
  ) -> String {
124
122
  let (module, mut args) = match parser {
125
123
  Some(command) => (
@@ -144,6 +142,13 @@ fn path_table_ddl(
144
142
  } else {
145
143
  scanner::NO_GITIGNORE_ARG
146
144
  }));
145
+ if parser.is_some() {
146
+ args.push(quote_literal(
147
+ &cache
148
+ .map(|p| p.to_string_lossy().into_owned())
149
+ .unwrap_or_default(),
150
+ ));
151
+ }
147
152
  args.extend(ignore.iter().map(|p| quote_literal(p)));
148
153
 
149
154
  format!(
@@ -222,6 +227,9 @@ pub struct Db {
222
227
  /// parser instead of the stat columns: its rows and schema come from the
223
228
  /// command's output. `None` keeps the stat path-table behavior.
224
229
  path_table_parser: Option<String>,
230
+ /// The persistent cache a parsed path-table reuses across runs. `None` for
231
+ /// an ephemeral index, which has nowhere to cache to.
232
+ path_table_cache: Option<PathBuf>,
225
233
  }
226
234
 
227
235
  /// The skip rules a fresh `Db` starts with.
@@ -256,6 +264,7 @@ impl Db {
256
264
  path_table_ignore: default_path_table_ignore(),
257
265
  path_table_gitignore: true,
258
266
  path_table_parser: None,
267
+ path_table_cache: None,
259
268
  })
260
269
  }
261
270
 
@@ -277,6 +286,7 @@ impl Db {
277
286
  path_table_ignore: default_path_table_ignore(),
278
287
  path_table_gitignore: true,
279
288
  path_table_parser: None,
289
+ path_table_cache: None,
280
290
  })
281
291
  }
282
292
 
@@ -313,6 +323,14 @@ impl Db {
313
323
  self.path_table_parser = Some(command);
314
324
  }
315
325
 
326
+ /// Point every parsed path-table minted on this connection at the
327
+ /// persistent cache, so an unchanged file's rows are served from it
328
+ /// instead of re-running the parser. Set only when the index persists;
329
+ /// an ephemeral index has nowhere to cache to.
330
+ pub fn set_path_table_cache(&mut self, path: PathBuf) {
331
+ self.path_table_cache = Some(path);
332
+ }
333
+
316
334
  /// Borrow the underlying SQLite connection. Internal use only — exposed
317
335
  /// to the `persist` module so it can manage the sidecar tables.
318
336
  #[doc(hidden)]
@@ -783,6 +801,7 @@ impl Db {
783
801
  &self.path_table_ignore,
784
802
  self.path_table_gitignore,
785
803
  self.path_table_parser.as_deref(),
804
+ self.path_table_cache.as_deref(),
786
805
  ))?;
787
806
  Ok(())
788
807
  }
@@ -2147,16 +2166,6 @@ mod tests {
2147
2166
  );
2148
2167
  }
2149
2168
 
2150
- #[test]
2151
- fn quote_literal_doubles_embedded_quotes() {
2152
- assert_eq!(quote_literal("it's"), "'it''s'");
2153
- }
2154
-
2155
- #[test]
2156
- fn quote_identifier_doubles_embedded_quotes() {
2157
- assert_eq!(quote_identifier("a\"b"), "\"a\"\"b\"");
2158
- }
2159
-
2160
2169
  fn docs_path_table() -> PathTable {
2161
2170
  PathTable {
2162
2171
  root: PathBuf::from("/root"),
@@ -2167,7 +2176,7 @@ mod tests {
2167
2176
 
2168
2177
  #[test]
2169
2178
  fn path_table_ddl_creates_in_temp_if_not_exists() {
2170
- let ddl = path_table_ddl("./docs/*.md", &docs_path_table(), &[], true, None);
2179
+ let ddl = path_table_ddl("./docs/*.md", &docs_path_table(), &[], true, None, None);
2171
2180
  assert_eq!(
2172
2181
  ddl,
2173
2182
  "CREATE VIRTUAL TABLE IF NOT EXISTS temp.\"./docs/*.md\" \
@@ -2183,16 +2192,16 @@ mod tests {
2183
2192
  path_prefix: "/var/log".to_string(),
2184
2193
  };
2185
2194
  assert!(
2186
- path_table_ddl("/var/log/*.log", &table, &[], true, None)
2195
+ path_table_ddl("/var/log/*.log", &table, &[], true, None, None)
2187
2196
  .contains("'/var/log', '*.log', '/var/log', 'gitignore')"),
2188
2197
  "got: {}",
2189
- path_table_ddl("/var/log/*.log", &table, &[], true, None)
2198
+ path_table_ddl("/var/log/*.log", &table, &[], true, None, None)
2190
2199
  );
2191
2200
  }
2192
2201
 
2193
2202
  #[test]
2194
2203
  fn path_table_ddl_carries_the_no_gitignore_switch() {
2195
- let ddl = path_table_ddl("./", &docs_path_table(), &[], false, None);
2204
+ let ddl = path_table_ddl("./", &docs_path_table(), &[], false, None, None);
2196
2205
  assert!(
2197
2206
  ddl.ends_with("'', 'no-gitignore')"),
2198
2207
  "gitignore off must emit the no-gitignore switch, got: {ddl}"
@@ -2207,6 +2216,7 @@ mod tests {
2207
2216
  &["node_modules/**".to_string(), "*.tmp".to_string()],
2208
2217
  true,
2209
2218
  None,
2219
+ None,
2210
2220
  );
2211
2221
  assert!(
2212
2222
  ddl.ends_with("'', 'gitignore', 'node_modules/**', '*.tmp')"),
@@ -2222,11 +2232,44 @@ mod tests {
2222
2232
  &[],
2223
2233
  true,
2224
2234
  Some("cat {path}"),
2235
+ None,
2225
2236
  );
2226
2237
  assert_eq!(
2227
2238
  ddl,
2228
2239
  "CREATE VIRTUAL TABLE IF NOT EXISTS temp.\"./docs/*.md\" \
2229
- USING dirsql_parsed('/root', 'docs/*.md', 'cat {path}', 'gitignore')"
2240
+ USING dirsql_parsed('/root', 'docs/*.md', 'cat {path}', 'gitignore', '')"
2241
+ );
2242
+ }
2243
+
2244
+ #[test]
2245
+ fn path_table_ddl_parser_form_carries_the_cache_path() {
2246
+ let ddl = path_table_ddl(
2247
+ "./docs/*.md",
2248
+ &docs_path_table(),
2249
+ &[],
2250
+ true,
2251
+ Some("cat {path}"),
2252
+ Some(Path::new("/cache/dirsql.db")),
2253
+ );
2254
+ assert!(
2255
+ ddl.ends_with("'cat {path}', 'gitignore', '/cache/dirsql.db')"),
2256
+ "a persisted index points the parsed module at its cache, got: {ddl}"
2257
+ );
2258
+ }
2259
+
2260
+ #[test]
2261
+ fn path_table_ddl_stat_form_takes_no_cache_path() {
2262
+ let ddl = path_table_ddl(
2263
+ "./docs/*.md",
2264
+ &docs_path_table(),
2265
+ &[],
2266
+ true,
2267
+ None,
2268
+ Some(Path::new("/cache/dirsql.db")),
2269
+ );
2270
+ assert!(
2271
+ ddl.ends_with("'', 'gitignore')"),
2272
+ "the stat form has nothing to cache, got: {ddl}"
2230
2273
  );
2231
2274
  }
2232
2275
 
@@ -2243,11 +2286,12 @@ mod tests {
2243
2286
  &["node_modules/**".to_string()],
2244
2287
  true,
2245
2288
  Some("parse.py {path}"),
2289
+ None,
2246
2290
  );
2247
2291
  assert!(
2248
2292
  ddl.ends_with(
2249
2293
  "USING dirsql_parsed('/var/log', '*.log', 'parse.py {path}', 'gitignore', \
2250
- 'node_modules/**')"
2294
+ '', 'node_modules/**')"
2251
2295
  ),
2252
2296
  "the parser form drops the path prefix and keeps ignore rules, got: {ddl}"
2253
2297
  );
@@ -2255,7 +2299,14 @@ mod tests {
2255
2299
 
2256
2300
  #[test]
2257
2301
  fn path_table_ddl_parser_form_quotes_a_command_with_a_quote() {
2258
- let ddl = path_table_ddl("./", &docs_path_table(), &[], true, Some("sh -c 'echo hi'"));
2302
+ let ddl = path_table_ddl(
2303
+ "./",
2304
+ &docs_path_table(),
2305
+ &[],
2306
+ true,
2307
+ Some("sh -c 'echo hi'"),
2308
+ None,
2309
+ );
2259
2310
  assert!(
2260
2311
  ddl.contains("'sh -c ''echo hi'''"),
2261
2312
  "an embedded quote must be doubled, got: {ddl}"
@@ -2269,6 +2320,17 @@ mod tests {
2269
2320
  assert_eq!(db.path_table_parser.as_deref(), Some("cat {path}"));
2270
2321
  }
2271
2322
 
2323
+ #[test]
2324
+ fn set_path_table_cache_points_parsed_tables_at_the_cache() {
2325
+ let mut db = Db::new().unwrap();
2326
+ assert_eq!(db.path_table_cache, None, "an ephemeral index has no cache");
2327
+ db.set_path_table_cache(PathBuf::from("/cache/dirsql.db"));
2328
+ assert_eq!(
2329
+ db.path_table_cache.as_deref(),
2330
+ Some(Path::new("/cache/dirsql.db"))
2331
+ );
2332
+ }
2333
+
2272
2334
  #[test]
2273
2335
  fn path_table_gitignore_defaults_on() {
2274
2336
  let db = Db::new().unwrap();