dirsql 0.4.29__tar.gz → 0.4.31__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.31}/Cargo.lock +2 -2
- {dirsql-0.4.29 → dirsql-0.4.31}/PKG-INFO +1 -1
- {dirsql-0.4.29 → dirsql-0.4.31}/docs/.vitepress/config.ts +1 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/docs/howto/define-tables.md +3 -1
- {dirsql-0.4.29/packages/rust → dirsql-0.4.31}/docs/howto/query-without-config.md +3 -1
- {dirsql-0.4.29/packages/python → dirsql-0.4.31}/docs/howto/search-by-meaning.md +41 -19
- dirsql-0.4.31/docs/howto/search-indexes.md +245 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/docs/reference/cli.md +44 -0
- {dirsql-0.4.29/packages/python → dirsql-0.4.31}/docs/reference/path-tables.md +7 -1
- {dirsql-0.4.29/packages/rust → dirsql-0.4.31}/docs/reference/sdk.md +19 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/python/Cargo.toml +1 -1
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/python/docs/.vitepress/config.ts +1 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/python/docs/howto/define-tables.md +3 -1
- {dirsql-0.4.29 → dirsql-0.4.31/packages/python}/docs/howto/query-without-config.md +3 -1
- {dirsql-0.4.29 → dirsql-0.4.31/packages/python}/docs/howto/search-by-meaning.md +41 -19
- dirsql-0.4.31/packages/python/docs/howto/search-indexes.md +245 -0
- {dirsql-0.4.29/packages/rust → dirsql-0.4.31/packages/python}/docs/reference/cli.md +44 -0
- {dirsql-0.4.29 → dirsql-0.4.31/packages/python}/docs/reference/path-tables.md +7 -1
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/python/docs/reference/sdk.md +19 -0
- dirsql-0.4.31/packages/python/e2e-attestations/cc-keen-knuth-f11gyq.json +7 -0
- dirsql-0.4.31/packages/python/e2e-attestations/claude-tackle-957-lrm0z6.json +7 -0
- dirsql-0.4.31/packages/python/e2e-attestations/claude-tackle-975-n8mc2h.json +7 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/rust/Cargo.toml +1 -1
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/rust/docs/howto/define-tables.md +3 -1
- {dirsql-0.4.29/packages/python → dirsql-0.4.31/packages/rust}/docs/howto/query-without-config.md +3 -1
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/rust/docs/howto/search-by-meaning.md +41 -19
- dirsql-0.4.31/packages/rust/docs/howto/search-indexes.md +245 -0
- {dirsql-0.4.29/packages/python → dirsql-0.4.31/packages/rust}/docs/reference/cli.md +44 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/rust/docs/reference/path-tables.md +7 -1
- {dirsql-0.4.29 → dirsql-0.4.31/packages/rust}/docs/reference/sdk.md +19 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/rust/src/lib.rs +170 -21
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/rust/src/matcher.rs +48 -0
- dirsql-0.4.31/packages/rust/src/progress.rs +590 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/rust/src/scanner.rs +24 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/Cargo.toml +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/README.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/dirsql/__init__.py +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/dirsql/_async.py +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/dirsql/_dirsql.pyi +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/dirsql/cli/__init__.py +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/dirsql/cli/discover_plugins/__init__.py +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/dirsql/cli/discover_plugins/discovered_fragments.py +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/dirsql/cli/discover_plugins/discovery_disabled.py +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/dirsql/cli/discover_plugins/fragment_path.py +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/dirsql/cli/discover_plugins/user_passed_config.py +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/dirsql/cli/discover_plugins/with_discovered_plugins.py +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/dirsql/cli/main.py +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/dirsql/cli/resolve_config_extensions.py +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/dirsql/py.typed +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/dirsql/resolve_config_extensions.py +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/dirsql/resolve_extension.py +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/docs/.claude/CLAUDE.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/docs/.vitepress/theme/index.ts +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/docs/.vitepress/theme/lang.ts +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/docs/AGENTS.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/docs/explanation.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/docs/getting-started.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/docs/howto/columns-from-paths.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/docs/howto/embed.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/docs/howto/extract-from-contents.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/docs/howto/load-extension.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/docs/howto/parse-files-into-columns.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/docs/howto/persist.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/docs/howto/query-json.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/docs/howto/react-to-changes.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/docs/howto/skip-files.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/docs/howto/write-a-plugin.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/docs/index.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/docs/package.json +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/docs/plugins.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/docs/pnpm-lock.yaml +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/docs/pnpm-workspace.yaml +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/docs/reference/columns.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/docs/reference/config.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/docs/reference/hooks.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/docs/reference/http-api.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/python/CHANGELOG.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/python/MIGRATIONS.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/python/README.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/python/changelog.d/2026-07-13-drop-cwd-config-auto-detect.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/python/changelog.d/2026-07-13-plugin-discovery.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/python/changelog.d/2026-07-13-rename-extract-to-on-file.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/python/changelog.d/2026-07-13-repeatable-config.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/python/changelog.d/2026-07-13-sdk-default-baked-in-config.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/python/changelog.d/2026-07-20-drop-implicit-files-table.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/python/changelog.d/2026-07-21-remove-glob-captures.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/python/changelog.d/2026-07-24-hookless-table-config-error.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/python/changelog.d/2026-07-24-remove-stat-fact-injection.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/python/changelog.d/2026-07-29-restore-python-310.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/python/changelog.d/2026-08-02-release-profile.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/python/changelog.d/2026-08-02-scan-failures.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/python/changelog.d/2026-08-03-discovery-ext-resolution.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/python/changelog.d/2026-08-03-manylinux-dynamic-binary.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/python/changelog.d/2026-08-03-python-no-ignore.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/python/changelog.d/2026-08-04-cast-lints.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/python/changelog.d/2026-08-04-cli-in-process.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/python/changelog.d/2026-08-20-curtaincall-dev-dependency.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/python/changelog.d/2026-08-20-declared-table-name.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/python/changelog.d/README.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/python/conftest.py +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/python/docs/.claude/CLAUDE.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/python/docs/.vitepress/theme/index.ts +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/python/docs/.vitepress/theme/lang.ts +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/python/docs/AGENTS.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/python/docs/explanation.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/python/docs/getting-started.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/python/docs/howto/columns-from-paths.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/python/docs/howto/embed.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/python/docs/howto/extract-from-contents.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/python/docs/howto/load-extension.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/python/docs/howto/parse-files-into-columns.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/python/docs/howto/persist.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/python/docs/howto/query-json.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/python/docs/howto/react-to-changes.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/python/docs/howto/skip-files.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/python/docs/howto/write-a-plugin.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/python/docs/index.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/python/docs/package.json +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/python/docs/plugins.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/python/docs/pnpm-lock.yaml +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/python/docs/pnpm-workspace.yaml +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/python/docs/reference/columns.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/python/docs/reference/config.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/python/docs/reference/hooks.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/python/docs/reference/http-api.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/python/e2e-attestations/claude-819-oneshot-timeout.json +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/python/e2e-attestations/claude-827-unquote-doubled-quotes.json +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/python/e2e-attestations/claude-epic-953-slice2-reedline.json +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/python/e2e-attestations/claude-epic-953-slice3-format.json +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/python/e2e-attestations/claude-epic-953-stacked-prs-x59wm3.json +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/python/e2e-attestations/claude-github-issue-825-e4mhhp.json +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/python/e2e-attestations/claude-issue-951-0eease.json +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/python/e2e-attestations/claude-issue-962-0jjrfv.json +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/python/e2e-attestations/claude-issue-986-red-test-e7sjrb.json +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/python/e2e-attestations/claude-tackle-956-xzen74.json +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/python/migrations.d/2026-07-13-drop-cwd-config-auto-detect.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/python/migrations.d/2026-07-13-rename-extract-to-on-file.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/python/migrations.d/2026-07-13-sdk-default-baked-in-config.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/python/migrations.d/2026-07-20-drop-implicit-files-table.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/python/migrations.d/2026-07-21-remove-glob-captures.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/python/migrations.d/2026-07-24-hookless-table-config-error.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/python/migrations.d/2026-07-24-remove-stat-fact-injection.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/python/migrations.d/2026-07-29-restore-python-310.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/python/migrations.d/2026-08-04-cli-in-process.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/python/migrations.d/2026-08-20-declared-table-name.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/python/migrations.d/README.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/python/src/lib.rs +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/python/testing-conventions.toml +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/python/tests/__init__.py +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/python/tests/conftest.py +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/python/tests/e2e/__init__.py +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/python/tests/e2e/fixtures/dirsql_plugin_fixture/__init__.py +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/python/tests/e2e/fixtures/dirsql_plugin_fixture/dirsql.toml +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/python/tests/integration/__init__.py +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/python/tests/integration/binding/__init__.py +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/python/tests/integration/hermetic/__init__.py +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/rust/CHANGELOG.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/rust/MIGRATIONS.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/rust/README.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/rust/benches/db_bench.rs +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/rust/benches/differ_bench.rs +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/rust/benches/matcher_bench.rs +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/rust/benches/scanner_bench.rs +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/rust/docs/explanation.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/rust/docs/getting-started.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/rust/docs/howto/columns-from-paths.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/rust/docs/howto/embed.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/rust/docs/howto/extract-from-contents.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/rust/docs/howto/load-extension.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/rust/docs/howto/parse-files-into-columns.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/rust/docs/howto/persist.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/rust/docs/howto/query-json.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/rust/docs/howto/react-to-changes.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/rust/docs/howto/skip-files.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/rust/docs/howto/write-a-plugin.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/rust/docs/index.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/rust/docs/plugins.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/rust/docs/reference/columns.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/rust/docs/reference/config.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/rust/docs/reference/hooks.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/rust/docs/reference/http-api.md +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/rust/src/bin/dirsql.rs +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/rust/src/cli/execute.rs +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/rust/src/cli/init.rs +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/rust/src/cli/mod.rs +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/rust/src/cli/repl.rs +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/rust/src/cli/router.rs +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/rust/src/cli/run.rs +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/rust/src/cli/serialize.rs +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/rust/src/cli/server.rs +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/rust/src/cli/table.rs +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/rust/src/command.rs +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/rust/src/config.rs +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/rust/src/db.rs +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/rust/src/default_config.toml +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/rust/src/differ.rs +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/rust/src/functions.rs +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/rust/src/infer.rs +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/rust/src/parsed_cache.rs +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/rust/src/parsed_vtab.rs +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/rust/src/path_table.rs +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/rust/src/persist.rs +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/rust/src/sql_literal.rs +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/rust/src/vtab.rs +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/packages/rust/src/watcher.rs +0 -0
- {dirsql-0.4.29 → dirsql-0.4.31}/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.31"
|
|
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.31"
|
|
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
|
|
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
Ask a question in plain language and get the closest documents back — even
|
|
4
4
|
when they share no keywords with it. Install
|
|
5
5
|
[`dirsql-plugin-embeddings`](../plugins.md#dirsql-plugin-embeddings) and
|
|
6
|
-
semantic search becomes plain SQL: the plugin's `embed()` function turns
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
semantic search becomes plain SQL: the plugin's `embed()` function turns text
|
|
7
|
+
into vectors and loads [`sqlite-vec`](https://github.com/asg017/sqlite-vec),
|
|
8
|
+
which does the distance math. No API keys, no services — the model runs
|
|
9
|
+
locally.
|
|
10
10
|
|
|
11
11
|
Suppose short notes live in `notes/*.md`:
|
|
12
12
|
|
|
@@ -40,7 +40,32 @@ The first-ever run downloads the model (on the order of a hundred megabytes,
|
|
|
40
40
|
with progress on stderr); after that it loads from the local cache. Results
|
|
41
41
|
print one `path<TAB>distance` line per match, closest first.
|
|
42
42
|
|
|
43
|
-
##
|
|
43
|
+
## Which shape
|
|
44
|
+
|
|
45
|
+
The one-liner embeds every matched file on every run. That is the right trade
|
|
46
|
+
for a question you ask once, and the wrong one for a corpus you search
|
|
47
|
+
repeatedly — where a stored [`vec0` index](./search-indexes.md#vector-search-vec0)
|
|
48
|
+
embeds each file once, at ingest, and a query embeds only the question:
|
|
49
|
+
|
|
50
|
+
| | This page: path-table | [Vector index](./search-indexes.md#vector-search-vec0) |
|
|
51
|
+
|---|---|---|
|
|
52
|
+
| Setup | none | a `[[table]]` with a `ddl` batch |
|
|
53
|
+
| Freshness | always current — the walk is the read | watcher-maintained; survives restarts under [`--persist`](./persist.md) |
|
|
54
|
+
| Per query | one `embed()` round trip **per matched file**, plus a walk that reads every file's content | one `embed()` round trip **total**, plus an in-process KNN scan |
|
|
55
|
+
| Top-k | `ORDER BY … LIMIT k` | `MATCH … AND k = …` |
|
|
56
|
+
| Good for | a one-off question, a small corpus, an ad-hoc glob | a corpus you query repeatedly |
|
|
57
|
+
|
|
58
|
+
The index only pays off when the table outlives the query — under `--persist`,
|
|
59
|
+
or inside a long-running [`dirsql server`](../reference/cli.md). A one-shot
|
|
60
|
+
`dirsql query` against an ephemeral index rebuilds the table, and therefore
|
|
61
|
+
re-embeds the corpus, before it answers; that is strictly more work than the
|
|
62
|
+
subquery below. The full recipe — the width probe, the `ddl` batch, both
|
|
63
|
+
triggers, and what a model-id edit costs — is
|
|
64
|
+
[Add a search index to a table](./search-indexes.md#vector-search-vec0).
|
|
65
|
+
|
|
66
|
+
The rest of this page is the zero-setup shape.
|
|
67
|
+
|
|
68
|
+
## The SQL behind the one-liner
|
|
44
69
|
|
|
45
70
|
The one-liner generates and runs ordinary `dirsql` SQL, and you can write it
|
|
46
71
|
yourself when you want more than ranked paths — a different projection, a
|
|
@@ -79,7 +104,9 @@ Reading the query inside-out:
|
|
|
79
104
|
and its distance are NULL too — and SQLite sorts NULLs *first* ascending,
|
|
80
105
|
so without this line the unrankable files take the top-k slots.
|
|
81
106
|
4. `vec_distance_cosine(...)` computes cosine distance between the two
|
|
82
|
-
vectors; `ORDER BY distance LIMIT 3` keeps the three nearest.
|
|
107
|
+
vectors; `ORDER BY distance LIMIT 3` keeps the three nearest. There is no
|
|
108
|
+
`vec0` table here, so `sqlite-vec`'s `MATCH … AND k = …` does not apply —
|
|
109
|
+
for a plain expression, `ORDER BY … LIMIT k` *is* its documented top-k.
|
|
83
110
|
|
|
84
111
|
Structured files compose with SQL's JSON operators — embed one field instead
|
|
85
112
|
of the whole file:
|
|
@@ -93,24 +120,19 @@ ORDER BY vec_distance_cosine(emb, embed('local private models'))
|
|
|
93
120
|
LIMIT 10
|
|
94
121
|
```
|
|
95
122
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
above does not use: a `[[table]]`'s own `name` is always a per-file row table.
|
|
100
|
-
For plain expressions, `sqlite-vec`'s own documented pattern is exactly what
|
|
101
|
-
this guide uses: `ORDER BY vec_distance_cosine(...) LIMIT k`. To get the `vec0`
|
|
102
|
-
idiom instead, declare the `vec0` table alongside the row table in the same
|
|
103
|
-
[`ddl` batch](../reference/config.md#batch-ddl) and fill it from a trigger.
|
|
104
|
-
:::
|
|
123
|
+
The same projection works as an `on-file` hook feeding the indexed shape:
|
|
124
|
+
parse the field in the hook, store it as a column, and the trigger embeds it
|
|
125
|
+
once instead of on every query.
|
|
105
126
|
|
|
106
127
|
## Repeat runs are cheap
|
|
107
128
|
|
|
108
129
|
Computed vectors are cached on disk, keyed on content and model
|
|
109
130
|
([vector cache](../plugins.md#vector-cache)) — re-running a search over
|
|
110
|
-
unchanged files skips the model entirely and re-embeds only what changed.
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
131
|
+
unchanged files skips the model entirely and re-embeds only what changed. That
|
|
132
|
+
takes the *inference* out of the shape above, but not the walk or the per-file
|
|
133
|
+
round trip; only a stored index removes those. And the plugin costs nothing
|
|
134
|
+
when idle: a query that never calls `embed()` spawns no worker and loads no
|
|
135
|
+
model ([zero cost when unused](../plugins.md#zero-cost-when-unused)).
|
|
114
136
|
|
|
115
137
|
## How `embed()` gets into SQL
|
|
116
138
|
|
|
@@ -0,0 +1,245 @@
|
|
|
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
|
|
159
|
+
USING vec0(embedding float[512] distance_metric=cosine);
|
|
160
|
+
CREATE TRIGGER notes_vi AFTER INSERT ON notes
|
|
161
|
+
WHEN new.body IS NOT NULL BEGIN
|
|
162
|
+
INSERT INTO notes_vec(rowid, embedding)
|
|
163
|
+
VALUES (new.rowid, embed(new.body, 'minishlab/potion-retrieval-32M'));
|
|
164
|
+
END;
|
|
165
|
+
CREATE TRIGGER notes_vd AFTER DELETE ON notes BEGIN
|
|
166
|
+
DELETE FROM notes_vec WHERE rowid = old.rowid;
|
|
167
|
+
END;
|
|
168
|
+
'''
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
The delete side is an ordinary `DELETE`, not FTS5's `'delete'` command row —
|
|
172
|
+
`vec0` is a normal-looking table that way.
|
|
173
|
+
|
|
174
|
+
Two clauses in there are load-bearing:
|
|
175
|
+
|
|
176
|
+
- **`distance_metric=cosine`.** `vec0` defaults to L2, which is not the metric
|
|
177
|
+
the rest of dirsql's semantic search uses — `vec_distance_cosine()` backs
|
|
178
|
+
both the plugin's one-liner and
|
|
179
|
+
[Search documents by meaning](./search-by-meaning.md). The two agree only for
|
|
180
|
+
vectors of equal length, so as soon as documents embed to vectors of
|
|
181
|
+
different magnitude they rank differently: against three notes, L2 returned
|
|
182
|
+
`a, c, b` where cosine returned `a, b, c`. Declaring the metric makes
|
|
183
|
+
`distance` the number `vec_distance_cosine()` would compute.
|
|
184
|
+
- **`WHEN new.body IS NOT NULL`.** `embed(NULL)` is `NULL`, and `vec0` rejects
|
|
185
|
+
a NULL vector outright — so without the guard a single row whose text is
|
|
186
|
+
missing fails the **whole** table load, not just its own insert:
|
|
187
|
+
|
|
188
|
+
```
|
|
189
|
+
dirsql query: failed to load config: SQLite error: Inserted vector for the
|
|
190
|
+
"embedding" column is invalid: Input must have type BLOB (compact format) or
|
|
191
|
+
TEXT (JSON), found NULL
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
With it, that row still lands in `notes`; only its vector is skipped. FTS5
|
|
195
|
+
needs no such guard — it indexes a NULL happily.
|
|
196
|
+
|
|
197
|
+
Query it with `sqlite-vec`'s KNN form, joined back on `rowid`:
|
|
198
|
+
|
|
199
|
+
```bash
|
|
200
|
+
uvx --with dirsql-plugin-embeddings dirsql query "
|
|
201
|
+
SELECT n.slug, v.distance
|
|
202
|
+
FROM notes_vec AS v JOIN notes AS n ON n.rowid = v.rowid
|
|
203
|
+
WHERE v.embedding MATCH embed('how do I cook pasta?', 'minishlab/potion-retrieval-32M')
|
|
204
|
+
AND k = 3
|
|
205
|
+
ORDER BY v.distance" -c ./.dirsql.toml
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
`MATCH` plus `k = 3` is what makes this a top-k scan rather than a full one —
|
|
209
|
+
`vec0` uses `k`, not `LIMIT`. `distance` is supplied by the virtual table,
|
|
210
|
+
closest first.
|
|
211
|
+
|
|
212
|
+
**Name the same model id in the trigger and the query.** Nothing checks that
|
|
213
|
+
the two agree, and vectors from different models are not comparable. If the
|
|
214
|
+
models happen to share a width the mismatch is entirely silent — rankings just
|
|
215
|
+
get worse. Spelling the id out in both places, rather than leaning on the
|
|
216
|
+
default in either, is the cheap defence; it also puts the model in the
|
|
217
|
+
[config hash](../reference/config.md#batch-ddl), so changing it rebuilds the
|
|
218
|
+
index instead of mixing old vectors with new.
|
|
219
|
+
|
|
220
|
+
Rows are embedded once, at ingest, and cached on disk by content and model
|
|
221
|
+
([vector cache](../plugins.md#vector-cache)). A search then costs one
|
|
222
|
+
`embed()` call for the query text plus an in-process scan.
|
|
223
|
+
|
|
224
|
+
::: warning Editing `ddl` wedges a persisted `vec0` cache
|
|
225
|
+
Under `--persist`, editing any part of a `ddl` batch whose cache holds a
|
|
226
|
+
`vec0` table currently fails with `SQLite error: no such module: vec0`, and
|
|
227
|
+
keeps failing until the cache is deleted (`rm -rf <root>/.dirsql`). Tracked in
|
|
228
|
+
[#1008](https://github.com/thekevinscott/dirsql/issues/1008); FTS5 is
|
|
229
|
+
unaffected.
|
|
230
|
+
:::
|
|
231
|
+
|
|
232
|
+
## What a rebuild does
|
|
233
|
+
|
|
234
|
+
`ddl` runs once, when the table is created. Editing any character of it
|
|
235
|
+
changes the config hash, which drops a persisted cache and re-ingests every
|
|
236
|
+
file — so a new index, a different tokenizer or a changed model id all rebuild
|
|
237
|
+
from scratch rather than leaving a half-migrated index behind. The full rules
|
|
238
|
+
are in [Batch `ddl`](../reference/config.md#batch-ddl).
|
|
239
|
+
|
|
240
|
+
## Going further
|
|
241
|
+
|
|
242
|
+
- Ranked semantic search over files with no config at all —
|
|
243
|
+
[Search documents by meaning](./search-by-meaning.md).
|
|
244
|
+
- Why indexes belong to declared tables and not to path-tables —
|
|
245
|
+
[how `dirsql` thinks](../explanation.md).
|
|
@@ -460,3 +460,47 @@ Turn discovery off with either:
|
|
|
460
460
|
|
|
461
461
|
A plugin that declares itself but is missing its module or its `dirsql.toml`
|
|
462
462
|
fragment is a launcher error naming the package — never a silent skip.
|
|
463
|
+
|
|
464
|
+
## Progress reporting
|
|
465
|
+
|
|
466
|
+
Building the index over a large tree is not instant: the walk visits every
|
|
467
|
+
file, then each matched file costs one `on-file` round trip plus whatever the
|
|
468
|
+
table's `ddl` fires on insert. On a big corpus that is minutes. dirsql reports
|
|
469
|
+
the two phases on **stderr** while they run:
|
|
470
|
+
|
|
471
|
+
```
|
|
472
|
+
dirsql: scanning 128413 files
|
|
473
|
+
dirsql: indexing 9204/41231 files (22%)
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
Each line is rewritten in place. When a phase ends its line is erased and
|
|
477
|
+
replaced by one summary of what it cost:
|
|
478
|
+
|
|
479
|
+
```
|
|
480
|
+
dirsql: scanned 128413 files in 4.2s
|
|
481
|
+
dirsql: indexed 41231 files in 3m12s
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
stdout is untouched — it carries the query result and nothing else.
|
|
485
|
+
|
|
486
|
+
By default this is **terminal-only, and only for work slow enough to wonder
|
|
487
|
+
about**: a phase that finishes in under half a second prints nothing at all,
|
|
488
|
+
and a run whose stderr is a pipe or a file prints nothing regardless of how
|
|
489
|
+
long it takes. `dirsql "…" 2>run.log` and `dirsql "…" | jq` are byte-for-byte
|
|
490
|
+
what they were before.
|
|
491
|
+
|
|
492
|
+
Override with `DIRSQL_PROGRESS`:
|
|
493
|
+
|
|
494
|
+
| Value | Effect |
|
|
495
|
+
|---|---|
|
|
496
|
+
| unset, or `auto` | Report only on a terminal, and only once a phase has run for half a second. The default. |
|
|
497
|
+
| `always`, `1`, `true` | Report from the first update, terminal or not. Use it to watch a scan whose stderr is redirected. |
|
|
498
|
+
| `never`, `0`, `false` | Report nothing, ever. |
|
|
499
|
+
|
|
500
|
+
Values are case-insensitive and surrounding whitespace is ignored; anything
|
|
501
|
+
unrecognized reads as `auto`, so a typo cannot stop a scan from running.
|
|
502
|
+
|
|
503
|
+
The setting is read by the **core**, not the CLI, so it governs an index built
|
|
504
|
+
from any SDK as well — a Python or TypeScript program that builds a `DirSQL`
|
|
505
|
+
with a terminal attached gets the same two phases on stderr, and the same
|
|
506
|
+
silence when piped.
|
|
@@ -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
|
|
|
@@ -396,3 +396,22 @@ not fit that range is a hard error, not a lossy conversion: Python raises
|
|
|
396
396
|
A TypeScript `bigint` **within** `i64` range maps to `INTEGER`. Only a real
|
|
397
397
|
`bytes`/`bytearray` (Python) or `Buffer`/`Uint8Array` (TypeScript) maps to
|
|
398
398
|
`BLOB` — a list/array of integers does not.
|
|
399
|
+
|
|
400
|
+
## Progress on construction
|
|
401
|
+
|
|
402
|
+
Constructing a `DirSQL` walks the tree and ingests every matched file, which
|
|
403
|
+
on a large corpus is the slowest thing your program does. The core reports
|
|
404
|
+
both phases on **stderr** while they run, then erases the live line and leaves
|
|
405
|
+
one summary of what each cost:
|
|
406
|
+
|
|
407
|
+
```
|
|
408
|
+
dirsql: indexed 41231 files in 3m12s
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
This is terminal-only by default, and only for a phase that runs longer than
|
|
412
|
+
half a second — a program whose stderr is a pipe, a file, or a log collector
|
|
413
|
+
sees nothing, whatever the scan costs. Set `DIRSQL_PROGRESS=never` to
|
|
414
|
+
guarantee silence even on a terminal, or `DIRSQL_PROGRESS=always` to report
|
|
415
|
+
regardless. The full table is in the
|
|
416
|
+
[CLI reference](cli.md#progress-reporting); the setting lives in the shared
|
|
417
|
+
core, so it behaves identically from all three SDKs.
|
|
@@ -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.31"
|
|
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
|
|
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
Ask a question in plain language and get the closest documents back — even
|
|
4
4
|
when they share no keywords with it. Install
|
|
5
5
|
[`dirsql-plugin-embeddings`](../plugins.md#dirsql-plugin-embeddings) and
|
|
6
|
-
semantic search becomes plain SQL: the plugin's `embed()` function turns
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
semantic search becomes plain SQL: the plugin's `embed()` function turns text
|
|
7
|
+
into vectors and loads [`sqlite-vec`](https://github.com/asg017/sqlite-vec),
|
|
8
|
+
which does the distance math. No API keys, no services — the model runs
|
|
9
|
+
locally.
|
|
10
10
|
|
|
11
11
|
Suppose short notes live in `notes/*.md`:
|
|
12
12
|
|
|
@@ -40,7 +40,32 @@ The first-ever run downloads the model (on the order of a hundred megabytes,
|
|
|
40
40
|
with progress on stderr); after that it loads from the local cache. Results
|
|
41
41
|
print one `path<TAB>distance` line per match, closest first.
|
|
42
42
|
|
|
43
|
-
##
|
|
43
|
+
## Which shape
|
|
44
|
+
|
|
45
|
+
The one-liner embeds every matched file on every run. That is the right trade
|
|
46
|
+
for a question you ask once, and the wrong one for a corpus you search
|
|
47
|
+
repeatedly — where a stored [`vec0` index](./search-indexes.md#vector-search-vec0)
|
|
48
|
+
embeds each file once, at ingest, and a query embeds only the question:
|
|
49
|
+
|
|
50
|
+
| | This page: path-table | [Vector index](./search-indexes.md#vector-search-vec0) |
|
|
51
|
+
|---|---|---|
|
|
52
|
+
| Setup | none | a `[[table]]` with a `ddl` batch |
|
|
53
|
+
| Freshness | always current — the walk is the read | watcher-maintained; survives restarts under [`--persist`](./persist.md) |
|
|
54
|
+
| Per query | one `embed()` round trip **per matched file**, plus a walk that reads every file's content | one `embed()` round trip **total**, plus an in-process KNN scan |
|
|
55
|
+
| Top-k | `ORDER BY … LIMIT k` | `MATCH … AND k = …` |
|
|
56
|
+
| Good for | a one-off question, a small corpus, an ad-hoc glob | a corpus you query repeatedly |
|
|
57
|
+
|
|
58
|
+
The index only pays off when the table outlives the query — under `--persist`,
|
|
59
|
+
or inside a long-running [`dirsql server`](../reference/cli.md). A one-shot
|
|
60
|
+
`dirsql query` against an ephemeral index rebuilds the table, and therefore
|
|
61
|
+
re-embeds the corpus, before it answers; that is strictly more work than the
|
|
62
|
+
subquery below. The full recipe — the width probe, the `ddl` batch, both
|
|
63
|
+
triggers, and what a model-id edit costs — is
|
|
64
|
+
[Add a search index to a table](./search-indexes.md#vector-search-vec0).
|
|
65
|
+
|
|
66
|
+
The rest of this page is the zero-setup shape.
|
|
67
|
+
|
|
68
|
+
## The SQL behind the one-liner
|
|
44
69
|
|
|
45
70
|
The one-liner generates and runs ordinary `dirsql` SQL, and you can write it
|
|
46
71
|
yourself when you want more than ranked paths — a different projection, a
|
|
@@ -79,7 +104,9 @@ Reading the query inside-out:
|
|
|
79
104
|
and its distance are NULL too — and SQLite sorts NULLs *first* ascending,
|
|
80
105
|
so without this line the unrankable files take the top-k slots.
|
|
81
106
|
4. `vec_distance_cosine(...)` computes cosine distance between the two
|
|
82
|
-
vectors; `ORDER BY distance LIMIT 3` keeps the three nearest.
|
|
107
|
+
vectors; `ORDER BY distance LIMIT 3` keeps the three nearest. There is no
|
|
108
|
+
`vec0` table here, so `sqlite-vec`'s `MATCH … AND k = …` does not apply —
|
|
109
|
+
for a plain expression, `ORDER BY … LIMIT k` *is* its documented top-k.
|
|
83
110
|
|
|
84
111
|
Structured files compose with SQL's JSON operators — embed one field instead
|
|
85
112
|
of the whole file:
|
|
@@ -93,24 +120,19 @@ ORDER BY vec_distance_cosine(emb, embed('local private models'))
|
|
|
93
120
|
LIMIT 10
|
|
94
121
|
```
|
|
95
122
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
above does not use: a `[[table]]`'s own `name` is always a per-file row table.
|
|
100
|
-
For plain expressions, `sqlite-vec`'s own documented pattern is exactly what
|
|
101
|
-
this guide uses: `ORDER BY vec_distance_cosine(...) LIMIT k`. To get the `vec0`
|
|
102
|
-
idiom instead, declare the `vec0` table alongside the row table in the same
|
|
103
|
-
[`ddl` batch](../reference/config.md#batch-ddl) and fill it from a trigger.
|
|
104
|
-
:::
|
|
123
|
+
The same projection works as an `on-file` hook feeding the indexed shape:
|
|
124
|
+
parse the field in the hook, store it as a column, and the trigger embeds it
|
|
125
|
+
once instead of on every query.
|
|
105
126
|
|
|
106
127
|
## Repeat runs are cheap
|
|
107
128
|
|
|
108
129
|
Computed vectors are cached on disk, keyed on content and model
|
|
109
130
|
([vector cache](../plugins.md#vector-cache)) — re-running a search over
|
|
110
|
-
unchanged files skips the model entirely and re-embeds only what changed.
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
131
|
+
unchanged files skips the model entirely and re-embeds only what changed. That
|
|
132
|
+
takes the *inference* out of the shape above, but not the walk or the per-file
|
|
133
|
+
round trip; only a stored index removes those. And the plugin costs nothing
|
|
134
|
+
when idle: a query that never calls `embed()` spawns no worker and loads no
|
|
135
|
+
model ([zero cost when unused](../plugins.md#zero-cost-when-unused)).
|
|
114
136
|
|
|
115
137
|
## How `embed()` gets into SQL
|
|
116
138
|
|