dirsql 0.3.34__tar.gz → 0.3.36__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.34 → dirsql-0.3.36}/Cargo.lock +1 -1
- {dirsql-0.3.34 → dirsql-0.3.36}/PKG-INFO +1 -1
- {dirsql-0.3.34 → dirsql-0.3.36}/dirsql/_async.py +23 -3
- {dirsql-0.3.34 → dirsql-0.3.36}/dirsql/_dirsql.pyi +4 -1
- {dirsql-0.3.34 → dirsql-0.3.36}/dirsql/resolve_config.py +5 -1
- {dirsql-0.3.34 → dirsql-0.3.36}/packages/python/Cargo.toml +1 -1
- {dirsql-0.3.34 → dirsql-0.3.36}/packages/rust/src/cli/mod.rs +9 -32
- {dirsql-0.3.34 → dirsql-0.3.36}/packages/rust/src/cli/native_config.rs +14 -215
- {dirsql-0.3.34 → dirsql-0.3.36}/packages/rust/src/cli/router.rs +9 -8
- {dirsql-0.3.34 → dirsql-0.3.36}/pyproject.toml +4 -3
- {dirsql-0.3.34 → dirsql-0.3.36}/Cargo.toml +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/README.md +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/dirsql/__init__.py +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/dirsql/cli/__init__.py +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/dirsql/cli/binary_path.py +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/dirsql/cli/interpret/__init__.py +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/dirsql/cli/interpret/dispatch_extract.py +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/dirsql/cli/interpret/load_app.py +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/dirsql/cli/interpret/run.py +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/dirsql/cli/interpret/write_message.py +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/dirsql/cli/is_windows.py +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/dirsql/cli/main.py +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/dirsql/py.typed +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/docs/.claude/CLAUDE.md +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/docs/.vitepress/config.ts +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/docs/.vitepress/theme/index.ts +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/docs/.vitepress/theme/lang.ts +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/docs/AGENTS.md +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/docs/api/index.md +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/docs/cli/config.md +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/docs/cli/http-api.md +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/docs/cli/index.md +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/docs/cli/init.md +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/docs/cli/server.md +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/docs/getting-started.md +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/docs/guide/async.md +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/docs/guide/crdt.md +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/docs/guide/persistence.md +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/docs/guide/querying.md +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/docs/guide/tables.md +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/docs/guide/watching.md +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/docs/index.md +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/docs/migrations.md +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/docs/package.json +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/docs/playwright.config.ts +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/docs/pnpm-lock.yaml +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/docs/pnpm-workspace.yaml +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/docs/tests/integration/home.spec.ts +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/docs/tests/integration/language-flag.spec.ts +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/docs/tests/unit/config.test.ts +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/docs/tests/unit/lang.test.ts +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/docs/vitest.config.ts +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/packages/python/README.md +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/packages/python/conftest.py +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/packages/python/docs/.claude/CLAUDE.md +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/packages/python/docs/.vitepress/config.ts +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/packages/python/docs/.vitepress/theme/index.ts +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/packages/python/docs/.vitepress/theme/lang.ts +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/packages/python/docs/AGENTS.md +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/packages/python/docs/api/index.md +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/packages/python/docs/cli/config.md +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/packages/python/docs/cli/http-api.md +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/packages/python/docs/cli/index.md +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/packages/python/docs/cli/init.md +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/packages/python/docs/cli/server.md +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/packages/python/docs/getting-started.md +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/packages/python/docs/guide/async.md +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/packages/python/docs/guide/crdt.md +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/packages/python/docs/guide/persistence.md +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/packages/python/docs/guide/querying.md +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/packages/python/docs/guide/tables.md +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/packages/python/docs/guide/watching.md +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/packages/python/docs/index.md +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/packages/python/docs/migrations.md +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/packages/python/docs/package.json +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/packages/python/docs/playwright.config.ts +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/packages/python/docs/pnpm-lock.yaml +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/packages/python/docs/pnpm-workspace.yaml +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/packages/python/docs/tests/integration/home.spec.ts +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/packages/python/docs/tests/integration/language-flag.spec.ts +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/packages/python/docs/tests/unit/config.test.ts +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/packages/python/docs/tests/unit/lang.test.ts +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/packages/python/docs/vitest.config.ts +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/packages/python/src/lib.rs +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/packages/python/tests/__init__.py +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/packages/python/tests/conftest.py +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/packages/python/tests/e2e/__init__.py +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/packages/python/tests/integration/__fixtures__/data/a/meta.json +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/packages/python/tests/integration/__fixtures__/data/b/meta.json +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/packages/python/tests/integration/__fixtures__/dirsql.config.py +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/packages/python/tests/integration/__fixtures__/interpret/data/a/meta.json +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/packages/python/tests/integration/__fixtures__/interpret/data/b/meta.json +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/packages/python/tests/integration/__fixtures__/interpret/dirsql.config.py +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/packages/python/tests/integration/__fixtures__/interpret/dirsql.config_no_app.py +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/packages/python/tests/integration/__fixtures__/interpret/dirsql.config_raises.py +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/packages/python/tests/integration/__init__.py +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/packages/python/tests/integration/interpret_subprocess.py +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/packages/rust/Cargo.toml +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/packages/rust/README.md +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/packages/rust/benches/db_bench.rs +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/packages/rust/benches/differ_bench.rs +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/packages/rust/benches/matcher_bench.rs +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/packages/rust/benches/scanner_bench.rs +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/packages/rust/docs/api/index.md +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/packages/rust/docs/cli/config.md +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/packages/rust/docs/cli/http-api.md +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/packages/rust/docs/cli/index.md +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/packages/rust/docs/cli/init.md +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/packages/rust/docs/cli/server.md +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/packages/rust/docs/getting-started.md +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/packages/rust/docs/guide/async.md +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/packages/rust/docs/guide/crdt.md +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/packages/rust/docs/guide/persistence.md +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/packages/rust/docs/guide/querying.md +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/packages/rust/docs/guide/tables.md +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/packages/rust/docs/guide/watching.md +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/packages/rust/docs/index.md +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/packages/rust/docs/migrations.md +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/packages/rust/src/bin/dirsql.rs +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/packages/rust/src/cli/init.rs +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/packages/rust/src/cli/serialize.rs +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/packages/rust/src/cli/server.rs +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/packages/rust/src/config.rs +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/packages/rust/src/db.rs +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/packages/rust/src/differ.rs +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/packages/rust/src/lib.rs +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/packages/rust/src/matcher.rs +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/packages/rust/src/persist.rs +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/packages/rust/src/scanner.rs +0 -0
- {dirsql-0.3.34 → dirsql-0.3.36}/packages/rust/src/watcher.rs +0 -0
|
@@ -1,10 +1,21 @@
|
|
|
1
1
|
"""Async-by-default DirSQL wrapper."""
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
4
5
|
|
|
5
6
|
from dirsql._dirsql import DirSQL as _RustDirSQL
|
|
6
7
|
from dirsql.resolve_config import resolve_config
|
|
7
8
|
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
# `typing.override` is 3.12+; the package supports 3.11, so source it from
|
|
11
|
+
# `typing_extensions` for the type checker only. At runtime `@override` is
|
|
12
|
+
# a pure marker, so a no-op identity avoids the runtime dependency.
|
|
13
|
+
from typing_extensions import override
|
|
14
|
+
else:
|
|
15
|
+
|
|
16
|
+
def override(func):
|
|
17
|
+
return func
|
|
18
|
+
|
|
8
19
|
|
|
9
20
|
class _WatchStream:
|
|
10
21
|
"""Async iterator that polls for file events."""
|
|
@@ -111,15 +122,24 @@ class DirSQL:
|
|
|
111
122
|
raise self._init_error
|
|
112
123
|
|
|
113
124
|
async def query(self, sql):
|
|
114
|
-
"""Execute a SQL query asynchronously.
|
|
115
|
-
|
|
125
|
+
"""Execute a SQL query asynchronously.
|
|
126
|
+
|
|
127
|
+
Awaits :meth:`ready` first, so calling ``query`` before an explicit
|
|
128
|
+
``await db.ready()`` waits for the background scan (and re-raises any
|
|
129
|
+
initialization error) instead of failing on a still-``None`` ``_db``.
|
|
130
|
+
"""
|
|
131
|
+
await self.ready()
|
|
132
|
+
db = self._db
|
|
133
|
+
assert db is not None # ready() returned, so _init_bg set _db
|
|
134
|
+
return await asyncio.to_thread(db.query, sql)
|
|
116
135
|
|
|
117
136
|
def watch(self):
|
|
118
137
|
"""Start watching for file changes. Returns an async iterable of RowEvent."""
|
|
119
138
|
return _WatchStream(self._db)
|
|
120
139
|
|
|
121
140
|
@property
|
|
122
|
-
|
|
141
|
+
@override
|
|
142
|
+
def __dict__(self):
|
|
123
143
|
"""Resolved construction state as a JSON-serializable dict.
|
|
124
144
|
|
|
125
145
|
Recomputed on each access; reads the ``.dirsql.toml`` if ``config=``
|
|
@@ -13,6 +13,8 @@ from collections.abc import Callable
|
|
|
13
13
|
from os import PathLike
|
|
14
14
|
from typing import Any, NotRequired, TypedDict
|
|
15
15
|
|
|
16
|
+
from typing_extensions import override
|
|
17
|
+
|
|
16
18
|
__version__: str
|
|
17
19
|
|
|
18
20
|
Row = dict[str, Any]
|
|
@@ -51,7 +53,8 @@ class RowEvent:
|
|
|
51
53
|
error: str | None
|
|
52
54
|
file_path: str | None
|
|
53
55
|
|
|
54
|
-
|
|
56
|
+
@override
|
|
57
|
+
def __repr__(self) -> str: ...
|
|
55
58
|
|
|
56
59
|
class DirSQL:
|
|
57
60
|
"""Synchronous binding class. ``dirsql._async.DirSQL`` wraps it."""
|
|
@@ -23,7 +23,11 @@ def resolve_config(
|
|
|
23
23
|
cfg_dir = os.path.dirname(os.path.abspath(config))
|
|
24
24
|
|
|
25
25
|
def _abs(p):
|
|
26
|
-
|
|
26
|
+
# Only reached from the `"<key>" in cfg` branches below, and cfg is
|
|
27
|
+
# non-empty only after a config file was loaded -- which sets cfg_dir.
|
|
28
|
+
base = cfg_dir
|
|
29
|
+
assert base is not None
|
|
30
|
+
return p if os.path.isabs(p) else os.path.join(base, p)
|
|
27
31
|
|
|
28
32
|
return {
|
|
29
33
|
"root": root or (_abs(cfg["root"]) if "root" in cfg else cfg_dir),
|
|
@@ -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.36"
|
|
8
8
|
edition.workspace = true
|
|
9
9
|
publish = false
|
|
10
10
|
readme = "README.md"
|
|
@@ -160,7 +160,15 @@ pub enum ServerError {
|
|
|
160
160
|
#[cfg(test)]
|
|
161
161
|
mod tests {
|
|
162
162
|
use super::*;
|
|
163
|
-
|
|
163
|
+
|
|
164
|
+
// `From<DirSQL> for AppState` produces the `Ready` arm -- this is
|
|
165
|
+
// verified at the integration tier by `from_dirsql_yields_ready_state`
|
|
166
|
+
// in `tests/cli_integration.rs`, which builds a real `DirSQL` over a
|
|
167
|
+
// temp directory (so the initial scan runs) and asserts the public
|
|
168
|
+
// `AppState::Ready` variant. It lived here once but needed
|
|
169
|
+
// `std::fs::write` to populate the scanned directory, which the
|
|
170
|
+
// `testing-conventions` `unit lint` isolation rule forbids in a unit
|
|
171
|
+
// test (effectful std). The pure config-default test below stays inline.
|
|
164
172
|
|
|
165
173
|
#[test]
|
|
166
174
|
fn default_config_binds_localhost_7117_with_30s_timeout() {
|
|
@@ -169,35 +177,4 @@ mod tests {
|
|
|
169
177
|
assert_eq!(cfg.port, 7117);
|
|
170
178
|
assert_eq!(cfg.query_timeout, Duration::from_secs(30));
|
|
171
179
|
}
|
|
172
|
-
|
|
173
|
-
#[test]
|
|
174
|
-
fn app_state_from_dirsql_is_ready() {
|
|
175
|
-
// `AppState: !Debug` (it wraps a `DirSQL`). Assert the `Ready` variant
|
|
176
|
-
// without a dead match arm by routing through `require_ready`, which
|
|
177
|
-
// returns `Ok` only for `Ready` -- a branch-free `.is_ok()` check.
|
|
178
|
-
let dir = TempDir::new().unwrap();
|
|
179
|
-
// Write a matching file so the extract closure runs during the initial
|
|
180
|
-
// scan (otherwise the closure body would be a dead coverage region).
|
|
181
|
-
std::fs::write(dir.path().join("a.txt"), b"").unwrap();
|
|
182
|
-
let db = DirSQL::with_ignore(
|
|
183
|
-
dir.path(),
|
|
184
|
-
vec![crate::Table::new(
|
|
185
|
-
"CREATE TABLE t (name TEXT)",
|
|
186
|
-
"*.txt",
|
|
187
|
-
|_| {
|
|
188
|
-
vec![crate::Row::from_iter([(
|
|
189
|
-
"name".to_string(),
|
|
190
|
-
crate::Value::Text("x".into()),
|
|
191
|
-
)])]
|
|
192
|
-
},
|
|
193
|
-
)],
|
|
194
|
-
Vec::<String>::new(),
|
|
195
|
-
)
|
|
196
|
-
.unwrap();
|
|
197
|
-
let state: AppState = db.into();
|
|
198
|
-
assert!(
|
|
199
|
-
router::require_ready(&state).is_ok(),
|
|
200
|
-
"From<DirSQL> must produce AppState::Ready",
|
|
201
|
-
);
|
|
202
|
-
}
|
|
203
180
|
}
|
|
@@ -296,7 +296,16 @@ fn json_to_value(v: serde_json::Value) -> Value {
|
|
|
296
296
|
mod tests {
|
|
297
297
|
use super::*;
|
|
298
298
|
use std::io::Cursor;
|
|
299
|
-
|
|
299
|
+
|
|
300
|
+
// The subprocess-driven tests (`InterpretHelper::from_child` against a
|
|
301
|
+
// real `bash`/`true` child, and the `build_dirsql` round-trips that
|
|
302
|
+
// spawn a fake helper and write real fixture files) live in
|
|
303
|
+
// `tests/native_config.rs`. They exercise effectful std
|
|
304
|
+
// (`std::process::Command`, `std::fs::write`) and so belong at the
|
|
305
|
+
// integration tier per the `testing-conventions` `unit lint` isolation
|
|
306
|
+
// rule. The pure wire-format tests below operate on in-memory
|
|
307
|
+
// `Cursor`/`Vec` streams and stay inline next to the private functions
|
|
308
|
+
// they cover.
|
|
300
309
|
|
|
301
310
|
// -- parse_handshake -----------------------------------------------
|
|
302
311
|
|
|
@@ -511,160 +520,10 @@ mod tests {
|
|
|
511
520
|
}
|
|
512
521
|
|
|
513
522
|
// -- build_dirsql --------------------------------------------------
|
|
514
|
-
|
|
515
|
-
//
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
/// emits one canned response per line received on stdin (an infinite
|
|
519
|
-
/// loop until stdin closes). Returns the helper paired with the
|
|
520
|
-
/// parsed [`NativeConfig`]. Drives the production
|
|
521
|
-
/// `InterpretHelper::from_child` constructor so the post-spawn
|
|
522
|
-
/// plumbing is exercised by these tests.
|
|
523
|
-
fn spawn_fake_helper(
|
|
524
|
-
handshake: &str,
|
|
525
|
-
response_per_request: &str,
|
|
526
|
-
) -> (Arc<InterpretHelper>, NativeConfig) {
|
|
527
|
-
let child = Command::new("bash")
|
|
528
|
-
.arg("-c")
|
|
529
|
-
.arg(format!(
|
|
530
|
-
"printf '%s\\n' '{handshake}'; while IFS= read -r line; do printf '%s\\n' '{response_per_request}'; done",
|
|
531
|
-
))
|
|
532
|
-
.stdin(Stdio::piped())
|
|
533
|
-
.stdout(Stdio::piped())
|
|
534
|
-
.spawn()
|
|
535
|
-
.unwrap();
|
|
536
|
-
InterpretHelper::from_child(child).unwrap()
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
#[test]
|
|
540
|
-
fn build_dirsql_threads_persist_and_persist_path_into_the_builder() {
|
|
541
|
-
// Cover the `if config.persist` and `if let Some(p) = config.persist_path`
|
|
542
|
-
// branches in `build_dirsql`. A real persist build needs the cache file
|
|
543
|
-
// to live under root, so point persist_path at a path inside the
|
|
544
|
-
// tempdir and pass persist=true.
|
|
545
|
-
let tmp = tempfile::TempDir::new().unwrap();
|
|
546
|
-
std::fs::write(tmp.path().join("a.json"), b"{}").unwrap();
|
|
547
|
-
let cache_path = tmp.path().join(".dirsql/cache.db");
|
|
548
|
-
|
|
549
|
-
let handshake = format!(
|
|
550
|
-
r#"{{"type":"config","state":{{"root":"{}","tables":[{{"ddl":"CREATE TABLE papers (title TEXT)","glob":"*.json"}}],"persist":true,"persist_path":"{}"}}}}"#,
|
|
551
|
-
tmp.path().display(),
|
|
552
|
-
cache_path.display(),
|
|
553
|
-
);
|
|
554
|
-
let (helper, config) = spawn_fake_helper(
|
|
555
|
-
&handshake,
|
|
556
|
-
r#"{"type":"result","id":1,"ok":true,"rows":[{"title":"y"}]}"#,
|
|
557
|
-
);
|
|
558
|
-
assert!(config.persist);
|
|
559
|
-
assert_eq!(config.persist_path.as_ref().unwrap(), &cache_path);
|
|
560
|
-
|
|
561
|
-
let db = build_dirsql(helper, config).unwrap();
|
|
562
|
-
let rows = db.query("SELECT COUNT(*) AS n FROM papers").unwrap();
|
|
563
|
-
assert_eq!(rows[0].get("n"), Some(&Value::Integer(1)));
|
|
564
|
-
assert!(cache_path.exists(), "persist build should create the cache");
|
|
565
|
-
}
|
|
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
|
-
|
|
596
|
-
#[test]
|
|
597
|
-
fn build_dirsql_round_trip_with_fake_helper_invokes_extract_per_matched_file() {
|
|
598
|
-
// Create a tempdir with two matching files so the scan invokes
|
|
599
|
-
// `extract` twice — exercising the closure created inside
|
|
600
|
-
// `build_dirsql`, the `dispatch_extract` path through the
|
|
601
|
-
// helper's IO mutex, and end-to-end DDL/glob wiring.
|
|
602
|
-
let tmp = tempfile::TempDir::new().unwrap();
|
|
603
|
-
std::fs::write(tmp.path().join("a.json"), b"{}").unwrap();
|
|
604
|
-
std::fs::write(tmp.path().join("b.json"), b"{}").unwrap();
|
|
605
|
-
|
|
606
|
-
let handshake = format!(
|
|
607
|
-
r#"{{"type":"config","state":{{"root":"{}","tables":[{{"ddl":"CREATE TABLE papers (title TEXT)","glob":"*.json"}}]}}}}"#,
|
|
608
|
-
tmp.path().display(),
|
|
609
|
-
);
|
|
610
|
-
let (helper, config) = spawn_fake_helper(
|
|
611
|
-
&handshake,
|
|
612
|
-
r#"{"type":"result","id":1,"ok":true,"rows":[{"title":"x"}]}"#,
|
|
613
|
-
);
|
|
614
|
-
|
|
615
|
-
let db = build_dirsql(helper, config).unwrap();
|
|
616
|
-
let rows = db.query("SELECT COUNT(*) AS n FROM papers").unwrap();
|
|
617
|
-
assert_eq!(rows.len(), 1);
|
|
618
|
-
assert_eq!(rows[0].get("n"), Some(&Value::Integer(2)));
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
#[test]
|
|
622
|
-
fn helper_extract_round_trip_succeeds_against_a_fake_subprocess() {
|
|
623
|
-
let (helper, _config) = spawn_fake_helper(
|
|
624
|
-
r#"{"type":"config","state":{"root":"/tmp","tables":[{"ddl":"CREATE TABLE papers (title TEXT)","glob":"*"}]}}"#,
|
|
625
|
-
r#"{"type":"result","id":1,"ok":true,"rows":[{"title":"Alpha"}]}"#,
|
|
626
|
-
);
|
|
627
|
-
|
|
628
|
-
let rows = helper.extract("papers", "/x/a.json").unwrap();
|
|
629
|
-
assert_eq!(rows.len(), 1);
|
|
630
|
-
assert_eq!(rows[0].get("title"), Some(&Value::Text("Alpha".into())));
|
|
631
|
-
|
|
632
|
-
// Second request increments the id counter and still works against
|
|
633
|
-
// the canned response (the fake echoes the same line per request).
|
|
634
|
-
let rows2 = helper.extract("papers", "/x/b.json").unwrap();
|
|
635
|
-
assert_eq!(rows2.len(), 1);
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
#[test]
|
|
639
|
-
fn helper_from_child_errors_when_stdin_was_not_piped() {
|
|
640
|
-
// `Command::spawn` without `.stdin(Stdio::piped())` inherits the
|
|
641
|
-
// parent's stdin — `child.stdin.take()` returns `None`, driving
|
|
642
|
-
// the first `.ok_or_else` arm in `from_child`.
|
|
643
|
-
let child = Command::new("true").spawn().unwrap();
|
|
644
|
-
let err = match InterpretHelper::from_child(child) {
|
|
645
|
-
Ok(_) => panic!("expected error when stdin is not piped"),
|
|
646
|
-
Err(e) => e,
|
|
647
|
-
};
|
|
648
|
-
assert!(
|
|
649
|
-
err.contains("failed to capture interpret stdin"),
|
|
650
|
-
"got: {err}",
|
|
651
|
-
);
|
|
652
|
-
}
|
|
653
|
-
|
|
654
|
-
#[test]
|
|
655
|
-
fn helper_from_child_errors_when_stdout_was_not_piped() {
|
|
656
|
-
// Pipe stdin but leave stdout inherited — `child.stdout.take()`
|
|
657
|
-
// returns `None`, driving the second `.ok_or_else` arm.
|
|
658
|
-
let child = Command::new("true").stdin(Stdio::piped()).spawn().unwrap();
|
|
659
|
-
let err = match InterpretHelper::from_child(child) {
|
|
660
|
-
Ok(_) => panic!("expected error when stdout is not piped"),
|
|
661
|
-
Err(e) => e,
|
|
662
|
-
};
|
|
663
|
-
assert!(
|
|
664
|
-
err.contains("failed to capture interpret stdout"),
|
|
665
|
-
"got: {err}",
|
|
666
|
-
);
|
|
667
|
-
}
|
|
523
|
+
//
|
|
524
|
+
// The `build_dirsql` round-trips and the `InterpretHelper::from_child`
|
|
525
|
+
// success/error paths spawn a real subprocess (`bash`/`true`) and write
|
|
526
|
+
// real fixture files, so they live in `tests/native_config.rs`.
|
|
668
527
|
|
|
669
528
|
/// A writer that fails every write with the given `io::ErrorKind`,
|
|
670
529
|
/// used to exercise `dispatch_extract`'s IO-error arms.
|
|
@@ -720,64 +579,4 @@ mod tests {
|
|
|
720
579
|
let err = dispatch_extract(&mut stdout, &mut stdin, 1, "t", "p").unwrap_err();
|
|
721
580
|
assert!(err.contains("read from interpret"), "got: {err}");
|
|
722
581
|
}
|
|
723
|
-
|
|
724
|
-
#[test]
|
|
725
|
-
fn helper_from_child_errors_when_subprocess_emits_no_handshake() {
|
|
726
|
-
// A subprocess that exits without writing anything drives
|
|
727
|
-
// `from_child` through to `parse_handshake`'s empty-line arm.
|
|
728
|
-
let child = Command::new("true")
|
|
729
|
-
.stdin(Stdio::piped())
|
|
730
|
-
.stdout(Stdio::piped())
|
|
731
|
-
.spawn()
|
|
732
|
-
.unwrap();
|
|
733
|
-
let err = match InterpretHelper::from_child(child) {
|
|
734
|
-
Ok(_) => panic!("expected error from empty-stdout subprocess"),
|
|
735
|
-
Err(e) => e,
|
|
736
|
-
};
|
|
737
|
-
assert!(
|
|
738
|
-
err.contains("exited before sending handshake"),
|
|
739
|
-
"got: {err}"
|
|
740
|
-
);
|
|
741
|
-
}
|
|
742
|
-
|
|
743
|
-
#[test]
|
|
744
|
-
fn build_dirsql_errors_when_ddl_has_no_table_name() {
|
|
745
|
-
// The DDL is rejected by `parse_table_name` before any extract
|
|
746
|
-
// closure is created, so the helper's IO is never touched. Use a
|
|
747
|
-
// real child (`true` exits cleanly) just to satisfy the
|
|
748
|
-
// `InterpretHelper` shape.
|
|
749
|
-
let mut child = Command::new("true")
|
|
750
|
-
.stdin(Stdio::piped())
|
|
751
|
-
.stdout(Stdio::piped())
|
|
752
|
-
.spawn()
|
|
753
|
-
.unwrap();
|
|
754
|
-
let stdin = child.stdin.take().unwrap();
|
|
755
|
-
let stdout = child.stdout.take().unwrap();
|
|
756
|
-
let helper = Arc::new(InterpretHelper {
|
|
757
|
-
_child: child,
|
|
758
|
-
io: Arc::new(Mutex::new(HelperIo {
|
|
759
|
-
stdin: BufWriter::new(stdin),
|
|
760
|
-
stdout: BufReader::new(stdout),
|
|
761
|
-
})),
|
|
762
|
-
next_id: AtomicU64::new(1),
|
|
763
|
-
});
|
|
764
|
-
let config = NativeConfig {
|
|
765
|
-
root: PathBuf::from("/tmp"),
|
|
766
|
-
tables: vec![HandshakeTable {
|
|
767
|
-
ddl: "NOT VALID DDL".into(),
|
|
768
|
-
glob: "*.json".into(),
|
|
769
|
-
strict: false,
|
|
770
|
-
}],
|
|
771
|
-
ignore: Vec::new(),
|
|
772
|
-
persist: false,
|
|
773
|
-
persist_path: None,
|
|
774
|
-
extensions: Vec::new(),
|
|
775
|
-
};
|
|
776
|
-
// `DirSQL` doesn't impl Debug, so we can't use `unwrap_err` directly.
|
|
777
|
-
let err = match build_dirsql(helper, config) {
|
|
778
|
-
Ok(_) => panic!("expected build_dirsql to fail on invalid DDL"),
|
|
779
|
-
Err(e) => e,
|
|
780
|
-
};
|
|
781
|
-
assert!(err.contains("could not parse table name"), "got: {err}");
|
|
782
|
-
}
|
|
783
582
|
}
|
|
@@ -167,15 +167,16 @@ pub(super) fn error_response(status: StatusCode, message: impl Into<String>) ->
|
|
|
167
167
|
#[cfg(test)]
|
|
168
168
|
mod tests {
|
|
169
169
|
use super::*;
|
|
170
|
-
use crate::db::DbError;
|
|
171
170
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
171
|
+
// The `DirSqlError::Core => 400` arm of `classify_query_error` is
|
|
172
|
+
// exercised end-to-end at the integration tier by
|
|
173
|
+
// `post_query_malformed_sql_returns_400_not_500` in
|
|
174
|
+
// `tests/cli_integration.rs`, which posts malformed SQL to `/query` and
|
|
175
|
+
// asserts the 400. Constructing a `Core` value inline would require
|
|
176
|
+
// importing the first-party `crate::db::DbError`, which the
|
|
177
|
+
// `testing-conventions` `unit lint` isolation rule forbids (a unit test
|
|
178
|
+
// may reach only `super::` and pure `std`). The non-Core arm below is
|
|
179
|
+
// pure -- it builds a `super::DirSqlError::Lock` -- so it stays inline.
|
|
179
180
|
|
|
180
181
|
#[test]
|
|
181
182
|
fn classify_non_core_error_is_internal_server_error() {
|
|
@@ -65,9 +65,10 @@ include = [
|
|
|
65
65
|
]
|
|
66
66
|
manifest-path = "packages/python/Cargo.toml"
|
|
67
67
|
|
|
68
|
-
# Type checking.
|
|
69
|
-
# `# ty: ignore[...]` baseline
|
|
70
|
-
# any new error in new or touched code fails CI
|
|
68
|
+
# Type checking. The source tree currently type-checks clean: the initial
|
|
69
|
+
# `# ty: ignore[...]` baseline (seeded by `ty check --add-ignore`) has been
|
|
70
|
+
# driven to zero, so any new error in new or touched code fails CI with no
|
|
71
|
+
# pre-existing suppressions to hide behind. Every rule is promoted to
|
|
71
72
|
# `"error"`, matching TypeScript-strict discipline. Test files are excluded
|
|
72
73
|
# because they monkeypatch fakes onto production module attributes
|
|
73
74
|
# (`monkeypatch.setattr(async_mod, "_RustDirSQL", ...)`) -- AGENTS.md flags
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dirsql-0.3.34 → dirsql-0.3.36}/packages/python/docs/tests/integration/language-flag.spec.ts
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dirsql-0.3.34 → dirsql-0.3.36}/packages/python/tests/integration/__fixtures__/data/a/meta.json
RENAMED
|
File without changes
|
{dirsql-0.3.34 → dirsql-0.3.36}/packages/python/tests/integration/__fixtures__/data/b/meta.json
RENAMED
|
File without changes
|
{dirsql-0.3.34 → dirsql-0.3.36}/packages/python/tests/integration/__fixtures__/dirsql.config.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|