dirsql 0.3.31__tar.gz → 0.3.33__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.3.31 → dirsql-0.3.33}/Cargo.lock +1 -1
- {dirsql-0.3.31 → dirsql-0.3.33}/PKG-INFO +22 -1
- {dirsql-0.3.31/packages/python → dirsql-0.3.33}/README.md +21 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/dirsql/_async.py +9 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/dirsql/_dirsql.pyi +10 -1
- {dirsql-0.3.31 → dirsql-0.3.33}/dirsql/resolve_config.py +16 -2
- {dirsql-0.3.31 → dirsql-0.3.33}/docs/api/index.md +5 -2
- {dirsql-0.3.31 → dirsql-0.3.33}/packages/python/Cargo.toml +1 -1
- {dirsql-0.3.31 → dirsql-0.3.33/packages/python}/README.md +21 -0
- {dirsql-0.3.31/packages/rust → dirsql-0.3.33/packages/python}/docs/api/index.md +5 -2
- {dirsql-0.3.31 → dirsql-0.3.33}/packages/python/src/lib.rs +29 -2
- {dirsql-0.3.31 → dirsql-0.3.33}/packages/rust/Cargo.toml +4 -0
- {dirsql-0.3.31/packages/python → dirsql-0.3.33/packages/rust}/docs/api/index.md +5 -2
- {dirsql-0.3.31 → dirsql-0.3.33}/packages/rust/src/cli/native_config.rs +91 -2
- {dirsql-0.3.31 → dirsql-0.3.33}/packages/rust/src/lib.rs +75 -41
- {dirsql-0.3.31 → dirsql-0.3.33}/pyproject.toml +6 -0
- dirsql-0.3.31/dirsql/_async_test.py +0 -95
- dirsql-0.3.31/dirsql/cli/binary_path_test.py +0 -51
- dirsql-0.3.31/dirsql/cli/interpret/dispatch_extract_test.py +0 -92
- dirsql-0.3.31/dirsql/cli/interpret/load_app_test.py +0 -85
- dirsql-0.3.31/dirsql/cli/interpret/run_test.py +0 -172
- dirsql-0.3.31/dirsql/cli/interpret/write_message_test.py +0 -30
- dirsql-0.3.31/dirsql/cli/is_windows_test.py +0 -20
- dirsql-0.3.31/dirsql/cli/main_test.py +0 -127
- dirsql-0.3.31/dirsql/resolve_config_test.py +0 -231
- dirsql-0.3.31/packages/python/tests/integration/test_async_dirsql.py +0 -415
- dirsql-0.3.31/packages/python/tests/integration/test_binding.py +0 -290
- dirsql-0.3.31/packages/python/tests/integration/test_dirsql.py +0 -544
- dirsql-0.3.31/packages/python/tests/integration/test_docs_examples.py +0 -825
- dirsql-0.3.31/packages/python/tests/integration/test_docs_gaps.py +0 -257
- dirsql-0.3.31/packages/python/tests/integration/test_from_config.py +0 -230
- dirsql-0.3.31/packages/python/tests/integration/test_interpret.py +0 -145
- dirsql-0.3.31/packages/python/tests/integration/test_native_config.py +0 -100
- dirsql-0.3.31/packages/python/tests/integration/test_persist.py +0 -307
- dirsql-0.3.31/packages/python/tests/integration/test_serialization.py +0 -237
- dirsql-0.3.31/packages/rust/tests/async_sdk.rs +0 -294
- dirsql-0.3.31/packages/rust/tests/cli_e2e.rs +0 -397
- dirsql-0.3.31/packages/rust/tests/cli_integration.rs +0 -547
- dirsql-0.3.31/packages/rust/tests/code_review_findings.rs +0 -288
- dirsql-0.3.31/packages/rust/tests/config.rs +0 -26
- dirsql-0.3.31/packages/rust/tests/docs_examples.rs +0 -829
- dirsql-0.3.31/packages/rust/tests/docs_gaps.rs +0 -144
- dirsql-0.3.31/packages/rust/tests/extensions.rs +0 -247
- dirsql-0.3.31/packages/rust/tests/from_config.rs +0 -361
- dirsql-0.3.31/packages/rust/tests/init_e2e.rs +0 -94
- dirsql-0.3.31/packages/rust/tests/init_integration.rs +0 -301
- dirsql-0.3.31/packages/rust/tests/persist.rs +0 -427
- dirsql-0.3.31/packages/rust/tests/readonly_query.rs +0 -211
- dirsql-0.3.31/packages/rust/tests/scanner.rs +0 -97
- dirsql-0.3.31/packages/rust/tests/sdk.rs +0 -857
- dirsql-0.3.31/packages/rust/tests/serialization.rs +0 -161
- dirsql-0.3.31/packages/rust/tests/watch_relative_root.rs +0 -134
- dirsql-0.3.31/packages/rust/tests/watcher.rs +0 -139
- {dirsql-0.3.31 → dirsql-0.3.33}/Cargo.toml +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/dirsql/__init__.py +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/dirsql/cli/__init__.py +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/dirsql/cli/binary_path.py +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/dirsql/cli/interpret/__init__.py +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/dirsql/cli/interpret/dispatch_extract.py +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/dirsql/cli/interpret/load_app.py +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/dirsql/cli/interpret/run.py +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/dirsql/cli/interpret/write_message.py +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/dirsql/cli/is_windows.py +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/dirsql/cli/main.py +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/dirsql/py.typed +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/docs/.claude/CLAUDE.md +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/docs/.vitepress/config.ts +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/docs/.vitepress/theme/index.ts +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/docs/.vitepress/theme/lang.ts +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/docs/AGENTS.md +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/docs/cli/config.md +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/docs/cli/http-api.md +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/docs/cli/index.md +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/docs/cli/init.md +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/docs/cli/server.md +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/docs/getting-started.md +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/docs/guide/async.md +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/docs/guide/crdt.md +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/docs/guide/persistence.md +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/docs/guide/querying.md +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/docs/guide/tables.md +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/docs/guide/watching.md +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/docs/index.md +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/docs/migrations.md +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/docs/package.json +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/docs/playwright.config.ts +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/docs/pnpm-lock.yaml +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/docs/pnpm-workspace.yaml +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/docs/tests/integration/home.spec.ts +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/docs/tests/integration/language-flag.spec.ts +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/docs/tests/unit/config.test.ts +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/docs/tests/unit/lang.test.ts +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/docs/vitest.config.ts +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/packages/python/conftest.py +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/packages/python/docs/.claude/CLAUDE.md +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/packages/python/docs/.vitepress/config.ts +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/packages/python/docs/.vitepress/theme/index.ts +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/packages/python/docs/.vitepress/theme/lang.ts +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/packages/python/docs/AGENTS.md +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/packages/python/docs/cli/config.md +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/packages/python/docs/cli/http-api.md +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/packages/python/docs/cli/index.md +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/packages/python/docs/cli/init.md +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/packages/python/docs/cli/server.md +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/packages/python/docs/getting-started.md +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/packages/python/docs/guide/async.md +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/packages/python/docs/guide/crdt.md +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/packages/python/docs/guide/persistence.md +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/packages/python/docs/guide/querying.md +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/packages/python/docs/guide/tables.md +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/packages/python/docs/guide/watching.md +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/packages/python/docs/index.md +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/packages/python/docs/migrations.md +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/packages/python/docs/package.json +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/packages/python/docs/playwright.config.ts +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/packages/python/docs/pnpm-lock.yaml +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/packages/python/docs/pnpm-workspace.yaml +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/packages/python/docs/tests/integration/home.spec.ts +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/packages/python/docs/tests/integration/language-flag.spec.ts +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/packages/python/docs/tests/unit/config.test.ts +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/packages/python/docs/tests/unit/lang.test.ts +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/packages/python/docs/vitest.config.ts +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/packages/python/tests/__init__.py +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/packages/python/tests/conftest.py +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/packages/python/tests/e2e/__init__.py +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/packages/python/tests/integration/__fixtures__/data/a/meta.json +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/packages/python/tests/integration/__fixtures__/data/b/meta.json +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/packages/python/tests/integration/__fixtures__/dirsql.config.py +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/packages/python/tests/integration/__fixtures__/interpret/data/a/meta.json +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/packages/python/tests/integration/__fixtures__/interpret/data/b/meta.json +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/packages/python/tests/integration/__fixtures__/interpret/dirsql.config.py +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/packages/python/tests/integration/__fixtures__/interpret/dirsql.config_no_app.py +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/packages/python/tests/integration/__fixtures__/interpret/dirsql.config_raises.py +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/packages/python/tests/integration/__init__.py +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/packages/python/tests/integration/interpret_subprocess.py +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/packages/rust/README.md +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/packages/rust/benches/db_bench.rs +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/packages/rust/benches/differ_bench.rs +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/packages/rust/benches/matcher_bench.rs +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/packages/rust/benches/scanner_bench.rs +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/packages/rust/docs/cli/config.md +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/packages/rust/docs/cli/http-api.md +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/packages/rust/docs/cli/index.md +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/packages/rust/docs/cli/init.md +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/packages/rust/docs/cli/server.md +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/packages/rust/docs/getting-started.md +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/packages/rust/docs/guide/async.md +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/packages/rust/docs/guide/crdt.md +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/packages/rust/docs/guide/persistence.md +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/packages/rust/docs/guide/querying.md +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/packages/rust/docs/guide/tables.md +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/packages/rust/docs/guide/watching.md +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/packages/rust/docs/index.md +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/packages/rust/docs/migrations.md +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/packages/rust/src/bin/dirsql.rs +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/packages/rust/src/cli/init.rs +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/packages/rust/src/cli/mod.rs +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/packages/rust/src/cli/router.rs +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/packages/rust/src/cli/serialize.rs +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/packages/rust/src/cli/server.rs +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/packages/rust/src/config.rs +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/packages/rust/src/db.rs +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/packages/rust/src/differ.rs +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/packages/rust/src/matcher.rs +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/packages/rust/src/persist.rs +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/packages/rust/src/scanner.rs +0 -0
- {dirsql-0.3.31 → dirsql-0.3.33}/packages/rust/src/watcher.rs +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dirsql
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.33
|
|
4
4
|
Requires-Dist: pytest>=8 ; extra == 'dev'
|
|
5
5
|
Requires-Dist: pytest-describe>=2 ; extra == 'dev'
|
|
6
6
|
Requires-Dist: pytest-asyncio>=0.23 ; extra == 'dev'
|
|
@@ -98,6 +98,27 @@ db = DirSQL(
|
|
|
98
98
|
)
|
|
99
99
|
```
|
|
100
100
|
|
|
101
|
+
## Loading SQLite extensions
|
|
102
|
+
|
|
103
|
+
Pass `extensions` to load SQLite extension shared libraries onto the connection at startup (before any `CREATE TABLE`). Each entry is a dict with a `path` and an optional `entrypoint` init-symbol override:
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
db = DirSQL(
|
|
107
|
+
"./my-blog",
|
|
108
|
+
tables=[...],
|
|
109
|
+
extensions=[
|
|
110
|
+
{"path": "./ext/vec0.dylib", "entrypoint": "sqlite3_vec_init"},
|
|
111
|
+
{"path": "./ext/myext.so"}, # entrypoint derived from the filename
|
|
112
|
+
],
|
|
113
|
+
)
|
|
114
|
+
await db.ready()
|
|
115
|
+
|
|
116
|
+
# The extension's functions are now callable in queries:
|
|
117
|
+
rows = await db.query("SELECT vec_version() AS v")
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
`dirsql` enables extension loading only while loading the configured libraries, then disables it again, so the SQL `load_extension()` function is never exposed to your queries. Programmatic entries load first, followed by any `[[dirsql.extension]]` entries declared in a `config` file. See the [config reference](https://github.com/thekevinscott/dirsql/blob/main/docs/cli/config.md#loading-extensions).
|
|
121
|
+
|
|
101
122
|
## Watching for changes
|
|
102
123
|
|
|
103
124
|
`db.watch()` returns an async iterator of row-level change events as files change on disk:
|
|
@@ -80,6 +80,27 @@ db = DirSQL(
|
|
|
80
80
|
)
|
|
81
81
|
```
|
|
82
82
|
|
|
83
|
+
## Loading SQLite extensions
|
|
84
|
+
|
|
85
|
+
Pass `extensions` to load SQLite extension shared libraries onto the connection at startup (before any `CREATE TABLE`). Each entry is a dict with a `path` and an optional `entrypoint` init-symbol override:
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
db = DirSQL(
|
|
89
|
+
"./my-blog",
|
|
90
|
+
tables=[...],
|
|
91
|
+
extensions=[
|
|
92
|
+
{"path": "./ext/vec0.dylib", "entrypoint": "sqlite3_vec_init"},
|
|
93
|
+
{"path": "./ext/myext.so"}, # entrypoint derived from the filename
|
|
94
|
+
],
|
|
95
|
+
)
|
|
96
|
+
await db.ready()
|
|
97
|
+
|
|
98
|
+
# The extension's functions are now callable in queries:
|
|
99
|
+
rows = await db.query("SELECT vec_version() AS v")
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
`dirsql` enables extension loading only while loading the configured libraries, then disables it again, so the SQL `load_extension()` function is never exposed to your queries. Programmatic entries load first, followed by any `[[dirsql.extension]]` entries declared in a `config` file. See the [config reference](https://github.com/thekevinscott/dirsql/blob/main/docs/cli/config.md#loading-extensions).
|
|
103
|
+
|
|
83
104
|
## Watching for changes
|
|
84
105
|
|
|
85
106
|
`db.watch()` returns an async iterator of row-level change events as files change on disk:
|
|
@@ -50,6 +50,11 @@ class DirSQL:
|
|
|
50
50
|
|
|
51
51
|
Pass ``persist=True`` to keep an on-disk SQLite cache (default location:
|
|
52
52
|
``<root>/.dirsql/cache.db``). Override the location with ``persist_path``.
|
|
53
|
+
|
|
54
|
+
Pass ``extensions`` -- a list of ``{"path": ..., "entrypoint": ...}`` dicts
|
|
55
|
+
(``entrypoint`` optional) -- to load SQLite extensions onto the connection
|
|
56
|
+
at startup. Any ``[[dirsql.extension]]`` entries in a ``config`` file are
|
|
57
|
+
appended after the programmatic ones.
|
|
53
58
|
"""
|
|
54
59
|
|
|
55
60
|
def __init__(
|
|
@@ -61,6 +66,7 @@ class DirSQL:
|
|
|
61
66
|
config=None,
|
|
62
67
|
persist=False,
|
|
63
68
|
persist_path=None,
|
|
69
|
+
extensions=None,
|
|
64
70
|
):
|
|
65
71
|
if root is None and config is None:
|
|
66
72
|
raise TypeError("DirSQL requires either a root directory or a config= path")
|
|
@@ -70,6 +76,7 @@ class DirSQL:
|
|
|
70
76
|
self._config = config
|
|
71
77
|
self._persist = persist
|
|
72
78
|
self._persist_path = persist_path
|
|
79
|
+
self._extensions = extensions
|
|
73
80
|
self._db = None
|
|
74
81
|
self._ready_event = asyncio.Event()
|
|
75
82
|
self._init_error = None
|
|
@@ -86,6 +93,7 @@ class DirSQL:
|
|
|
86
93
|
config=self._config,
|
|
87
94
|
persist=self._persist,
|
|
88
95
|
persist_path=self._persist_path,
|
|
96
|
+
extensions=self._extensions,
|
|
89
97
|
)
|
|
90
98
|
except Exception as exc:
|
|
91
99
|
self._init_error = exc
|
|
@@ -124,4 +132,5 @@ class DirSQL:
|
|
|
124
132
|
self._config,
|
|
125
133
|
self._persist,
|
|
126
134
|
self._persist_path,
|
|
135
|
+
self._extensions,
|
|
127
136
|
)
|
|
@@ -11,12 +11,20 @@ same PR -- and ``PARITY.md`` is the canonical reminder.
|
|
|
11
11
|
|
|
12
12
|
from collections.abc import Callable
|
|
13
13
|
from os import PathLike
|
|
14
|
-
from typing import Any
|
|
14
|
+
from typing import Any, NotRequired, TypedDict
|
|
15
15
|
|
|
16
16
|
__version__: str
|
|
17
17
|
|
|
18
18
|
Row = dict[str, Any]
|
|
19
19
|
|
|
20
|
+
class ExtensionSpec(TypedDict):
|
|
21
|
+
"""A SQLite extension to load at startup: a shared-library ``path`` and an
|
|
22
|
+
optional ``entrypoint`` init-symbol override. Mirrors a
|
|
23
|
+
``[[dirsql.extension]]`` config entry."""
|
|
24
|
+
|
|
25
|
+
path: str
|
|
26
|
+
entrypoint: NotRequired[str]
|
|
27
|
+
|
|
20
28
|
class Table:
|
|
21
29
|
"""A table definition. Construct via keyword arguments only."""
|
|
22
30
|
|
|
@@ -57,6 +65,7 @@ class DirSQL:
|
|
|
57
65
|
config: str | None = None,
|
|
58
66
|
persist: bool = False,
|
|
59
67
|
persist_path: str | PathLike[str] | None = None,
|
|
68
|
+
extensions: list[ExtensionSpec] | None = None,
|
|
60
69
|
) -> None: ...
|
|
61
70
|
def query(self, sql: str) -> list[Row]: ...
|
|
62
71
|
def _start_watcher(self) -> None: ...
|
|
@@ -9,14 +9,17 @@ import os
|
|
|
9
9
|
import tomllib
|
|
10
10
|
|
|
11
11
|
|
|
12
|
-
def resolve_config(
|
|
12
|
+
def resolve_config(
|
|
13
|
+
root, tables, ignore, config, persist, persist_path, extensions=None
|
|
14
|
+
):
|
|
13
15
|
"""Merge kwargs with a `.dirsql.toml` into the serialized state shape."""
|
|
14
|
-
cfg, cfg_tables, cfg_dir = {}, [], None
|
|
16
|
+
cfg, cfg_tables, cfg_extensions, cfg_dir = {}, [], [], None
|
|
15
17
|
if config is not None:
|
|
16
18
|
with open(config, "rb") as f:
|
|
17
19
|
doc = tomllib.load(f)
|
|
18
20
|
cfg = doc.get("dirsql") or {}
|
|
19
21
|
cfg_tables = doc.get("table") or []
|
|
22
|
+
cfg_extensions = cfg.get("extension") or []
|
|
20
23
|
cfg_dir = os.path.dirname(os.path.abspath(config))
|
|
21
24
|
|
|
22
25
|
def _abs(p):
|
|
@@ -40,4 +43,15 @@ def resolve_config(root, tables, ignore, config, persist, persist_path):
|
|
|
40
43
|
"persist": bool(persist or cfg.get("persist")),
|
|
41
44
|
"persist_path": persist_path
|
|
42
45
|
or (_abs(cfg["persist_path"]) if "persist_path" in cfg else None),
|
|
46
|
+
# Programmatic extensions first (verbatim paths, mirroring the Rust
|
|
47
|
+
# builder), then config-file `[[dirsql.extension]]` entries with
|
|
48
|
+
# relative paths resolved against the config's parent directory.
|
|
49
|
+
"extensions": [
|
|
50
|
+
{"path": e["path"], "entrypoint": e.get("entrypoint")}
|
|
51
|
+
for e in (extensions or [])
|
|
52
|
+
]
|
|
53
|
+
+ [
|
|
54
|
+
{"path": _abs(e["path"]), "entrypoint": e.get("entrypoint")}
|
|
55
|
+
for e in cfg_extensions
|
|
56
|
+
],
|
|
43
57
|
}
|
|
@@ -37,6 +37,7 @@ DirSQL(
|
|
|
37
37
|
tables: list[Table] | None = None,
|
|
38
38
|
ignore: list[str] | None = None,
|
|
39
39
|
config: str | None = None,
|
|
40
|
+
extensions: list[dict] | None = None, # [{ "path": str, "entrypoint"?: str }]
|
|
40
41
|
)
|
|
41
42
|
```
|
|
42
43
|
|
|
@@ -46,6 +47,7 @@ DirSQL::builder()
|
|
|
46
47
|
.tables(tables) // optional; append with .table(t)
|
|
47
48
|
.ignore(patterns) // optional
|
|
48
49
|
.config(config_toml_path) // optional
|
|
50
|
+
.extensions(extensions) // optional; Extension { path, entrypoint }
|
|
49
51
|
.build() // -> Result<DirSQL>
|
|
50
52
|
```
|
|
51
53
|
|
|
@@ -73,7 +75,8 @@ In Python, the constructor starts scanning in a background thread and returns im
|
|
|
73
75
|
- `root` -- Path to the directory to index. Optional if `config` is supplied.
|
|
74
76
|
- `tables` -- List of `Table` definitions. Each defines a SQLite table, a glob pattern, and an extract function.
|
|
75
77
|
- `ignore` -- Optional list of glob patterns. Files matching any ignore pattern are skipped regardless of table globs.
|
|
76
|
-
- `config` -- Optional path to a `.dirsql.toml` config file. Its `[[table]]` entries are appended to any programmatic `tables`; its `[dirsql].ignore` patterns are appended to any explicit `ignore`; its optional `[dirsql].root` supplies the root directory when `root` is not passed explicitly.
|
|
78
|
+
- `config` -- Optional path to a `.dirsql.toml` config file. Its `[[table]]` entries are appended to any programmatic `tables`; its `[dirsql].ignore` patterns are appended to any explicit `ignore`; its optional `[dirsql].root` supplies the root directory when `root` is not passed explicitly; its `[[dirsql.extension]]` entries are appended to any programmatic `extensions`.
|
|
79
|
+
- `extensions` -- Optional SQLite extensions to load onto the connection at startup, before any table DDL (enable → load → disable, so the SQL `load_extension()` function is never left exposed). Each entry pairs a shared-library `path` with an optional `entrypoint` init-symbol override (Python: `{ "path", "entrypoint"? }` dicts; Rust: `Extension { path, entrypoint }`). Programmatic entries load first, then any `[[dirsql.extension]]` from `config`. Available in the Python and Rust SDKs; the TypeScript constructor parameter is tracked in [#230](https://github.com/thekevinscott/dirsql/issues/230). See [Loading extensions](../cli/config.md#loading-extensions).
|
|
77
80
|
|
|
78
81
|
### Methods
|
|
79
82
|
|
|
@@ -159,7 +162,7 @@ JSON.stringify(db) // via db.toJSON()
|
|
|
159
162
|
|
|
160
163
|
:::
|
|
161
164
|
|
|
162
|
-
Returns the resolved construction state as a JSON-compatible value with fields `root`, `tables`, `ignore`, `persist`, `persist_path` (camelCase `persistPath` in TypeScript). Each table is `{ ddl, glob, strict }`. Excludes the original `config` path (already merged into `root` / `tables` / `ignore`), per-table `extract`, and per-table `name`. Available immediately after construction in Python and TypeScript; Rust's sync `build()` returns a ready instance.
|
|
165
|
+
Returns the resolved construction state as a JSON-compatible value with fields `root`, `tables`, `ignore`, `persist`, `persist_path` (camelCase `persistPath` in TypeScript). Each table is `{ ddl, glob, strict }`. The Python and Rust snapshots also include `extensions` -- an array of `{ path, entrypoint }` (empty when none are configured); the TypeScript `toJSON` snapshot will gain it with [#230](https://github.com/thekevinscott/dirsql/issues/230). Excludes the original `config` path (already merged into `root` / `tables` / `ignore`), per-table `extract`, and per-table `name`. Available immediately after construction in Python and TypeScript; Rust's sync `build()` returns a ready instance.
|
|
163
166
|
|
|
164
167
|
---
|
|
165
168
|
|
|
@@ -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.3.
|
|
7
|
+
version = "0.3.33"
|
|
8
8
|
edition.workspace = true
|
|
9
9
|
publish = false
|
|
10
10
|
readme = "README.md"
|
|
@@ -80,6 +80,27 @@ db = DirSQL(
|
|
|
80
80
|
)
|
|
81
81
|
```
|
|
82
82
|
|
|
83
|
+
## Loading SQLite extensions
|
|
84
|
+
|
|
85
|
+
Pass `extensions` to load SQLite extension shared libraries onto the connection at startup (before any `CREATE TABLE`). Each entry is a dict with a `path` and an optional `entrypoint` init-symbol override:
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
db = DirSQL(
|
|
89
|
+
"./my-blog",
|
|
90
|
+
tables=[...],
|
|
91
|
+
extensions=[
|
|
92
|
+
{"path": "./ext/vec0.dylib", "entrypoint": "sqlite3_vec_init"},
|
|
93
|
+
{"path": "./ext/myext.so"}, # entrypoint derived from the filename
|
|
94
|
+
],
|
|
95
|
+
)
|
|
96
|
+
await db.ready()
|
|
97
|
+
|
|
98
|
+
# The extension's functions are now callable in queries:
|
|
99
|
+
rows = await db.query("SELECT vec_version() AS v")
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
`dirsql` enables extension loading only while loading the configured libraries, then disables it again, so the SQL `load_extension()` function is never exposed to your queries. Programmatic entries load first, followed by any `[[dirsql.extension]]` entries declared in a `config` file. See the [config reference](https://github.com/thekevinscott/dirsql/blob/main/docs/cli/config.md#loading-extensions).
|
|
103
|
+
|
|
83
104
|
## Watching for changes
|
|
84
105
|
|
|
85
106
|
`db.watch()` returns an async iterator of row-level change events as files change on disk:
|
|
@@ -37,6 +37,7 @@ DirSQL(
|
|
|
37
37
|
tables: list[Table] | None = None,
|
|
38
38
|
ignore: list[str] | None = None,
|
|
39
39
|
config: str | None = None,
|
|
40
|
+
extensions: list[dict] | None = None, # [{ "path": str, "entrypoint"?: str }]
|
|
40
41
|
)
|
|
41
42
|
```
|
|
42
43
|
|
|
@@ -46,6 +47,7 @@ DirSQL::builder()
|
|
|
46
47
|
.tables(tables) // optional; append with .table(t)
|
|
47
48
|
.ignore(patterns) // optional
|
|
48
49
|
.config(config_toml_path) // optional
|
|
50
|
+
.extensions(extensions) // optional; Extension { path, entrypoint }
|
|
49
51
|
.build() // -> Result<DirSQL>
|
|
50
52
|
```
|
|
51
53
|
|
|
@@ -73,7 +75,8 @@ In Python, the constructor starts scanning in a background thread and returns im
|
|
|
73
75
|
- `root` -- Path to the directory to index. Optional if `config` is supplied.
|
|
74
76
|
- `tables` -- List of `Table` definitions. Each defines a SQLite table, a glob pattern, and an extract function.
|
|
75
77
|
- `ignore` -- Optional list of glob patterns. Files matching any ignore pattern are skipped regardless of table globs.
|
|
76
|
-
- `config` -- Optional path to a `.dirsql.toml` config file. Its `[[table]]` entries are appended to any programmatic `tables`; its `[dirsql].ignore` patterns are appended to any explicit `ignore`; its optional `[dirsql].root` supplies the root directory when `root` is not passed explicitly.
|
|
78
|
+
- `config` -- Optional path to a `.dirsql.toml` config file. Its `[[table]]` entries are appended to any programmatic `tables`; its `[dirsql].ignore` patterns are appended to any explicit `ignore`; its optional `[dirsql].root` supplies the root directory when `root` is not passed explicitly; its `[[dirsql.extension]]` entries are appended to any programmatic `extensions`.
|
|
79
|
+
- `extensions` -- Optional SQLite extensions to load onto the connection at startup, before any table DDL (enable → load → disable, so the SQL `load_extension()` function is never left exposed). Each entry pairs a shared-library `path` with an optional `entrypoint` init-symbol override (Python: `{ "path", "entrypoint"? }` dicts; Rust: `Extension { path, entrypoint }`). Programmatic entries load first, then any `[[dirsql.extension]]` from `config`. Available in the Python and Rust SDKs; the TypeScript constructor parameter is tracked in [#230](https://github.com/thekevinscott/dirsql/issues/230). See [Loading extensions](../cli/config.md#loading-extensions).
|
|
77
80
|
|
|
78
81
|
### Methods
|
|
79
82
|
|
|
@@ -159,7 +162,7 @@ JSON.stringify(db) // via db.toJSON()
|
|
|
159
162
|
|
|
160
163
|
:::
|
|
161
164
|
|
|
162
|
-
Returns the resolved construction state as a JSON-compatible value with fields `root`, `tables`, `ignore`, `persist`, `persist_path` (camelCase `persistPath` in TypeScript). Each table is `{ ddl, glob, strict }`. Excludes the original `config` path (already merged into `root` / `tables` / `ignore`), per-table `extract`, and per-table `name`. Available immediately after construction in Python and TypeScript; Rust's sync `build()` returns a ready instance.
|
|
165
|
+
Returns the resolved construction state as a JSON-compatible value with fields `root`, `tables`, `ignore`, `persist`, `persist_path` (camelCase `persistPath` in TypeScript). Each table is `{ ddl, glob, strict }`. The Python and Rust snapshots also include `extensions` -- an array of `{ path, entrypoint }` (empty when none are configured); the TypeScript `toJSON` snapshot will gain it with [#230](https://github.com/thekevinscott/dirsql/issues/230). Excludes the original `config` path (already merged into `root` / `tables` / `ignore`), per-table `extract`, and per-table `name`. Available immediately after construction in Python and TypeScript; Rust's sync `build()` returns a ready instance.
|
|
163
166
|
|
|
164
167
|
---
|
|
165
168
|
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
#[cfg(feature = "extension-module")]
|
|
14
14
|
mod python {
|
|
15
|
-
use ::dirsql::{DirSQL, Row, RowEvent, Table, Value, db::parse_table_name};
|
|
15
|
+
use ::dirsql::{DirSQL, Extension, Row, RowEvent, Table, Value, db::parse_table_name};
|
|
16
16
|
use pyo3::exceptions::PyRuntimeError;
|
|
17
17
|
use pyo3::prelude::*;
|
|
18
18
|
use pyo3::types::{PyDict, PyList};
|
|
@@ -63,6 +63,20 @@ mod python {
|
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
/// Marshals a Python `{"path": str, "entrypoint"?: str}` mapping from the
|
|
67
|
+
/// `extensions=` constructor argument into a [`dirsql::Extension`]. Mirrors
|
|
68
|
+
/// the `[[dirsql.extension]]` config-file fields and the Rust builder's
|
|
69
|
+
/// `Extension { path, entrypoint }`. Paths are taken verbatim (the
|
|
70
|
+
/// programmatic surface does not resolve relative paths), matching
|
|
71
|
+
/// `DirSQLBuilder::extensions`.
|
|
72
|
+
#[derive(FromPyObject)]
|
|
73
|
+
struct PyExtensionSpec {
|
|
74
|
+
#[pyo3(item)]
|
|
75
|
+
path: String,
|
|
76
|
+
#[pyo3(item, default)]
|
|
77
|
+
entrypoint: Option<String>,
|
|
78
|
+
}
|
|
79
|
+
|
|
66
80
|
/// A row event produced by the watch loop.
|
|
67
81
|
///
|
|
68
82
|
/// `table` is `Optional[str]` because error events may occur before a
|
|
@@ -101,7 +115,7 @@ mod python {
|
|
|
101
115
|
#[pymethods]
|
|
102
116
|
impl PyDirSQL {
|
|
103
117
|
#[new]
|
|
104
|
-
#[pyo3(signature = (root=None, *, tables=None, ignore=None, config=None, persist=false, persist_path=None))]
|
|
118
|
+
#[pyo3(signature = (root=None, *, tables=None, ignore=None, config=None, persist=false, persist_path=None, extensions=None))]
|
|
105
119
|
fn new(
|
|
106
120
|
py: Python<'_>,
|
|
107
121
|
root: Option<String>,
|
|
@@ -110,12 +124,22 @@ mod python {
|
|
|
110
124
|
config: Option<String>,
|
|
111
125
|
persist: bool,
|
|
112
126
|
persist_path: Option<PathBuf>,
|
|
127
|
+
extensions: Option<Vec<PyExtensionSpec>>,
|
|
113
128
|
) -> PyResult<Self> {
|
|
114
129
|
let rust_tables: Vec<Table> = tables
|
|
115
130
|
.as_deref()
|
|
116
131
|
.map(|ts| ts.iter().map(|t| build_table(py, t)).collect())
|
|
117
132
|
.unwrap_or_default();
|
|
118
133
|
|
|
134
|
+
let rust_extensions: Vec<Extension> = extensions
|
|
135
|
+
.unwrap_or_default()
|
|
136
|
+
.into_iter()
|
|
137
|
+
.map(|e| Extension {
|
|
138
|
+
path: PathBuf::from(e.path),
|
|
139
|
+
entrypoint: e.entrypoint,
|
|
140
|
+
})
|
|
141
|
+
.collect();
|
|
142
|
+
|
|
119
143
|
let inner = py
|
|
120
144
|
.detach(move || {
|
|
121
145
|
let mut builder = DirSQL::builder();
|
|
@@ -137,6 +161,9 @@ mod python {
|
|
|
137
161
|
if let Some(p) = persist_path {
|
|
138
162
|
builder = builder.persist_path(p);
|
|
139
163
|
}
|
|
164
|
+
if !rust_extensions.is_empty() {
|
|
165
|
+
builder = builder.extensions(rust_extensions);
|
|
166
|
+
}
|
|
140
167
|
builder.build()
|
|
141
168
|
})
|
|
142
169
|
.map_err(to_py_err)?;
|
|
@@ -16,7 +16,11 @@ license = "MIT"
|
|
|
16
16
|
repository.workspace = true
|
|
17
17
|
# `docs/` is a symlink to the workspace `docs/` so the markdown ships with the
|
|
18
18
|
# crate; everything below is the VitePress build tooling we don't want to ship.
|
|
19
|
+
# `tests/` is the integration suite -- excluded so test files never ship in the
|
|
20
|
+
# published `.crate` (enforced by the `packaging` gate in CI). Inline unit tests
|
|
21
|
+
# stay (they are `#[cfg(test)]` modules inside `src/`, not separate files).
|
|
19
22
|
exclude = [
|
|
23
|
+
"tests/",
|
|
20
24
|
"docs/.vitepress",
|
|
21
25
|
"docs/.claude",
|
|
22
26
|
"docs/tests",
|
|
@@ -37,6 +37,7 @@ DirSQL(
|
|
|
37
37
|
tables: list[Table] | None = None,
|
|
38
38
|
ignore: list[str] | None = None,
|
|
39
39
|
config: str | None = None,
|
|
40
|
+
extensions: list[dict] | None = None, # [{ "path": str, "entrypoint"?: str }]
|
|
40
41
|
)
|
|
41
42
|
```
|
|
42
43
|
|
|
@@ -46,6 +47,7 @@ DirSQL::builder()
|
|
|
46
47
|
.tables(tables) // optional; append with .table(t)
|
|
47
48
|
.ignore(patterns) // optional
|
|
48
49
|
.config(config_toml_path) // optional
|
|
50
|
+
.extensions(extensions) // optional; Extension { path, entrypoint }
|
|
49
51
|
.build() // -> Result<DirSQL>
|
|
50
52
|
```
|
|
51
53
|
|
|
@@ -73,7 +75,8 @@ In Python, the constructor starts scanning in a background thread and returns im
|
|
|
73
75
|
- `root` -- Path to the directory to index. Optional if `config` is supplied.
|
|
74
76
|
- `tables` -- List of `Table` definitions. Each defines a SQLite table, a glob pattern, and an extract function.
|
|
75
77
|
- `ignore` -- Optional list of glob patterns. Files matching any ignore pattern are skipped regardless of table globs.
|
|
76
|
-
- `config` -- Optional path to a `.dirsql.toml` config file. Its `[[table]]` entries are appended to any programmatic `tables`; its `[dirsql].ignore` patterns are appended to any explicit `ignore`; its optional `[dirsql].root` supplies the root directory when `root` is not passed explicitly.
|
|
78
|
+
- `config` -- Optional path to a `.dirsql.toml` config file. Its `[[table]]` entries are appended to any programmatic `tables`; its `[dirsql].ignore` patterns are appended to any explicit `ignore`; its optional `[dirsql].root` supplies the root directory when `root` is not passed explicitly; its `[[dirsql.extension]]` entries are appended to any programmatic `extensions`.
|
|
79
|
+
- `extensions` -- Optional SQLite extensions to load onto the connection at startup, before any table DDL (enable → load → disable, so the SQL `load_extension()` function is never left exposed). Each entry pairs a shared-library `path` with an optional `entrypoint` init-symbol override (Python: `{ "path", "entrypoint"? }` dicts; Rust: `Extension { path, entrypoint }`). Programmatic entries load first, then any `[[dirsql.extension]]` from `config`. Available in the Python and Rust SDKs; the TypeScript constructor parameter is tracked in [#230](https://github.com/thekevinscott/dirsql/issues/230). See [Loading extensions](../cli/config.md#loading-extensions).
|
|
77
80
|
|
|
78
81
|
### Methods
|
|
79
82
|
|
|
@@ -159,7 +162,7 @@ JSON.stringify(db) // via db.toJSON()
|
|
|
159
162
|
|
|
160
163
|
:::
|
|
161
164
|
|
|
162
|
-
Returns the resolved construction state as a JSON-compatible value with fields `root`, `tables`, `ignore`, `persist`, `persist_path` (camelCase `persistPath` in TypeScript). Each table is `{ ddl, glob, strict }`. Excludes the original `config` path (already merged into `root` / `tables` / `ignore`), per-table `extract`, and per-table `name`. Available immediately after construction in Python and TypeScript; Rust's sync `build()` returns a ready instance.
|
|
165
|
+
Returns the resolved construction state as a JSON-compatible value with fields `root`, `tables`, `ignore`, `persist`, `persist_path` (camelCase `persistPath` in TypeScript). Each table is `{ ddl, glob, strict }`. The Python and Rust snapshots also include `extensions` -- an array of `{ path, entrypoint }` (empty when none are configured); the TypeScript `toJSON` snapshot will gain it with [#230](https://github.com/thekevinscott/dirsql/issues/230). Excludes the original `config` path (already merged into `root` / `tables` / `ignore`), per-table `extract`, and per-table `name`. Available immediately after construction in Python and TypeScript; Rust's sync `build()` returns a ready instance.
|
|
163
166
|
|
|
164
167
|
---
|
|
165
168
|
|
|
@@ -21,7 +21,7 @@ use std::sync::{Arc, Mutex};
|
|
|
21
21
|
use serde::Deserialize;
|
|
22
22
|
|
|
23
23
|
use crate::db::parse_table_name;
|
|
24
|
-
use crate::{DirSQL, Row, Table, Value};
|
|
24
|
+
use crate::{DirSQL, Extension, Row, Table, Value};
|
|
25
25
|
|
|
26
26
|
/// NDJSON helper subprocess + the shared IO it dispatches over.
|
|
27
27
|
pub struct InterpretHelper {
|
|
@@ -57,6 +57,12 @@ struct HandshakeState {
|
|
|
57
57
|
persist: bool,
|
|
58
58
|
#[serde(default, alias = "persistPath")]
|
|
59
59
|
persist_path: Option<String>,
|
|
60
|
+
/// SQLite extensions the config declared (`{path, entrypoint?}` each).
|
|
61
|
+
/// Defaults to empty so a handshake from an SDK that doesn't yet emit
|
|
62
|
+
/// the key still parses. Paths are taken verbatim — the SDK already
|
|
63
|
+
/// resolved config-relative paths before serializing the snapshot.
|
|
64
|
+
#[serde(default)]
|
|
65
|
+
extensions: Vec<HandshakeExtension>,
|
|
60
66
|
}
|
|
61
67
|
|
|
62
68
|
#[derive(Debug, Deserialize)]
|
|
@@ -67,6 +73,13 @@ struct HandshakeTable {
|
|
|
67
73
|
strict: bool,
|
|
68
74
|
}
|
|
69
75
|
|
|
76
|
+
#[derive(Debug, Deserialize)]
|
|
77
|
+
struct HandshakeExtension {
|
|
78
|
+
path: String,
|
|
79
|
+
#[serde(default)]
|
|
80
|
+
entrypoint: Option<String>,
|
|
81
|
+
}
|
|
82
|
+
|
|
70
83
|
/// Response per extract request. The discriminator `type` field and the
|
|
71
84
|
/// echoed `id` field are ignored — V1 of the protocol is strictly
|
|
72
85
|
/// sequential, so the helper's reply is unambiguous.
|
|
@@ -149,6 +162,14 @@ fn parse_handshake(stdout: &mut dyn BufRead) -> Result<NativeConfig, String> {
|
|
|
149
162
|
ignore: state.ignore,
|
|
150
163
|
persist: state.persist,
|
|
151
164
|
persist_path: state.persist_path.map(PathBuf::from),
|
|
165
|
+
extensions: state
|
|
166
|
+
.extensions
|
|
167
|
+
.into_iter()
|
|
168
|
+
.map(|e| Extension {
|
|
169
|
+
path: PathBuf::from(e.path),
|
|
170
|
+
entrypoint: e.entrypoint,
|
|
171
|
+
})
|
|
172
|
+
.collect(),
|
|
152
173
|
})
|
|
153
174
|
}
|
|
154
175
|
|
|
@@ -203,6 +224,10 @@ pub struct NativeConfig {
|
|
|
203
224
|
pub ignore: Vec<String>,
|
|
204
225
|
pub persist: bool,
|
|
205
226
|
pub persist_path: Option<PathBuf>,
|
|
227
|
+
/// SQLite extensions to load onto the connection at startup. Resolved
|
|
228
|
+
/// verbatim from the handshake (the SDK already merged config-file and
|
|
229
|
+
/// programmatic entries and resolved relative paths). See [`Extension`].
|
|
230
|
+
pub extensions: Vec<Extension>,
|
|
206
231
|
}
|
|
207
232
|
|
|
208
233
|
/// Build a [`DirSQL`] from a spawned interpret helper. Each table's
|
|
@@ -229,7 +254,8 @@ pub fn build_dirsql(helper: Arc<InterpretHelper>, config: NativeConfig) -> Resul
|
|
|
229
254
|
let mut builder = DirSQL::builder()
|
|
230
255
|
.root(config.root)
|
|
231
256
|
.tables(tables)
|
|
232
|
-
.ignore(config.ignore)
|
|
257
|
+
.ignore(config.ignore)
|
|
258
|
+
.extensions(config.extensions);
|
|
233
259
|
if config.persist {
|
|
234
260
|
builder = builder.persist(true);
|
|
235
261
|
}
|
|
@@ -317,6 +343,39 @@ mod tests {
|
|
|
317
343
|
assert_eq!(cfg.ignore, vec!["a".to_string(), "b".to_string()]);
|
|
318
344
|
}
|
|
319
345
|
|
|
346
|
+
#[test]
|
|
347
|
+
fn parse_handshake_carries_extensions_with_path_and_entrypoint() {
|
|
348
|
+
// A `.py` / `.js` config that declares `extensions=[...]` serializes
|
|
349
|
+
// them into the handshake `state`; the parser must carry both `path`
|
|
350
|
+
// and the optional `entrypoint` through to the resolved config (#229).
|
|
351
|
+
let line = br#"{"type":"config","state":{"root":"/r","tables":[],"extensions":[{"path":"/ext/vec0.so","entrypoint":"sqlite3_vec_init"}]}}
|
|
352
|
+
"#;
|
|
353
|
+
let cfg = parse_handshake(&mut Cursor::new(line.as_slice())).unwrap();
|
|
354
|
+
assert_eq!(cfg.extensions.len(), 1);
|
|
355
|
+
assert_eq!(cfg.extensions[0].path, PathBuf::from("/ext/vec0.so"));
|
|
356
|
+
assert_eq!(
|
|
357
|
+
cfg.extensions[0].entrypoint.as_deref(),
|
|
358
|
+
Some("sqlite3_vec_init"),
|
|
359
|
+
);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
#[test]
|
|
363
|
+
fn parse_handshake_extension_entrypoint_is_optional() {
|
|
364
|
+
let line = br#"{"type":"config","state":{"root":"/r","tables":[],"extensions":[{"path":"/ext/a.so"}]}}
|
|
365
|
+
"#;
|
|
366
|
+
let cfg = parse_handshake(&mut Cursor::new(line.as_slice())).unwrap();
|
|
367
|
+
assert_eq!(cfg.extensions.len(), 1);
|
|
368
|
+
assert!(cfg.extensions[0].entrypoint.is_none());
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
#[test]
|
|
372
|
+
fn parse_handshake_defaults_extensions_to_empty_when_absent() {
|
|
373
|
+
let line = br#"{"type":"config","state":{"root":"/r","tables":[]}}
|
|
374
|
+
"#;
|
|
375
|
+
let cfg = parse_handshake(&mut Cursor::new(line.as_slice())).unwrap();
|
|
376
|
+
assert!(cfg.extensions.is_empty());
|
|
377
|
+
}
|
|
378
|
+
|
|
320
379
|
#[test]
|
|
321
380
|
fn parse_handshake_errors_on_empty_stream() {
|
|
322
381
|
let mut reader = Cursor::new(b"".as_slice());
|
|
@@ -505,6 +564,35 @@ mod tests {
|
|
|
505
564
|
assert!(cache_path.exists(), "persist build should create the cache");
|
|
506
565
|
}
|
|
507
566
|
|
|
567
|
+
#[test]
|
|
568
|
+
fn build_dirsql_threads_extensions_into_the_builder() {
|
|
569
|
+
// A handshake whose `extensions` names a missing shared library must
|
|
570
|
+
// fail the build, proving the parsed extensions reach the core's
|
|
571
|
+
// load-at-startup path (enable -> load -> disable). (#229)
|
|
572
|
+
let tmp = tempfile::TempDir::new().unwrap();
|
|
573
|
+
std::fs::write(tmp.path().join("a.json"), b"{}").unwrap();
|
|
574
|
+
|
|
575
|
+
let handshake = format!(
|
|
576
|
+
r#"{{"type":"config","state":{{"root":"{}","tables":[{{"ddl":"CREATE TABLE papers (title TEXT)","glob":"*.json"}}],"extensions":[{{"path":"/nonexistent/dirsql-no-such-ext.so"}}]}}}}"#,
|
|
577
|
+
tmp.path().display(),
|
|
578
|
+
);
|
|
579
|
+
let (helper, config) = spawn_fake_helper(
|
|
580
|
+
&handshake,
|
|
581
|
+
r#"{"type":"result","id":1,"ok":true,"rows":[{"title":"y"}]}"#,
|
|
582
|
+
);
|
|
583
|
+
assert_eq!(config.extensions.len(), 1);
|
|
584
|
+
assert_eq!(
|
|
585
|
+
config.extensions[0].path,
|
|
586
|
+
PathBuf::from("/nonexistent/dirsql-no-such-ext.so"),
|
|
587
|
+
);
|
|
588
|
+
|
|
589
|
+
let err = match build_dirsql(helper, config) {
|
|
590
|
+
Ok(_) => panic!("expected build to fail on a missing extension"),
|
|
591
|
+
Err(e) => e,
|
|
592
|
+
};
|
|
593
|
+
assert!(err.contains("failed to load extension"), "got: {err}");
|
|
594
|
+
}
|
|
595
|
+
|
|
508
596
|
#[test]
|
|
509
597
|
fn build_dirsql_round_trip_with_fake_helper_invokes_extract_per_matched_file() {
|
|
510
598
|
// Create a tempdir with two matching files so the scan invokes
|
|
@@ -683,6 +771,7 @@ mod tests {
|
|
|
683
771
|
ignore: Vec::new(),
|
|
684
772
|
persist: false,
|
|
685
773
|
persist_path: None,
|
|
774
|
+
extensions: Vec::new(),
|
|
686
775
|
};
|
|
687
776
|
// `DirSQL` doesn't impl Debug, so we can't use `unwrap_err` directly.
|
|
688
777
|
let err = match build_dirsql(helper, config) {
|