dirsql 0.4.29__tar.gz → 0.4.30__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.
- {dirsql-0.4.29 → dirsql-0.4.30}/Cargo.lock +2 -2
- {dirsql-0.4.29 → dirsql-0.4.30}/PKG-INFO +1 -1
- {dirsql-0.4.29 → dirsql-0.4.30}/docs/.vitepress/config.ts +1 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/docs/howto/define-tables.md +3 -1
- {dirsql-0.4.29/packages/rust → dirsql-0.4.30}/docs/howto/query-without-config.md +3 -1
- dirsql-0.4.30/docs/howto/search-indexes.md +220 -0
- {dirsql-0.4.29/packages/python → dirsql-0.4.30}/docs/reference/path-tables.md +7 -1
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/python/Cargo.toml +1 -1
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/python/docs/.vitepress/config.ts +1 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/python/docs/howto/define-tables.md +3 -1
- {dirsql-0.4.29 → dirsql-0.4.30/packages/python}/docs/howto/query-without-config.md +3 -1
- dirsql-0.4.30/packages/python/docs/howto/search-indexes.md +220 -0
- {dirsql-0.4.29 → dirsql-0.4.30/packages/python}/docs/reference/path-tables.md +7 -1
- dirsql-0.4.30/packages/python/e2e-attestations/cc-keen-knuth-f11gyq.json +7 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/rust/Cargo.toml +1 -1
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/rust/docs/howto/define-tables.md +3 -1
- {dirsql-0.4.29/packages/python → dirsql-0.4.30/packages/rust}/docs/howto/query-without-config.md +3 -1
- dirsql-0.4.30/packages/rust/docs/howto/search-indexes.md +220 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/rust/docs/reference/path-tables.md +7 -1
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/rust/src/lib.rs +87 -7
- {dirsql-0.4.29 → dirsql-0.4.30}/Cargo.toml +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/README.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/dirsql/__init__.py +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/dirsql/_async.py +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/dirsql/_dirsql.pyi +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/dirsql/cli/__init__.py +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/dirsql/cli/discover_plugins/__init__.py +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/dirsql/cli/discover_plugins/discovered_fragments.py +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/dirsql/cli/discover_plugins/discovery_disabled.py +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/dirsql/cli/discover_plugins/fragment_path.py +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/dirsql/cli/discover_plugins/user_passed_config.py +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/dirsql/cli/discover_plugins/with_discovered_plugins.py +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/dirsql/cli/main.py +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/dirsql/cli/resolve_config_extensions.py +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/dirsql/py.typed +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/dirsql/resolve_config_extensions.py +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/dirsql/resolve_extension.py +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/docs/.claude/CLAUDE.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/docs/.vitepress/theme/index.ts +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/docs/.vitepress/theme/lang.ts +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/docs/AGENTS.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/docs/explanation.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/docs/getting-started.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/docs/howto/columns-from-paths.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/docs/howto/embed.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/docs/howto/extract-from-contents.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/docs/howto/load-extension.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/docs/howto/parse-files-into-columns.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/docs/howto/persist.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/docs/howto/query-json.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/docs/howto/react-to-changes.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/docs/howto/search-by-meaning.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/docs/howto/skip-files.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/docs/howto/write-a-plugin.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/docs/index.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/docs/package.json +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/docs/plugins.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/docs/pnpm-lock.yaml +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/docs/pnpm-workspace.yaml +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/docs/reference/cli.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/docs/reference/columns.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/docs/reference/config.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/docs/reference/hooks.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/docs/reference/http-api.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/docs/reference/sdk.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/python/CHANGELOG.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/python/MIGRATIONS.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/python/README.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/python/changelog.d/2026-07-13-drop-cwd-config-auto-detect.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/python/changelog.d/2026-07-13-plugin-discovery.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/python/changelog.d/2026-07-13-rename-extract-to-on-file.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/python/changelog.d/2026-07-13-repeatable-config.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/python/changelog.d/2026-07-13-sdk-default-baked-in-config.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/python/changelog.d/2026-07-20-drop-implicit-files-table.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/python/changelog.d/2026-07-21-remove-glob-captures.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/python/changelog.d/2026-07-24-hookless-table-config-error.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/python/changelog.d/2026-07-24-remove-stat-fact-injection.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/python/changelog.d/2026-07-29-restore-python-310.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/python/changelog.d/2026-08-02-release-profile.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/python/changelog.d/2026-08-02-scan-failures.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/python/changelog.d/2026-08-03-discovery-ext-resolution.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/python/changelog.d/2026-08-03-manylinux-dynamic-binary.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/python/changelog.d/2026-08-03-python-no-ignore.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/python/changelog.d/2026-08-04-cast-lints.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/python/changelog.d/2026-08-04-cli-in-process.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/python/changelog.d/2026-08-20-curtaincall-dev-dependency.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/python/changelog.d/2026-08-20-declared-table-name.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/python/changelog.d/README.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/python/conftest.py +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/python/docs/.claude/CLAUDE.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/python/docs/.vitepress/theme/index.ts +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/python/docs/.vitepress/theme/lang.ts +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/python/docs/AGENTS.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/python/docs/explanation.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/python/docs/getting-started.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/python/docs/howto/columns-from-paths.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/python/docs/howto/embed.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/python/docs/howto/extract-from-contents.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/python/docs/howto/load-extension.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/python/docs/howto/parse-files-into-columns.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/python/docs/howto/persist.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/python/docs/howto/query-json.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/python/docs/howto/react-to-changes.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/python/docs/howto/search-by-meaning.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/python/docs/howto/skip-files.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/python/docs/howto/write-a-plugin.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/python/docs/index.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/python/docs/package.json +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/python/docs/plugins.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/python/docs/pnpm-lock.yaml +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/python/docs/pnpm-workspace.yaml +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/python/docs/reference/cli.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/python/docs/reference/columns.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/python/docs/reference/config.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/python/docs/reference/hooks.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/python/docs/reference/http-api.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/python/docs/reference/sdk.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/python/e2e-attestations/claude-819-oneshot-timeout.json +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/python/e2e-attestations/claude-827-unquote-doubled-quotes.json +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/python/e2e-attestations/claude-epic-953-slice2-reedline.json +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/python/e2e-attestations/claude-epic-953-slice3-format.json +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/python/e2e-attestations/claude-epic-953-stacked-prs-x59wm3.json +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/python/e2e-attestations/claude-github-issue-825-e4mhhp.json +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/python/e2e-attestations/claude-issue-951-0eease.json +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/python/e2e-attestations/claude-issue-962-0jjrfv.json +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/python/e2e-attestations/claude-issue-986-red-test-e7sjrb.json +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/python/e2e-attestations/claude-tackle-956-xzen74.json +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/python/migrations.d/2026-07-13-drop-cwd-config-auto-detect.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/python/migrations.d/2026-07-13-rename-extract-to-on-file.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/python/migrations.d/2026-07-13-sdk-default-baked-in-config.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/python/migrations.d/2026-07-20-drop-implicit-files-table.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/python/migrations.d/2026-07-21-remove-glob-captures.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/python/migrations.d/2026-07-24-hookless-table-config-error.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/python/migrations.d/2026-07-24-remove-stat-fact-injection.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/python/migrations.d/2026-07-29-restore-python-310.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/python/migrations.d/2026-08-04-cli-in-process.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/python/migrations.d/2026-08-20-declared-table-name.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/python/migrations.d/README.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/python/src/lib.rs +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/python/testing-conventions.toml +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/python/tests/__init__.py +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/python/tests/conftest.py +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/python/tests/e2e/__init__.py +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/python/tests/e2e/fixtures/dirsql_plugin_fixture/__init__.py +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/python/tests/e2e/fixtures/dirsql_plugin_fixture/dirsql.toml +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/python/tests/integration/__init__.py +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/python/tests/integration/binding/__init__.py +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/python/tests/integration/hermetic/__init__.py +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/rust/CHANGELOG.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/rust/MIGRATIONS.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/rust/README.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/rust/benches/db_bench.rs +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/rust/benches/differ_bench.rs +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/rust/benches/matcher_bench.rs +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/rust/benches/scanner_bench.rs +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/rust/docs/explanation.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/rust/docs/getting-started.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/rust/docs/howto/columns-from-paths.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/rust/docs/howto/embed.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/rust/docs/howto/extract-from-contents.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/rust/docs/howto/load-extension.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/rust/docs/howto/parse-files-into-columns.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/rust/docs/howto/persist.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/rust/docs/howto/query-json.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/rust/docs/howto/react-to-changes.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/rust/docs/howto/search-by-meaning.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/rust/docs/howto/skip-files.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/rust/docs/howto/write-a-plugin.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/rust/docs/index.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/rust/docs/plugins.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/rust/docs/reference/cli.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/rust/docs/reference/columns.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/rust/docs/reference/config.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/rust/docs/reference/hooks.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/rust/docs/reference/http-api.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/rust/docs/reference/sdk.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/rust/src/bin/dirsql.rs +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/rust/src/cli/execute.rs +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/rust/src/cli/init.rs +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/rust/src/cli/mod.rs +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/rust/src/cli/repl.rs +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/rust/src/cli/router.rs +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/rust/src/cli/run.rs +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/rust/src/cli/serialize.rs +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/rust/src/cli/server.rs +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/rust/src/cli/table.rs +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/rust/src/command.rs +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/rust/src/config.rs +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/rust/src/db.rs +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/rust/src/default_config.toml +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/rust/src/differ.rs +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/rust/src/functions.rs +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/rust/src/infer.rs +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/rust/src/matcher.rs +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/rust/src/parsed_cache.rs +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/rust/src/parsed_vtab.rs +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/rust/src/path_table.rs +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/rust/src/persist.rs +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/rust/src/scanner.rs +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/rust/src/sql_literal.rs +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/rust/src/vtab.rs +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/packages/rust/src/watcher.rs +0 -0
- {dirsql-0.4.29 → dirsql-0.4.30}/pyproject.toml +0 -0
|
@@ -540,7 +540,7 @@ checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8"
|
|
|
540
540
|
|
|
541
541
|
[[package]]
|
|
542
542
|
name = "dirsql"
|
|
543
|
-
version = "0.4.
|
|
543
|
+
version = "0.4.30"
|
|
544
544
|
dependencies = [
|
|
545
545
|
"assert_cmd",
|
|
546
546
|
"axum",
|
|
@@ -583,7 +583,7 @@ dependencies = [
|
|
|
583
583
|
|
|
584
584
|
[[package]]
|
|
585
585
|
name = "dirsql-py-ext"
|
|
586
|
-
version = "0.4.
|
|
586
|
+
version = "0.4.30"
|
|
587
587
|
dependencies = [
|
|
588
588
|
"dirsql",
|
|
589
589
|
"pyo3",
|
|
@@ -56,6 +56,7 @@ export default defineConfig({
|
|
|
56
56
|
{ text: 'Parse your files into columns', link: '/howto/parse-files-into-columns' },
|
|
57
57
|
{ text: 'Query JSON file contents', link: '/howto/query-json' },
|
|
58
58
|
{ text: 'Search documents by meaning', link: '/howto/search-by-meaning' },
|
|
59
|
+
{ text: 'Add a search index to a table', link: '/howto/search-indexes' },
|
|
59
60
|
{ text: "Skip files you don't want indexed", link: '/howto/skip-files' },
|
|
60
61
|
{ text: 'Load a SQLite extension', link: '/howto/load-extension' },
|
|
61
62
|
{ text: 'Keep the index across restarts', link: '/howto/persist' },
|
|
@@ -107,7 +107,9 @@ maintain nothing. `name` still has to be the row table (`posts`); the virtual
|
|
|
107
107
|
table lives beside it under its own name. The batch runs once, when the table
|
|
108
108
|
is created, and editing any part of it rebuilds a
|
|
109
109
|
[persistent cache](./persist.md) from scratch. Full rules:
|
|
110
|
-
[Batch `ddl`](../reference/config.md#batch-ddl)
|
|
110
|
+
[Batch `ddl`](../reference/config.md#batch-ddl); pasteable FTS5 and vector
|
|
111
|
+
templates, with the trigger mistakes that fail silently, are in
|
|
112
|
+
[Add a search index to a table](./search-indexes.md).
|
|
111
113
|
|
|
112
114
|
## Going further
|
|
113
115
|
|
|
@@ -55,7 +55,9 @@ question over a few hundred files, wasteful for a large tree you query
|
|
|
55
55
|
repeatedly. When that day comes, [declare a table](./define-tables.md): it is
|
|
56
56
|
indexed once, kept fresh by the watcher, and can persist across restarts. The
|
|
57
57
|
zero-config query is the floor; a named table is the escalation path — nothing
|
|
58
|
-
you write here has to be thrown away to get there.
|
|
58
|
+
you write here has to be thrown away to get there. The two sides in full, and
|
|
59
|
+
why indexes belong to only one of them, are in
|
|
60
|
+
[how `dirsql` thinks](../explanation.md#two-table-kinds-opposite-trade-offs).
|
|
59
61
|
|
|
60
62
|
## Notes
|
|
61
63
|
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
# Add a search index to a table
|
|
2
|
+
|
|
3
|
+
Make a declared table answer search queries instead of scanning: a B-tree for
|
|
4
|
+
lookups, an FTS5 index for keywords, a `vec0` index for meaning. All three are
|
|
5
|
+
statements in the table's [`ddl` batch](../reference/config.md#batch-ddl), so
|
|
6
|
+
SQLite does the work and `dirsql` maintains nothing.
|
|
7
|
+
|
|
8
|
+
The templates below are meant to be pasted and edited. Both are shaped around
|
|
9
|
+
one worked example — notes in `notes/*.md`, parsed into a `notes` table by an
|
|
10
|
+
`extract.py` that prints `{"slug": …, "title": …, "body": …}` per file, exactly
|
|
11
|
+
as in [Define tables for your files](./define-tables.md).
|
|
12
|
+
|
|
13
|
+
## Why triggers, and why only two
|
|
14
|
+
|
|
15
|
+
`dirsql` writes file rows with plain `INSERT` and `DELETE`; an update is a
|
|
16
|
+
delete and an insert in one transaction, and there is no `UPDATE` path on user
|
|
17
|
+
rows. So an insert trigger and a delete trigger cover every way a row can
|
|
18
|
+
change — during the initial build, on each [watcher](./react-to-changes.md)
|
|
19
|
+
event, and when a [persistent cache](./persist.md) reconciles a tree that
|
|
20
|
+
moved on. There is no third trigger to write and no maintenance command to run.
|
|
21
|
+
|
|
22
|
+
The triggers are also the part that goes wrong, which is why the templates
|
|
23
|
+
spell them out rather than hiding them behind an abstraction.
|
|
24
|
+
|
|
25
|
+
## Keyword search (FTS5)
|
|
26
|
+
|
|
27
|
+
FTS5 ships inside SQLite — no extension, no install. Declare an
|
|
28
|
+
external-content index beside the row table and let the two triggers feed it:
|
|
29
|
+
|
|
30
|
+
```toml
|
|
31
|
+
[[table]]
|
|
32
|
+
name = "notes"
|
|
33
|
+
glob = "notes/**/*.md"
|
|
34
|
+
on-file = "python3 extract.py {path}"
|
|
35
|
+
ddl = '''
|
|
36
|
+
CREATE TABLE notes (slug TEXT, title TEXT, body TEXT);
|
|
37
|
+
|
|
38
|
+
CREATE VIRTUAL TABLE notes_fts USING fts5(
|
|
39
|
+
body,
|
|
40
|
+
content='notes',
|
|
41
|
+
content_rowid='rowid',
|
|
42
|
+
tokenize='porter unicode61'
|
|
43
|
+
);
|
|
44
|
+
CREATE TRIGGER notes_ai AFTER INSERT ON notes BEGIN
|
|
45
|
+
INSERT INTO notes_fts(rowid, body) VALUES (new.rowid, new.body);
|
|
46
|
+
END;
|
|
47
|
+
CREATE TRIGGER notes_ad AFTER DELETE ON notes BEGIN
|
|
48
|
+
INSERT INTO notes_fts(notes_fts, rowid, body)
|
|
49
|
+
VALUES ('delete', old.rowid, old.body);
|
|
50
|
+
END;
|
|
51
|
+
'''
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
`content='notes'` makes the index store only its own search structures and
|
|
55
|
+
read column values back from `notes`, so the text is not duplicated.
|
|
56
|
+
`tokenize='porter unicode61'` stems English on top of the default
|
|
57
|
+
case-folding, so *deploying* matches *deploy*; drop the `porter` half to match
|
|
58
|
+
whole words only.
|
|
59
|
+
|
|
60
|
+
Query the index and join back to the row table on `rowid`:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
dirsql query "
|
|
64
|
+
SELECT n.slug,
|
|
65
|
+
bm25(notes_fts) AS score,
|
|
66
|
+
snippet(notes_fts, 0, '[', ']', '…', 8) AS excerpt
|
|
67
|
+
FROM notes_fts JOIN notes AS n ON n.rowid = notes_fts.rowid
|
|
68
|
+
WHERE notes_fts MATCH 'deploy'
|
|
69
|
+
ORDER BY score" -c ./.dirsql.toml
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
```json
|
|
73
|
+
[{"excerpt":"Rebase before you [deploy]. Keep pull requests small.","score":-1.0476190476190478e-6,"slug":"branches"},
|
|
74
|
+
{"excerpt":"…the spaghetti goes in. [Deploy] the garlic late.","score":-8.461538461538463e-7,"slug":"pasta"}]
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
**`bm25()` returns negative scores, and better matches are more negative** —
|
|
78
|
+
so `ORDER BY score` ascending is best-first, with no `DESC`. `snippet()`'s
|
|
79
|
+
arguments are the table, the column index, the open and close markers, the
|
|
80
|
+
ellipsis, and the token budget.
|
|
81
|
+
|
|
82
|
+
### Check the delete trigger
|
|
83
|
+
|
|
84
|
+
A wrong or missing delete trigger fails **silently**: the index keeps rows for
|
|
85
|
+
files that are gone, and they keep matching. Nothing errors.
|
|
86
|
+
|
|
87
|
+
The symptom is a hit that has no row behind it, which a `LEFT JOIN` exposes:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
dirsql query "
|
|
91
|
+
SELECT n.slug
|
|
92
|
+
FROM notes_fts LEFT JOIN notes AS n ON n.rowid = notes_fts.rowid
|
|
93
|
+
WHERE notes_fts MATCH 'frost'" -c ./.dirsql.toml --persist
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
```json
|
|
97
|
+
[{"slug":null}]
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
A `null` slug is a stale index entry — the base row is gone and the index did
|
|
101
|
+
not hear about it. With the `notes_ad` trigger above, the deleted file's entry
|
|
102
|
+
is gone too and the query returns nothing.
|
|
103
|
+
|
|
104
|
+
Note the `--persist` flag: without it every run rebuilds the index from
|
|
105
|
+
scratch, so a broken delete trigger cannot show itself. Deletes are visible
|
|
106
|
+
against a [warm cache](./persist.md) and through the watcher, which is exactly
|
|
107
|
+
where the staleness would have bitten.
|
|
108
|
+
|
|
109
|
+
## Vector search (`vec0`)
|
|
110
|
+
|
|
111
|
+
Two pieces beyond SQLite: the
|
|
112
|
+
[`sqlite-vec`](https://github.com/asg017/sqlite-vec) extension supplies the
|
|
113
|
+
`vec0` virtual table, and
|
|
114
|
+
[`dirsql-plugin-embeddings`](../plugins.md#dirsql-plugin-embeddings) supplies
|
|
115
|
+
`embed()`. Installing the plugin brings both — its config fragment declares
|
|
116
|
+
the extension too, and the launcher
|
|
117
|
+
[discovers it](../reference/cli.md#plugins):
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
uvx --with dirsql-plugin-embeddings dirsql query "…" -c ./.dirsql.toml
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Loading `sqlite-vec` by hand instead (another runtime, a pinned build) is
|
|
124
|
+
[Load a SQLite extension](./load-extension.md).
|
|
125
|
+
|
|
126
|
+
### Find your model's dimension
|
|
127
|
+
|
|
128
|
+
A `vec0` column declares a fixed width, so the template needs the number of
|
|
129
|
+
components your model emits. `embed()` returns the vector as JSON text, so ask
|
|
130
|
+
it:
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
uvx --with dirsql-plugin-embeddings dirsql query \
|
|
134
|
+
"SELECT json_array_length(embed('probe', 'minishlab/potion-retrieval-32M')) AS dims"
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Run it once per model id you intend to use, and substitute the answer for the
|
|
138
|
+
`512` in the template below. (The first call for a model downloads it — see
|
|
139
|
+
[model](../plugins.md#model).) Getting it wrong is at least loud — the build fails naming both
|
|
140
|
+
numbers:
|
|
141
|
+
|
|
142
|
+
```
|
|
143
|
+
dirsql query: failed to load config: SQLite error: Dimension mismatch for
|
|
144
|
+
inserted vector for the "embedding" column. Expected 8 dimensions but received 4.
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### The template
|
|
148
|
+
|
|
149
|
+
```toml
|
|
150
|
+
[[table]]
|
|
151
|
+
name = "notes"
|
|
152
|
+
glob = "notes/**/*.md"
|
|
153
|
+
on-file = "python3 extract.py {path}"
|
|
154
|
+
ddl = '''
|
|
155
|
+
CREATE TABLE notes (slug TEXT, title TEXT, body TEXT);
|
|
156
|
+
|
|
157
|
+
-- Width must equal what the probe printed for the model id named below.
|
|
158
|
+
CREATE VIRTUAL TABLE notes_vec USING vec0(embedding float[512]);
|
|
159
|
+
CREATE TRIGGER notes_vi AFTER INSERT ON notes BEGIN
|
|
160
|
+
INSERT INTO notes_vec(rowid, embedding)
|
|
161
|
+
VALUES (new.rowid, embed(new.body, 'minishlab/potion-retrieval-32M'));
|
|
162
|
+
END;
|
|
163
|
+
CREATE TRIGGER notes_vd AFTER DELETE ON notes BEGIN
|
|
164
|
+
DELETE FROM notes_vec WHERE rowid = old.rowid;
|
|
165
|
+
END;
|
|
166
|
+
'''
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
The delete side is an ordinary `DELETE`, not FTS5's `'delete'` command row —
|
|
170
|
+
`vec0` is a normal-looking table that way.
|
|
171
|
+
|
|
172
|
+
Query it with `sqlite-vec`'s KNN form, joined back on `rowid`:
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
uvx --with dirsql-plugin-embeddings dirsql query "
|
|
176
|
+
SELECT n.slug, v.distance
|
|
177
|
+
FROM notes_vec AS v JOIN notes AS n ON n.rowid = v.rowid
|
|
178
|
+
WHERE v.embedding MATCH embed('how do I cook pasta?', 'minishlab/potion-retrieval-32M')
|
|
179
|
+
AND k = 3
|
|
180
|
+
ORDER BY v.distance" -c ./.dirsql.toml
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
`MATCH` plus `k = 3` is what makes this a top-k scan rather than a full one —
|
|
184
|
+
`vec0` uses `k`, not `LIMIT`. `distance` is supplied by the virtual table,
|
|
185
|
+
closest first.
|
|
186
|
+
|
|
187
|
+
**Name the same model id in the trigger and the query.** Nothing checks that
|
|
188
|
+
the two agree, and vectors from different models are not comparable. If the
|
|
189
|
+
models happen to share a width the mismatch is entirely silent — rankings just
|
|
190
|
+
get worse. Spelling the id out in both places, rather than leaning on the
|
|
191
|
+
default in either, is the cheap defence; it also puts the model in the
|
|
192
|
+
[config hash](../reference/config.md#batch-ddl), so changing it rebuilds the
|
|
193
|
+
index instead of mixing old vectors with new.
|
|
194
|
+
|
|
195
|
+
Rows are embedded once, at ingest, and cached on disk by content and model
|
|
196
|
+
([vector cache](../plugins.md#vector-cache)). A search then costs one
|
|
197
|
+
`embed()` call for the query text plus an in-process scan.
|
|
198
|
+
|
|
199
|
+
::: warning Editing `ddl` wedges a persisted `vec0` cache
|
|
200
|
+
Under `--persist`, editing any part of a `ddl` batch whose cache holds a
|
|
201
|
+
`vec0` table currently fails with `SQLite error: no such module: vec0`, and
|
|
202
|
+
keeps failing until the cache is deleted (`rm -rf <root>/.dirsql`). Tracked in
|
|
203
|
+
[#1008](https://github.com/thekevinscott/dirsql/issues/1008); FTS5 is
|
|
204
|
+
unaffected.
|
|
205
|
+
:::
|
|
206
|
+
|
|
207
|
+
## What a rebuild does
|
|
208
|
+
|
|
209
|
+
`ddl` runs once, when the table is created. Editing any character of it
|
|
210
|
+
changes the config hash, which drops a persisted cache and re-ingests every
|
|
211
|
+
file — so a new index, a different tokenizer or a changed model id all rebuild
|
|
212
|
+
from scratch rather than leaving a half-migrated index behind. The full rules
|
|
213
|
+
are in [Batch `ddl`](../reference/config.md#batch-ddl).
|
|
214
|
+
|
|
215
|
+
## Going further
|
|
216
|
+
|
|
217
|
+
- Ranked semantic search over files with no config at all —
|
|
218
|
+
[Search documents by meaning](./search-by-meaning.md).
|
|
219
|
+
- Why indexes belong to declared tables and not to path-tables —
|
|
220
|
+
[how `dirsql` thinks](../explanation.md).
|
|
@@ -190,7 +190,13 @@ That is the right trade for a hundreds-of-files, run-it-once question. When the
|
|
|
190
190
|
same tree is queried repeatedly, or is large, declare a
|
|
191
191
|
[table](/reference/config) for it instead — a declared table is indexed on
|
|
192
192
|
build, kept fresh by the watcher, and (with `--persist`) survives restarts, so
|
|
193
|
-
its rows are read from SQLite rather than re-walked each time.
|
|
193
|
+
its rows are read from SQLite rather than re-walked each time. It is also the
|
|
194
|
+
only place a keyword or vector index can live
|
|
195
|
+
([templates](/howto/search-indexes)).
|
|
196
|
+
|
|
197
|
+
Neither kind is the better one: each buys what the other gives up, and the
|
|
198
|
+
choice is stated as one trade in
|
|
199
|
+
[how `dirsql` thinks](/explanation#two-table-kinds-opposite-trade-offs).
|
|
194
200
|
|
|
195
201
|
## Skip rules
|
|
196
202
|
|
|
@@ -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.
|
|
7
|
+
version = "0.4.30"
|
|
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
|
|
@@ -56,6 +56,7 @@ export default defineConfig({
|
|
|
56
56
|
{ text: 'Parse your files into columns', link: '/howto/parse-files-into-columns' },
|
|
57
57
|
{ text: 'Query JSON file contents', link: '/howto/query-json' },
|
|
58
58
|
{ text: 'Search documents by meaning', link: '/howto/search-by-meaning' },
|
|
59
|
+
{ text: 'Add a search index to a table', link: '/howto/search-indexes' },
|
|
59
60
|
{ text: "Skip files you don't want indexed", link: '/howto/skip-files' },
|
|
60
61
|
{ text: 'Load a SQLite extension', link: '/howto/load-extension' },
|
|
61
62
|
{ text: 'Keep the index across restarts', link: '/howto/persist' },
|
|
@@ -107,7 +107,9 @@ maintain nothing. `name` still has to be the row table (`posts`); the virtual
|
|
|
107
107
|
table lives beside it under its own name. The batch runs once, when the table
|
|
108
108
|
is created, and editing any part of it rebuilds a
|
|
109
109
|
[persistent cache](./persist.md) from scratch. Full rules:
|
|
110
|
-
[Batch `ddl`](../reference/config.md#batch-ddl)
|
|
110
|
+
[Batch `ddl`](../reference/config.md#batch-ddl); pasteable FTS5 and vector
|
|
111
|
+
templates, with the trigger mistakes that fail silently, are in
|
|
112
|
+
[Add a search index to a table](./search-indexes.md).
|
|
111
113
|
|
|
112
114
|
## Going further
|
|
113
115
|
|
|
@@ -55,7 +55,9 @@ question over a few hundred files, wasteful for a large tree you query
|
|
|
55
55
|
repeatedly. When that day comes, [declare a table](./define-tables.md): it is
|
|
56
56
|
indexed once, kept fresh by the watcher, and can persist across restarts. The
|
|
57
57
|
zero-config query is the floor; a named table is the escalation path — nothing
|
|
58
|
-
you write here has to be thrown away to get there.
|
|
58
|
+
you write here has to be thrown away to get there. The two sides in full, and
|
|
59
|
+
why indexes belong to only one of them, are in
|
|
60
|
+
[how `dirsql` thinks](../explanation.md#two-table-kinds-opposite-trade-offs).
|
|
59
61
|
|
|
60
62
|
## Notes
|
|
61
63
|
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
# Add a search index to a table
|
|
2
|
+
|
|
3
|
+
Make a declared table answer search queries instead of scanning: a B-tree for
|
|
4
|
+
lookups, an FTS5 index for keywords, a `vec0` index for meaning. All three are
|
|
5
|
+
statements in the table's [`ddl` batch](../reference/config.md#batch-ddl), so
|
|
6
|
+
SQLite does the work and `dirsql` maintains nothing.
|
|
7
|
+
|
|
8
|
+
The templates below are meant to be pasted and edited. Both are shaped around
|
|
9
|
+
one worked example — notes in `notes/*.md`, parsed into a `notes` table by an
|
|
10
|
+
`extract.py` that prints `{"slug": …, "title": …, "body": …}` per file, exactly
|
|
11
|
+
as in [Define tables for your files](./define-tables.md).
|
|
12
|
+
|
|
13
|
+
## Why triggers, and why only two
|
|
14
|
+
|
|
15
|
+
`dirsql` writes file rows with plain `INSERT` and `DELETE`; an update is a
|
|
16
|
+
delete and an insert in one transaction, and there is no `UPDATE` path on user
|
|
17
|
+
rows. So an insert trigger and a delete trigger cover every way a row can
|
|
18
|
+
change — during the initial build, on each [watcher](./react-to-changes.md)
|
|
19
|
+
event, and when a [persistent cache](./persist.md) reconciles a tree that
|
|
20
|
+
moved on. There is no third trigger to write and no maintenance command to run.
|
|
21
|
+
|
|
22
|
+
The triggers are also the part that goes wrong, which is why the templates
|
|
23
|
+
spell them out rather than hiding them behind an abstraction.
|
|
24
|
+
|
|
25
|
+
## Keyword search (FTS5)
|
|
26
|
+
|
|
27
|
+
FTS5 ships inside SQLite — no extension, no install. Declare an
|
|
28
|
+
external-content index beside the row table and let the two triggers feed it:
|
|
29
|
+
|
|
30
|
+
```toml
|
|
31
|
+
[[table]]
|
|
32
|
+
name = "notes"
|
|
33
|
+
glob = "notes/**/*.md"
|
|
34
|
+
on-file = "python3 extract.py {path}"
|
|
35
|
+
ddl = '''
|
|
36
|
+
CREATE TABLE notes (slug TEXT, title TEXT, body TEXT);
|
|
37
|
+
|
|
38
|
+
CREATE VIRTUAL TABLE notes_fts USING fts5(
|
|
39
|
+
body,
|
|
40
|
+
content='notes',
|
|
41
|
+
content_rowid='rowid',
|
|
42
|
+
tokenize='porter unicode61'
|
|
43
|
+
);
|
|
44
|
+
CREATE TRIGGER notes_ai AFTER INSERT ON notes BEGIN
|
|
45
|
+
INSERT INTO notes_fts(rowid, body) VALUES (new.rowid, new.body);
|
|
46
|
+
END;
|
|
47
|
+
CREATE TRIGGER notes_ad AFTER DELETE ON notes BEGIN
|
|
48
|
+
INSERT INTO notes_fts(notes_fts, rowid, body)
|
|
49
|
+
VALUES ('delete', old.rowid, old.body);
|
|
50
|
+
END;
|
|
51
|
+
'''
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
`content='notes'` makes the index store only its own search structures and
|
|
55
|
+
read column values back from `notes`, so the text is not duplicated.
|
|
56
|
+
`tokenize='porter unicode61'` stems English on top of the default
|
|
57
|
+
case-folding, so *deploying* matches *deploy*; drop the `porter` half to match
|
|
58
|
+
whole words only.
|
|
59
|
+
|
|
60
|
+
Query the index and join back to the row table on `rowid`:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
dirsql query "
|
|
64
|
+
SELECT n.slug,
|
|
65
|
+
bm25(notes_fts) AS score,
|
|
66
|
+
snippet(notes_fts, 0, '[', ']', '…', 8) AS excerpt
|
|
67
|
+
FROM notes_fts JOIN notes AS n ON n.rowid = notes_fts.rowid
|
|
68
|
+
WHERE notes_fts MATCH 'deploy'
|
|
69
|
+
ORDER BY score" -c ./.dirsql.toml
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
```json
|
|
73
|
+
[{"excerpt":"Rebase before you [deploy]. Keep pull requests small.","score":-1.0476190476190478e-6,"slug":"branches"},
|
|
74
|
+
{"excerpt":"…the spaghetti goes in. [Deploy] the garlic late.","score":-8.461538461538463e-7,"slug":"pasta"}]
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
**`bm25()` returns negative scores, and better matches are more negative** —
|
|
78
|
+
so `ORDER BY score` ascending is best-first, with no `DESC`. `snippet()`'s
|
|
79
|
+
arguments are the table, the column index, the open and close markers, the
|
|
80
|
+
ellipsis, and the token budget.
|
|
81
|
+
|
|
82
|
+
### Check the delete trigger
|
|
83
|
+
|
|
84
|
+
A wrong or missing delete trigger fails **silently**: the index keeps rows for
|
|
85
|
+
files that are gone, and they keep matching. Nothing errors.
|
|
86
|
+
|
|
87
|
+
The symptom is a hit that has no row behind it, which a `LEFT JOIN` exposes:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
dirsql query "
|
|
91
|
+
SELECT n.slug
|
|
92
|
+
FROM notes_fts LEFT JOIN notes AS n ON n.rowid = notes_fts.rowid
|
|
93
|
+
WHERE notes_fts MATCH 'frost'" -c ./.dirsql.toml --persist
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
```json
|
|
97
|
+
[{"slug":null}]
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
A `null` slug is a stale index entry — the base row is gone and the index did
|
|
101
|
+
not hear about it. With the `notes_ad` trigger above, the deleted file's entry
|
|
102
|
+
is gone too and the query returns nothing.
|
|
103
|
+
|
|
104
|
+
Note the `--persist` flag: without it every run rebuilds the index from
|
|
105
|
+
scratch, so a broken delete trigger cannot show itself. Deletes are visible
|
|
106
|
+
against a [warm cache](./persist.md) and through the watcher, which is exactly
|
|
107
|
+
where the staleness would have bitten.
|
|
108
|
+
|
|
109
|
+
## Vector search (`vec0`)
|
|
110
|
+
|
|
111
|
+
Two pieces beyond SQLite: the
|
|
112
|
+
[`sqlite-vec`](https://github.com/asg017/sqlite-vec) extension supplies the
|
|
113
|
+
`vec0` virtual table, and
|
|
114
|
+
[`dirsql-plugin-embeddings`](../plugins.md#dirsql-plugin-embeddings) supplies
|
|
115
|
+
`embed()`. Installing the plugin brings both — its config fragment declares
|
|
116
|
+
the extension too, and the launcher
|
|
117
|
+
[discovers it](../reference/cli.md#plugins):
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
uvx --with dirsql-plugin-embeddings dirsql query "…" -c ./.dirsql.toml
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Loading `sqlite-vec` by hand instead (another runtime, a pinned build) is
|
|
124
|
+
[Load a SQLite extension](./load-extension.md).
|
|
125
|
+
|
|
126
|
+
### Find your model's dimension
|
|
127
|
+
|
|
128
|
+
A `vec0` column declares a fixed width, so the template needs the number of
|
|
129
|
+
components your model emits. `embed()` returns the vector as JSON text, so ask
|
|
130
|
+
it:
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
uvx --with dirsql-plugin-embeddings dirsql query \
|
|
134
|
+
"SELECT json_array_length(embed('probe', 'minishlab/potion-retrieval-32M')) AS dims"
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Run it once per model id you intend to use, and substitute the answer for the
|
|
138
|
+
`512` in the template below. (The first call for a model downloads it — see
|
|
139
|
+
[model](../plugins.md#model).) Getting it wrong is at least loud — the build fails naming both
|
|
140
|
+
numbers:
|
|
141
|
+
|
|
142
|
+
```
|
|
143
|
+
dirsql query: failed to load config: SQLite error: Dimension mismatch for
|
|
144
|
+
inserted vector for the "embedding" column. Expected 8 dimensions but received 4.
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### The template
|
|
148
|
+
|
|
149
|
+
```toml
|
|
150
|
+
[[table]]
|
|
151
|
+
name = "notes"
|
|
152
|
+
glob = "notes/**/*.md"
|
|
153
|
+
on-file = "python3 extract.py {path}"
|
|
154
|
+
ddl = '''
|
|
155
|
+
CREATE TABLE notes (slug TEXT, title TEXT, body TEXT);
|
|
156
|
+
|
|
157
|
+
-- Width must equal what the probe printed for the model id named below.
|
|
158
|
+
CREATE VIRTUAL TABLE notes_vec USING vec0(embedding float[512]);
|
|
159
|
+
CREATE TRIGGER notes_vi AFTER INSERT ON notes BEGIN
|
|
160
|
+
INSERT INTO notes_vec(rowid, embedding)
|
|
161
|
+
VALUES (new.rowid, embed(new.body, 'minishlab/potion-retrieval-32M'));
|
|
162
|
+
END;
|
|
163
|
+
CREATE TRIGGER notes_vd AFTER DELETE ON notes BEGIN
|
|
164
|
+
DELETE FROM notes_vec WHERE rowid = old.rowid;
|
|
165
|
+
END;
|
|
166
|
+
'''
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
The delete side is an ordinary `DELETE`, not FTS5's `'delete'` command row —
|
|
170
|
+
`vec0` is a normal-looking table that way.
|
|
171
|
+
|
|
172
|
+
Query it with `sqlite-vec`'s KNN form, joined back on `rowid`:
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
uvx --with dirsql-plugin-embeddings dirsql query "
|
|
176
|
+
SELECT n.slug, v.distance
|
|
177
|
+
FROM notes_vec AS v JOIN notes AS n ON n.rowid = v.rowid
|
|
178
|
+
WHERE v.embedding MATCH embed('how do I cook pasta?', 'minishlab/potion-retrieval-32M')
|
|
179
|
+
AND k = 3
|
|
180
|
+
ORDER BY v.distance" -c ./.dirsql.toml
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
`MATCH` plus `k = 3` is what makes this a top-k scan rather than a full one —
|
|
184
|
+
`vec0` uses `k`, not `LIMIT`. `distance` is supplied by the virtual table,
|
|
185
|
+
closest first.
|
|
186
|
+
|
|
187
|
+
**Name the same model id in the trigger and the query.** Nothing checks that
|
|
188
|
+
the two agree, and vectors from different models are not comparable. If the
|
|
189
|
+
models happen to share a width the mismatch is entirely silent — rankings just
|
|
190
|
+
get worse. Spelling the id out in both places, rather than leaning on the
|
|
191
|
+
default in either, is the cheap defence; it also puts the model in the
|
|
192
|
+
[config hash](../reference/config.md#batch-ddl), so changing it rebuilds the
|
|
193
|
+
index instead of mixing old vectors with new.
|
|
194
|
+
|
|
195
|
+
Rows are embedded once, at ingest, and cached on disk by content and model
|
|
196
|
+
([vector cache](../plugins.md#vector-cache)). A search then costs one
|
|
197
|
+
`embed()` call for the query text plus an in-process scan.
|
|
198
|
+
|
|
199
|
+
::: warning Editing `ddl` wedges a persisted `vec0` cache
|
|
200
|
+
Under `--persist`, editing any part of a `ddl` batch whose cache holds a
|
|
201
|
+
`vec0` table currently fails with `SQLite error: no such module: vec0`, and
|
|
202
|
+
keeps failing until the cache is deleted (`rm -rf <root>/.dirsql`). Tracked in
|
|
203
|
+
[#1008](https://github.com/thekevinscott/dirsql/issues/1008); FTS5 is
|
|
204
|
+
unaffected.
|
|
205
|
+
:::
|
|
206
|
+
|
|
207
|
+
## What a rebuild does
|
|
208
|
+
|
|
209
|
+
`ddl` runs once, when the table is created. Editing any character of it
|
|
210
|
+
changes the config hash, which drops a persisted cache and re-ingests every
|
|
211
|
+
file — so a new index, a different tokenizer or a changed model id all rebuild
|
|
212
|
+
from scratch rather than leaving a half-migrated index behind. The full rules
|
|
213
|
+
are in [Batch `ddl`](../reference/config.md#batch-ddl).
|
|
214
|
+
|
|
215
|
+
## Going further
|
|
216
|
+
|
|
217
|
+
- Ranked semantic search over files with no config at all —
|
|
218
|
+
[Search documents by meaning](./search-by-meaning.md).
|
|
219
|
+
- Why indexes belong to declared tables and not to path-tables —
|
|
220
|
+
[how `dirsql` thinks](../explanation.md).
|
|
@@ -190,7 +190,13 @@ That is the right trade for a hundreds-of-files, run-it-once question. When the
|
|
|
190
190
|
same tree is queried repeatedly, or is large, declare a
|
|
191
191
|
[table](/reference/config) for it instead — a declared table is indexed on
|
|
192
192
|
build, kept fresh by the watcher, and (with `--persist`) survives restarts, so
|
|
193
|
-
its rows are read from SQLite rather than re-walked each time.
|
|
193
|
+
its rows are read from SQLite rather than re-walked each time. It is also the
|
|
194
|
+
only place a keyword or vector index can live
|
|
195
|
+
([templates](/howto/search-indexes)).
|
|
196
|
+
|
|
197
|
+
Neither kind is the better one: each buys what the other gives up, and the
|
|
198
|
+
choice is stated as one trade in
|
|
199
|
+
[how `dirsql` thinks](/explanation#two-table-kinds-opposite-trade-offs).
|
|
194
200
|
|
|
195
201
|
## Skip rules
|
|
196
202
|
|
|
@@ -6,7 +6,7 @@ description = "Ephemeral SQL index over a local directory"
|
|
|
6
6
|
# version stays for the binding crates (publish = false). The literal
|
|
7
7
|
# intentionally lags the published version: putitoutthere rewrites it to
|
|
8
8
|
# the planned release version at build time and never commits it back.
|
|
9
|
-
version = "0.4.
|
|
9
|
+
version = "0.4.30"
|
|
10
10
|
edition.workspace = true
|
|
11
11
|
# Literal `license` (not `license.workspace = true`) because
|
|
12
12
|
# putitoutthere's preflight check reads `[package].license` directly
|
|
@@ -107,7 +107,9 @@ maintain nothing. `name` still has to be the row table (`posts`); the virtual
|
|
|
107
107
|
table lives beside it under its own name. The batch runs once, when the table
|
|
108
108
|
is created, and editing any part of it rebuilds a
|
|
109
109
|
[persistent cache](./persist.md) from scratch. Full rules:
|
|
110
|
-
[Batch `ddl`](../reference/config.md#batch-ddl)
|
|
110
|
+
[Batch `ddl`](../reference/config.md#batch-ddl); pasteable FTS5 and vector
|
|
111
|
+
templates, with the trigger mistakes that fail silently, are in
|
|
112
|
+
[Add a search index to a table](./search-indexes.md).
|
|
111
113
|
|
|
112
114
|
## Going further
|
|
113
115
|
|
{dirsql-0.4.29/packages/python → dirsql-0.4.30/packages/rust}/docs/howto/query-without-config.md
RENAMED
|
@@ -55,7 +55,9 @@ question over a few hundred files, wasteful for a large tree you query
|
|
|
55
55
|
repeatedly. When that day comes, [declare a table](./define-tables.md): it is
|
|
56
56
|
indexed once, kept fresh by the watcher, and can persist across restarts. The
|
|
57
57
|
zero-config query is the floor; a named table is the escalation path — nothing
|
|
58
|
-
you write here has to be thrown away to get there.
|
|
58
|
+
you write here has to be thrown away to get there. The two sides in full, and
|
|
59
|
+
why indexes belong to only one of them, are in
|
|
60
|
+
[how `dirsql` thinks](../explanation.md#two-table-kinds-opposite-trade-offs).
|
|
59
61
|
|
|
60
62
|
## Notes
|
|
61
63
|
|