dirsql 0.4.41__tar.gz → 0.4.42__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.41 → dirsql-0.4.42}/Cargo.lock +2 -2
- {dirsql-0.4.41 → dirsql-0.4.42}/PKG-INFO +1 -1
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/python/Cargo.toml +1 -1
- dirsql-0.4.42/packages/python/e2e-attestations/claude-1080-share-vtab-scaffolding.json +7 -0
- dirsql-0.4.42/packages/python/e2e-attestations/claude-1109-watch-moved-out.json +7 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/rust/Cargo.toml +1 -1
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/rust/src/db.rs +54 -2
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/rust/src/lib.rs +177 -5
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/rust/src/parsed_vtab.rs +35 -149
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/rust/src/vtab.rs +41 -201
- dirsql-0.4.42/packages/rust/src/vtab_scaffold.rs +378 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/Cargo.toml +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/README.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/dirsql/__init__.py +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/dirsql/_async.py +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/dirsql/_dirsql.pyi +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/dirsql/cli/__init__.py +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/dirsql/cli/discover_plugins/__init__.py +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/dirsql/cli/discover_plugins/discovered_fragments.py +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/dirsql/cli/discover_plugins/discovery_disabled.py +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/dirsql/cli/discover_plugins/fragment_path.py +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/dirsql/cli/discover_plugins/user_passed_config.py +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/dirsql/cli/discover_plugins/with_discovered_plugins.py +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/dirsql/cli/main.py +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/dirsql/cli/resolve_config_extensions.py +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/dirsql/has_bare_name.py +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/dirsql/is_bare_name.py +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/dirsql/load_extension_entries.py +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/dirsql/platform_patterns.py +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/dirsql/py.typed +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/dirsql/resolve_config_extensions.py +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/dirsql/resolve_configs_extension_specs.py +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/dirsql/resolve_entries.py +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/dirsql/resolve_extension.py +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/dirsql/resolve_package.py +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/dirsql/toml_module.py +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/docs/.claude/CLAUDE.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/docs/.vitepress/config.ts +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/docs/.vitepress/theme/index.ts +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/docs/.vitepress/theme/lang.ts +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/docs/AGENTS.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/docs/explanation.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/docs/getting-started.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/docs/howto/columns-from-paths.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/docs/howto/define-tables.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/docs/howto/embed.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/docs/howto/extract-from-contents.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/docs/howto/load-extension.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/docs/howto/parse-files-into-columns.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/docs/howto/persist.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/docs/howto/query-json.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/docs/howto/query-without-config.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/docs/howto/react-to-changes.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/docs/howto/search-by-meaning.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/docs/howto/search-indexes.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/docs/howto/skip-files.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/docs/howto/write-a-plugin.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/docs/index.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/docs/package.json +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/docs/plugins.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/docs/pnpm-lock.yaml +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/docs/pnpm-workspace.yaml +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/docs/reference/cli.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/docs/reference/columns.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/docs/reference/config.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/docs/reference/hooks.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/docs/reference/http-api.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/docs/reference/path-tables.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/docs/reference/sdk.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/python/CHANGELOG.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/python/MIGRATIONS.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/python/README.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/python/changelog.d/2026-07-13-drop-cwd-config-auto-detect.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/python/changelog.d/2026-07-13-plugin-discovery.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/python/changelog.d/2026-07-13-rename-extract-to-on-file.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/python/changelog.d/2026-07-13-repeatable-config.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/python/changelog.d/2026-07-13-sdk-default-baked-in-config.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/python/changelog.d/2026-07-20-drop-implicit-files-table.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/python/changelog.d/2026-07-21-remove-glob-captures.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/python/changelog.d/2026-07-24-hookless-table-config-error.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/python/changelog.d/2026-07-24-remove-stat-fact-injection.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/python/changelog.d/2026-07-29-restore-python-310.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/python/changelog.d/2026-08-02-release-profile.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/python/changelog.d/2026-08-02-scan-failures.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/python/changelog.d/2026-08-03-discovery-ext-resolution.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/python/changelog.d/2026-08-03-manylinux-dynamic-binary.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/python/changelog.d/2026-08-03-python-no-ignore.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/python/changelog.d/2026-08-04-cast-lints.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/python/changelog.d/2026-08-04-cli-in-process.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/python/changelog.d/2026-08-20-curtaincall-dev-dependency.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/python/changelog.d/2026-08-20-declared-table-name.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/python/changelog.d/2026-09-04-config-paths-from-argv-module.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/python/changelog.d/2026-09-05-config-extension-loader-split.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/python/changelog.d/2026-09-05-resolve-config-extensions-split.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/python/changelog.d/2026-09-05-resolve-extension-split.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/python/changelog.d/2026-09-09-launcher-config-path-scan.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/python/changelog.d/README.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/python/conftest.py +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/python/docs/.claude/CLAUDE.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/python/docs/.vitepress/config.ts +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/python/docs/.vitepress/theme/index.ts +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/python/docs/.vitepress/theme/lang.ts +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/python/docs/AGENTS.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/python/docs/explanation.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/python/docs/getting-started.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/python/docs/howto/columns-from-paths.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/python/docs/howto/define-tables.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/python/docs/howto/embed.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/python/docs/howto/extract-from-contents.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/python/docs/howto/load-extension.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/python/docs/howto/parse-files-into-columns.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/python/docs/howto/persist.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/python/docs/howto/query-json.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/python/docs/howto/query-without-config.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/python/docs/howto/react-to-changes.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/python/docs/howto/search-by-meaning.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/python/docs/howto/search-indexes.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/python/docs/howto/skip-files.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/python/docs/howto/write-a-plugin.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/python/docs/index.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/python/docs/package.json +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/python/docs/plugins.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/python/docs/pnpm-lock.yaml +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/python/docs/pnpm-workspace.yaml +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/python/docs/reference/cli.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/python/docs/reference/columns.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/python/docs/reference/config.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/python/docs/reference/hooks.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/python/docs/reference/http-api.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/python/docs/reference/path-tables.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/python/docs/reference/sdk.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/python/e2e-attestations/cc-keen-knuth-f11gyq.json +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/python/e2e-attestations/claude-1056-py-ofpf9.json +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/python/e2e-attestations/claude-1066-p1-resolve-extension.json +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/python/e2e-attestations/claude-1078-argv-config-paths-core.json +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/python/e2e-attestations/claude-1079-p2-resolve-config-extensions.json +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/python/e2e-attestations/claude-1096-watch-moved-dir.json +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/python/e2e-attestations/claude-819-oneshot-timeout.json +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/python/e2e-attestations/claude-827-unquote-doubled-quotes.json +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/python/e2e-attestations/claude-epic-953-slice2-reedline.json +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/python/e2e-attestations/claude-epic-953-slice3-format.json +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/python/e2e-attestations/claude-epic-953-stacked-prs-x59wm3.json +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/python/e2e-attestations/claude-github-issue-825-e4mhhp.json +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/python/e2e-attestations/claude-issue-951-0eease.json +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/python/e2e-attestations/claude-issue-962-0jjrfv.json +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/python/e2e-attestations/claude-issue-986-red-test-e7sjrb.json +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/python/e2e-attestations/claude-tackle-1034-cachesplit.json +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/python/e2e-attestations/claude-tackle-1042-py12.json +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/python/e2e-attestations/claude-tackle-956-xzen74.json +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/python/e2e-attestations/claude-tackle-957-lrm0z6-worker-calls.json +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/python/e2e-attestations/claude-tackle-957-lrm0z6.json +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/python/e2e-attestations/claude-tackle-975-n8mc2h.json +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/python/migrations.d/2026-07-13-drop-cwd-config-auto-detect.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/python/migrations.d/2026-07-13-rename-extract-to-on-file.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/python/migrations.d/2026-07-13-sdk-default-baked-in-config.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/python/migrations.d/2026-07-20-drop-implicit-files-table.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/python/migrations.d/2026-07-21-remove-glob-captures.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/python/migrations.d/2026-07-24-hookless-table-config-error.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/python/migrations.d/2026-07-24-remove-stat-fact-injection.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/python/migrations.d/2026-07-29-restore-python-310.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/python/migrations.d/2026-08-04-cli-in-process.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/python/migrations.d/2026-08-20-declared-table-name.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/python/migrations.d/README.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/python/src/lib.rs +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/python/testing-conventions.toml +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/python/tests/__init__.py +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/python/tests/conftest.py +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/python/tests/e2e/__init__.py +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/python/tests/e2e/fixtures/dirsql_plugin_fixture/__init__.py +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/python/tests/e2e/fixtures/dirsql_plugin_fixture/dirsql.toml +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/python/tests/integration/__init__.py +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/python/tests/integration/binding/__init__.py +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/python/tests/integration/hermetic/__init__.py +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/rust/CHANGELOG.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/rust/MIGRATIONS.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/rust/README.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/rust/benches/db_bench.rs +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/rust/benches/differ_bench.rs +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/rust/benches/matcher_bench.rs +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/rust/benches/scanner_bench.rs +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/rust/docs/explanation.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/rust/docs/getting-started.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/rust/docs/howto/columns-from-paths.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/rust/docs/howto/define-tables.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/rust/docs/howto/embed.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/rust/docs/howto/extract-from-contents.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/rust/docs/howto/load-extension.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/rust/docs/howto/parse-files-into-columns.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/rust/docs/howto/persist.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/rust/docs/howto/query-json.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/rust/docs/howto/query-without-config.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/rust/docs/howto/react-to-changes.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/rust/docs/howto/search-by-meaning.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/rust/docs/howto/search-indexes.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/rust/docs/howto/skip-files.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/rust/docs/howto/write-a-plugin.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/rust/docs/index.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/rust/docs/plugins.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/rust/docs/reference/cli.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/rust/docs/reference/columns.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/rust/docs/reference/config.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/rust/docs/reference/hooks.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/rust/docs/reference/http-api.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/rust/docs/reference/path-tables.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/rust/docs/reference/sdk.md +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/rust/src/bin/dirsql.rs +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/rust/src/cli/execute.rs +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/rust/src/cli/init.rs +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/rust/src/cli/mod.rs +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/rust/src/cli/repl.rs +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/rust/src/cli/router.rs +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/rust/src/cli/run.rs +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/rust/src/cli/serialize.rs +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/rust/src/cli/server.rs +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/rust/src/cli/table.rs +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/rust/src/command.rs +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/rust/src/config.rs +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/rust/src/default_config.toml +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/rust/src/differ.rs +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/rust/src/functions.rs +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/rust/src/infer.rs +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/rust/src/launcher.rs +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/rust/src/matcher.rs +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/rust/src/parsed_cache.rs +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/rust/src/path_table.rs +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/rust/src/persist.rs +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/rust/src/progress.rs +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/rust/src/scanner.rs +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/rust/src/sql_literal.rs +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/packages/rust/src/watcher.rs +0 -0
- {dirsql-0.4.41 → dirsql-0.4.42}/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.42"
|
|
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.42"
|
|
587
587
|
dependencies = [
|
|
588
588
|
"dirsql",
|
|
589
589
|
"pyo3",
|
|
@@ -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.42"
|
|
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
|
|
@@ -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.42"
|
|
10
10
|
edition.workspace = true
|
|
11
11
|
# Literal `license` (not `license.workspace = true`) because
|
|
12
12
|
# putitoutthere's preflight check reads `[package].license` directly
|
|
@@ -645,6 +645,19 @@ impl Db {
|
|
|
645
645
|
Ok(out)
|
|
646
646
|
}
|
|
647
647
|
|
|
648
|
+
/// Every distinct `(table_name, file_path)` whose file sits beneath the
|
|
649
|
+
/// directory `dir`, ordered by table then path.
|
|
650
|
+
pub fn files_under(&self, dir: &str) -> Result<Vec<(String, String)>> {
|
|
651
|
+
let prefix = format!("{dir}{}", std::path::MAIN_SEPARATOR);
|
|
652
|
+
let mut stmt = self.conn.prepare(
|
|
653
|
+
"SELECT DISTINCT table_name, file_path FROM _dirsql_internal_rows \
|
|
654
|
+
WHERE substr(file_path, 1, length(?1)) = ?1 \
|
|
655
|
+
ORDER BY table_name, file_path",
|
|
656
|
+
)?;
|
|
657
|
+
let rows = stmt.query_map([prefix], |r| Ok((r.get(0)?, r.get(1)?)))?;
|
|
658
|
+
Ok(rows.collect::<rusqlite::Result<Vec<_>>>()?)
|
|
659
|
+
}
|
|
660
|
+
|
|
648
661
|
/// Helper to execute delete_rows_by_file statements on any connection.
|
|
649
662
|
/// Called from delete_rows_by_file() either inside a caller-supplied
|
|
650
663
|
/// transaction or inside a transaction opened by delete_rows_by_file().
|
|
@@ -1700,6 +1713,45 @@ mod tests {
|
|
|
1700
1713
|
assert_eq!(rows[0].2, 42, "rowid_ref must be the user-declared rowid");
|
|
1701
1714
|
}
|
|
1702
1715
|
|
|
1716
|
+
#[test]
|
|
1717
|
+
fn files_under_lists_each_file_beneath_the_directory_once_across_tables() {
|
|
1718
|
+
let db = Db::new().unwrap();
|
|
1719
|
+
db.create_table("t1", "CREATE TABLE t1 (id TEXT)").unwrap();
|
|
1720
|
+
db.create_table("t2", "CREATE TABLE t2 (id TEXT)").unwrap();
|
|
1721
|
+
let row = HashMap::from([("id".into(), Value::Text("x".into()))]);
|
|
1722
|
+
for (table, file, idx) in [
|
|
1723
|
+
("t1", "dir/a.jsonl", 0),
|
|
1724
|
+
("t1", "dir/a.jsonl", 1),
|
|
1725
|
+
("t1", "dir/sub/b.jsonl", 0),
|
|
1726
|
+
("t2", "dir/c.jsonl", 0),
|
|
1727
|
+
("t1", "dir2/x.jsonl", 0),
|
|
1728
|
+
("t1", "other.jsonl", 0),
|
|
1729
|
+
] {
|
|
1730
|
+
db.insert_row(table, &row, file, idx).unwrap();
|
|
1731
|
+
}
|
|
1732
|
+
|
|
1733
|
+
assert_eq!(
|
|
1734
|
+
db.files_under("dir").unwrap(),
|
|
1735
|
+
vec![
|
|
1736
|
+
("t1".to_string(), "dir/a.jsonl".to_string()),
|
|
1737
|
+
("t1".to_string(), "dir/sub/b.jsonl".to_string()),
|
|
1738
|
+
("t2".to_string(), "dir/c.jsonl".to_string()),
|
|
1739
|
+
],
|
|
1740
|
+
"a multi-row file is listed once; `dir2/` is not beneath `dir`"
|
|
1741
|
+
);
|
|
1742
|
+
assert!(db.files_under("nothing").unwrap().is_empty());
|
|
1743
|
+
}
|
|
1744
|
+
|
|
1745
|
+
#[test]
|
|
1746
|
+
fn files_under_surfaces_sql_failure() {
|
|
1747
|
+
let db = Db::new().unwrap();
|
|
1748
|
+
db.conn
|
|
1749
|
+
.execute("DROP TABLE _dirsql_internal_rows", [])
|
|
1750
|
+
.unwrap();
|
|
1751
|
+
let err = db.files_under("dir").unwrap_err();
|
|
1752
|
+
assert!(matches!(err, DbError::Sqlite(_)), "got: {err:?}");
|
|
1753
|
+
}
|
|
1754
|
+
|
|
1703
1755
|
#[test]
|
|
1704
1756
|
fn delete_rows_by_file_removes_mapping() {
|
|
1705
1757
|
let db = Db::new().unwrap();
|
|
@@ -2340,7 +2392,7 @@ mod tests {
|
|
|
2340
2392
|
let mut db = Db::new().unwrap();
|
|
2341
2393
|
db.set_path_table_root(PathBuf::from("/nonexistent-dirsql-root"));
|
|
2342
2394
|
|
|
2343
|
-
let rows = db.query("SELECT path FROM './'").unwrap();
|
|
2395
|
+
let rows = db.query("SELECT path FROM './' LIMIT 1").unwrap();
|
|
2344
2396
|
|
|
2345
2397
|
assert!(rows.is_empty(), "an empty root yields no rows: {rows:?}");
|
|
2346
2398
|
}
|
|
@@ -2361,7 +2413,7 @@ mod tests {
|
|
|
2361
2413
|
db.set_path_table_root(PathBuf::from("/nonexistent-dirsql-root"));
|
|
2362
2414
|
|
|
2363
2415
|
let rows = db
|
|
2364
|
-
.query("SELECT path FROM '/nonexistent-dirsql-dir/*.log'")
|
|
2416
|
+
.query("SELECT path FROM '/nonexistent-dirsql-dir/*.log' LIMIT 1")
|
|
2365
2417
|
.unwrap();
|
|
2366
2418
|
|
|
2367
2419
|
assert!(
|
|
@@ -39,6 +39,7 @@ pub mod scanner;
|
|
|
39
39
|
pub mod sql_literal;
|
|
40
40
|
#[doc(hidden)]
|
|
41
41
|
pub mod vtab;
|
|
42
|
+
mod vtab_scaffold;
|
|
42
43
|
#[doc(hidden)]
|
|
43
44
|
pub mod watcher;
|
|
44
45
|
|
|
@@ -557,9 +558,6 @@ impl DirSQL {
|
|
|
557
558
|
// unspecified. An `on_file` failure produces an error event for that
|
|
558
559
|
// table only; the other matching tables still process the event.
|
|
559
560
|
let matches = self.inner.matcher.match_all(&rel_path_buf);
|
|
560
|
-
if matches.is_empty() {
|
|
561
|
-
return Vec::new();
|
|
562
|
-
}
|
|
563
561
|
let rel_path = rel_path_buf.to_string_lossy().to_string();
|
|
564
562
|
|
|
565
563
|
let mut events = Vec::new();
|
|
@@ -573,6 +571,33 @@ impl DirSQL {
|
|
|
573
571
|
}
|
|
574
572
|
}
|
|
575
573
|
}
|
|
574
|
+
// The mirror of the walk above: a directory that leaves the tree
|
|
575
|
+
// (`rm -r`, or a rename out of or within the root) arrives as one
|
|
576
|
+
// event naming the directory and none for the files beneath it. It is
|
|
577
|
+
// already gone, so the rows are the only record of what it held.
|
|
578
|
+
if matches!(event, FileEvent::Deleted(_)) {
|
|
579
|
+
events.extend(self.delete_subtree(&rel_path));
|
|
580
|
+
}
|
|
581
|
+
events
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
/// Delete the rows of every file recorded beneath `rel_dir`, in every
|
|
585
|
+
/// table, as if each file had its own delete event.
|
|
586
|
+
fn delete_subtree(&self, rel_dir: &str) -> Vec<RowEvent> {
|
|
587
|
+
let files = {
|
|
588
|
+
let db = match self.inner.db.lock() {
|
|
589
|
+
Ok(db) => db,
|
|
590
|
+
Err(e) => return vec![error_event(None, rel_dir, e.to_string())],
|
|
591
|
+
};
|
|
592
|
+
match db.files_under(rel_dir) {
|
|
593
|
+
Ok(files) => files,
|
|
594
|
+
Err(e) => return vec![error_event(None, rel_dir, e.to_string())],
|
|
595
|
+
}
|
|
596
|
+
};
|
|
597
|
+
let mut events = Vec::new();
|
|
598
|
+
for (table, file_path) in files {
|
|
599
|
+
events.extend(self.handle_delete(&table, &file_path));
|
|
600
|
+
}
|
|
576
601
|
events
|
|
577
602
|
}
|
|
578
603
|
|
|
@@ -2753,6 +2778,153 @@ mod internal_tests {
|
|
|
2753
2778
|
);
|
|
2754
2779
|
}
|
|
2755
2780
|
|
|
2781
|
+
/// A one-table db whose fake fs stats every file in `files`, each already
|
|
2782
|
+
/// upserted so the db holds a row per file.
|
|
2783
|
+
fn populated_db(root: &Path, glob: &str, files: &[&str]) -> DirSQL {
|
|
2784
|
+
let mut fake = FakeFs::default();
|
|
2785
|
+
for f in files {
|
|
2786
|
+
fake.stats.insert(root.join(f), fake_stat());
|
|
2787
|
+
}
|
|
2788
|
+
let db = DirSQL::with_ignore_and_fs(
|
|
2789
|
+
root,
|
|
2790
|
+
vec![Table::new(
|
|
2791
|
+
"items",
|
|
2792
|
+
"CREATE TABLE items (name TEXT)",
|
|
2793
|
+
glob,
|
|
2794
|
+
|path| {
|
|
2795
|
+
vec![Row::from_iter([(
|
|
2796
|
+
"name".to_string(),
|
|
2797
|
+
Value::Text(path.to_string()),
|
|
2798
|
+
)])]
|
|
2799
|
+
},
|
|
2800
|
+
)],
|
|
2801
|
+
Vec::<String>::new(),
|
|
2802
|
+
Arc::new(fake),
|
|
2803
|
+
)
|
|
2804
|
+
.unwrap();
|
|
2805
|
+
for f in files {
|
|
2806
|
+
let events = db.handle_upsert("items", &root.join(f), f);
|
|
2807
|
+
assert!(
|
|
2808
|
+
matches!(&events[..], [RowEvent::Insert { .. }]),
|
|
2809
|
+
"got: {events:?}"
|
|
2810
|
+
);
|
|
2811
|
+
}
|
|
2812
|
+
db
|
|
2813
|
+
}
|
|
2814
|
+
|
|
2815
|
+
fn row_names(db: &DirSQL) -> Vec<String> {
|
|
2816
|
+
let mut names: Vec<String> = db
|
|
2817
|
+
.query("SELECT name FROM items")
|
|
2818
|
+
.unwrap()
|
|
2819
|
+
.into_iter()
|
|
2820
|
+
.filter_map(|r| match r.get("name") {
|
|
2821
|
+
Some(Value::Text(n)) => Some(n.clone()),
|
|
2822
|
+
_ => None,
|
|
2823
|
+
})
|
|
2824
|
+
.collect();
|
|
2825
|
+
names.sort();
|
|
2826
|
+
names
|
|
2827
|
+
}
|
|
2828
|
+
|
|
2829
|
+
fn deleted_paths(events: &[RowEvent]) -> Vec<String> {
|
|
2830
|
+
assert!(
|
|
2831
|
+
events.iter().all(|e| matches!(e, RowEvent::Delete { .. })),
|
|
2832
|
+
"expected only Delete events: {events:?}"
|
|
2833
|
+
);
|
|
2834
|
+
let mut paths: Vec<String> = events
|
|
2835
|
+
.iter()
|
|
2836
|
+
.filter_map(|e| match e {
|
|
2837
|
+
RowEvent::Delete { file_path, .. } => Some(file_path.clone()),
|
|
2838
|
+
_ => None,
|
|
2839
|
+
})
|
|
2840
|
+
.collect();
|
|
2841
|
+
paths.sort();
|
|
2842
|
+
paths
|
|
2843
|
+
}
|
|
2844
|
+
|
|
2845
|
+
/// A deleted directory matches no file glob, yet every row recorded
|
|
2846
|
+
/// beneath it goes, with one Delete per file. A sibling whose name merely
|
|
2847
|
+
/// starts with the directory's (`moved2/`) is untouched.
|
|
2848
|
+
#[test]
|
|
2849
|
+
fn process_file_event_deleted_directory_removes_the_rows_beneath_it() {
|
|
2850
|
+
let dir = TempDir::new().unwrap();
|
|
2851
|
+
let root = dir.path().to_path_buf();
|
|
2852
|
+
let files = ["moved/a.txt", "moved/one/deep.txt", "moved2/x.txt"];
|
|
2853
|
+
let db = populated_db(&root, "**/*.txt", &files);
|
|
2854
|
+
|
|
2855
|
+
let events = db.process_file_event(FileEvent::Deleted(root.join("moved")));
|
|
2856
|
+
|
|
2857
|
+
assert_eq!(
|
|
2858
|
+
deleted_paths(&events),
|
|
2859
|
+
vec!["moved/a.txt".to_string(), "moved/one/deep.txt".to_string()]
|
|
2860
|
+
);
|
|
2861
|
+
assert_eq!(
|
|
2862
|
+
row_names(&db),
|
|
2863
|
+
vec![root.join("moved2/x.txt").to_string_lossy().to_string()]
|
|
2864
|
+
);
|
|
2865
|
+
}
|
|
2866
|
+
|
|
2867
|
+
/// With a glob that also matches the directory itself (`**/*`), the
|
|
2868
|
+
/// exact-path delete finds no row and the subtree delete still runs.
|
|
2869
|
+
#[test]
|
|
2870
|
+
fn process_file_event_deleted_directory_matching_a_glob_removes_the_rows_beneath_it() {
|
|
2871
|
+
let dir = TempDir::new().unwrap();
|
|
2872
|
+
let root = dir.path().to_path_buf();
|
|
2873
|
+
let db = populated_db(&root, "**/*", &["moved/a.txt", "keep.txt"]);
|
|
2874
|
+
|
|
2875
|
+
let events = db.process_file_event(FileEvent::Deleted(root.join("moved")));
|
|
2876
|
+
|
|
2877
|
+
assert_eq!(deleted_paths(&events), vec!["moved/a.txt".to_string()]);
|
|
2878
|
+
assert_eq!(
|
|
2879
|
+
row_names(&db),
|
|
2880
|
+
vec![root.join("keep.txt").to_string_lossy().to_string()]
|
|
2881
|
+
);
|
|
2882
|
+
}
|
|
2883
|
+
|
|
2884
|
+
/// Only a *deleted* directory empties its subtree; a metadata change on
|
|
2885
|
+
/// one says nothing about its files.
|
|
2886
|
+
#[test]
|
|
2887
|
+
fn process_file_event_keeps_the_rows_beneath_a_modified_directory() {
|
|
2888
|
+
let dir = TempDir::new().unwrap();
|
|
2889
|
+
let root = dir.path().to_path_buf();
|
|
2890
|
+
let db = populated_db(&root, "**/*.txt", &["moved/a.txt"]);
|
|
2891
|
+
|
|
2892
|
+
let events = db.process_file_event(FileEvent::Modified(root.join("moved")));
|
|
2893
|
+
|
|
2894
|
+
assert!(events.is_empty(), "got: {events:?}");
|
|
2895
|
+
assert_eq!(
|
|
2896
|
+
row_names(&db),
|
|
2897
|
+
vec![root.join("moved/a.txt").to_string_lossy().to_string()]
|
|
2898
|
+
);
|
|
2899
|
+
}
|
|
2900
|
+
|
|
2901
|
+
#[test]
|
|
2902
|
+
fn delete_subtree_surfaces_db_poison() {
|
|
2903
|
+
let (_dir, db, _abs, _rel) = upsert_fixture();
|
|
2904
|
+
poison(&db.inner.db);
|
|
2905
|
+
let events = db.delete_subtree("moved");
|
|
2906
|
+
assert_single_lock_error(&events);
|
|
2907
|
+
}
|
|
2908
|
+
|
|
2909
|
+
#[test]
|
|
2910
|
+
fn delete_subtree_surfaces_db_failure() {
|
|
2911
|
+
let (_dir, db, _abs, _rel) = upsert_fixture();
|
|
2912
|
+
db.inner
|
|
2913
|
+
.db
|
|
2914
|
+
.lock()
|
|
2915
|
+
.unwrap()
|
|
2916
|
+
.conn()
|
|
2917
|
+
.execute("DROP TABLE _dirsql_internal_rows", [])
|
|
2918
|
+
.unwrap();
|
|
2919
|
+
let events = db.delete_subtree("moved");
|
|
2920
|
+
assert_eq!(events.len(), 1, "expected one error event: {events:?}");
|
|
2921
|
+
assert!(
|
|
2922
|
+
matches!(&events[0], RowEvent::Error { table: None, file_path, error }
|
|
2923
|
+
if file_path == Path::new("moved") && error.contains("no such table")),
|
|
2924
|
+
"got: {events:?}"
|
|
2925
|
+
);
|
|
2926
|
+
}
|
|
2927
|
+
|
|
2756
2928
|
/// Building with a **relative** root canonicalizes `watch_root` to an
|
|
2757
2929
|
/// absolute path while leaving `root` exactly as the caller supplied it,
|
|
2758
2930
|
/// so `notify` never sees `.`.
|
|
@@ -3538,7 +3710,7 @@ mod internal_tests {
|
|
|
3538
3710
|
let db = DirSQL::builder().root(dir.path()).build().unwrap();
|
|
3539
3711
|
|
|
3540
3712
|
assert!(
|
|
3541
|
-
db.query("SELECT path FROM './'").is_ok(),
|
|
3713
|
+
db.query("SELECT path FROM './' LIMIT 1").is_ok(),
|
|
3542
3714
|
"the path-table fallback must be armed on an ephemeral db"
|
|
3543
3715
|
);
|
|
3544
3716
|
}
|
|
@@ -3579,7 +3751,7 @@ mod internal_tests {
|
|
|
3579
3751
|
.unwrap();
|
|
3580
3752
|
|
|
3581
3753
|
assert!(
|
|
3582
|
-
db.query("SELECT path FROM './'").is_ok(),
|
|
3754
|
+
db.query("SELECT path FROM './' LIMIT 1").is_ok(),
|
|
3583
3755
|
"the persist branch must arm the fallback too"
|
|
3584
3756
|
);
|
|
3585
3757
|
}
|
|
@@ -23,13 +23,11 @@
|
|
|
23
23
|
use std::collections::{HashMap, HashSet};
|
|
24
24
|
use std::ffi::c_int;
|
|
25
25
|
use std::path::{Path, PathBuf};
|
|
26
|
+
use std::sync::Arc;
|
|
26
27
|
|
|
27
28
|
use globset::GlobSet;
|
|
28
|
-
use rusqlite::vtab::
|
|
29
|
-
|
|
30
|
-
read_only_module,
|
|
31
|
-
};
|
|
32
|
-
use rusqlite::{Connection, Error, Result, ffi};
|
|
29
|
+
use rusqlite::vtab::Context;
|
|
30
|
+
use rusqlite::{Connection, Error, Result};
|
|
33
31
|
|
|
34
32
|
use crate::Value;
|
|
35
33
|
use crate::command::{Placeholder, run_command};
|
|
@@ -38,16 +36,15 @@ use crate::matcher::TableMatcher;
|
|
|
38
36
|
use crate::parsed_cache::{self, CachedParse, Entry, RowCache, SqliteRowCache};
|
|
39
37
|
use crate::path_table;
|
|
40
38
|
use crate::persist::{FileStat, hash_file, now_ns};
|
|
41
|
-
use crate::scanner::
|
|
42
|
-
use crate::
|
|
39
|
+
use crate::scanner::scan_glob;
|
|
40
|
+
use crate::vtab_scaffold::{self, TableSource};
|
|
43
41
|
|
|
44
42
|
/// SQL module name a parsed path-table is created with.
|
|
45
43
|
pub const MODULE_NAME: &str = "dirsql_parsed";
|
|
46
44
|
|
|
47
45
|
/// Register the parsed path-table module on `conn`.
|
|
48
46
|
pub fn load_module(conn: &Connection) -> Result<()> {
|
|
49
|
-
|
|
50
|
-
conn.create_module(MODULE_NAME, read_only_module::<ParsedTab>(), aux)
|
|
47
|
+
vtab_scaffold::load_module::<ParsedTable>(conn)
|
|
51
48
|
}
|
|
52
49
|
|
|
53
50
|
/// Number of module arguments that are not ignore patterns.
|
|
@@ -67,43 +64,32 @@ struct ModuleArgs {
|
|
|
67
64
|
ignore: TableMatcher,
|
|
68
65
|
}
|
|
69
66
|
|
|
70
|
-
/// Compile the ignore patterns a parsed path-table scan applies.
|
|
71
|
-
fn compile_ignore(patterns: &[String]) -> Result<TableMatcher> {
|
|
72
|
-
let refs: Vec<&str> = patterns.iter().map(String::as_str).collect();
|
|
73
|
-
TableMatcher::new(&[], &refs).map_err(|e| Error::ModuleError(e.to_string()))
|
|
74
|
-
}
|
|
75
|
-
|
|
76
67
|
/// Parse a parsed path-table's `CREATE VIRTUAL TABLE` arguments. `args[0..3]`
|
|
77
68
|
/// are the module, database and table names; the module's own follow — root,
|
|
78
69
|
/// glob, parser, the gitignore switch, the cache path, then any ignore
|
|
79
70
|
/// patterns.
|
|
80
71
|
fn parse_module_args(args: &[&[u8]]) -> Result<ModuleArgs> {
|
|
81
|
-
let user_args
|
|
82
|
-
.iter()
|
|
83
|
-
.skip(3)
|
|
84
|
-
.map(|a| unquote(&String::from_utf8_lossy(a)).to_string())
|
|
85
|
-
.collect();
|
|
72
|
+
let user_args = vtab_scaffold::user_args(args);
|
|
86
73
|
|
|
87
74
|
let [root, pattern, command, gitignore, cache, ignore @ ..] = user_args.as_slice() else {
|
|
88
|
-
return Err(
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
75
|
+
return Err(vtab_scaffold::arity_error(
|
|
76
|
+
MODULE_NAME,
|
|
77
|
+
FIXED_ARGS,
|
|
78
|
+
"root, glob, parser, gitignore switch, cache path",
|
|
79
|
+
user_args.len(),
|
|
80
|
+
));
|
|
93
81
|
};
|
|
94
82
|
|
|
95
|
-
let glob = scanner::compile_glob(pattern).map_err(|e| Error::ModuleError(e.to_string()))?;
|
|
96
|
-
|
|
97
83
|
Ok(ModuleArgs {
|
|
98
84
|
root: PathBuf::from(root),
|
|
99
85
|
pattern: pattern.clone(),
|
|
100
|
-
glob
|
|
86
|
+
glob: vtab_scaffold::compile_glob(pattern)?,
|
|
101
87
|
command: command.clone(),
|
|
102
|
-
gitignore:
|
|
88
|
+
gitignore: vtab_scaffold::parse_gitignore(gitignore)?,
|
|
103
89
|
// The empty string is how "no cache" is spelled: a module argument
|
|
104
90
|
// cannot be absent without shifting every argument after it.
|
|
105
91
|
cache: Some(PathBuf::from(cache)).filter(|p| !p.as_os_str().is_empty()),
|
|
106
|
-
ignore: compile_ignore(ignore)?,
|
|
92
|
+
ignore: vtab_scaffold::compile_ignore(ignore)?,
|
|
107
93
|
})
|
|
108
94
|
}
|
|
109
95
|
|
|
@@ -321,37 +307,21 @@ fn column_name(names: &[String], i: c_int) -> Option<&str> {
|
|
|
321
307
|
names.get(index).map(String::as_str)
|
|
322
308
|
}
|
|
323
309
|
|
|
324
|
-
///
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
/// Rowid for a cursor position, saturating rather than wrapping on a row count
|
|
330
|
-
/// no filesystem will produce.
|
|
331
|
-
fn rowid_of(index: usize) -> i64 {
|
|
332
|
-
i64::try_from(index).unwrap_or(i64::MAX)
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
#[repr(C)]
|
|
336
|
-
struct ParsedTab {
|
|
337
|
-
/// Base class. Must be first.
|
|
338
|
-
base: ffi::sqlite3_vtab,
|
|
310
|
+
/// The table a `CREATE` produced: the inferred column names and the rows the
|
|
311
|
+
/// parsers emitted, both fixed for the life of the table.
|
|
312
|
+
struct ParsedTable {
|
|
339
313
|
/// Only the names survive registration: the types live in the schema
|
|
340
314
|
/// SQLite already holds, and a cursor addresses columns by index.
|
|
341
315
|
column_names: Vec<String>,
|
|
342
|
-
rows: Vec<JsonRow
|
|
316
|
+
rows: Arc<Vec<JsonRow>>,
|
|
343
317
|
}
|
|
344
318
|
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
319
|
+
impl TableSource for ParsedTable {
|
|
320
|
+
type Row = JsonRow;
|
|
321
|
+
|
|
322
|
+
const NAME: &'static str = MODULE_NAME;
|
|
349
323
|
|
|
350
|
-
fn connect(
|
|
351
|
-
_db: &mut VTabConnection,
|
|
352
|
-
_aux: Option<&()>,
|
|
353
|
-
args: &[&[u8]],
|
|
354
|
-
) -> Result<(String, Self)> {
|
|
324
|
+
fn connect(args: &[&[u8]]) -> Result<(String, Self)> {
|
|
355
325
|
let ModuleArgs {
|
|
356
326
|
root,
|
|
357
327
|
pattern,
|
|
@@ -384,81 +354,25 @@ unsafe impl<'vtab> VTab<'vtab> for ParsedTab {
|
|
|
384
354
|
}
|
|
385
355
|
|
|
386
356
|
let schema = declared_schema(&columns);
|
|
387
|
-
let
|
|
388
|
-
base: ffi::sqlite3_vtab::default(),
|
|
357
|
+
let table = Self {
|
|
389
358
|
column_names: columns.into_iter().map(|c| c.name).collect(),
|
|
390
|
-
rows,
|
|
359
|
+
rows: Arc::new(rows),
|
|
391
360
|
};
|
|
392
|
-
Ok((schema,
|
|
361
|
+
Ok((schema, table))
|
|
393
362
|
}
|
|
394
363
|
|
|
395
|
-
fn
|
|
396
|
-
|
|
397
|
-
|
|
364
|
+
fn rows(&self) -> Arc<Vec<JsonRow>> {
|
|
365
|
+
// Materialized once at CREATE, so every statement reads the same rows
|
|
366
|
+
// rather than re-spawning a parser per file.
|
|
367
|
+
Arc::clone(&self.rows)
|
|
398
368
|
}
|
|
399
369
|
|
|
400
|
-
fn
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
column_names: self.column_names.clone(),
|
|
404
|
-
rows: self.rows.clone(),
|
|
405
|
-
index: 0,
|
|
406
|
-
})
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
impl<'vtab> CreateVTab<'vtab> for ParsedTab {
|
|
411
|
-
const KIND: VTabKind = VTabKind::Default;
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
#[repr(C)]
|
|
415
|
-
struct ParsedTabCursor {
|
|
416
|
-
/// Base class. Must be first: `rust_open` hands this pointer straight to
|
|
417
|
-
/// SQLite as a `sqlite3_vtab_cursor`, so anything ahead of it gets
|
|
418
|
-
/// overwritten.
|
|
419
|
-
base: ffi::sqlite3_vtab_cursor,
|
|
420
|
-
column_names: Vec<String>,
|
|
421
|
-
rows: Vec<JsonRow>,
|
|
422
|
-
index: usize,
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
#[expect(
|
|
426
|
-
unsafe_code,
|
|
427
|
-
reason = "rusqlite requires VTabCursor to be an unsafe trait"
|
|
428
|
-
)]
|
|
429
|
-
unsafe impl VTabCursor for ParsedTabCursor {
|
|
430
|
-
fn filter(
|
|
431
|
-
&mut self,
|
|
432
|
-
_idx_num: c_int,
|
|
433
|
-
_idx_str: Option<&str>,
|
|
434
|
-
_args: &Values<'_>,
|
|
435
|
-
) -> Result<()> {
|
|
436
|
-
self.index = 0;
|
|
437
|
-
Ok(())
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
fn next(&mut self) -> Result<()> {
|
|
441
|
-
self.index += 1;
|
|
442
|
-
Ok(())
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
fn eof(&self) -> bool {
|
|
446
|
-
at_eof(self.index, self.rows.len())
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
fn column(&self, ctx: &mut Context, i: c_int) -> Result<()> {
|
|
450
|
-
let value = self
|
|
451
|
-
.rows
|
|
452
|
-
.get(self.index)
|
|
453
|
-
.zip(column_name(&self.column_names, i))
|
|
454
|
-
.map(|(row, name)| cell(row, name))
|
|
370
|
+
fn column(&self, row: &JsonRow, ctx: &mut Context, i: c_int) -> Result<()> {
|
|
371
|
+
let value = column_name(&self.column_names, i)
|
|
372
|
+
.map(|name| cell(row, name))
|
|
455
373
|
.unwrap_or(Value::Null);
|
|
456
374
|
ctx.set_result(&value)
|
|
457
375
|
}
|
|
458
|
-
|
|
459
|
-
fn rowid(&self) -> Result<i64> {
|
|
460
|
-
Ok(rowid_of(self.index))
|
|
461
|
-
}
|
|
462
376
|
}
|
|
463
377
|
|
|
464
378
|
#[cfg(test)]
|
|
@@ -729,34 +643,6 @@ mod tests {
|
|
|
729
643
|
assert_eq!(column_name(&names(), -1), None);
|
|
730
644
|
}
|
|
731
645
|
|
|
732
|
-
#[test]
|
|
733
|
-
fn at_eof_is_false_while_rows_remain() {
|
|
734
|
-
assert!(!at_eof(0, 2));
|
|
735
|
-
assert!(!at_eof(1, 2));
|
|
736
|
-
}
|
|
737
|
-
|
|
738
|
-
#[test]
|
|
739
|
-
fn at_eof_is_true_once_past_the_last_row() {
|
|
740
|
-
assert!(at_eof(2, 2));
|
|
741
|
-
assert!(at_eof(3, 2));
|
|
742
|
-
}
|
|
743
|
-
|
|
744
|
-
#[test]
|
|
745
|
-
fn at_eof_is_true_for_an_empty_row_set() {
|
|
746
|
-
assert!(at_eof(0, 0));
|
|
747
|
-
}
|
|
748
|
-
|
|
749
|
-
#[test]
|
|
750
|
-
fn rowid_of_tracks_the_cursor_position() {
|
|
751
|
-
assert_eq!(rowid_of(0), 0);
|
|
752
|
-
assert_eq!(rowid_of(7), 7);
|
|
753
|
-
}
|
|
754
|
-
|
|
755
|
-
#[test]
|
|
756
|
-
fn rowid_of_saturates_rather_than_wrapping() {
|
|
757
|
-
assert_eq!(rowid_of(usize::MAX), i64::MAX);
|
|
758
|
-
}
|
|
759
|
-
|
|
760
646
|
// The run_parser tests spawn a real `sh`. Their test code statically
|
|
761
647
|
// references only `super::` items (plus pure std), matching the pattern
|
|
762
648
|
// `command.rs` uses for the runner underneath.
|