dirsql 0.3.26__tar.gz → 0.3.27__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.
Files changed (167) hide show
  1. {dirsql-0.3.26 → dirsql-0.3.27}/Cargo.lock +1 -1
  2. {dirsql-0.3.26 → dirsql-0.3.27}/PKG-INFO +1 -1
  3. {dirsql-0.3.26 → dirsql-0.3.27}/packages/python/Cargo.toml +1 -1
  4. {dirsql-0.3.26 → dirsql-0.3.27}/packages/rust/src/lib.rs +70 -47
  5. {dirsql-0.3.26 → dirsql-0.3.27}/Cargo.toml +0 -0
  6. {dirsql-0.3.26 → dirsql-0.3.27}/README.md +0 -0
  7. {dirsql-0.3.26 → dirsql-0.3.27}/dirsql/__init__.py +0 -0
  8. {dirsql-0.3.26 → dirsql-0.3.27}/dirsql/_async.py +0 -0
  9. {dirsql-0.3.26 → dirsql-0.3.27}/dirsql/_async_test.py +0 -0
  10. {dirsql-0.3.26 → dirsql-0.3.27}/dirsql/_dirsql.pyi +0 -0
  11. {dirsql-0.3.26 → dirsql-0.3.27}/dirsql/cli/__init__.py +0 -0
  12. {dirsql-0.3.26 → dirsql-0.3.27}/dirsql/cli/binary_path.py +0 -0
  13. {dirsql-0.3.26 → dirsql-0.3.27}/dirsql/cli/binary_path_test.py +0 -0
  14. {dirsql-0.3.26 → dirsql-0.3.27}/dirsql/cli/interpret/__init__.py +0 -0
  15. {dirsql-0.3.26 → dirsql-0.3.27}/dirsql/cli/interpret/dispatch_extract.py +0 -0
  16. {dirsql-0.3.26 → dirsql-0.3.27}/dirsql/cli/interpret/dispatch_extract_test.py +0 -0
  17. {dirsql-0.3.26 → dirsql-0.3.27}/dirsql/cli/interpret/load_app.py +0 -0
  18. {dirsql-0.3.26 → dirsql-0.3.27}/dirsql/cli/interpret/load_app_test.py +0 -0
  19. {dirsql-0.3.26 → dirsql-0.3.27}/dirsql/cli/interpret/run.py +0 -0
  20. {dirsql-0.3.26 → dirsql-0.3.27}/dirsql/cli/interpret/run_test.py +0 -0
  21. {dirsql-0.3.26 → dirsql-0.3.27}/dirsql/cli/interpret/write_message.py +0 -0
  22. {dirsql-0.3.26 → dirsql-0.3.27}/dirsql/cli/interpret/write_message_test.py +0 -0
  23. {dirsql-0.3.26 → dirsql-0.3.27}/dirsql/cli/is_windows.py +0 -0
  24. {dirsql-0.3.26 → dirsql-0.3.27}/dirsql/cli/is_windows_test.py +0 -0
  25. {dirsql-0.3.26 → dirsql-0.3.27}/dirsql/cli/main.py +0 -0
  26. {dirsql-0.3.26 → dirsql-0.3.27}/dirsql/cli/main_test.py +0 -0
  27. {dirsql-0.3.26 → dirsql-0.3.27}/dirsql/py.typed +0 -0
  28. {dirsql-0.3.26 → dirsql-0.3.27}/dirsql/resolve_config.py +0 -0
  29. {dirsql-0.3.26 → dirsql-0.3.27}/dirsql/resolve_config_test.py +0 -0
  30. {dirsql-0.3.26 → dirsql-0.3.27}/docs/.claude/CLAUDE.md +0 -0
  31. {dirsql-0.3.26 → dirsql-0.3.27}/docs/.vitepress/config.ts +0 -0
  32. {dirsql-0.3.26 → dirsql-0.3.27}/docs/.vitepress/theme/index.ts +0 -0
  33. {dirsql-0.3.26 → dirsql-0.3.27}/docs/.vitepress/theme/lang.ts +0 -0
  34. {dirsql-0.3.26 → dirsql-0.3.27}/docs/AGENTS.md +0 -0
  35. {dirsql-0.3.26 → dirsql-0.3.27}/docs/api/index.md +0 -0
  36. {dirsql-0.3.26 → dirsql-0.3.27}/docs/cli/config.md +0 -0
  37. {dirsql-0.3.26 → dirsql-0.3.27}/docs/cli/http-api.md +0 -0
  38. {dirsql-0.3.26 → dirsql-0.3.27}/docs/cli/index.md +0 -0
  39. {dirsql-0.3.26 → dirsql-0.3.27}/docs/cli/init.md +0 -0
  40. {dirsql-0.3.26 → dirsql-0.3.27}/docs/cli/server.md +0 -0
  41. {dirsql-0.3.26 → dirsql-0.3.27}/docs/getting-started.md +0 -0
  42. {dirsql-0.3.26 → dirsql-0.3.27}/docs/guide/async.md +0 -0
  43. {dirsql-0.3.26 → dirsql-0.3.27}/docs/guide/crdt.md +0 -0
  44. {dirsql-0.3.26 → dirsql-0.3.27}/docs/guide/persistence.md +0 -0
  45. {dirsql-0.3.26 → dirsql-0.3.27}/docs/guide/querying.md +0 -0
  46. {dirsql-0.3.26 → dirsql-0.3.27}/docs/guide/tables.md +0 -0
  47. {dirsql-0.3.26 → dirsql-0.3.27}/docs/guide/watching.md +0 -0
  48. {dirsql-0.3.26 → dirsql-0.3.27}/docs/index.md +0 -0
  49. {dirsql-0.3.26 → dirsql-0.3.27}/docs/migrations.md +0 -0
  50. {dirsql-0.3.26 → dirsql-0.3.27}/docs/package.json +0 -0
  51. {dirsql-0.3.26 → dirsql-0.3.27}/docs/playwright.config.ts +0 -0
  52. {dirsql-0.3.26 → dirsql-0.3.27}/docs/pnpm-lock.yaml +0 -0
  53. {dirsql-0.3.26 → dirsql-0.3.27}/docs/pnpm-workspace.yaml +0 -0
  54. {dirsql-0.3.26 → dirsql-0.3.27}/docs/tests/integration/home.spec.ts +0 -0
  55. {dirsql-0.3.26 → dirsql-0.3.27}/docs/tests/integration/language-flag.spec.ts +0 -0
  56. {dirsql-0.3.26 → dirsql-0.3.27}/docs/tests/unit/config.test.ts +0 -0
  57. {dirsql-0.3.26 → dirsql-0.3.27}/docs/tests/unit/lang.test.ts +0 -0
  58. {dirsql-0.3.26 → dirsql-0.3.27}/docs/vitest.config.ts +0 -0
  59. {dirsql-0.3.26 → dirsql-0.3.27}/packages/python/README.md +0 -0
  60. {dirsql-0.3.26 → dirsql-0.3.27}/packages/python/conftest.py +0 -0
  61. {dirsql-0.3.26 → dirsql-0.3.27}/packages/python/docs/.claude/CLAUDE.md +0 -0
  62. {dirsql-0.3.26 → dirsql-0.3.27}/packages/python/docs/.vitepress/config.ts +0 -0
  63. {dirsql-0.3.26 → dirsql-0.3.27}/packages/python/docs/.vitepress/theme/index.ts +0 -0
  64. {dirsql-0.3.26 → dirsql-0.3.27}/packages/python/docs/.vitepress/theme/lang.ts +0 -0
  65. {dirsql-0.3.26 → dirsql-0.3.27}/packages/python/docs/AGENTS.md +0 -0
  66. {dirsql-0.3.26 → dirsql-0.3.27}/packages/python/docs/api/index.md +0 -0
  67. {dirsql-0.3.26 → dirsql-0.3.27}/packages/python/docs/cli/config.md +0 -0
  68. {dirsql-0.3.26 → dirsql-0.3.27}/packages/python/docs/cli/http-api.md +0 -0
  69. {dirsql-0.3.26 → dirsql-0.3.27}/packages/python/docs/cli/index.md +0 -0
  70. {dirsql-0.3.26 → dirsql-0.3.27}/packages/python/docs/cli/init.md +0 -0
  71. {dirsql-0.3.26 → dirsql-0.3.27}/packages/python/docs/cli/server.md +0 -0
  72. {dirsql-0.3.26 → dirsql-0.3.27}/packages/python/docs/getting-started.md +0 -0
  73. {dirsql-0.3.26 → dirsql-0.3.27}/packages/python/docs/guide/async.md +0 -0
  74. {dirsql-0.3.26 → dirsql-0.3.27}/packages/python/docs/guide/crdt.md +0 -0
  75. {dirsql-0.3.26 → dirsql-0.3.27}/packages/python/docs/guide/persistence.md +0 -0
  76. {dirsql-0.3.26 → dirsql-0.3.27}/packages/python/docs/guide/querying.md +0 -0
  77. {dirsql-0.3.26 → dirsql-0.3.27}/packages/python/docs/guide/tables.md +0 -0
  78. {dirsql-0.3.26 → dirsql-0.3.27}/packages/python/docs/guide/watching.md +0 -0
  79. {dirsql-0.3.26 → dirsql-0.3.27}/packages/python/docs/index.md +0 -0
  80. {dirsql-0.3.26 → dirsql-0.3.27}/packages/python/docs/migrations.md +0 -0
  81. {dirsql-0.3.26 → dirsql-0.3.27}/packages/python/docs/package.json +0 -0
  82. {dirsql-0.3.26 → dirsql-0.3.27}/packages/python/docs/playwright.config.ts +0 -0
  83. {dirsql-0.3.26 → dirsql-0.3.27}/packages/python/docs/pnpm-lock.yaml +0 -0
  84. {dirsql-0.3.26 → dirsql-0.3.27}/packages/python/docs/pnpm-workspace.yaml +0 -0
  85. {dirsql-0.3.26 → dirsql-0.3.27}/packages/python/docs/tests/integration/home.spec.ts +0 -0
  86. {dirsql-0.3.26 → dirsql-0.3.27}/packages/python/docs/tests/integration/language-flag.spec.ts +0 -0
  87. {dirsql-0.3.26 → dirsql-0.3.27}/packages/python/docs/tests/unit/config.test.ts +0 -0
  88. {dirsql-0.3.26 → dirsql-0.3.27}/packages/python/docs/tests/unit/lang.test.ts +0 -0
  89. {dirsql-0.3.26 → dirsql-0.3.27}/packages/python/docs/vitest.config.ts +0 -0
  90. {dirsql-0.3.26 → dirsql-0.3.27}/packages/python/src/lib.rs +0 -0
  91. {dirsql-0.3.26 → dirsql-0.3.27}/packages/python/tests/__init__.py +0 -0
  92. {dirsql-0.3.26 → dirsql-0.3.27}/packages/python/tests/conftest.py +0 -0
  93. {dirsql-0.3.26 → dirsql-0.3.27}/packages/python/tests/e2e/__init__.py +0 -0
  94. {dirsql-0.3.26 → dirsql-0.3.27}/packages/python/tests/integration/__fixtures__/data/a/meta.json +0 -0
  95. {dirsql-0.3.26 → dirsql-0.3.27}/packages/python/tests/integration/__fixtures__/data/b/meta.json +0 -0
  96. {dirsql-0.3.26 → dirsql-0.3.27}/packages/python/tests/integration/__fixtures__/dirsql.config.py +0 -0
  97. {dirsql-0.3.26 → dirsql-0.3.27}/packages/python/tests/integration/__fixtures__/interpret/data/a/meta.json +0 -0
  98. {dirsql-0.3.26 → dirsql-0.3.27}/packages/python/tests/integration/__fixtures__/interpret/data/b/meta.json +0 -0
  99. {dirsql-0.3.26 → dirsql-0.3.27}/packages/python/tests/integration/__fixtures__/interpret/dirsql.config.py +0 -0
  100. {dirsql-0.3.26 → dirsql-0.3.27}/packages/python/tests/integration/__fixtures__/interpret/dirsql.config_no_app.py +0 -0
  101. {dirsql-0.3.26 → dirsql-0.3.27}/packages/python/tests/integration/__fixtures__/interpret/dirsql.config_raises.py +0 -0
  102. {dirsql-0.3.26 → dirsql-0.3.27}/packages/python/tests/integration/__init__.py +0 -0
  103. {dirsql-0.3.26 → dirsql-0.3.27}/packages/python/tests/integration/interpret_subprocess.py +0 -0
  104. {dirsql-0.3.26 → dirsql-0.3.27}/packages/python/tests/integration/test_async_dirsql.py +0 -0
  105. {dirsql-0.3.26 → dirsql-0.3.27}/packages/python/tests/integration/test_binding.py +0 -0
  106. {dirsql-0.3.26 → dirsql-0.3.27}/packages/python/tests/integration/test_dirsql.py +0 -0
  107. {dirsql-0.3.26 → dirsql-0.3.27}/packages/python/tests/integration/test_docs_examples.py +0 -0
  108. {dirsql-0.3.26 → dirsql-0.3.27}/packages/python/tests/integration/test_docs_gaps.py +0 -0
  109. {dirsql-0.3.26 → dirsql-0.3.27}/packages/python/tests/integration/test_from_config.py +0 -0
  110. {dirsql-0.3.26 → dirsql-0.3.27}/packages/python/tests/integration/test_interpret.py +0 -0
  111. {dirsql-0.3.26 → dirsql-0.3.27}/packages/python/tests/integration/test_native_config.py +0 -0
  112. {dirsql-0.3.26 → dirsql-0.3.27}/packages/python/tests/integration/test_persist.py +0 -0
  113. {dirsql-0.3.26 → dirsql-0.3.27}/packages/python/tests/integration/test_serialization.py +0 -0
  114. {dirsql-0.3.26 → dirsql-0.3.27}/packages/rust/Cargo.toml +0 -0
  115. {dirsql-0.3.26 → dirsql-0.3.27}/packages/rust/README.md +0 -0
  116. {dirsql-0.3.26 → dirsql-0.3.27}/packages/rust/benches/db_bench.rs +0 -0
  117. {dirsql-0.3.26 → dirsql-0.3.27}/packages/rust/benches/differ_bench.rs +0 -0
  118. {dirsql-0.3.26 → dirsql-0.3.27}/packages/rust/benches/matcher_bench.rs +0 -0
  119. {dirsql-0.3.26 → dirsql-0.3.27}/packages/rust/benches/scanner_bench.rs +0 -0
  120. {dirsql-0.3.26 → dirsql-0.3.27}/packages/rust/docs/api/index.md +0 -0
  121. {dirsql-0.3.26 → dirsql-0.3.27}/packages/rust/docs/cli/config.md +0 -0
  122. {dirsql-0.3.26 → dirsql-0.3.27}/packages/rust/docs/cli/http-api.md +0 -0
  123. {dirsql-0.3.26 → dirsql-0.3.27}/packages/rust/docs/cli/index.md +0 -0
  124. {dirsql-0.3.26 → dirsql-0.3.27}/packages/rust/docs/cli/init.md +0 -0
  125. {dirsql-0.3.26 → dirsql-0.3.27}/packages/rust/docs/cli/server.md +0 -0
  126. {dirsql-0.3.26 → dirsql-0.3.27}/packages/rust/docs/getting-started.md +0 -0
  127. {dirsql-0.3.26 → dirsql-0.3.27}/packages/rust/docs/guide/async.md +0 -0
  128. {dirsql-0.3.26 → dirsql-0.3.27}/packages/rust/docs/guide/crdt.md +0 -0
  129. {dirsql-0.3.26 → dirsql-0.3.27}/packages/rust/docs/guide/persistence.md +0 -0
  130. {dirsql-0.3.26 → dirsql-0.3.27}/packages/rust/docs/guide/querying.md +0 -0
  131. {dirsql-0.3.26 → dirsql-0.3.27}/packages/rust/docs/guide/tables.md +0 -0
  132. {dirsql-0.3.26 → dirsql-0.3.27}/packages/rust/docs/guide/watching.md +0 -0
  133. {dirsql-0.3.26 → dirsql-0.3.27}/packages/rust/docs/index.md +0 -0
  134. {dirsql-0.3.26 → dirsql-0.3.27}/packages/rust/docs/migrations.md +0 -0
  135. {dirsql-0.3.26 → dirsql-0.3.27}/packages/rust/src/bin/dirsql.rs +0 -0
  136. {dirsql-0.3.26 → dirsql-0.3.27}/packages/rust/src/cli/init.rs +0 -0
  137. {dirsql-0.3.26 → dirsql-0.3.27}/packages/rust/src/cli/mod.rs +0 -0
  138. {dirsql-0.3.26 → dirsql-0.3.27}/packages/rust/src/cli/native_config.rs +0 -0
  139. {dirsql-0.3.26 → dirsql-0.3.27}/packages/rust/src/cli/router.rs +0 -0
  140. {dirsql-0.3.26 → dirsql-0.3.27}/packages/rust/src/cli/serialize.rs +0 -0
  141. {dirsql-0.3.26 → dirsql-0.3.27}/packages/rust/src/cli/server.rs +0 -0
  142. {dirsql-0.3.26 → dirsql-0.3.27}/packages/rust/src/config.rs +0 -0
  143. {dirsql-0.3.26 → dirsql-0.3.27}/packages/rust/src/db.rs +0 -0
  144. {dirsql-0.3.26 → dirsql-0.3.27}/packages/rust/src/differ.rs +0 -0
  145. {dirsql-0.3.26 → dirsql-0.3.27}/packages/rust/src/matcher.rs +0 -0
  146. {dirsql-0.3.26 → dirsql-0.3.27}/packages/rust/src/persist.rs +0 -0
  147. {dirsql-0.3.26 → dirsql-0.3.27}/packages/rust/src/scanner.rs +0 -0
  148. {dirsql-0.3.26 → dirsql-0.3.27}/packages/rust/src/watcher.rs +0 -0
  149. {dirsql-0.3.26 → dirsql-0.3.27}/packages/rust/tests/async_sdk.rs +0 -0
  150. {dirsql-0.3.26 → dirsql-0.3.27}/packages/rust/tests/cli_e2e.rs +0 -0
  151. {dirsql-0.3.26 → dirsql-0.3.27}/packages/rust/tests/cli_integration.rs +0 -0
  152. {dirsql-0.3.26 → dirsql-0.3.27}/packages/rust/tests/code_review_findings.rs +0 -0
  153. {dirsql-0.3.26 → dirsql-0.3.27}/packages/rust/tests/config.rs +0 -0
  154. {dirsql-0.3.26 → dirsql-0.3.27}/packages/rust/tests/docs_examples.rs +0 -0
  155. {dirsql-0.3.26 → dirsql-0.3.27}/packages/rust/tests/docs_gaps.rs +0 -0
  156. {dirsql-0.3.26 → dirsql-0.3.27}/packages/rust/tests/extensions.rs +0 -0
  157. {dirsql-0.3.26 → dirsql-0.3.27}/packages/rust/tests/from_config.rs +0 -0
  158. {dirsql-0.3.26 → dirsql-0.3.27}/packages/rust/tests/init_e2e.rs +0 -0
  159. {dirsql-0.3.26 → dirsql-0.3.27}/packages/rust/tests/init_integration.rs +0 -0
  160. {dirsql-0.3.26 → dirsql-0.3.27}/packages/rust/tests/persist.rs +0 -0
  161. {dirsql-0.3.26 → dirsql-0.3.27}/packages/rust/tests/readonly_query.rs +0 -0
  162. {dirsql-0.3.26 → dirsql-0.3.27}/packages/rust/tests/scanner.rs +0 -0
  163. {dirsql-0.3.26 → dirsql-0.3.27}/packages/rust/tests/sdk.rs +0 -0
  164. {dirsql-0.3.26 → dirsql-0.3.27}/packages/rust/tests/serialization.rs +0 -0
  165. {dirsql-0.3.26 → dirsql-0.3.27}/packages/rust/tests/watch_relative_root.rs +0 -0
  166. {dirsql-0.3.26 → dirsql-0.3.27}/packages/rust/tests/watcher.rs +0 -0
  167. {dirsql-0.3.26 → dirsql-0.3.27}/pyproject.toml +0 -0
@@ -499,7 +499,7 @@ dependencies = [
499
499
 
500
500
  [[package]]
501
501
  name = "dirsql-py-ext"
502
- version = "0.3.26"
502
+ version = "0.3.27"
503
503
  dependencies = [
504
504
  "dirsql",
505
505
  "pyo3",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dirsql
3
- Version: 0.3.26
3
+ Version: 0.3.27
4
4
  Requires-Dist: pytest>=8 ; extra == 'dev'
5
5
  Requires-Dist: pytest-describe>=2 ; extra == 'dev'
6
6
  Requires-Dist: pytest-asyncio>=0.23 ; extra == 'dev'
@@ -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.26"
7
+ version = "0.3.27"
8
8
  edition.workspace = true
9
9
  publish = false
10
10
  readme = "README.md"
@@ -1484,6 +1484,40 @@ const STAT_CTIME: &str = "_ctime";
1484
1484
  /// (`_path`, `_basename`, `_dir`, `_ext`) and stat-derived (`_size`,
1485
1485
  /// `_mtime`, `_ctime`).
1486
1486
  fn compute_stat_virtuals(rel_path: &str, abs_path: &Path) -> Row {
1487
+ // Read the file's stats once; a missing/unreadable file yields all-`None`,
1488
+ // which `stat_virtuals` renders as absent `_size`/`_mtime`/`_ctime`
1489
+ // columns. `_mtime`/`_ctime` are `None` when the platform can't supply
1490
+ // them (or the value predates the epoch). The pure column-building logic
1491
+ // lives in `stat_virtuals`.
1492
+ let (size, mtime_secs, ctime_secs) = match std::fs::metadata(abs_path) {
1493
+ Ok(metadata) => {
1494
+ let to_secs = |t: std::io::Result<std::time::SystemTime>| {
1495
+ t.ok()
1496
+ .and_then(|st| st.duration_since(std::time::UNIX_EPOCH).ok())
1497
+ .map(|d| d.as_secs() as i64)
1498
+ };
1499
+ (
1500
+ Some(metadata.len() as i64),
1501
+ to_secs(metadata.modified()),
1502
+ to_secs(metadata.created()),
1503
+ )
1504
+ }
1505
+ Err(_) => (None, None, None),
1506
+ };
1507
+ stat_virtuals(rel_path, size, mtime_secs, ctime_secs)
1508
+ }
1509
+
1510
+ /// Pure core of [`compute_stat_virtuals`]: build the filesystem-fact columns
1511
+ /// from the relative path plus already-read stat values (each `None` when the
1512
+ /// corresponding fact is unavailable). Split out so the column-mapping logic
1513
+ /// is unit-testable without touching the filesystem; the metadata read lives
1514
+ /// in the caller.
1515
+ fn stat_virtuals(
1516
+ rel_path: &str,
1517
+ size: Option<i64>,
1518
+ mtime_secs: Option<i64>,
1519
+ ctime_secs: Option<i64>,
1520
+ ) -> Row {
1487
1521
  let mut out = Row::new();
1488
1522
 
1489
1523
  out.insert(STAT_PATH.into(), Value::Text(rel_path.to_string()));
@@ -1511,18 +1545,14 @@ fn compute_stat_virtuals(rel_path: &str, abs_path: &Path) -> Row {
1511
1545
  );
1512
1546
  }
1513
1547
 
1514
- if let Ok(metadata) = std::fs::metadata(abs_path) {
1515
- out.insert(STAT_SIZE.into(), Value::Integer(metadata.len() as i64));
1516
- if let Ok(mtime) = metadata.modified()
1517
- && let Ok(d) = mtime.duration_since(std::time::UNIX_EPOCH)
1518
- {
1519
- out.insert(STAT_MTIME.into(), Value::Integer(d.as_secs() as i64));
1520
- }
1521
- if let Ok(ctime) = metadata.created()
1522
- && let Ok(d) = ctime.duration_since(std::time::UNIX_EPOCH)
1523
- {
1524
- out.insert(STAT_CTIME.into(), Value::Integer(d.as_secs() as i64));
1525
- }
1548
+ if let Some(size) = size {
1549
+ out.insert(STAT_SIZE.into(), Value::Integer(size));
1550
+ }
1551
+ if let Some(mtime) = mtime_secs {
1552
+ out.insert(STAT_MTIME.into(), Value::Integer(mtime));
1553
+ }
1554
+ if let Some(ctime) = ctime_secs {
1555
+ out.insert(STAT_CTIME.into(), Value::Integer(ctime));
1526
1556
  }
1527
1557
 
1528
1558
  out
@@ -1780,26 +1810,29 @@ mod internal_tests {
1780
1810
  use super::*;
1781
1811
  use tempfile::TempDir;
1782
1812
 
1783
- /// A real file with a basename, parent dir, and extension exercises the
1784
- /// `Some` arms of `compute_stat_virtuals`' path inspection plus the
1785
- /// `Ok(metadata)` arm.
1813
+ /// A relative path with a basename, parent dir, and extension exercises the
1814
+ /// `Some` arms of `stat_virtuals`' path inspection, plus the `Some` arms of
1815
+ /// the size/mtime/ctime inserts. The metadata read that supplies those
1816
+ /// values lives in `compute_stat_virtuals` and is covered by the
1817
+ /// integration suite (real-file scans).
1786
1818
  #[test]
1787
- fn compute_stat_virtuals_populates_from_real_file() {
1788
- let dir = TempDir::new().unwrap();
1789
- let abs = dir.path().join("sub.txt");
1790
- std::fs::create_dir_all(dir.path().join("sub")).ok();
1791
- std::fs::write(&abs, b"hello").unwrap();
1792
- let stat = compute_stat_virtuals("nested/sub.txt", &abs);
1819
+ fn stat_virtuals_populates_all_fields() {
1820
+ let stat = stat_virtuals("nested/sub.txt", Some(5), Some(100), Some(50));
1793
1821
  assert_eq!(stat[STAT_PATH], Value::Text("nested/sub.txt".into()));
1794
1822
  assert_eq!(stat[STAT_BASENAME], Value::Text("sub.txt".into()));
1795
1823
  assert_eq!(stat[STAT_DIR], Value::Text("nested".into()));
1796
1824
  assert_eq!(stat[STAT_EXT], Value::Text("txt".into()));
1797
1825
  assert!(matches!(stat.get(STAT_SIZE), Some(Value::Integer(5))));
1826
+ assert!(matches!(stat.get(STAT_MTIME), Some(Value::Integer(100))));
1827
+ assert!(matches!(stat.get(STAT_CTIME), Some(Value::Integer(50))));
1798
1828
  }
1799
1829
 
1800
1830
  /// A bare filename has no parent component and no extension, and a
1801
- /// nonexistent abs path makes `std::fs::metadata` fail. This drives the
1802
- /// skip (false) branches: no `_dir`, no `_ext`, no `_size`/`_mtime`/`_ctime`.
1831
+ /// nonexistent abs path makes `compute_stat_virtuals`' `std::fs::metadata`
1832
+ /// read fail (its `Err` arm -> all-`None`). This drives the skip branches:
1833
+ /// no `_ext`, no `_size`/`_mtime`/`_ctime`. (Calling the real
1834
+ /// `compute_stat_virtuals` with a nonexistent path keeps the test free of a
1835
+ /// direct `std::fs` call while still covering the read-failure arm.)
1803
1836
  #[test]
1804
1837
  fn compute_stat_virtuals_skips_absent_fields() {
1805
1838
  let stat = compute_stat_virtuals("bare", Path::new("/nonexistent-xyz/bare"));
@@ -2158,36 +2191,26 @@ mod internal_tests {
2158
2191
  // public-API integration suite.
2159
2192
  // -----------------------------------------------------------------------
2160
2193
 
2161
- /// Poison a mutex by panicking while holding its guard on a scoped thread.
2194
+ /// Poison a mutex by panicking while holding its guard. `catch_unwind` on
2195
+ /// the current thread does this without `std::thread` (the `unit lint`
2196
+ /// isolation rule keeps effectful std out of unit tests): the guard's
2197
+ /// `Drop` runs during unwinding and marks the mutex poisoned.
2162
2198
  fn poison<T: Send>(m: &Mutex<T>) {
2163
- let _ = std::thread::scope(|s| {
2164
- s.spawn(|| {
2165
- let _g = m.lock().unwrap();
2166
- panic!("poison");
2167
- })
2168
- .join()
2169
- });
2199
+ let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
2200
+ let _g = m.lock().unwrap();
2201
+ panic!("poison");
2202
+ }));
2170
2203
  assert!(m.is_poisoned(), "mutex should be poisoned");
2171
2204
  }
2172
2205
 
2173
- /// Build a one-table `DirSQL` over a temp dir that already contains a
2174
- /// matching file, so the table's extract closure runs during the initial
2175
- /// scan (keeping the closure body out of the uncovered set). Returns the
2176
- /// TempDir guard alongside the db.
2206
+ /// Build a tableless `DirSQL` over an empty temp dir. These tests only
2207
+ /// need a live instance whose inner mutexes can be poisoned, so there is no
2208
+ /// table or file to stage (which keeps `std::fs` out of this unit module).
2209
+ /// Extract-closure coverage lives in the `process_file_event_*` tests.
2177
2210
  fn simple_db() -> (TempDir, DirSQL) {
2178
2211
  let dir = TempDir::new().unwrap();
2179
- std::fs::write(dir.path().join("a.txt"), b"").unwrap();
2180
- let db = DirSQL::with_ignore(
2181
- dir.path(),
2182
- vec![Table::new("CREATE TABLE t (name TEXT)", "*.txt", |_| {
2183
- vec![Row::from_iter([(
2184
- "name".to_string(),
2185
- Value::Text("x".into()),
2186
- )])]
2187
- })],
2188
- Vec::<String>::new(),
2189
- )
2190
- .unwrap();
2212
+ let db =
2213
+ DirSQL::with_ignore(dir.path(), Vec::<Table>::new(), Vec::<String>::new()).unwrap();
2191
2214
  (dir, db)
2192
2215
  }
2193
2216
 
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes