dirsql 0.4.19__tar.gz → 0.4.21__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.19 → dirsql-0.4.21}/Cargo.lock +1 -1
  2. {dirsql-0.4.19 → dirsql-0.4.21}/PKG-INFO +1 -1
  3. {dirsql-0.4.19 → dirsql-0.4.21}/docs/.vitepress/config.ts +1 -2
  4. {dirsql-0.4.19/packages/python → dirsql-0.4.21}/docs/AGENTS.md +4 -6
  5. {dirsql-0.4.19/packages/python → dirsql-0.4.21}/docs/howto/search-by-meaning.md +8 -1
  6. {dirsql-0.4.19/packages/rust → dirsql-0.4.21}/docs/plugins.md +1 -0
  7. {dirsql-0.4.19 → dirsql-0.4.21}/packages/python/Cargo.toml +1 -1
  8. {dirsql-0.4.19 → dirsql-0.4.21}/packages/python/MIGRATIONS.md +1 -2
  9. {dirsql-0.4.19 → dirsql-0.4.21}/packages/python/docs/.vitepress/config.ts +1 -2
  10. {dirsql-0.4.19 → dirsql-0.4.21/packages/python}/docs/AGENTS.md +4 -6
  11. {dirsql-0.4.19 → dirsql-0.4.21/packages/python}/docs/howto/search-by-meaning.md +8 -1
  12. {dirsql-0.4.19 → dirsql-0.4.21/packages/python}/docs/plugins.md +1 -0
  13. dirsql-0.4.21/packages/python/e2e-attestations/claude-827-unquote-doubled-quotes.json +7 -0
  14. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/MIGRATIONS.md +1 -2
  15. dirsql-0.4.21/packages/rust/changelog.d/2026-08-12-unquote-inverts-quote-literal.md +3 -0
  16. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/docs/howto/search-by-meaning.md +8 -1
  17. {dirsql-0.4.19/packages/python → dirsql-0.4.21/packages/rust}/docs/plugins.md +1 -0
  18. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/src/db.rs +1 -19
  19. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/src/lib.rs +2 -0
  20. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/src/parsed_vtab.rs +1 -42
  21. dirsql-0.4.21/packages/rust/src/sql_literal.rs +127 -0
  22. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/src/vtab.rs +1 -42
  23. dirsql-0.4.19/docs/migrations.md +0 -5
  24. dirsql-0.4.19/packages/python/docs/migrations.md +0 -5
  25. dirsql-0.4.19/packages/rust/docs/migrations.md +0 -5
  26. {dirsql-0.4.19 → dirsql-0.4.21}/Cargo.toml +0 -0
  27. {dirsql-0.4.19 → dirsql-0.4.21}/README.md +0 -0
  28. {dirsql-0.4.19 → dirsql-0.4.21}/dirsql/__init__.py +0 -0
  29. {dirsql-0.4.19 → dirsql-0.4.21}/dirsql/_async.py +0 -0
  30. {dirsql-0.4.19 → dirsql-0.4.21}/dirsql/_dirsql.pyi +0 -0
  31. {dirsql-0.4.19 → dirsql-0.4.21}/dirsql/cli/__init__.py +0 -0
  32. {dirsql-0.4.19 → dirsql-0.4.21}/dirsql/cli/discover_plugins/__init__.py +0 -0
  33. {dirsql-0.4.19 → dirsql-0.4.21}/dirsql/cli/discover_plugins/discovered_fragments.py +0 -0
  34. {dirsql-0.4.19 → dirsql-0.4.21}/dirsql/cli/discover_plugins/discovery_disabled.py +0 -0
  35. {dirsql-0.4.19 → dirsql-0.4.21}/dirsql/cli/discover_plugins/fragment_path.py +0 -0
  36. {dirsql-0.4.19 → dirsql-0.4.21}/dirsql/cli/discover_plugins/user_passed_config.py +0 -0
  37. {dirsql-0.4.19 → dirsql-0.4.21}/dirsql/cli/discover_plugins/with_discovered_plugins.py +0 -0
  38. {dirsql-0.4.19 → dirsql-0.4.21}/dirsql/cli/main.py +0 -0
  39. {dirsql-0.4.19 → dirsql-0.4.21}/dirsql/cli/resolve_config_extensions.py +0 -0
  40. {dirsql-0.4.19 → dirsql-0.4.21}/dirsql/py.typed +0 -0
  41. {dirsql-0.4.19 → dirsql-0.4.21}/dirsql/resolve_config_extensions.py +0 -0
  42. {dirsql-0.4.19 → dirsql-0.4.21}/dirsql/resolve_extension.py +0 -0
  43. {dirsql-0.4.19 → dirsql-0.4.21}/docs/.claude/CLAUDE.md +0 -0
  44. {dirsql-0.4.19 → dirsql-0.4.21}/docs/.vitepress/theme/index.ts +0 -0
  45. {dirsql-0.4.19 → dirsql-0.4.21}/docs/.vitepress/theme/lang.ts +0 -0
  46. {dirsql-0.4.19 → dirsql-0.4.21}/docs/explanation.md +0 -0
  47. {dirsql-0.4.19 → dirsql-0.4.21}/docs/getting-started.md +0 -0
  48. {dirsql-0.4.19 → dirsql-0.4.21}/docs/howto/columns-from-paths.md +0 -0
  49. {dirsql-0.4.19 → dirsql-0.4.21}/docs/howto/define-tables.md +0 -0
  50. {dirsql-0.4.19 → dirsql-0.4.21}/docs/howto/embed.md +0 -0
  51. {dirsql-0.4.19 → dirsql-0.4.21}/docs/howto/extract-from-contents.md +0 -0
  52. {dirsql-0.4.19 → dirsql-0.4.21}/docs/howto/load-extension.md +0 -0
  53. {dirsql-0.4.19 → dirsql-0.4.21}/docs/howto/parse-files-into-columns.md +0 -0
  54. {dirsql-0.4.19 → dirsql-0.4.21}/docs/howto/persist.md +0 -0
  55. {dirsql-0.4.19 → dirsql-0.4.21}/docs/howto/query-without-config.md +0 -0
  56. {dirsql-0.4.19 → dirsql-0.4.21}/docs/howto/react-to-changes.md +0 -0
  57. {dirsql-0.4.19 → dirsql-0.4.21}/docs/howto/skip-files.md +0 -0
  58. {dirsql-0.4.19 → dirsql-0.4.21}/docs/howto/write-a-plugin.md +0 -0
  59. {dirsql-0.4.19 → dirsql-0.4.21}/docs/index.md +0 -0
  60. {dirsql-0.4.19 → dirsql-0.4.21}/docs/package.json +0 -0
  61. {dirsql-0.4.19 → dirsql-0.4.21}/docs/pnpm-lock.yaml +0 -0
  62. {dirsql-0.4.19 → dirsql-0.4.21}/docs/pnpm-workspace.yaml +0 -0
  63. {dirsql-0.4.19 → dirsql-0.4.21}/docs/reference/cli.md +0 -0
  64. {dirsql-0.4.19 → dirsql-0.4.21}/docs/reference/columns.md +0 -0
  65. {dirsql-0.4.19 → dirsql-0.4.21}/docs/reference/config.md +0 -0
  66. {dirsql-0.4.19 → dirsql-0.4.21}/docs/reference/hooks.md +0 -0
  67. {dirsql-0.4.19 → dirsql-0.4.21}/docs/reference/http-api.md +0 -0
  68. {dirsql-0.4.19 → dirsql-0.4.21}/docs/reference/path-tables.md +0 -0
  69. {dirsql-0.4.19 → dirsql-0.4.21}/docs/reference/sdk.md +0 -0
  70. {dirsql-0.4.19 → dirsql-0.4.21}/packages/python/CHANGELOG.md +0 -0
  71. {dirsql-0.4.19 → dirsql-0.4.21}/packages/python/README.md +0 -0
  72. {dirsql-0.4.19 → dirsql-0.4.21}/packages/python/changelog.d/2026-07-13-drop-cwd-config-auto-detect.md +0 -0
  73. {dirsql-0.4.19 → dirsql-0.4.21}/packages/python/changelog.d/2026-07-13-plugin-discovery.md +0 -0
  74. {dirsql-0.4.19 → dirsql-0.4.21}/packages/python/changelog.d/2026-07-13-rename-extract-to-on-file.md +0 -0
  75. {dirsql-0.4.19 → dirsql-0.4.21}/packages/python/changelog.d/2026-07-13-repeatable-config.md +0 -0
  76. {dirsql-0.4.19 → dirsql-0.4.21}/packages/python/changelog.d/2026-07-13-sdk-default-baked-in-config.md +0 -0
  77. {dirsql-0.4.19 → dirsql-0.4.21}/packages/python/changelog.d/2026-07-20-drop-implicit-files-table.md +0 -0
  78. {dirsql-0.4.19 → dirsql-0.4.21}/packages/python/changelog.d/2026-07-21-remove-glob-captures.md +0 -0
  79. {dirsql-0.4.19 → dirsql-0.4.21}/packages/python/changelog.d/2026-07-24-hookless-table-config-error.md +0 -0
  80. {dirsql-0.4.19 → dirsql-0.4.21}/packages/python/changelog.d/2026-07-24-remove-stat-fact-injection.md +0 -0
  81. {dirsql-0.4.19 → dirsql-0.4.21}/packages/python/changelog.d/2026-07-29-restore-python-310.md +0 -0
  82. {dirsql-0.4.19 → dirsql-0.4.21}/packages/python/changelog.d/2026-08-02-release-profile.md +0 -0
  83. {dirsql-0.4.19 → dirsql-0.4.21}/packages/python/changelog.d/2026-08-02-scan-failures.md +0 -0
  84. {dirsql-0.4.19 → dirsql-0.4.21}/packages/python/changelog.d/2026-08-03-discovery-ext-resolution.md +0 -0
  85. {dirsql-0.4.19 → dirsql-0.4.21}/packages/python/changelog.d/2026-08-03-manylinux-dynamic-binary.md +0 -0
  86. {dirsql-0.4.19 → dirsql-0.4.21}/packages/python/changelog.d/2026-08-03-python-no-ignore.md +0 -0
  87. {dirsql-0.4.19 → dirsql-0.4.21}/packages/python/changelog.d/2026-08-04-cast-lints.md +0 -0
  88. {dirsql-0.4.19 → dirsql-0.4.21}/packages/python/changelog.d/2026-08-04-cli-in-process.md +0 -0
  89. {dirsql-0.4.19 → dirsql-0.4.21}/packages/python/changelog.d/README.md +0 -0
  90. {dirsql-0.4.19 → dirsql-0.4.21}/packages/python/conftest.py +0 -0
  91. {dirsql-0.4.19 → dirsql-0.4.21}/packages/python/docs/.claude/CLAUDE.md +0 -0
  92. {dirsql-0.4.19 → dirsql-0.4.21}/packages/python/docs/.vitepress/theme/index.ts +0 -0
  93. {dirsql-0.4.19 → dirsql-0.4.21}/packages/python/docs/.vitepress/theme/lang.ts +0 -0
  94. {dirsql-0.4.19 → dirsql-0.4.21}/packages/python/docs/explanation.md +0 -0
  95. {dirsql-0.4.19 → dirsql-0.4.21}/packages/python/docs/getting-started.md +0 -0
  96. {dirsql-0.4.19 → dirsql-0.4.21}/packages/python/docs/howto/columns-from-paths.md +0 -0
  97. {dirsql-0.4.19 → dirsql-0.4.21}/packages/python/docs/howto/define-tables.md +0 -0
  98. {dirsql-0.4.19 → dirsql-0.4.21}/packages/python/docs/howto/embed.md +0 -0
  99. {dirsql-0.4.19 → dirsql-0.4.21}/packages/python/docs/howto/extract-from-contents.md +0 -0
  100. {dirsql-0.4.19 → dirsql-0.4.21}/packages/python/docs/howto/load-extension.md +0 -0
  101. {dirsql-0.4.19 → dirsql-0.4.21}/packages/python/docs/howto/parse-files-into-columns.md +0 -0
  102. {dirsql-0.4.19 → dirsql-0.4.21}/packages/python/docs/howto/persist.md +0 -0
  103. {dirsql-0.4.19 → dirsql-0.4.21}/packages/python/docs/howto/query-without-config.md +0 -0
  104. {dirsql-0.4.19 → dirsql-0.4.21}/packages/python/docs/howto/react-to-changes.md +0 -0
  105. {dirsql-0.4.19 → dirsql-0.4.21}/packages/python/docs/howto/skip-files.md +0 -0
  106. {dirsql-0.4.19 → dirsql-0.4.21}/packages/python/docs/howto/write-a-plugin.md +0 -0
  107. {dirsql-0.4.19 → dirsql-0.4.21}/packages/python/docs/index.md +0 -0
  108. {dirsql-0.4.19 → dirsql-0.4.21}/packages/python/docs/package.json +0 -0
  109. {dirsql-0.4.19 → dirsql-0.4.21}/packages/python/docs/pnpm-lock.yaml +0 -0
  110. {dirsql-0.4.19 → dirsql-0.4.21}/packages/python/docs/pnpm-workspace.yaml +0 -0
  111. {dirsql-0.4.19 → dirsql-0.4.21}/packages/python/docs/reference/cli.md +0 -0
  112. {dirsql-0.4.19 → dirsql-0.4.21}/packages/python/docs/reference/columns.md +0 -0
  113. {dirsql-0.4.19 → dirsql-0.4.21}/packages/python/docs/reference/config.md +0 -0
  114. {dirsql-0.4.19 → dirsql-0.4.21}/packages/python/docs/reference/hooks.md +0 -0
  115. {dirsql-0.4.19 → dirsql-0.4.21}/packages/python/docs/reference/http-api.md +0 -0
  116. {dirsql-0.4.19 → dirsql-0.4.21}/packages/python/docs/reference/path-tables.md +0 -0
  117. {dirsql-0.4.19 → dirsql-0.4.21}/packages/python/docs/reference/sdk.md +0 -0
  118. {dirsql-0.4.19 → dirsql-0.4.21}/packages/python/e2e-attestations/claude-819-oneshot-timeout.json +0 -0
  119. {dirsql-0.4.19 → dirsql-0.4.21}/packages/python/e2e-attestations/claude-github-issue-825-e4mhhp.json +0 -0
  120. {dirsql-0.4.19 → dirsql-0.4.21}/packages/python/migrations.d/2026-07-13-drop-cwd-config-auto-detect.md +0 -0
  121. {dirsql-0.4.19 → dirsql-0.4.21}/packages/python/migrations.d/2026-07-13-rename-extract-to-on-file.md +0 -0
  122. {dirsql-0.4.19 → dirsql-0.4.21}/packages/python/migrations.d/2026-07-13-sdk-default-baked-in-config.md +0 -0
  123. {dirsql-0.4.19 → dirsql-0.4.21}/packages/python/migrations.d/2026-07-20-drop-implicit-files-table.md +0 -0
  124. {dirsql-0.4.19 → dirsql-0.4.21}/packages/python/migrations.d/2026-07-21-remove-glob-captures.md +0 -0
  125. {dirsql-0.4.19 → dirsql-0.4.21}/packages/python/migrations.d/2026-07-24-hookless-table-config-error.md +0 -0
  126. {dirsql-0.4.19 → dirsql-0.4.21}/packages/python/migrations.d/2026-07-24-remove-stat-fact-injection.md +0 -0
  127. {dirsql-0.4.19 → dirsql-0.4.21}/packages/python/migrations.d/2026-07-29-restore-python-310.md +0 -0
  128. {dirsql-0.4.19 → dirsql-0.4.21}/packages/python/migrations.d/2026-08-04-cli-in-process.md +0 -0
  129. {dirsql-0.4.19 → dirsql-0.4.21}/packages/python/migrations.d/README.md +0 -0
  130. {dirsql-0.4.19 → dirsql-0.4.21}/packages/python/src/lib.rs +0 -0
  131. {dirsql-0.4.19 → dirsql-0.4.21}/packages/python/tests/__init__.py +0 -0
  132. {dirsql-0.4.19 → dirsql-0.4.21}/packages/python/tests/conftest.py +0 -0
  133. {dirsql-0.4.19 → dirsql-0.4.21}/packages/python/tests/e2e/__init__.py +0 -0
  134. {dirsql-0.4.19 → dirsql-0.4.21}/packages/python/tests/e2e/fixtures/dirsql_plugin_fixture/__init__.py +0 -0
  135. {dirsql-0.4.19 → dirsql-0.4.21}/packages/python/tests/e2e/fixtures/dirsql_plugin_fixture/dirsql.toml +0 -0
  136. {dirsql-0.4.19 → dirsql-0.4.21}/packages/python/tests/integration/__init__.py +0 -0
  137. {dirsql-0.4.19 → dirsql-0.4.21}/packages/python/tests/integration/binding/__init__.py +0 -0
  138. {dirsql-0.4.19 → dirsql-0.4.21}/packages/python/tests/integration/hermetic/__init__.py +0 -0
  139. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/CHANGELOG.md +0 -0
  140. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/Cargo.toml +0 -0
  141. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/README.md +0 -0
  142. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/benches/db_bench.rs +0 -0
  143. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/benches/differ_bench.rs +0 -0
  144. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/benches/matcher_bench.rs +0 -0
  145. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/benches/scanner_bench.rs +0 -0
  146. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/changelog.d/2026-07-13-batch-ingest-transaction.md +0 -0
  147. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/changelog.d/2026-07-13-chained-pre-post-query-hooks.md +0 -0
  148. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/changelog.d/2026-07-13-drop-cwd-config-auto-detect.md +0 -0
  149. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/changelog.d/2026-07-13-fan-out-file-table-matching.md +0 -0
  150. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/changelog.d/2026-07-13-persist-wal.md +0 -0
  151. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/changelog.d/2026-07-13-rename-extract-to-on-file.md +0 -0
  152. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/changelog.d/2026-07-13-repeatable-builder-config.md +0 -0
  153. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/changelog.d/2026-07-13-repeatable-cli-config.md +0 -0
  154. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/changelog.d/2026-07-13-sdk-default-baked-in-config.md +0 -0
  155. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/changelog.d/2026-07-13-subcommand-local-config-flags.md +0 -0
  156. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/changelog.d/2026-07-19-path-table-query-fallback.md +0 -0
  157. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/changelog.d/2026-07-19-path-table-vtab.md +0 -0
  158. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/changelog.d/2026-07-20-drop-implicit-files-table.md +0 -0
  159. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/changelog.d/2026-07-20-path-table-glob-semantics.md +0 -0
  160. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/changelog.d/2026-07-20-schema-inference.md +0 -0
  161. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/changelog.d/2026-07-21-on-file-flag.md +0 -0
  162. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/changelog.d/2026-07-21-remove-glob-captures.md +0 -0
  163. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/changelog.d/2026-07-23-init-escalation-example.md +0 -0
  164. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/changelog.d/2026-07-23-query-default-cli.md +0 -0
  165. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/changelog.d/2026-07-24-hookless-table-config-error.md +0 -0
  166. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/changelog.d/2026-07-24-remove-stat-fact-injection.md +0 -0
  167. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/changelog.d/2026-07-29-on-file-failures-accumulate.md +0 -0
  168. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/changelog.d/2026-08-02-drop-regex.md +0 -0
  169. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/changelog.d/2026-08-02-per-file-scan-failures.md +0 -0
  170. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/changelog.d/2026-08-02-release-profile.md +0 -0
  171. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/changelog.d/2026-08-03-gitignore-by-default.md +0 -0
  172. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/changelog.d/2026-08-03-nested-default-ignores.md +0 -0
  173. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/changelog.d/2026-08-04-cast-lints.md +0 -0
  174. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/changelog.d/2026-08-04-cli-run-shim.md +0 -0
  175. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/changelog.d/2026-08-04-configless-extension.md +0 -0
  176. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/changelog.d/2026-08-10-dirsql-function.md +0 -0
  177. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/changelog.d/2026-08-10-remove-query-hooks.md +0 -0
  178. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/changelog.d/2026-08-11-oneshot-query-no-timeout.md +0 -0
  179. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/changelog.d/2026-08-11-persist-parsed-path-tables.md +0 -0
  180. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/changelog.d/2026-08-11-retire-hook-timeout.md +0 -0
  181. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/changelog.d/README.md +0 -0
  182. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/docs/explanation.md +0 -0
  183. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/docs/getting-started.md +0 -0
  184. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/docs/howto/columns-from-paths.md +0 -0
  185. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/docs/howto/define-tables.md +0 -0
  186. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/docs/howto/embed.md +0 -0
  187. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/docs/howto/extract-from-contents.md +0 -0
  188. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/docs/howto/load-extension.md +0 -0
  189. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/docs/howto/parse-files-into-columns.md +0 -0
  190. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/docs/howto/persist.md +0 -0
  191. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/docs/howto/query-without-config.md +0 -0
  192. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/docs/howto/react-to-changes.md +0 -0
  193. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/docs/howto/skip-files.md +0 -0
  194. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/docs/howto/write-a-plugin.md +0 -0
  195. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/docs/index.md +0 -0
  196. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/docs/reference/cli.md +0 -0
  197. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/docs/reference/columns.md +0 -0
  198. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/docs/reference/config.md +0 -0
  199. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/docs/reference/hooks.md +0 -0
  200. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/docs/reference/http-api.md +0 -0
  201. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/docs/reference/path-tables.md +0 -0
  202. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/docs/reference/sdk.md +0 -0
  203. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/migrations.d/2026-07-13-drop-cwd-config-auto-detect.md +0 -0
  204. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/migrations.d/2026-07-13-fan-out-file-table-matching.md +0 -0
  205. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/migrations.d/2026-07-13-persist-wal.md +0 -0
  206. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/migrations.d/2026-07-13-rename-extract-to-on-file.md +0 -0
  207. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/migrations.d/2026-07-13-sdk-default-baked-in-config.md +0 -0
  208. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/migrations.d/2026-07-13-subcommand-local-config-flags.md +0 -0
  209. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/migrations.d/2026-07-20-drop-implicit-files-table.md +0 -0
  210. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/migrations.d/2026-07-20-path-table-glob-semantics.md +0 -0
  211. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/migrations.d/2026-07-21-remove-glob-captures.md +0 -0
  212. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/migrations.d/2026-07-23-init-escalation-example.md +0 -0
  213. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/migrations.d/2026-07-23-query-default-cli.md +0 -0
  214. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/migrations.d/2026-07-24-hookless-table-config-error.md +0 -0
  215. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/migrations.d/2026-07-24-remove-stat-fact-injection.md +0 -0
  216. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/migrations.d/2026-08-02-per-file-scan-failures.md +0 -0
  217. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/migrations.d/2026-08-03-gitignore-by-default.md +0 -0
  218. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/migrations.d/2026-08-03-nested-default-ignores.md +0 -0
  219. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/migrations.d/2026-08-10-remove-query-hooks.md +0 -0
  220. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/migrations.d/2026-08-11-oneshot-query-no-timeout.md +0 -0
  221. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/migrations.d/2026-08-11-persist-parsed-path-tables.md +0 -0
  222. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/migrations.d/2026-08-11-retire-hook-timeout.md +0 -0
  223. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/migrations.d/README.md +0 -0
  224. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/src/bin/dirsql.rs +0 -0
  225. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/src/cli/execute.rs +0 -0
  226. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/src/cli/init.rs +0 -0
  227. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/src/cli/mod.rs +0 -0
  228. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/src/cli/router.rs +0 -0
  229. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/src/cli/run.rs +0 -0
  230. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/src/cli/serialize.rs +0 -0
  231. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/src/cli/server.rs +0 -0
  232. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/src/command.rs +0 -0
  233. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/src/config.rs +0 -0
  234. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/src/default_config.toml +0 -0
  235. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/src/differ.rs +0 -0
  236. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/src/functions.rs +0 -0
  237. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/src/infer.rs +0 -0
  238. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/src/matcher.rs +0 -0
  239. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/src/parsed_cache.rs +0 -0
  240. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/src/path_table.rs +0 -0
  241. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/src/persist.rs +0 -0
  242. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/src/scanner.rs +0 -0
  243. {dirsql-0.4.19 → dirsql-0.4.21}/packages/rust/src/watcher.rs +0 -0
  244. {dirsql-0.4.19 → dirsql-0.4.21}/pyproject.toml +0 -0
@@ -499,7 +499,7 @@ dependencies = [
499
499
 
500
500
  [[package]]
501
501
  name = "dirsql-py-ext"
502
- version = "0.4.19"
502
+ version = "0.4.21"
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.19
3
+ Version: 0.4.21
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
@@ -72,8 +72,7 @@ export default defineConfig({
72
72
  { text: 'Columns', link: '/reference/columns' },
73
73
  { text: 'Path-tables', link: '/reference/path-tables' },
74
74
  { text: 'HTTP API', link: '/reference/http-api' },
75
- { text: 'SDK', link: '/reference/sdk' },
76
- { text: 'Migrations', link: '/migrations' }
75
+ { text: 'SDK', link: '/reference/sdk' }
77
76
  ]
78
77
  },
79
78
  {
@@ -60,15 +60,13 @@ migrated -- a page survives only if a slot wants its content):
60
60
  protocol, exit codes, timeouts); the columns model (named tables get their
61
61
  hook's output; path-tables get stat columns); the
62
62
  HTTP API; the SDK page (`reference/sdk.md`, one page with
63
- Python/TypeScript/Rust code-groups -- the sole SDK home); plus the
64
- Migrations include (`migrations.md`).
63
+ Python/TypeScript/Rust code-groups -- the sole SDK home).
65
64
  - **Explanation** (`explanation.md`) -- one page: how `dirsql` thinks (the
66
65
  filesystem is the source of truth; the database is a derived, ephemeral,
67
66
  read-only view; reconcile and diffing). Its canonical home is the root
68
- `ARCHITECTURE.md`;
69
- #374 surfaces it via an include page, the same mechanism
70
- `docs/migrations.md` uses for `MIGRATIONS.md`. Edit the root file, never
71
- a rendered include.
67
+ `ARCHITECTURE.md`; #374 surfaces it via a VitePress include
68
+ (`<!--@include: ../ARCHITECTURE.md-->`). Edit the root file, never a
69
+ rendered include.
72
70
 
73
71
  Working rules:
74
72
 
@@ -51,6 +51,7 @@ uvx --with dirsql-plugin-embeddings dirsql "
51
51
  SELECT path,
52
52
  vec_distance_cosine(emb, embed('how do I cook pasta?')) AS distance
53
53
  FROM (SELECT path, embed(content) AS emb FROM './notes/*.md')
54
+ WHERE emb IS NOT NULL
54
55
  ORDER BY distance
55
56
  LIMIT 3"
56
57
  ```
@@ -72,7 +73,12 @@ Reading the query inside-out:
72
73
  the one-liner normalizes a bare glob for you.
73
74
  2. `embed('how do I cook pasta?')` embeds the question once (the function is
74
75
  deterministic, so SQLite reuses the value across rows).
75
- 3. `vec_distance_cosine(...)` computes cosine distance between the two
76
+ 3. `WHERE emb IS NOT NULL` drops the files that could not be embedded. A
77
+ file that is unreadable or not valid UTF-8 has
78
+ [`NULL` content](../reference/path-tables.md#columns), so its embedding
79
+ and its distance are NULL too — and SQLite sorts NULLs *first* ascending,
80
+ so without this line the unrankable files take the top-k slots.
81
+ 4. `vec_distance_cosine(...)` computes cosine distance between the two
76
82
  vectors; `ORDER BY distance LIMIT 3` keeps the three nearest.
77
83
 
78
84
  Structured files compose with SQL's JSON operators — embed one field instead
@@ -82,6 +88,7 @@ of the whole file:
82
88
  SELECT path
83
89
  FROM (SELECT path, embed(content ->> 'abstract') AS emb
84
90
  FROM './papers/**/metadata.json')
91
+ WHERE emb IS NOT NULL
85
92
  ORDER BY vec_distance_cosine(emb, embed('local private models'))
86
93
  LIMIT 10
87
94
  ```
@@ -35,6 +35,7 @@ uvx --with dirsql-plugin-embeddings dirsql "
35
35
  SELECT path
36
36
  FROM (SELECT path, embed(content ->> 'abstract') AS emb
37
37
  FROM './arxiv-firehose/data/**/metadata.json')
38
+ WHERE emb IS NOT NULL
38
39
  ORDER BY vec_distance_cosine(emb, embed('local private models'))
39
40
  LIMIT 10"
40
41
  ```
@@ -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.19"
7
+ version = "0.4.21"
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
@@ -7,5 +7,4 @@ Migration entries for this package live as one file per breaking change under
7
7
  Nothing is assembled back into this file.
8
8
 
9
9
  Pre-fragment history (all packages) is frozen in the repository-root
10
- [`MIGRATIONS.md`](../../MIGRATIONS.md), surfaced on the docs site at
11
- [`/migrations`](https://thekevinscott.github.io/dirsql/migrations).
10
+ [`MIGRATIONS.md`](../../MIGRATIONS.md).
@@ -72,8 +72,7 @@ export default defineConfig({
72
72
  { text: 'Columns', link: '/reference/columns' },
73
73
  { text: 'Path-tables', link: '/reference/path-tables' },
74
74
  { text: 'HTTP API', link: '/reference/http-api' },
75
- { text: 'SDK', link: '/reference/sdk' },
76
- { text: 'Migrations', link: '/migrations' }
75
+ { text: 'SDK', link: '/reference/sdk' }
77
76
  ]
78
77
  },
79
78
  {
@@ -60,15 +60,13 @@ migrated -- a page survives only if a slot wants its content):
60
60
  protocol, exit codes, timeouts); the columns model (named tables get their
61
61
  hook's output; path-tables get stat columns); the
62
62
  HTTP API; the SDK page (`reference/sdk.md`, one page with
63
- Python/TypeScript/Rust code-groups -- the sole SDK home); plus the
64
- Migrations include (`migrations.md`).
63
+ Python/TypeScript/Rust code-groups -- the sole SDK home).
65
64
  - **Explanation** (`explanation.md`) -- one page: how `dirsql` thinks (the
66
65
  filesystem is the source of truth; the database is a derived, ephemeral,
67
66
  read-only view; reconcile and diffing). Its canonical home is the root
68
- `ARCHITECTURE.md`;
69
- #374 surfaces it via an include page, the same mechanism
70
- `docs/migrations.md` uses for `MIGRATIONS.md`. Edit the root file, never
71
- a rendered include.
67
+ `ARCHITECTURE.md`; #374 surfaces it via a VitePress include
68
+ (`<!--@include: ../ARCHITECTURE.md-->`). Edit the root file, never a
69
+ rendered include.
72
70
 
73
71
  Working rules:
74
72
 
@@ -51,6 +51,7 @@ uvx --with dirsql-plugin-embeddings dirsql "
51
51
  SELECT path,
52
52
  vec_distance_cosine(emb, embed('how do I cook pasta?')) AS distance
53
53
  FROM (SELECT path, embed(content) AS emb FROM './notes/*.md')
54
+ WHERE emb IS NOT NULL
54
55
  ORDER BY distance
55
56
  LIMIT 3"
56
57
  ```
@@ -72,7 +73,12 @@ Reading the query inside-out:
72
73
  the one-liner normalizes a bare glob for you.
73
74
  2. `embed('how do I cook pasta?')` embeds the question once (the function is
74
75
  deterministic, so SQLite reuses the value across rows).
75
- 3. `vec_distance_cosine(...)` computes cosine distance between the two
76
+ 3. `WHERE emb IS NOT NULL` drops the files that could not be embedded. A
77
+ file that is unreadable or not valid UTF-8 has
78
+ [`NULL` content](../reference/path-tables.md#columns), so its embedding
79
+ and its distance are NULL too — and SQLite sorts NULLs *first* ascending,
80
+ so without this line the unrankable files take the top-k slots.
81
+ 4. `vec_distance_cosine(...)` computes cosine distance between the two
76
82
  vectors; `ORDER BY distance LIMIT 3` keeps the three nearest.
77
83
 
78
84
  Structured files compose with SQL's JSON operators — embed one field instead
@@ -82,6 +88,7 @@ of the whole file:
82
88
  SELECT path
83
89
  FROM (SELECT path, embed(content ->> 'abstract') AS emb
84
90
  FROM './papers/**/metadata.json')
91
+ WHERE emb IS NOT NULL
85
92
  ORDER BY vec_distance_cosine(emb, embed('local private models'))
86
93
  LIMIT 10
87
94
  ```
@@ -35,6 +35,7 @@ uvx --with dirsql-plugin-embeddings dirsql "
35
35
  SELECT path
36
36
  FROM (SELECT path, embed(content ->> 'abstract') AS emb
37
37
  FROM './arxiv-firehose/data/**/metadata.json')
38
+ WHERE emb IS NOT NULL
38
39
  ORDER BY vec_distance_cosine(emb, embed('local private models'))
39
40
  LIMIT 10"
40
41
  ```
@@ -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
+ }
@@ -7,5 +7,4 @@ Migration entries for this package live as one file per breaking change under
7
7
  Nothing is assembled back into this file.
8
8
 
9
9
  Pre-fragment history (all packages) is frozen in the repository-root
10
- [`MIGRATIONS.md`](../../MIGRATIONS.md), surfaced on the docs site at
11
- [`/migrations`](https://thekevinscott.github.io/dirsql/migrations).
10
+ [`MIGRATIONS.md`](../../MIGRATIONS.md).
@@ -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)
@@ -51,6 +51,7 @@ uvx --with dirsql-plugin-embeddings dirsql "
51
51
  SELECT path,
52
52
  vec_distance_cosine(emb, embed('how do I cook pasta?')) AS distance
53
53
  FROM (SELECT path, embed(content) AS emb FROM './notes/*.md')
54
+ WHERE emb IS NOT NULL
54
55
  ORDER BY distance
55
56
  LIMIT 3"
56
57
  ```
@@ -72,7 +73,12 @@ Reading the query inside-out:
72
73
  the one-liner normalizes a bare glob for you.
73
74
  2. `embed('how do I cook pasta?')` embeds the question once (the function is
74
75
  deterministic, so SQLite reuses the value across rows).
75
- 3. `vec_distance_cosine(...)` computes cosine distance between the two
76
+ 3. `WHERE emb IS NOT NULL` drops the files that could not be embedded. A
77
+ file that is unreadable or not valid UTF-8 has
78
+ [`NULL` content](../reference/path-tables.md#columns), so its embedding
79
+ and its distance are NULL too — and SQLite sorts NULLs *first* ascending,
80
+ so without this line the unrankable files take the top-k slots.
81
+ 4. `vec_distance_cosine(...)` computes cosine distance between the two
76
82
  vectors; `ORDER BY distance LIMIT 3` keeps the three nearest.
77
83
 
78
84
  Structured files compose with SQL's JSON operators — embed one field instead
@@ -82,6 +88,7 @@ of the whole file:
82
88
  SELECT path
83
89
  FROM (SELECT path, embed(content ->> 'abstract') AS emb
84
90
  FROM './papers/**/metadata.json')
91
+ WHERE emb IS NOT NULL
85
92
  ORDER BY vec_distance_cosine(emb, embed('local private models'))
86
93
  LIMIT 10
87
94
  ```
@@ -35,6 +35,7 @@ uvx --with dirsql-plugin-embeddings dirsql "
35
35
  SELECT path
36
36
  FROM (SELECT path, embed(content ->> 'abstract') AS emb
37
37
  FROM './arxiv-firehose/data/**/metadata.json')
38
+ WHERE emb IS NOT NULL
38
39
  ORDER BY vec_distance_cosine(emb, embed('local private models'))
39
40
  LIMIT 10"
40
41
  ```
@@ -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
@@ -2174,16 +2166,6 @@ mod tests {
2174
2166
  );
2175
2167
  }
2176
2168
 
2177
- #[test]
2178
- fn quote_literal_doubles_embedded_quotes() {
2179
- assert_eq!(quote_literal("it's"), "'it''s'");
2180
- }
2181
-
2182
- #[test]
2183
- fn quote_identifier_doubles_embedded_quotes() {
2184
- assert_eq!(quote_identifier("a\"b"), "\"a\"\"b\"");
2185
- }
2186
-
2187
2169
  fn docs_path_table() -> PathTable {
2188
2170
  PathTable {
2189
2171
  root: PathBuf::from("/root"),
@@ -32,6 +32,8 @@ pub mod persist;
32
32
  #[doc(hidden)]
33
33
  pub mod scanner;
34
34
  #[doc(hidden)]
35
+ pub mod sql_literal;
36
+ #[doc(hidden)]
35
37
  pub mod vtab;
36
38
  #[doc(hidden)]
37
39
  pub mod watcher;
@@ -39,6 +39,7 @@ use crate::parsed_cache::{self, CachedParse, Entry, RowCache, SqliteRowCache};
39
39
  use crate::path_table;
40
40
  use crate::persist::{FileStat, hash_file, now_ns};
41
41
  use crate::scanner::{self, scan_glob};
42
+ use crate::sql_literal::unquote;
42
43
 
43
44
  /// SQL module name a parsed path-table is created with.
44
45
  pub const MODULE_NAME: &str = "dirsql_parsed";
@@ -49,18 +50,6 @@ pub fn load_module(conn: &Connection) -> Result<()> {
49
50
  conn.create_module(MODULE_NAME, read_only_module::<ParsedTab>(), aux)
50
51
  }
51
52
 
52
- /// Strip one layer of SQL string quoting from a `CREATE VIRTUAL TABLE`
53
- /// argument. SQLite hands module arguments through verbatim, quotes included.
54
- fn unquote(arg: &str) -> &str {
55
- let trimmed = arg.trim();
56
- for quote in ['\'', '"'] {
57
- if trimmed.len() >= 2 && trimmed.starts_with(quote) && trimmed.ends_with(quote) {
58
- return &trimmed[1..trimmed.len() - 1];
59
- }
60
- }
61
- trimmed
62
- }
63
-
64
53
  /// Number of module arguments that are not ignore patterns.
65
54
  const FIXED_ARGS: usize = 5;
66
55
 
@@ -485,36 +474,6 @@ mod tests {
485
474
  assert_eq!(MODULE_NAME, "dirsql_parsed");
486
475
  }
487
476
 
488
- #[test]
489
- fn unquote_strips_single_quotes() {
490
- assert_eq!(unquote("'./docs'"), "./docs");
491
- }
492
-
493
- #[test]
494
- fn unquote_strips_double_quotes() {
495
- assert_eq!(unquote("\"./docs\""), "./docs");
496
- }
497
-
498
- #[test]
499
- fn unquote_trims_surrounding_whitespace() {
500
- assert_eq!(unquote(" './docs' "), "./docs");
501
- }
502
-
503
- #[test]
504
- fn unquote_leaves_unquoted_arguments_alone() {
505
- assert_eq!(unquote("./docs"), "./docs");
506
- }
507
-
508
- #[test]
509
- fn unquote_leaves_a_lone_quote_alone() {
510
- assert_eq!(unquote("'"), "'");
511
- }
512
-
513
- #[test]
514
- fn unquote_leaves_mismatched_quotes_alone() {
515
- assert_eq!(unquote("'./docs\""), "'./docs\"");
516
- }
517
-
518
477
  /// The three fixed arguments SQLite prepends before the module's own.
519
478
  fn args_with<'a>(user: &[&'a [u8]]) -> Vec<&'a [u8]> {
520
479
  let mut all: Vec<&'a [u8]> = vec![
@@ -0,0 +1,127 @@
1
+ //! The one encoding a path-table's arguments travel through.
2
+ //!
3
+ //! `dirsql` mints a path-table by writing a `CREATE VIRTUAL TABLE` statement
4
+ //! whose module arguments are SQL literals ([`quote_literal`]), and SQLite
5
+ //! hands those arguments to the module verbatim, quotes included, for
6
+ //! [`unquote`] to decode. The two are inverses, and they live together so they
7
+ //! stay that way: a value that survives one but not the other reaches the
8
+ //! module as something the user never wrote.
9
+
10
+ use std::borrow::Cow;
11
+
12
+ /// Wrap `s` as a single-quoted SQL string literal.
13
+ pub fn quote_literal(s: &str) -> String {
14
+ format!("'{}'", s.replace('\'', "''"))
15
+ }
16
+
17
+ /// Wrap `s` as a double-quoted SQL identifier.
18
+ pub fn quote_identifier(s: &str) -> String {
19
+ format!("\"{}\"", s.replace('"', "\"\""))
20
+ }
21
+
22
+ /// Decode one quoted `CREATE VIRTUAL TABLE` argument: strip the surrounding
23
+ /// quotes and collapse the doubling that escaped them. Borrowed when there is
24
+ /// nothing to collapse, which is the ordinary case.
25
+ pub fn unquote(arg: &str) -> Cow<'_, str> {
26
+ let trimmed = arg.trim();
27
+ for quote in ['\'', '"'] {
28
+ if trimmed.len() >= 2 && trimmed.starts_with(quote) && trimmed.ends_with(quote) {
29
+ let inner = &trimmed[1..trimmed.len() - 1];
30
+ let doubled = [quote, quote].iter().collect::<String>();
31
+ return if inner.contains(&doubled) {
32
+ Cow::Owned(inner.replace(&doubled, &quote.to_string()))
33
+ } else {
34
+ Cow::Borrowed(inner)
35
+ };
36
+ }
37
+ }
38
+ Cow::Borrowed(trimmed)
39
+ }
40
+
41
+ #[cfg(test)]
42
+ mod tests {
43
+ use super::*;
44
+
45
+ #[test]
46
+ fn quote_literal_doubles_embedded_quotes() {
47
+ assert_eq!(quote_literal("it's"), "'it''s'");
48
+ }
49
+
50
+ #[test]
51
+ fn quote_identifier_doubles_embedded_quotes() {
52
+ assert_eq!(quote_identifier("a\"b"), "\"a\"\"b\"");
53
+ }
54
+
55
+ #[test]
56
+ fn unquote_strips_single_quotes() {
57
+ assert_eq!(unquote("'./docs'"), "./docs");
58
+ }
59
+
60
+ #[test]
61
+ fn unquote_strips_double_quotes() {
62
+ assert_eq!(unquote("\"./docs\""), "./docs");
63
+ }
64
+
65
+ #[test]
66
+ fn unquote_trims_surrounding_whitespace() {
67
+ assert_eq!(unquote(" './docs' "), "./docs");
68
+ }
69
+
70
+ #[test]
71
+ fn unquote_leaves_unquoted_arguments_alone() {
72
+ assert_eq!(unquote("./docs"), "./docs");
73
+ }
74
+
75
+ #[test]
76
+ fn unquote_leaves_a_lone_quote_alone() {
77
+ assert_eq!(unquote("'"), "'");
78
+ }
79
+
80
+ #[test]
81
+ fn unquote_leaves_mismatched_quotes_alone() {
82
+ assert_eq!(unquote("'./docs\""), "'./docs\"");
83
+ }
84
+
85
+ #[test]
86
+ fn unquote_collapses_the_doubling_that_escaped_a_quote() {
87
+ assert_eq!(unquote("'sh -c ''echo hi'''"), "sh -c 'echo hi'");
88
+ }
89
+
90
+ #[test]
91
+ fn unquote_collapses_only_the_quote_character_it_stripped() {
92
+ // A double-quote inside a single-quoted literal was never doubled, so
93
+ // collapsing it would corrupt the value in the other direction.
94
+ assert_eq!(unquote("'say \"\"hi\"\"'"), "say \"\"hi\"\"");
95
+ }
96
+
97
+ #[test]
98
+ fn unquote_borrows_when_there_is_nothing_to_collapse() {
99
+ assert!(matches!(unquote("'plain'"), Cow::Borrowed("plain")));
100
+ }
101
+
102
+ #[test]
103
+ fn unquote_inverts_quote_literal() {
104
+ for value in [
105
+ "plain",
106
+ "it's",
107
+ "sh -c 'echo hi'",
108
+ "''",
109
+ "a''b",
110
+ "say \"hi\"",
111
+ "",
112
+ ] {
113
+ assert_eq!(
114
+ unquote(&quote_literal(value)),
115
+ value,
116
+ "round trip must be lossless for {value:?}"
117
+ );
118
+ }
119
+ }
120
+
121
+ #[test]
122
+ fn unquote_inverts_quote_identifier() {
123
+ for value in ["plain", "a\"b", "./docs/*.md"] {
124
+ assert_eq!(unquote(&quote_identifier(value)), value);
125
+ }
126
+ }
127
+ }
@@ -13,6 +13,7 @@ use crate::compute_stat_virtuals;
13
13
  use crate::matcher::TableMatcher;
14
14
  use crate::path_table;
15
15
  use crate::scanner::{self, scan_glob};
16
+ use crate::sql_literal::unquote;
16
17
  use crate::{Row, Value};
17
18
 
18
19
  /// SQL module name a path-table is created with:
@@ -58,18 +59,6 @@ fn compile_glob(pattern: &str) -> Result<GlobSet> {
58
59
  scanner::compile_glob(pattern).map_err(|e| Error::ModuleError(e.to_string()))
59
60
  }
60
61
 
61
- /// Strip one layer of SQL string quoting from a `CREATE VIRTUAL TABLE`
62
- /// argument. SQLite hands module arguments through verbatim, quotes included.
63
- fn unquote(arg: &str) -> &str {
64
- let trimmed = arg.trim();
65
- for quote in ['\'', '"'] {
66
- if trimmed.len() >= 2 && trimmed.starts_with(quote) && trimmed.ends_with(quote) {
67
- return &trimmed[1..trimmed.len() - 1];
68
- }
69
- }
70
- trimmed
71
- }
72
-
73
62
  /// Read `path` as text, yielding `None` when it is unreadable or not valid
74
63
  /// UTF-8. A file that cannot be read is a NULL cell, never a failed row: the
75
64
  /// filesystem is allowed to be messy and a query over it should still return.
@@ -376,36 +365,6 @@ mod tests {
376
365
  assert_eq!(MODULE_NAME, "dirsql_path");
377
366
  }
378
367
 
379
- #[test]
380
- fn unquote_strips_single_quotes() {
381
- assert_eq!(unquote("'./docs'"), "./docs");
382
- }
383
-
384
- #[test]
385
- fn unquote_strips_double_quotes() {
386
- assert_eq!(unquote("\"./docs\""), "./docs");
387
- }
388
-
389
- #[test]
390
- fn unquote_trims_surrounding_whitespace() {
391
- assert_eq!(unquote(" './docs' "), "./docs");
392
- }
393
-
394
- #[test]
395
- fn unquote_leaves_unquoted_arguments_alone() {
396
- assert_eq!(unquote("./docs"), "./docs");
397
- }
398
-
399
- #[test]
400
- fn unquote_leaves_a_lone_quote_alone() {
401
- assert_eq!(unquote("'"), "'");
402
- }
403
-
404
- #[test]
405
- fn unquote_leaves_mismatched_quotes_alone() {
406
- assert_eq!(unquote("'./docs\""), "'./docs\"");
407
- }
408
-
409
368
  #[test]
410
369
  fn compile_glob_accepts_a_valid_pattern() {
411
370
  assert!(compile_glob("**/*.md").is_ok());
@@ -1,5 +0,0 @@
1
- ---
2
- title: Migrations
3
- ---
4
-
5
- <!--@include: ../MIGRATIONS.md-->
@@ -1,5 +0,0 @@
1
- ---
2
- title: Migrations
3
- ---
4
-
5
- <!--@include: ../MIGRATIONS.md-->
@@ -1,5 +0,0 @@
1
- ---
2
- title: Migrations
3
- ---
4
-
5
- <!--@include: ../MIGRATIONS.md-->
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes