dirsql 0.3.25__tar.gz → 0.3.26__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.25 → dirsql-0.3.26}/Cargo.lock +1 -1
- {dirsql-0.3.25 → dirsql-0.3.26}/PKG-INFO +1 -1
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/python/Cargo.toml +1 -1
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/rust/src/lib.rs +194 -2
- dirsql-0.3.26/packages/rust/tests/watch_relative_root.rs +134 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/Cargo.toml +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/README.md +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/dirsql/__init__.py +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/dirsql/_async.py +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/dirsql/_async_test.py +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/dirsql/_dirsql.pyi +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/dirsql/cli/__init__.py +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/dirsql/cli/binary_path.py +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/dirsql/cli/binary_path_test.py +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/dirsql/cli/interpret/__init__.py +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/dirsql/cli/interpret/dispatch_extract.py +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/dirsql/cli/interpret/dispatch_extract_test.py +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/dirsql/cli/interpret/load_app.py +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/dirsql/cli/interpret/load_app_test.py +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/dirsql/cli/interpret/run.py +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/dirsql/cli/interpret/run_test.py +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/dirsql/cli/interpret/write_message.py +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/dirsql/cli/interpret/write_message_test.py +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/dirsql/cli/is_windows.py +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/dirsql/cli/is_windows_test.py +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/dirsql/cli/main.py +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/dirsql/cli/main_test.py +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/dirsql/py.typed +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/dirsql/resolve_config.py +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/dirsql/resolve_config_test.py +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/docs/.claude/CLAUDE.md +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/docs/.vitepress/config.ts +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/docs/.vitepress/theme/index.ts +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/docs/.vitepress/theme/lang.ts +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/docs/AGENTS.md +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/docs/api/index.md +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/docs/cli/config.md +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/docs/cli/http-api.md +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/docs/cli/index.md +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/docs/cli/init.md +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/docs/cli/server.md +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/docs/getting-started.md +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/docs/guide/async.md +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/docs/guide/crdt.md +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/docs/guide/persistence.md +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/docs/guide/querying.md +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/docs/guide/tables.md +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/docs/guide/watching.md +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/docs/index.md +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/docs/migrations.md +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/docs/package.json +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/docs/playwright.config.ts +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/docs/pnpm-lock.yaml +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/docs/pnpm-workspace.yaml +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/docs/tests/integration/home.spec.ts +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/docs/tests/integration/language-flag.spec.ts +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/docs/tests/unit/config.test.ts +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/docs/tests/unit/lang.test.ts +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/docs/vitest.config.ts +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/python/README.md +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/python/conftest.py +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/python/docs/.claude/CLAUDE.md +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/python/docs/.vitepress/config.ts +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/python/docs/.vitepress/theme/index.ts +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/python/docs/.vitepress/theme/lang.ts +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/python/docs/AGENTS.md +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/python/docs/api/index.md +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/python/docs/cli/config.md +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/python/docs/cli/http-api.md +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/python/docs/cli/index.md +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/python/docs/cli/init.md +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/python/docs/cli/server.md +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/python/docs/getting-started.md +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/python/docs/guide/async.md +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/python/docs/guide/crdt.md +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/python/docs/guide/persistence.md +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/python/docs/guide/querying.md +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/python/docs/guide/tables.md +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/python/docs/guide/watching.md +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/python/docs/index.md +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/python/docs/migrations.md +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/python/docs/package.json +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/python/docs/playwright.config.ts +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/python/docs/pnpm-lock.yaml +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/python/docs/pnpm-workspace.yaml +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/python/docs/tests/integration/home.spec.ts +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/python/docs/tests/integration/language-flag.spec.ts +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/python/docs/tests/unit/config.test.ts +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/python/docs/tests/unit/lang.test.ts +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/python/docs/vitest.config.ts +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/python/src/lib.rs +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/python/tests/__init__.py +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/python/tests/conftest.py +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/python/tests/e2e/__init__.py +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/python/tests/integration/__fixtures__/data/a/meta.json +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/python/tests/integration/__fixtures__/data/b/meta.json +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/python/tests/integration/__fixtures__/dirsql.config.py +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/python/tests/integration/__fixtures__/interpret/data/a/meta.json +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/python/tests/integration/__fixtures__/interpret/data/b/meta.json +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/python/tests/integration/__fixtures__/interpret/dirsql.config.py +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/python/tests/integration/__fixtures__/interpret/dirsql.config_no_app.py +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/python/tests/integration/__fixtures__/interpret/dirsql.config_raises.py +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/python/tests/integration/__init__.py +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/python/tests/integration/interpret_subprocess.py +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/python/tests/integration/test_async_dirsql.py +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/python/tests/integration/test_binding.py +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/python/tests/integration/test_dirsql.py +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/python/tests/integration/test_docs_examples.py +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/python/tests/integration/test_docs_gaps.py +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/python/tests/integration/test_from_config.py +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/python/tests/integration/test_interpret.py +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/python/tests/integration/test_native_config.py +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/python/tests/integration/test_persist.py +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/python/tests/integration/test_serialization.py +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/rust/Cargo.toml +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/rust/README.md +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/rust/benches/db_bench.rs +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/rust/benches/differ_bench.rs +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/rust/benches/matcher_bench.rs +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/rust/benches/scanner_bench.rs +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/rust/docs/api/index.md +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/rust/docs/cli/config.md +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/rust/docs/cli/http-api.md +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/rust/docs/cli/index.md +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/rust/docs/cli/init.md +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/rust/docs/cli/server.md +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/rust/docs/getting-started.md +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/rust/docs/guide/async.md +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/rust/docs/guide/crdt.md +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/rust/docs/guide/persistence.md +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/rust/docs/guide/querying.md +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/rust/docs/guide/tables.md +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/rust/docs/guide/watching.md +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/rust/docs/index.md +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/rust/docs/migrations.md +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/rust/src/bin/dirsql.rs +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/rust/src/cli/init.rs +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/rust/src/cli/mod.rs +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/rust/src/cli/native_config.rs +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/rust/src/cli/router.rs +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/rust/src/cli/serialize.rs +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/rust/src/cli/server.rs +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/rust/src/config.rs +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/rust/src/db.rs +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/rust/src/differ.rs +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/rust/src/matcher.rs +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/rust/src/persist.rs +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/rust/src/scanner.rs +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/rust/src/watcher.rs +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/rust/tests/async_sdk.rs +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/rust/tests/cli_e2e.rs +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/rust/tests/cli_integration.rs +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/rust/tests/code_review_findings.rs +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/rust/tests/config.rs +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/rust/tests/docs_examples.rs +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/rust/tests/docs_gaps.rs +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/rust/tests/extensions.rs +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/rust/tests/from_config.rs +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/rust/tests/init_e2e.rs +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/rust/tests/init_integration.rs +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/rust/tests/persist.rs +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/rust/tests/readonly_query.rs +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/rust/tests/scanner.rs +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/rust/tests/sdk.rs +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/rust/tests/serialization.rs +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/packages/rust/tests/watcher.rs +0 -0
- {dirsql-0.3.25 → dirsql-0.3.26}/pyproject.toml +0 -0
|
@@ -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.26"
|
|
8
8
|
edition.workspace = true
|
|
9
9
|
publish = false
|
|
10
10
|
readme = "README.md"
|
|
@@ -227,6 +227,17 @@ impl Table {
|
|
|
227
227
|
struct DirSqlInner {
|
|
228
228
|
db: Mutex<Db>,
|
|
229
229
|
root: PathBuf,
|
|
230
|
+
/// Canonicalized form of `root`, used **only** for the live filesystem
|
|
231
|
+
/// watcher. `notify` has surprising behavior when handed a relative path
|
|
232
|
+
/// like `.` / `./data` (it may deliver no events at all, or deliver them
|
|
233
|
+
/// under the cwd-joined path so the relative prefix no longer strips):
|
|
234
|
+
/// the CLI binary works around this by canonicalizing its root before
|
|
235
|
+
/// watching, and the SDK now does the same (#250). Derived once at
|
|
236
|
+
/// construction via [`canonical_root`] (literal fallback when
|
|
237
|
+
/// canonicalization fails, e.g. a not-yet-created root), so the user's
|
|
238
|
+
/// `root` — and therefore the initial scan, [`DirSQL::config`], and the
|
|
239
|
+
/// `_path` virtual column — stay byte-for-byte unchanged.
|
|
240
|
+
watch_root: PathBuf,
|
|
230
241
|
/// Pre-compiled matcher over all table globs plus ignore patterns.
|
|
231
242
|
/// Built once at construction, reused by the initial scan and every
|
|
232
243
|
/// subsequent watch iteration.
|
|
@@ -393,7 +404,9 @@ impl DirSQL {
|
|
|
393
404
|
pub fn start_watching(&self) -> Result<()> {
|
|
394
405
|
let mut guard = self.inner.watcher.lock().map_err(DirSqlError::lock)?;
|
|
395
406
|
if guard.is_none() {
|
|
396
|
-
|
|
407
|
+
// Watch the canonicalized root, never the (possibly relative)
|
|
408
|
+
// user-supplied one — `notify` misbehaves on relative paths (#250).
|
|
409
|
+
let watcher = Watcher::new(&self.inner.watch_root).map_err(DirSqlError::watch)?;
|
|
397
410
|
*guard = Some(watcher);
|
|
398
411
|
}
|
|
399
412
|
Ok(())
|
|
@@ -515,8 +528,15 @@ impl DirSQL {
|
|
|
515
528
|
let abs_path = match &event {
|
|
516
529
|
FileEvent::Created(p) | FileEvent::Modified(p) | FileEvent::Deleted(p) => p.clone(),
|
|
517
530
|
};
|
|
531
|
+
// Events now arrive under the canonical `watch_root` (the watcher was
|
|
532
|
+
// started on it), so strip that first; fall back to the user-supplied
|
|
533
|
+
// `root` (covers the already-canonical/absolute-root case and any
|
|
534
|
+
// event whose path predates the watch-root change), then to the raw
|
|
535
|
+
// absolute path. This keeps the computed relative `_path` identical to
|
|
536
|
+
// the pre-#250 behavior for both absolute and relative roots.
|
|
518
537
|
let rel_path_buf = abs_path
|
|
519
|
-
.strip_prefix(&self.inner.
|
|
538
|
+
.strip_prefix(&self.inner.watch_root)
|
|
539
|
+
.or_else(|_| abs_path.strip_prefix(&self.inner.root))
|
|
520
540
|
.unwrap_or(&abs_path)
|
|
521
541
|
.to_path_buf();
|
|
522
542
|
|
|
@@ -870,10 +890,17 @@ impl DirSQL {
|
|
|
870
890
|
write_meta(db.conn(), meta).map_err(DirSqlError::sqlite)?;
|
|
871
891
|
}
|
|
872
892
|
|
|
893
|
+
// Canonicalize the watch root once, here at the single shared
|
|
894
|
+
// construction point reached by both `build()` and `build_async()`,
|
|
895
|
+
// so the live watcher never sees a relative path (#250). `root` itself
|
|
896
|
+
// is left untouched.
|
|
897
|
+
let watch_root = PathBuf::from(canonical_root(&root));
|
|
898
|
+
|
|
873
899
|
Ok(Self {
|
|
874
900
|
inner: Arc::new(DirSqlInner {
|
|
875
901
|
db: Mutex::new(db),
|
|
876
902
|
root,
|
|
903
|
+
watch_root,
|
|
877
904
|
matcher,
|
|
878
905
|
extract_map,
|
|
879
906
|
strict_map,
|
|
@@ -1867,6 +1894,171 @@ mod internal_tests {
|
|
|
1867
1894
|
assert_eq!(events.len(), 1, "non-ignored path must produce one event");
|
|
1868
1895
|
}
|
|
1869
1896
|
|
|
1897
|
+
// -----------------------------------------------------------------------
|
|
1898
|
+
// #250: canonical `watch_root` and the strip-prefix fallbacks.
|
|
1899
|
+
//
|
|
1900
|
+
// `std::env::set_current_dir` is process-global, so the relative-root test
|
|
1901
|
+
// serializes through this lock and restores the cwd on the way out.
|
|
1902
|
+
// -----------------------------------------------------------------------
|
|
1903
|
+
|
|
1904
|
+
fn cwd_lock() -> &'static Mutex<()> {
|
|
1905
|
+
static LOCK: std::sync::OnceLock<Mutex<()>> = std::sync::OnceLock::new();
|
|
1906
|
+
LOCK.get_or_init(|| Mutex::new(()))
|
|
1907
|
+
}
|
|
1908
|
+
|
|
1909
|
+
/// Building with a **relative** root canonicalizes `watch_root` to an
|
|
1910
|
+
/// absolute path while leaving `root` (and therefore `config()` / `_path`)
|
|
1911
|
+
/// exactly as the caller supplied it. This is the core of the #250 fix:
|
|
1912
|
+
/// `start_watching` watches `watch_root`, so `notify` never sees `.`.
|
|
1913
|
+
#[test]
|
|
1914
|
+
fn relative_root_canonicalizes_watch_root_only() {
|
|
1915
|
+
let dir = TempDir::new().unwrap();
|
|
1916
|
+
let canonical = std::fs::canonicalize(dir.path()).unwrap();
|
|
1917
|
+
|
|
1918
|
+
let _guard = cwd_lock().lock().unwrap_or_else(|p| p.into_inner());
|
|
1919
|
+
let original = std::env::current_dir().unwrap();
|
|
1920
|
+
std::env::set_current_dir(&canonical).unwrap();
|
|
1921
|
+
|
|
1922
|
+
let db = DirSQL::new(
|
|
1923
|
+
".",
|
|
1924
|
+
vec![Table::new("CREATE TABLE t (x TEXT)", "*.txt", |_| vec![])],
|
|
1925
|
+
)
|
|
1926
|
+
.unwrap();
|
|
1927
|
+
|
|
1928
|
+
// Restore cwd before asserting so a failure can't strand the process.
|
|
1929
|
+
std::env::set_current_dir(&original).unwrap();
|
|
1930
|
+
|
|
1931
|
+
// `root` is preserved verbatim; `config()` echoes it.
|
|
1932
|
+
assert_eq!(db.inner.root, PathBuf::from("."));
|
|
1933
|
+
assert_eq!(db.config().root, PathBuf::from("."));
|
|
1934
|
+
// `watch_root` is absolute and points at the canonical temp dir.
|
|
1935
|
+
assert!(
|
|
1936
|
+
db.inner.watch_root.is_absolute(),
|
|
1937
|
+
"watch_root must be absolute, got {:?}",
|
|
1938
|
+
db.inner.watch_root
|
|
1939
|
+
);
|
|
1940
|
+
assert_eq!(db.inner.watch_root, canonical);
|
|
1941
|
+
}
|
|
1942
|
+
|
|
1943
|
+
/// With an absolute root the canonical `watch_root` equals the (already
|
|
1944
|
+
/// canonical) root on this platform, and `process_file_event` strips that
|
|
1945
|
+
/// prefix to yield a root-relative `_path` — the first `strip_prefix`
|
|
1946
|
+
/// (watch_root) arm.
|
|
1947
|
+
#[test]
|
|
1948
|
+
fn process_file_event_strips_watch_root_prefix() {
|
|
1949
|
+
let dir = TempDir::new().unwrap();
|
|
1950
|
+
let canonical = std::fs::canonicalize(dir.path()).unwrap();
|
|
1951
|
+
let db = DirSQL::new(
|
|
1952
|
+
&canonical,
|
|
1953
|
+
vec![Table::new(
|
|
1954
|
+
"CREATE TABLE items (name TEXT, _path TEXT)",
|
|
1955
|
+
"**/*.txt",
|
|
1956
|
+
|_| {
|
|
1957
|
+
vec![Row::from_iter([(
|
|
1958
|
+
"name".to_string(),
|
|
1959
|
+
Value::Text("x".into()),
|
|
1960
|
+
)])]
|
|
1961
|
+
},
|
|
1962
|
+
)],
|
|
1963
|
+
)
|
|
1964
|
+
.unwrap();
|
|
1965
|
+
|
|
1966
|
+
let abs = canonical.join("nested").join("a.txt");
|
|
1967
|
+
std::fs::create_dir_all(canonical.join("nested")).unwrap();
|
|
1968
|
+
std::fs::write(&abs, b"").unwrap();
|
|
1969
|
+
|
|
1970
|
+
let events = db.process_file_event(FileEvent::Created(abs));
|
|
1971
|
+
assert_eq!(events.len(), 1, "expected one insert: {events:?}");
|
|
1972
|
+
match &events[0] {
|
|
1973
|
+
RowEvent::Insert { row, .. } => {
|
|
1974
|
+
assert_eq!(
|
|
1975
|
+
row.get("_path"),
|
|
1976
|
+
Some(&Value::Text("nested/a.txt".to_string())),
|
|
1977
|
+
"watch_root prefix must be stripped to a root-relative path"
|
|
1978
|
+
);
|
|
1979
|
+
}
|
|
1980
|
+
other => panic!("expected Insert, got {other:?}"),
|
|
1981
|
+
}
|
|
1982
|
+
}
|
|
1983
|
+
|
|
1984
|
+
/// When an event path lies under the user-supplied `root` but not under
|
|
1985
|
+
/// the canonical `watch_root`, the `.or_else` fallback strips `root`
|
|
1986
|
+
/// instead. We force that split by pointing `watch_root` at a sibling that
|
|
1987
|
+
/// is not a prefix of the event path, leaving `root` as the real dir.
|
|
1988
|
+
#[test]
|
|
1989
|
+
fn process_file_event_falls_back_to_root_prefix() {
|
|
1990
|
+
let dir = TempDir::new().unwrap();
|
|
1991
|
+
let canonical = std::fs::canonicalize(dir.path()).unwrap();
|
|
1992
|
+
let mut db = DirSQL::new(
|
|
1993
|
+
&canonical,
|
|
1994
|
+
vec![Table::new(
|
|
1995
|
+
"CREATE TABLE items (name TEXT, _path TEXT)",
|
|
1996
|
+
"**/*.txt",
|
|
1997
|
+
|_| {
|
|
1998
|
+
vec![Row::from_iter([(
|
|
1999
|
+
"name".to_string(),
|
|
2000
|
+
Value::Text("x".into()),
|
|
2001
|
+
)])]
|
|
2002
|
+
},
|
|
2003
|
+
)],
|
|
2004
|
+
)
|
|
2005
|
+
.unwrap();
|
|
2006
|
+
|
|
2007
|
+
// Repoint watch_root to a non-prefix sibling so the first strip misses
|
|
2008
|
+
// and the `.or_else(root)` arm runs. `root` stays the real dir.
|
|
2009
|
+
Arc::get_mut(&mut db.inner).unwrap().watch_root = canonical.join("does-not-prefix");
|
|
2010
|
+
|
|
2011
|
+
let abs = canonical.join("b.txt");
|
|
2012
|
+
std::fs::write(&abs, b"").unwrap();
|
|
2013
|
+
let events = db.process_file_event(FileEvent::Created(abs));
|
|
2014
|
+
assert_eq!(events.len(), 1, "expected one insert: {events:?}");
|
|
2015
|
+
match &events[0] {
|
|
2016
|
+
RowEvent::Insert { row, .. } => {
|
|
2017
|
+
assert_eq!(
|
|
2018
|
+
row.get("_path"),
|
|
2019
|
+
Some(&Value::Text("b.txt".to_string())),
|
|
2020
|
+
"root fallback must strip the user-supplied root prefix"
|
|
2021
|
+
);
|
|
2022
|
+
}
|
|
2023
|
+
other => panic!("expected Insert, got {other:?}"),
|
|
2024
|
+
}
|
|
2025
|
+
}
|
|
2026
|
+
|
|
2027
|
+
/// When the event path is under neither `watch_root` nor `root`, the final
|
|
2028
|
+
/// `unwrap_or(&abs_path)` arm keeps the absolute path. A path that matches
|
|
2029
|
+
/// no table glob then yields no events, but the strip fallback is still
|
|
2030
|
+
/// executed — we assert the no-event outcome to pin the arm without relying
|
|
2031
|
+
/// on a row.
|
|
2032
|
+
#[test]
|
|
2033
|
+
fn process_file_event_keeps_absolute_path_when_no_prefix_matches() {
|
|
2034
|
+
let dir = TempDir::new().unwrap();
|
|
2035
|
+
let canonical = std::fs::canonicalize(dir.path()).unwrap();
|
|
2036
|
+
let db = DirSQL::new(
|
|
2037
|
+
&canonical,
|
|
2038
|
+
vec![Table::new(
|
|
2039
|
+
"CREATE TABLE items (name TEXT)",
|
|
2040
|
+
"*.txt",
|
|
2041
|
+
|_| {
|
|
2042
|
+
vec![Row::from_iter([(
|
|
2043
|
+
"name".to_string(),
|
|
2044
|
+
Value::Text("x".into()),
|
|
2045
|
+
)])]
|
|
2046
|
+
},
|
|
2047
|
+
)],
|
|
2048
|
+
)
|
|
2049
|
+
.unwrap();
|
|
2050
|
+
|
|
2051
|
+
// A path outside both roots: neither strip matches, so the absolute
|
|
2052
|
+
// path is used as the relative path. It does not match `*.txt` at the
|
|
2053
|
+
// root, so no events are produced.
|
|
2054
|
+
let outside = PathBuf::from("/some/elsewhere/c.md");
|
|
2055
|
+
let events = db.process_file_event(FileEvent::Created(outside));
|
|
2056
|
+
assert!(
|
|
2057
|
+
events.is_empty(),
|
|
2058
|
+
"unmatched absolute path must produce no events: {events:?}"
|
|
2059
|
+
);
|
|
2060
|
+
}
|
|
2061
|
+
|
|
1870
2062
|
/// Drive `reconcile_scan` directly with a cached file whose
|
|
1871
2063
|
/// `snapshot_ns <= mtime_ns`, forcing the racy-window hash-confirm branch.
|
|
1872
2064
|
/// With a matching content hash the file is trusted.
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
//! Regression test for #250: the file watcher must deliver `RowEvent`s when
|
|
2
|
+
//! `DirSQL` is constructed with a **relative** `root` (e.g. `DirSQL::new(".",
|
|
3
|
+
//! ...)`), not just an absolute one.
|
|
4
|
+
//!
|
|
5
|
+
//! Root cause: `start_watching()` handed the user-supplied (possibly relative)
|
|
6
|
+
//! `root` straight to `notify`, which has surprising behavior when watching
|
|
7
|
+
//! relative paths like `./`. The initial scan worked fine either way; only the
|
|
8
|
+
//! live watcher was broken. The CLI binary already canonicalized its root for
|
|
9
|
+
//! exactly this reason — the SDK did not. The fix watches a canonicalized
|
|
10
|
+
//! `watch_root` while keeping the user-supplied `root` for scanning,
|
|
11
|
+
//! `config()`, and `_path` output.
|
|
12
|
+
//!
|
|
13
|
+
//! `std::env::set_current_dir` mutates **process-global** state, so every test
|
|
14
|
+
//! in this file serializes through `CWD_LOCK` and restores the original cwd on
|
|
15
|
+
//! the way out (via the `CwdGuard` drop), even on panic.
|
|
16
|
+
|
|
17
|
+
use dirsql::{DirSQL, Table, Value};
|
|
18
|
+
use std::fs;
|
|
19
|
+
use std::path::PathBuf;
|
|
20
|
+
use std::sync::{Mutex, MutexGuard, OnceLock};
|
|
21
|
+
use std::time::{Duration, Instant};
|
|
22
|
+
|
|
23
|
+
/// Serializes the process-global cwd across the tests in this file. A `OnceLock`
|
|
24
|
+
/// avoids pulling in a `lazy_static`/`once_cell` dependency just for the test.
|
|
25
|
+
fn cwd_lock() -> &'static Mutex<()> {
|
|
26
|
+
static LOCK: OnceLock<Mutex<()>> = OnceLock::new();
|
|
27
|
+
LOCK.get_or_init(|| Mutex::new(()))
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/// Holds the cwd lock and the original working directory, restoring it on drop.
|
|
31
|
+
/// Recovers a poisoned lock: a prior test panicking while holding the guard
|
|
32
|
+
/// must not cascade-fail every later cwd test.
|
|
33
|
+
struct CwdGuard {
|
|
34
|
+
_guard: MutexGuard<'static, ()>,
|
|
35
|
+
original: PathBuf,
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
impl CwdGuard {
|
|
39
|
+
fn enter(target: &std::path::Path) -> Self {
|
|
40
|
+
let guard = cwd_lock().lock().unwrap_or_else(|p| p.into_inner());
|
|
41
|
+
let original = std::env::current_dir().expect("read cwd");
|
|
42
|
+
std::env::set_current_dir(target).expect("chdir into target");
|
|
43
|
+
Self {
|
|
44
|
+
_guard: guard,
|
|
45
|
+
original,
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
impl Drop for CwdGuard {
|
|
51
|
+
fn drop(&mut self) {
|
|
52
|
+
// Best-effort restore; if this fails the process is already in a bad
|
|
53
|
+
// state and later tests will surface it.
|
|
54
|
+
let _ = std::env::set_current_dir(&self.original);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
fn items_table() -> Table {
|
|
59
|
+
Table::new(
|
|
60
|
+
"CREATE TABLE items (name TEXT, _path TEXT)",
|
|
61
|
+
"**/*.txt",
|
|
62
|
+
|path| {
|
|
63
|
+
let content = std::fs::read_to_string(path).unwrap_or_default();
|
|
64
|
+
vec![std::collections::HashMap::from([(
|
|
65
|
+
"name".to_string(),
|
|
66
|
+
Value::Text(content.trim().to_string()),
|
|
67
|
+
)])]
|
|
68
|
+
},
|
|
69
|
+
)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/// #250: a watcher built on a relative `root` (`"."`) must still emit an insert
|
|
73
|
+
/// `RowEvent` when a matching file is created after `start_watching()`.
|
|
74
|
+
///
|
|
75
|
+
/// Without the fix the watcher hands `"."` to `notify`, which never delivers
|
|
76
|
+
/// events, so this poll loop times out with zero events (RED). With the fix the
|
|
77
|
+
/// watcher canonicalizes the root, the create is observed, and an `Insert`
|
|
78
|
+
/// arrives (GREEN). The companion absolute-root assertion is intentionally
|
|
79
|
+
/// *not* a separate `#[test]` so it can't race for the cwd lock; it shares this
|
|
80
|
+
/// one to prove the two roots behave identically.
|
|
81
|
+
#[test]
|
|
82
|
+
fn watch_with_relative_root_emits_events() {
|
|
83
|
+
let dir = tempfile::TempDir::new().unwrap();
|
|
84
|
+
// Canonicalize the temp dir up front: on macOS `TempDir` lives under
|
|
85
|
+
// `/var/...` which is a symlink to `/private/var/...`, and the post-fix
|
|
86
|
+
// `_path` is computed by stripping the *canonical* watch root. Comparing
|
|
87
|
+
// against a canonical base keeps the relative-path assertion portable.
|
|
88
|
+
let canonical_dir = fs::canonicalize(dir.path()).unwrap();
|
|
89
|
+
|
|
90
|
+
let _cwd = CwdGuard::enter(&canonical_dir);
|
|
91
|
+
|
|
92
|
+
// Relative root: the exact shape from the bug report / docs examples.
|
|
93
|
+
let db = DirSQL::new(".", vec![items_table()]).unwrap();
|
|
94
|
+
db.start_watching().unwrap();
|
|
95
|
+
|
|
96
|
+
// Give the watcher a moment to register before mutating the tree.
|
|
97
|
+
std::thread::sleep(Duration::from_millis(250));
|
|
98
|
+
fs::write(canonical_dir.join("apple.txt"), "apple").unwrap();
|
|
99
|
+
|
|
100
|
+
let mut events = Vec::new();
|
|
101
|
+
let deadline = Instant::now() + Duration::from_secs(5);
|
|
102
|
+
while events.is_empty() && Instant::now() < deadline {
|
|
103
|
+
events.extend(db.poll_events(Duration::from_millis(200)).unwrap());
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
let insert = events
|
|
107
|
+
.iter()
|
|
108
|
+
.find(|e| matches!(e, dirsql::RowEvent::Insert { .. }));
|
|
109
|
+
assert!(
|
|
110
|
+
insert.is_some(),
|
|
111
|
+
"relative-root watcher must emit an Insert event (#250); saw: {events:?}"
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
// The relative `_path` must be the root-relative file name, identical to
|
|
115
|
+
// what an absolute root would have produced — proving the canonical
|
|
116
|
+
// watch-root did not leak the absolute prefix into the event path.
|
|
117
|
+
if let Some(dirsql::RowEvent::Insert { row, .. }) = insert {
|
|
118
|
+
assert_eq!(
|
|
119
|
+
row.get("_path"),
|
|
120
|
+
Some(&Value::Text("apple.txt".to_string())),
|
|
121
|
+
"_path must stay root-relative for a relative root"
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// And the row landed in the in-memory index.
|
|
126
|
+
let rows = db.query("SELECT name FROM items").unwrap();
|
|
127
|
+
assert!(
|
|
128
|
+
rows.iter().any(|r| matches!(
|
|
129
|
+
r.get("name"),
|
|
130
|
+
Some(Value::Text(name)) if name == "apple"
|
|
131
|
+
)),
|
|
132
|
+
"indexed rows should include the created file"
|
|
133
|
+
);
|
|
134
|
+
}
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dirsql-0.3.25 → dirsql-0.3.26}/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.25 → dirsql-0.3.26}/packages/python/tests/integration/__fixtures__/data/a/meta.json
RENAMED
|
File without changes
|
{dirsql-0.3.25 → dirsql-0.3.26}/packages/python/tests/integration/__fixtures__/data/b/meta.json
RENAMED
|
File without changes
|
{dirsql-0.3.25 → dirsql-0.3.26}/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
|
|
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
|