dirsql 0.3.66__tar.gz → 0.3.69__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.66 → dirsql-0.3.69}/Cargo.lock +1 -1
- {dirsql-0.3.66 → dirsql-0.3.69}/PKG-INFO +1 -9
- {dirsql-0.3.66 → dirsql-0.3.69}/packages/python/Cargo.toml +1 -1
- {dirsql-0.3.66 → dirsql-0.3.69}/packages/python/e2e-attestation.json +2 -2
- {dirsql-0.3.66 → dirsql-0.3.69}/packages/python/src/lib.rs +134 -18
- {dirsql-0.3.66 → dirsql-0.3.69}/packages/rust/src/cli/init.rs +42 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/packages/rust/src/cli/mod.rs +22 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/packages/rust/src/cli/router.rs +34 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/pyproject.toml +7 -1
- {dirsql-0.3.66 → dirsql-0.3.69}/Cargo.toml +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/README.md +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/dirsql/__init__.py +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/dirsql/_async.py +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/dirsql/_dirsql.pyi +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/dirsql/cli/__init__.py +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/dirsql/cli/binary_path.py +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/dirsql/cli/is_windows.py +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/dirsql/cli/main.py +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/dirsql/cli/resolve_config_extensions.py +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/dirsql/py.typed +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/dirsql/resolve_config_extensions.py +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/dirsql/resolve_extension.py +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/docs/.claude/CLAUDE.md +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/docs/.vitepress/config.ts +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/docs/.vitepress/theme/index.ts +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/docs/.vitepress/theme/lang.ts +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/docs/AGENTS.md +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/docs/explanation.md +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/docs/getting-started.md +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/docs/howto/columns-from-paths.md +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/docs/howto/define-tables.md +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/docs/howto/embed.md +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/docs/howto/extract-from-contents.md +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/docs/howto/load-extension.md +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/docs/howto/persist.md +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/docs/howto/react-to-changes.md +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/docs/howto/search-by-meaning.md +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/docs/howto/skip-files.md +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/docs/index.md +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/docs/migrations.md +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/docs/package.json +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/docs/playwright.config.ts +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/docs/pnpm-lock.yaml +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/docs/pnpm-workspace.yaml +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/docs/reference/cli.md +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/docs/reference/columns.md +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/docs/reference/config.md +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/docs/reference/hooks.md +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/docs/reference/http-api.md +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/docs/reference/sdk.md +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/docs/tests/integration/home.spec.ts +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/docs/tests/integration/language-flag.spec.ts +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/docs/tests/integration/sidebar.spec.ts +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/docs/tests/unit/config.test.ts +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/docs/tests/unit/lang.test.ts +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/docs/vitest.config.ts +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/packages/python/README.md +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/packages/python/conftest.py +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/packages/python/docs/.claude/CLAUDE.md +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/packages/python/docs/.vitepress/config.ts +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/packages/python/docs/.vitepress/theme/index.ts +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/packages/python/docs/.vitepress/theme/lang.ts +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/packages/python/docs/AGENTS.md +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/packages/python/docs/explanation.md +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/packages/python/docs/getting-started.md +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/packages/python/docs/howto/columns-from-paths.md +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/packages/python/docs/howto/define-tables.md +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/packages/python/docs/howto/embed.md +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/packages/python/docs/howto/extract-from-contents.md +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/packages/python/docs/howto/load-extension.md +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/packages/python/docs/howto/persist.md +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/packages/python/docs/howto/react-to-changes.md +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/packages/python/docs/howto/search-by-meaning.md +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/packages/python/docs/howto/skip-files.md +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/packages/python/docs/index.md +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/packages/python/docs/migrations.md +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/packages/python/docs/package.json +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/packages/python/docs/playwright.config.ts +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/packages/python/docs/pnpm-lock.yaml +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/packages/python/docs/pnpm-workspace.yaml +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/packages/python/docs/reference/cli.md +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/packages/python/docs/reference/columns.md +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/packages/python/docs/reference/config.md +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/packages/python/docs/reference/hooks.md +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/packages/python/docs/reference/http-api.md +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/packages/python/docs/reference/sdk.md +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/packages/python/docs/tests/integration/home.spec.ts +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/packages/python/docs/tests/integration/language-flag.spec.ts +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/packages/python/docs/tests/integration/sidebar.spec.ts +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/packages/python/docs/tests/unit/config.test.ts +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/packages/python/docs/tests/unit/lang.test.ts +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/packages/python/docs/vitest.config.ts +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/packages/python/tests/__init__.py +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/packages/python/tests/binding/__init__.py +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/packages/python/tests/conftest.py +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/packages/python/tests/e2e/__init__.py +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/packages/python/tests/integration/__init__.py +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/packages/python/tests/smoke/__init__.py +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/packages/rust/Cargo.toml +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/packages/rust/README.md +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/packages/rust/benches/db_bench.rs +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/packages/rust/benches/differ_bench.rs +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/packages/rust/benches/matcher_bench.rs +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/packages/rust/benches/scanner_bench.rs +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/packages/rust/docs/explanation.md +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/packages/rust/docs/getting-started.md +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/packages/rust/docs/howto/columns-from-paths.md +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/packages/rust/docs/howto/define-tables.md +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/packages/rust/docs/howto/embed.md +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/packages/rust/docs/howto/extract-from-contents.md +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/packages/rust/docs/howto/load-extension.md +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/packages/rust/docs/howto/persist.md +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/packages/rust/docs/howto/react-to-changes.md +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/packages/rust/docs/howto/search-by-meaning.md +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/packages/rust/docs/howto/skip-files.md +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/packages/rust/docs/index.md +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/packages/rust/docs/migrations.md +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/packages/rust/docs/reference/cli.md +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/packages/rust/docs/reference/columns.md +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/packages/rust/docs/reference/config.md +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/packages/rust/docs/reference/hooks.md +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/packages/rust/docs/reference/http-api.md +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/packages/rust/docs/reference/sdk.md +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/packages/rust/src/bin/dirsql.rs +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/packages/rust/src/cli/serialize.rs +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/packages/rust/src/cli/server.rs +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/packages/rust/src/command.rs +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/packages/rust/src/config.rs +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/packages/rust/src/db.rs +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/packages/rust/src/differ.rs +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/packages/rust/src/lib.rs +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/packages/rust/src/matcher.rs +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/packages/rust/src/persist.rs +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/packages/rust/src/scanner.rs +0 -0
- {dirsql-0.3.66 → dirsql-0.3.69}/packages/rust/src/watcher.rs +0 -0
|
@@ -1,14 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dirsql
|
|
3
|
-
Version: 0.3.
|
|
4
|
-
Requires-Dist: pytest>=8 ; extra == 'dev'
|
|
5
|
-
Requires-Dist: pytest-describe>=2 ; extra == 'dev'
|
|
6
|
-
Requires-Dist: pytest-asyncio>=0.23 ; extra == 'dev'
|
|
7
|
-
Requires-Dist: pytest-cov>=5 ; extra == 'dev'
|
|
8
|
-
Requires-Dist: ruff>=0.4 ; extra == 'dev'
|
|
9
|
-
Requires-Dist: maturin>=1.0 ; extra == 'dev'
|
|
10
|
-
Requires-Dist: ty==0.0.42 ; extra == 'dev'
|
|
11
|
-
Provides-Extra: dev
|
|
3
|
+
Version: 0.3.69
|
|
12
4
|
Summary: Ephemeral SQL index over a local directory
|
|
13
5
|
Keywords: sql,filesystem,directory,sqlite,index
|
|
14
6
|
Author: Kevin Scott
|
|
@@ -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.69"
|
|
8
8
|
edition.workspace = true
|
|
9
9
|
publish = false
|
|
10
10
|
readme = "README.md"
|
|
@@ -257,57 +257,93 @@ mod python {
|
|
|
257
257
|
PyRuntimeError::new_err(e.to_string())
|
|
258
258
|
}
|
|
259
259
|
|
|
260
|
-
|
|
261
|
-
|
|
260
|
+
/// Pure, GIL-free intermediate for a row event. [`row_event_to_plain`]
|
|
261
|
+
/// builds it from a core [`RowEvent`] (unit-testable without a Python
|
|
262
|
+
/// interpreter); [`row_event_to_py`] then marshals it into the
|
|
263
|
+
/// Python-facing [`PyRowEvent`] (the GIL step). Splitting the two keeps the
|
|
264
|
+
/// variant -> action / field-selection mapping testable at the unit tier,
|
|
265
|
+
/// mirroring the napi binding's pure `row_event_to_js`. The value-level
|
|
266
|
+
/// `Row -> PyDict` conversion stays GIL-bound (binding-tier covered).
|
|
267
|
+
struct PlainRowEvent {
|
|
268
|
+
table: Option<String>,
|
|
269
|
+
action: &'static str,
|
|
270
|
+
row: Option<Row>,
|
|
271
|
+
old_row: Option<Row>,
|
|
272
|
+
error: Option<String>,
|
|
273
|
+
file_path: String,
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
fn row_event_to_plain(event: &RowEvent) -> PlainRowEvent {
|
|
277
|
+
match event {
|
|
262
278
|
RowEvent::Insert {
|
|
263
279
|
table,
|
|
264
280
|
row,
|
|
265
281
|
file_path,
|
|
266
|
-
} =>
|
|
282
|
+
} => PlainRowEvent {
|
|
267
283
|
table: Some(table.clone()),
|
|
268
|
-
action: "insert"
|
|
269
|
-
row: Some(
|
|
284
|
+
action: "insert",
|
|
285
|
+
row: Some(row.clone()),
|
|
270
286
|
old_row: None,
|
|
271
287
|
error: None,
|
|
272
|
-
file_path:
|
|
288
|
+
file_path: file_path.clone(),
|
|
273
289
|
},
|
|
274
290
|
RowEvent::Update {
|
|
275
291
|
table,
|
|
276
292
|
old_row,
|
|
277
293
|
new_row,
|
|
278
294
|
file_path,
|
|
279
|
-
} =>
|
|
295
|
+
} => PlainRowEvent {
|
|
280
296
|
table: Some(table.clone()),
|
|
281
|
-
action: "update"
|
|
282
|
-
row: Some(
|
|
283
|
-
old_row: Some(
|
|
297
|
+
action: "update",
|
|
298
|
+
row: Some(new_row.clone()),
|
|
299
|
+
old_row: Some(old_row.clone()),
|
|
284
300
|
error: None,
|
|
285
|
-
file_path:
|
|
301
|
+
file_path: file_path.clone(),
|
|
286
302
|
},
|
|
287
303
|
RowEvent::Delete {
|
|
288
304
|
table,
|
|
289
305
|
row,
|
|
290
306
|
file_path,
|
|
291
|
-
} =>
|
|
307
|
+
} => PlainRowEvent {
|
|
292
308
|
table: Some(table.clone()),
|
|
293
|
-
action: "delete"
|
|
294
|
-
row: Some(
|
|
309
|
+
action: "delete",
|
|
310
|
+
row: Some(row.clone()),
|
|
295
311
|
old_row: None,
|
|
296
312
|
error: None,
|
|
297
|
-
file_path:
|
|
313
|
+
file_path: file_path.clone(),
|
|
298
314
|
},
|
|
299
315
|
RowEvent::Error {
|
|
300
316
|
table,
|
|
301
317
|
file_path,
|
|
302
318
|
error,
|
|
303
|
-
} =>
|
|
319
|
+
} => PlainRowEvent {
|
|
304
320
|
table: table.clone(),
|
|
305
|
-
action: "error"
|
|
321
|
+
action: "error",
|
|
306
322
|
row: None,
|
|
307
323
|
old_row: None,
|
|
308
324
|
error: Some(error.clone()),
|
|
309
|
-
file_path:
|
|
325
|
+
file_path: file_path.to_string_lossy().to_string(),
|
|
310
326
|
},
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
fn row_event_to_py(py: Python<'_>, event: &RowEvent) -> PyResult<PyRowEvent> {
|
|
331
|
+
let plain = row_event_to_plain(event);
|
|
332
|
+
Ok(PyRowEvent {
|
|
333
|
+
table: plain.table,
|
|
334
|
+
action: plain.action.to_string(),
|
|
335
|
+
row: plain
|
|
336
|
+
.row
|
|
337
|
+
.as_ref()
|
|
338
|
+
.map(|r| value_row_to_py_dict(py, r))
|
|
339
|
+
.transpose()?,
|
|
340
|
+
old_row: plain
|
|
341
|
+
.old_row
|
|
342
|
+
.as_ref()
|
|
343
|
+
.map(|r| value_row_to_py_dict(py, r))
|
|
344
|
+
.transpose()?,
|
|
345
|
+
error: plain.error,
|
|
346
|
+
file_path: Some(plain.file_path),
|
|
311
347
|
})
|
|
312
348
|
}
|
|
313
349
|
|
|
@@ -381,4 +417,84 @@ mod python {
|
|
|
381
417
|
m.add_class::<PyRowEvent>()?;
|
|
382
418
|
Ok(())
|
|
383
419
|
}
|
|
420
|
+
|
|
421
|
+
#[cfg(test)]
|
|
422
|
+
mod tests {
|
|
423
|
+
use super::*;
|
|
424
|
+
|
|
425
|
+
fn one_row() -> HashMap<String, Value> {
|
|
426
|
+
HashMap::from([("k".to_string(), Value::Integer(7))])
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
#[test]
|
|
430
|
+
fn plain_insert_maps_row_and_action() {
|
|
431
|
+
let p = row_event_to_plain(&RowEvent::Insert {
|
|
432
|
+
table: "t".into(),
|
|
433
|
+
row: one_row(),
|
|
434
|
+
file_path: "/f".into(),
|
|
435
|
+
});
|
|
436
|
+
assert_eq!(p.action, "insert");
|
|
437
|
+
assert_eq!(p.table.as_deref(), Some("t"));
|
|
438
|
+
assert_eq!(
|
|
439
|
+
p.row.as_ref().and_then(|r| r.get("k")),
|
|
440
|
+
Some(&Value::Integer(7))
|
|
441
|
+
);
|
|
442
|
+
assert!(p.old_row.is_none());
|
|
443
|
+
assert!(p.error.is_none());
|
|
444
|
+
assert_eq!(p.file_path, "/f");
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
#[test]
|
|
448
|
+
fn plain_update_carries_old_and_new() {
|
|
449
|
+
let mut new = one_row();
|
|
450
|
+
new.insert("k".to_string(), Value::Integer(9));
|
|
451
|
+
let p = row_event_to_plain(&RowEvent::Update {
|
|
452
|
+
table: "t".into(),
|
|
453
|
+
old_row: one_row(),
|
|
454
|
+
new_row: new,
|
|
455
|
+
file_path: "/f".into(),
|
|
456
|
+
});
|
|
457
|
+
assert_eq!(p.action, "update");
|
|
458
|
+
assert_eq!(
|
|
459
|
+
p.row.as_ref().and_then(|r| r.get("k")),
|
|
460
|
+
Some(&Value::Integer(9))
|
|
461
|
+
);
|
|
462
|
+
assert_eq!(
|
|
463
|
+
p.old_row.as_ref().and_then(|r| r.get("k")),
|
|
464
|
+
Some(&Value::Integer(7))
|
|
465
|
+
);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
#[test]
|
|
469
|
+
fn plain_delete_has_row_no_old() {
|
|
470
|
+
let p = row_event_to_plain(&RowEvent::Delete {
|
|
471
|
+
table: "t".into(),
|
|
472
|
+
row: one_row(),
|
|
473
|
+
file_path: "/f".into(),
|
|
474
|
+
});
|
|
475
|
+
assert_eq!(p.action, "delete");
|
|
476
|
+
assert!(p.row.is_some());
|
|
477
|
+
assert!(p.old_row.is_none());
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
#[test]
|
|
481
|
+
fn plain_error_has_no_row_and_optional_table() {
|
|
482
|
+
let p = row_event_to_plain(&RowEvent::Error {
|
|
483
|
+
table: None,
|
|
484
|
+
file_path: std::path::PathBuf::from("/f"),
|
|
485
|
+
error: "boom".into(),
|
|
486
|
+
});
|
|
487
|
+
assert_eq!(p.action, "error");
|
|
488
|
+
assert!(p.table.is_none());
|
|
489
|
+
assert!(p.row.is_none());
|
|
490
|
+
assert!(p.old_row.is_none());
|
|
491
|
+
assert_eq!(p.error.as_deref(), Some("boom"));
|
|
492
|
+
assert_eq!(p.file_path, "/f");
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
#[test]
|
|
496
|
+
fn extract_error_displays_inner() {
|
|
497
|
+
assert_eq!(ExtractError("bad".to_string()).to_string(), "bad");
|
|
498
|
+
}
|
|
499
|
+
}
|
|
384
500
|
}
|
|
@@ -166,4 +166,46 @@ mod tests {
|
|
|
166
166
|
"got: {err:?}"
|
|
167
167
|
);
|
|
168
168
|
}
|
|
169
|
+
|
|
170
|
+
// The spawn / non-zero-exit / write failure arms of `run` require actually
|
|
171
|
+
// invoking `claude` and touching the filesystem, so they are exercised at
|
|
172
|
+
// the e2e tier. Their `Display` renderings, however, are pure and covered
|
|
173
|
+
// here (the values are constructed inline, no process or fs I/O).
|
|
174
|
+
|
|
175
|
+
#[test]
|
|
176
|
+
fn spawn_error_display_mentions_spawn() {
|
|
177
|
+
let err = InitError::Spawn(std::io::Error::new(
|
|
178
|
+
std::io::ErrorKind::PermissionDenied,
|
|
179
|
+
"denied",
|
|
180
|
+
));
|
|
181
|
+
let msg = format!("{err}");
|
|
182
|
+
assert!(msg.contains("failed to spawn"), "got: {msg}");
|
|
183
|
+
assert!(msg.contains("denied"), "got: {msg}");
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
#[test]
|
|
187
|
+
fn write_error_display_names_the_path_and_source() {
|
|
188
|
+
let err = InitError::Write {
|
|
189
|
+
path: PathBuf::from("/tmp/out.toml"),
|
|
190
|
+
source: std::io::Error::other("disk full"),
|
|
191
|
+
};
|
|
192
|
+
let msg = format!("{err}");
|
|
193
|
+
assert!(msg.contains("/tmp/out.toml"), "got: {msg}");
|
|
194
|
+
assert!(msg.contains("disk full"), "got: {msg}");
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
#[test]
|
|
198
|
+
fn invalid_utf8_error_display_mentions_utf8() {
|
|
199
|
+
// A non-UTF8 byte sequence yields the `FromUtf8Error` that `#[from]`
|
|
200
|
+
// converts into `InitError::InvalidUtf8`.
|
|
201
|
+
let utf8_err = String::from_utf8(vec![0xff, 0xfe]).unwrap_err();
|
|
202
|
+
let err: InitError = utf8_err.into();
|
|
203
|
+
let msg = format!("{err}");
|
|
204
|
+
assert!(msg.contains("non-UTF8"), "got: {msg}");
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// NOTE: `InitError::ClaudeFailed`'s `Display` is not unit-tested -- building
|
|
208
|
+
// it needs an `ExitStatus`, and `std::process::ExitStatus::from_raw` is
|
|
209
|
+
// effectful std the unit-isolation lint (`no-out-of-module-call`) forbids.
|
|
210
|
+
// Its rendering is covered end-to-end at the e2e tier instead.
|
|
169
211
|
}
|
|
@@ -330,4 +330,26 @@ mod tests {
|
|
|
330
330
|
assert_eq!(pq.command, "reshape {args}");
|
|
331
331
|
assert_eq!(pq.config_dir, PathBuf::from("/proj"));
|
|
332
332
|
}
|
|
333
|
+
|
|
334
|
+
#[test]
|
|
335
|
+
fn with_query_timeout_overrides_the_default() {
|
|
336
|
+
// The builder replaces the 30s default with the supplied timeout and
|
|
337
|
+
// leaves the rest of the config untouched.
|
|
338
|
+
let cfg = ServerConfig::bind("127.0.0.1", 8080).with_query_timeout(Duration::from_secs(5));
|
|
339
|
+
assert_eq!(cfg.query_timeout, Duration::from_secs(5));
|
|
340
|
+
assert_eq!(cfg.host, "127.0.0.1");
|
|
341
|
+
assert_eq!(cfg.port, 8080);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
#[test]
|
|
345
|
+
fn app_state_from_string_builds_the_unavailable_arm() {
|
|
346
|
+
// `String -> AppState` yields the degraded arm carrying the diagnostic
|
|
347
|
+
// verbatim (the `/query`/`/events` 503 body). `AppState` isn't `Debug`,
|
|
348
|
+
// so match rather than assert on a rendering.
|
|
349
|
+
let state: AppState = "config failed to load".to_string().into();
|
|
350
|
+
match state {
|
|
351
|
+
AppState::Unavailable(reason) => assert_eq!(reason, "config failed to load"),
|
|
352
|
+
AppState::Ready(_) => panic!("String must map to the Unavailable arm"),
|
|
353
|
+
}
|
|
354
|
+
}
|
|
333
355
|
}
|
|
@@ -356,4 +356,38 @@ mod tests {
|
|
|
356
356
|
"application/json"
|
|
357
357
|
);
|
|
358
358
|
}
|
|
359
|
+
|
|
360
|
+
// `parse_sql_body` is the no-`pre-query` request path: it is pure (serde
|
|
361
|
+
// only), so it is unit-tested here directly rather than through the async
|
|
362
|
+
// `handle_query` handler (which needs a live server and is covered at the
|
|
363
|
+
// integration tier).
|
|
364
|
+
|
|
365
|
+
#[test]
|
|
366
|
+
fn parse_sql_body_returns_trimmed_sql() {
|
|
367
|
+
// Surrounding whitespace is stripped; the inner SQL is returned as-is.
|
|
368
|
+
let sql = parse_sql_body(r#"{"sql": " SELECT 1 "}"#).expect("valid body");
|
|
369
|
+
assert_eq!(sql, "SELECT 1");
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
#[test]
|
|
373
|
+
fn parse_sql_body_rejects_malformed_json() {
|
|
374
|
+
// A body that isn't JSON fails at the serde step -> 400.
|
|
375
|
+
let resp = parse_sql_body("not json").expect_err("malformed JSON must be rejected");
|
|
376
|
+
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
#[test]
|
|
380
|
+
fn parse_sql_body_rejects_whitespace_only_sql() {
|
|
381
|
+
// A present-but-blank `sql` trims to empty -> 400 (the `Some(_)` arm,
|
|
382
|
+
// and the `false` side of the `!s.is_empty()` guard).
|
|
383
|
+
let resp = parse_sql_body(r#"{"sql": " "}"#).expect_err("empty sql must be rejected");
|
|
384
|
+
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
#[test]
|
|
388
|
+
fn parse_sql_body_rejects_missing_sql_field() {
|
|
389
|
+
// Valid JSON object with no `sql` key -> the `None` arm -> 400.
|
|
390
|
+
let resp = parse_sql_body("{}").expect_err("missing sql must be rejected");
|
|
391
|
+
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
|
|
392
|
+
}
|
|
359
393
|
}
|
|
@@ -19,7 +19,13 @@ repository = "https://github.com/thekevinscott/dirsql"
|
|
|
19
19
|
keywords = ["sql", "filesystem", "directory", "sqlite", "index"]
|
|
20
20
|
readme = "README.md"
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
# Dev/test dependencies live in a PEP 735 dependency-group (not a
|
|
23
|
+
# `[project.optional-dependencies]` extra) so a plain `uv sync` installs them
|
|
24
|
+
# by default -- the reusable testing-conventions coverage/mutation jobs run
|
|
25
|
+
# `uv sync` with no `--extra`, and our colocated suite needs pytest-describe /
|
|
26
|
+
# pytest-asyncio to collect at all. Every in-repo flow is uv-based and now uses
|
|
27
|
+
# plain `uv sync`; the old `pip install .[dev]` extra is intentionally dropped.
|
|
28
|
+
[dependency-groups]
|
|
23
29
|
dev = [
|
|
24
30
|
"pytest>=8",
|
|
25
31
|
"pytest-describe>=2",
|
|
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
|
|
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.66 → dirsql-0.3.69}/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
|
|
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
|