dirsql 0.3.78__tar.gz → 0.3.79__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 (137) hide show
  1. {dirsql-0.3.78 → dirsql-0.3.79}/Cargo.lock +1 -1
  2. {dirsql-0.3.78 → dirsql-0.3.79}/PKG-INFO +1 -1
  3. {dirsql-0.3.78 → dirsql-0.3.79}/packages/python/Cargo.toml +1 -1
  4. {dirsql-0.3.78 → dirsql-0.3.79}/packages/python/e2e-attestation.json +2 -2
  5. {dirsql-0.3.78 → dirsql-0.3.79}/packages/rust/src/db.rs +66 -3
  6. {dirsql-0.3.78 → dirsql-0.3.79}/Cargo.toml +0 -0
  7. {dirsql-0.3.78 → dirsql-0.3.79}/README.md +0 -0
  8. {dirsql-0.3.78 → dirsql-0.3.79}/dirsql/__init__.py +0 -0
  9. {dirsql-0.3.78 → dirsql-0.3.79}/dirsql/_async.py +0 -0
  10. {dirsql-0.3.78 → dirsql-0.3.79}/dirsql/_dirsql.pyi +0 -0
  11. {dirsql-0.3.78 → dirsql-0.3.79}/dirsql/cli/__init__.py +0 -0
  12. {dirsql-0.3.78 → dirsql-0.3.79}/dirsql/cli/binary_path.py +0 -0
  13. {dirsql-0.3.78 → dirsql-0.3.79}/dirsql/cli/is_windows.py +0 -0
  14. {dirsql-0.3.78 → dirsql-0.3.79}/dirsql/cli/main.py +0 -0
  15. {dirsql-0.3.78 → dirsql-0.3.79}/dirsql/cli/resolve_config_extensions.py +0 -0
  16. {dirsql-0.3.78 → dirsql-0.3.79}/dirsql/py.typed +0 -0
  17. {dirsql-0.3.78 → dirsql-0.3.79}/dirsql/resolve_config_extensions.py +0 -0
  18. {dirsql-0.3.78 → dirsql-0.3.79}/dirsql/resolve_extension.py +0 -0
  19. {dirsql-0.3.78 → dirsql-0.3.79}/docs/.claude/CLAUDE.md +0 -0
  20. {dirsql-0.3.78 → dirsql-0.3.79}/docs/.vitepress/config.ts +0 -0
  21. {dirsql-0.3.78 → dirsql-0.3.79}/docs/.vitepress/theme/index.ts +0 -0
  22. {dirsql-0.3.78 → dirsql-0.3.79}/docs/.vitepress/theme/lang.ts +0 -0
  23. {dirsql-0.3.78 → dirsql-0.3.79}/docs/AGENTS.md +0 -0
  24. {dirsql-0.3.78 → dirsql-0.3.79}/docs/explanation.md +0 -0
  25. {dirsql-0.3.78 → dirsql-0.3.79}/docs/getting-started.md +0 -0
  26. {dirsql-0.3.78 → dirsql-0.3.79}/docs/howto/columns-from-paths.md +0 -0
  27. {dirsql-0.3.78 → dirsql-0.3.79}/docs/howto/define-tables.md +0 -0
  28. {dirsql-0.3.78 → dirsql-0.3.79}/docs/howto/embed.md +0 -0
  29. {dirsql-0.3.78 → dirsql-0.3.79}/docs/howto/extract-from-contents.md +0 -0
  30. {dirsql-0.3.78 → dirsql-0.3.79}/docs/howto/load-extension.md +0 -0
  31. {dirsql-0.3.78 → dirsql-0.3.79}/docs/howto/persist.md +0 -0
  32. {dirsql-0.3.78 → dirsql-0.3.79}/docs/howto/react-to-changes.md +0 -0
  33. {dirsql-0.3.78 → dirsql-0.3.79}/docs/howto/search-by-meaning.md +0 -0
  34. {dirsql-0.3.78 → dirsql-0.3.79}/docs/howto/skip-files.md +0 -0
  35. {dirsql-0.3.78 → dirsql-0.3.79}/docs/index.md +0 -0
  36. {dirsql-0.3.78 → dirsql-0.3.79}/docs/migrations.md +0 -0
  37. {dirsql-0.3.78 → dirsql-0.3.79}/docs/package.json +0 -0
  38. {dirsql-0.3.78 → dirsql-0.3.79}/docs/playwright.config.ts +0 -0
  39. {dirsql-0.3.78 → dirsql-0.3.79}/docs/pnpm-lock.yaml +0 -0
  40. {dirsql-0.3.78 → dirsql-0.3.79}/docs/pnpm-workspace.yaml +0 -0
  41. {dirsql-0.3.78 → dirsql-0.3.79}/docs/reference/cli.md +0 -0
  42. {dirsql-0.3.78 → dirsql-0.3.79}/docs/reference/columns.md +0 -0
  43. {dirsql-0.3.78 → dirsql-0.3.79}/docs/reference/config.md +0 -0
  44. {dirsql-0.3.78 → dirsql-0.3.79}/docs/reference/hooks.md +0 -0
  45. {dirsql-0.3.78 → dirsql-0.3.79}/docs/reference/http-api.md +0 -0
  46. {dirsql-0.3.78 → dirsql-0.3.79}/docs/reference/sdk.md +0 -0
  47. {dirsql-0.3.78 → dirsql-0.3.79}/docs/tests/integration/home.spec.ts +0 -0
  48. {dirsql-0.3.78 → dirsql-0.3.79}/docs/tests/integration/language-flag.spec.ts +0 -0
  49. {dirsql-0.3.78 → dirsql-0.3.79}/docs/tests/integration/sidebar.spec.ts +0 -0
  50. {dirsql-0.3.78 → dirsql-0.3.79}/docs/tests/unit/config.test.ts +0 -0
  51. {dirsql-0.3.78 → dirsql-0.3.79}/docs/tests/unit/lang.test.ts +0 -0
  52. {dirsql-0.3.78 → dirsql-0.3.79}/docs/vitest.config.ts +0 -0
  53. {dirsql-0.3.78 → dirsql-0.3.79}/packages/python/README.md +0 -0
  54. {dirsql-0.3.78 → dirsql-0.3.79}/packages/python/conftest.py +0 -0
  55. {dirsql-0.3.78 → dirsql-0.3.79}/packages/python/docs/.claude/CLAUDE.md +0 -0
  56. {dirsql-0.3.78 → dirsql-0.3.79}/packages/python/docs/.vitepress/config.ts +0 -0
  57. {dirsql-0.3.78 → dirsql-0.3.79}/packages/python/docs/.vitepress/theme/index.ts +0 -0
  58. {dirsql-0.3.78 → dirsql-0.3.79}/packages/python/docs/.vitepress/theme/lang.ts +0 -0
  59. {dirsql-0.3.78 → dirsql-0.3.79}/packages/python/docs/AGENTS.md +0 -0
  60. {dirsql-0.3.78 → dirsql-0.3.79}/packages/python/docs/explanation.md +0 -0
  61. {dirsql-0.3.78 → dirsql-0.3.79}/packages/python/docs/getting-started.md +0 -0
  62. {dirsql-0.3.78 → dirsql-0.3.79}/packages/python/docs/howto/columns-from-paths.md +0 -0
  63. {dirsql-0.3.78 → dirsql-0.3.79}/packages/python/docs/howto/define-tables.md +0 -0
  64. {dirsql-0.3.78 → dirsql-0.3.79}/packages/python/docs/howto/embed.md +0 -0
  65. {dirsql-0.3.78 → dirsql-0.3.79}/packages/python/docs/howto/extract-from-contents.md +0 -0
  66. {dirsql-0.3.78 → dirsql-0.3.79}/packages/python/docs/howto/load-extension.md +0 -0
  67. {dirsql-0.3.78 → dirsql-0.3.79}/packages/python/docs/howto/persist.md +0 -0
  68. {dirsql-0.3.78 → dirsql-0.3.79}/packages/python/docs/howto/react-to-changes.md +0 -0
  69. {dirsql-0.3.78 → dirsql-0.3.79}/packages/python/docs/howto/search-by-meaning.md +0 -0
  70. {dirsql-0.3.78 → dirsql-0.3.79}/packages/python/docs/howto/skip-files.md +0 -0
  71. {dirsql-0.3.78 → dirsql-0.3.79}/packages/python/docs/index.md +0 -0
  72. {dirsql-0.3.78 → dirsql-0.3.79}/packages/python/docs/migrations.md +0 -0
  73. {dirsql-0.3.78 → dirsql-0.3.79}/packages/python/docs/package.json +0 -0
  74. {dirsql-0.3.78 → dirsql-0.3.79}/packages/python/docs/playwright.config.ts +0 -0
  75. {dirsql-0.3.78 → dirsql-0.3.79}/packages/python/docs/pnpm-lock.yaml +0 -0
  76. {dirsql-0.3.78 → dirsql-0.3.79}/packages/python/docs/pnpm-workspace.yaml +0 -0
  77. {dirsql-0.3.78 → dirsql-0.3.79}/packages/python/docs/reference/cli.md +0 -0
  78. {dirsql-0.3.78 → dirsql-0.3.79}/packages/python/docs/reference/columns.md +0 -0
  79. {dirsql-0.3.78 → dirsql-0.3.79}/packages/python/docs/reference/config.md +0 -0
  80. {dirsql-0.3.78 → dirsql-0.3.79}/packages/python/docs/reference/hooks.md +0 -0
  81. {dirsql-0.3.78 → dirsql-0.3.79}/packages/python/docs/reference/http-api.md +0 -0
  82. {dirsql-0.3.78 → dirsql-0.3.79}/packages/python/docs/reference/sdk.md +0 -0
  83. {dirsql-0.3.78 → dirsql-0.3.79}/packages/python/docs/tests/integration/home.spec.ts +0 -0
  84. {dirsql-0.3.78 → dirsql-0.3.79}/packages/python/docs/tests/integration/language-flag.spec.ts +0 -0
  85. {dirsql-0.3.78 → dirsql-0.3.79}/packages/python/docs/tests/integration/sidebar.spec.ts +0 -0
  86. {dirsql-0.3.78 → dirsql-0.3.79}/packages/python/docs/tests/unit/config.test.ts +0 -0
  87. {dirsql-0.3.78 → dirsql-0.3.79}/packages/python/docs/tests/unit/lang.test.ts +0 -0
  88. {dirsql-0.3.78 → dirsql-0.3.79}/packages/python/docs/vitest.config.ts +0 -0
  89. {dirsql-0.3.78 → dirsql-0.3.79}/packages/python/src/lib.rs +0 -0
  90. {dirsql-0.3.78 → dirsql-0.3.79}/packages/python/tests/__init__.py +0 -0
  91. {dirsql-0.3.78 → dirsql-0.3.79}/packages/python/tests/binding/__init__.py +0 -0
  92. {dirsql-0.3.78 → dirsql-0.3.79}/packages/python/tests/conftest.py +0 -0
  93. {dirsql-0.3.78 → dirsql-0.3.79}/packages/python/tests/e2e/__init__.py +0 -0
  94. {dirsql-0.3.78 → dirsql-0.3.79}/packages/python/tests/integration/__init__.py +0 -0
  95. {dirsql-0.3.78 → dirsql-0.3.79}/packages/python/tests/smoke/__init__.py +0 -0
  96. {dirsql-0.3.78 → dirsql-0.3.79}/packages/rust/Cargo.toml +0 -0
  97. {dirsql-0.3.78 → dirsql-0.3.79}/packages/rust/README.md +0 -0
  98. {dirsql-0.3.78 → dirsql-0.3.79}/packages/rust/benches/db_bench.rs +0 -0
  99. {dirsql-0.3.78 → dirsql-0.3.79}/packages/rust/benches/differ_bench.rs +0 -0
  100. {dirsql-0.3.78 → dirsql-0.3.79}/packages/rust/benches/matcher_bench.rs +0 -0
  101. {dirsql-0.3.78 → dirsql-0.3.79}/packages/rust/benches/scanner_bench.rs +0 -0
  102. {dirsql-0.3.78 → dirsql-0.3.79}/packages/rust/docs/explanation.md +0 -0
  103. {dirsql-0.3.78 → dirsql-0.3.79}/packages/rust/docs/getting-started.md +0 -0
  104. {dirsql-0.3.78 → dirsql-0.3.79}/packages/rust/docs/howto/columns-from-paths.md +0 -0
  105. {dirsql-0.3.78 → dirsql-0.3.79}/packages/rust/docs/howto/define-tables.md +0 -0
  106. {dirsql-0.3.78 → dirsql-0.3.79}/packages/rust/docs/howto/embed.md +0 -0
  107. {dirsql-0.3.78 → dirsql-0.3.79}/packages/rust/docs/howto/extract-from-contents.md +0 -0
  108. {dirsql-0.3.78 → dirsql-0.3.79}/packages/rust/docs/howto/load-extension.md +0 -0
  109. {dirsql-0.3.78 → dirsql-0.3.79}/packages/rust/docs/howto/persist.md +0 -0
  110. {dirsql-0.3.78 → dirsql-0.3.79}/packages/rust/docs/howto/react-to-changes.md +0 -0
  111. {dirsql-0.3.78 → dirsql-0.3.79}/packages/rust/docs/howto/search-by-meaning.md +0 -0
  112. {dirsql-0.3.78 → dirsql-0.3.79}/packages/rust/docs/howto/skip-files.md +0 -0
  113. {dirsql-0.3.78 → dirsql-0.3.79}/packages/rust/docs/index.md +0 -0
  114. {dirsql-0.3.78 → dirsql-0.3.79}/packages/rust/docs/migrations.md +0 -0
  115. {dirsql-0.3.78 → dirsql-0.3.79}/packages/rust/docs/reference/cli.md +0 -0
  116. {dirsql-0.3.78 → dirsql-0.3.79}/packages/rust/docs/reference/columns.md +0 -0
  117. {dirsql-0.3.78 → dirsql-0.3.79}/packages/rust/docs/reference/config.md +0 -0
  118. {dirsql-0.3.78 → dirsql-0.3.79}/packages/rust/docs/reference/hooks.md +0 -0
  119. {dirsql-0.3.78 → dirsql-0.3.79}/packages/rust/docs/reference/http-api.md +0 -0
  120. {dirsql-0.3.78 → dirsql-0.3.79}/packages/rust/docs/reference/sdk.md +0 -0
  121. {dirsql-0.3.78 → dirsql-0.3.79}/packages/rust/src/bin/dirsql.rs +0 -0
  122. {dirsql-0.3.78 → dirsql-0.3.79}/packages/rust/src/cli/execute.rs +0 -0
  123. {dirsql-0.3.78 → dirsql-0.3.79}/packages/rust/src/cli/init.rs +0 -0
  124. {dirsql-0.3.78 → dirsql-0.3.79}/packages/rust/src/cli/mod.rs +0 -0
  125. {dirsql-0.3.78 → dirsql-0.3.79}/packages/rust/src/cli/router.rs +0 -0
  126. {dirsql-0.3.78 → dirsql-0.3.79}/packages/rust/src/cli/serialize.rs +0 -0
  127. {dirsql-0.3.78 → dirsql-0.3.79}/packages/rust/src/cli/server.rs +0 -0
  128. {dirsql-0.3.78 → dirsql-0.3.79}/packages/rust/src/command.rs +0 -0
  129. {dirsql-0.3.78 → dirsql-0.3.79}/packages/rust/src/config.rs +0 -0
  130. {dirsql-0.3.78 → dirsql-0.3.79}/packages/rust/src/default_config.toml +0 -0
  131. {dirsql-0.3.78 → dirsql-0.3.79}/packages/rust/src/differ.rs +0 -0
  132. {dirsql-0.3.78 → dirsql-0.3.79}/packages/rust/src/lib.rs +0 -0
  133. {dirsql-0.3.78 → dirsql-0.3.79}/packages/rust/src/matcher.rs +0 -0
  134. {dirsql-0.3.78 → dirsql-0.3.79}/packages/rust/src/persist.rs +0 -0
  135. {dirsql-0.3.78 → dirsql-0.3.79}/packages/rust/src/scanner.rs +0 -0
  136. {dirsql-0.3.78 → dirsql-0.3.79}/packages/rust/src/watcher.rs +0 -0
  137. {dirsql-0.3.78 → dirsql-0.3.79}/pyproject.toml +0 -0
@@ -500,7 +500,7 @@ dependencies = [
500
500
 
501
501
  [[package]]
502
502
  name = "dirsql-py-ext"
503
- version = "0.3.78"
503
+ version = "0.3.79"
504
504
  dependencies = [
505
505
  "dirsql",
506
506
  "pyo3",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dirsql
3
- Version: 0.3.78
3
+ Version: 0.3.79
4
4
  Summary: Ephemeral SQL index over a local directory
5
5
  Keywords: sql,filesystem,directory,sqlite,index
6
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.78"
7
+ version = "0.3.79"
8
8
  edition.workspace = true
9
9
  publish = false
10
10
  readme = "README.md"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "command": "uv run python -m pytest tests/e2e/ -x -q",
3
- "ran_at": 1783533003,
3
+ "ran_at": 1783534394,
4
4
  "exit_code": 0,
5
- "commit": "3f25c20a8da1e922cc543bdc1ee43496b7e8ecd9"
5
+ "commit": "b66bf19ce2c568fc2aa08773caf8c708bad462e9"
6
6
  }
@@ -37,6 +37,13 @@ const INTERNAL_TABLE_PREFIX: &str = "_dirsql_";
37
37
  const INTERNAL_TABLE_DENIED_MSG: &str = "not authorized: dirsql's internal bookkeeping tables (the `_dirsql_*` namespace) \
38
38
  are not readable through query()";
39
39
 
40
+ /// The message carried by [`DbError::Unauthorized`] when `query()` rejects an
41
+ /// `ATTACH`/`DETACH`. SQLite classifies both as read-only, so the `readonly()`
42
+ /// gate lets them through; they are effectful (ATTACH creates/opens an
43
+ /// arbitrary database file) and denied at prepare time instead.
44
+ const ATTACH_DENIED_MSG: &str = "not authorized: query() does not permit ATTACH or DETACH; \
45
+ attaching external databases is disabled on this surface";
46
+
40
47
  fn is_internal_table(name: &str) -> bool {
41
48
  name.starts_with(INTERNAL_TABLE_PREFIX)
42
49
  }
@@ -402,16 +409,33 @@ impl Db {
402
409
  /// through `query()`, are unaffected.
403
410
  pub fn query(&self, sql: &str) -> Result<Vec<HashMap<String, Value>>> {
404
411
  use rusqlite::hooks::{AuthAction, AuthContext, Authorization};
412
+ use std::sync::{Arc, Mutex};
405
413
 
414
+ // The authorizer runs at prepare time and may deny for two distinct
415
+ // reasons; it records which one here so the caught error can carry the
416
+ // matching message (the closure must be `'static`, so it owns a clone).
417
+ let denial: Arc<Mutex<Option<&'static str>>> = Arc::new(Mutex::new(None));
418
+ let denial_cb = Arc::clone(&denial);
406
419
  self.conn
407
- .authorizer(Some(|ctx: AuthContext<'_>| match ctx.action {
420
+ .authorizer(Some(move |ctx: AuthContext<'_>| match ctx.action {
408
421
  AuthAction::Read { table_name, .. } if is_internal_table(table_name) => {
422
+ *denial_cb.lock().unwrap() = Some(INTERNAL_TABLE_DENIED_MSG);
409
423
  Authorization::Deny
410
424
  }
411
425
  AuthAction::Pragma {
412
426
  pragma_value: Some(value),
413
427
  ..
414
- } if is_internal_table(value) => Authorization::Deny,
428
+ } if is_internal_table(value) => {
429
+ *denial_cb.lock().unwrap() = Some(INTERNAL_TABLE_DENIED_MSG);
430
+ Authorization::Deny
431
+ }
432
+ // ATTACH/DETACH are the only effectful actions SQLite classifies
433
+ // as read-only, so the `readonly()` gate below can't catch them;
434
+ // deny here before the file is ever created or opened.
435
+ AuthAction::Attach { .. } | AuthAction::Detach { .. } => {
436
+ *denial_cb.lock().unwrap() = Some(ATTACH_DENIED_MSG);
437
+ Authorization::Deny
438
+ }
415
439
  _ => Authorization::Allow,
416
440
  }));
417
441
  let prepared = self.conn.prepare(sql);
@@ -426,7 +450,8 @@ impl Db {
426
450
  if e.sqlite_error_code()
427
451
  == Some(rusqlite::ErrorCode::AuthorizationForStatementDenied) =>
428
452
  {
429
- return Err(DbError::Unauthorized(INTERNAL_TABLE_DENIED_MSG.to_string()));
453
+ let msg = denial.lock().unwrap().unwrap_or(INTERNAL_TABLE_DENIED_MSG);
454
+ return Err(DbError::Unauthorized(msg.to_string()));
430
455
  }
431
456
  Err(e) => return Err(DbError::Sqlite(e)),
432
457
  };
@@ -1057,6 +1082,44 @@ mod tests {
1057
1082
  assert!(INTERNAL_TABLE_DENIED_MSG.contains("_dirsql_"));
1058
1083
  }
1059
1084
 
1085
+ #[test]
1086
+ fn query_denies_attach() {
1087
+ let db = Db::new().unwrap();
1088
+ let err = db
1089
+ .query("ATTACH 'file:should-not-open?mode=ro' AS ext")
1090
+ .unwrap_err();
1091
+ assert!(matches!(err, DbError::Unauthorized(_)), "got: {err}");
1092
+ let msg = err.to_string();
1093
+ assert!(msg.contains("not authorized"), "got: {msg}");
1094
+ assert!(msg.contains("ATTACH"), "got: {msg}");
1095
+ }
1096
+
1097
+ #[test]
1098
+ fn query_denies_detach() {
1099
+ let db = Db::new().unwrap();
1100
+ let err = db.query("DETACH ext").unwrap_err();
1101
+ assert!(matches!(err, DbError::Unauthorized(_)), "got: {err}");
1102
+ assert!(err.to_string().contains("not authorized"), "got: {err}");
1103
+ }
1104
+
1105
+ #[test]
1106
+ fn attach_denied_message_mentions_attach_and_detach() {
1107
+ assert!(ATTACH_DENIED_MSG.contains("not authorized"));
1108
+ assert!(ATTACH_DENIED_MSG.contains("ATTACH"));
1109
+ assert!(ATTACH_DENIED_MSG.contains("DETACH"));
1110
+ }
1111
+
1112
+ #[test]
1113
+ fn query_still_allows_select_after_attach_denial() {
1114
+ // The authorizer is cleared after each query, so a denied ATTACH must
1115
+ // not poison the next normal read.
1116
+ let db = Db::new().unwrap();
1117
+ db.create_table("CREATE TABLE t (id TEXT)").unwrap();
1118
+ let _ = db.query("ATTACH 'x.db' AS ext").unwrap_err();
1119
+ let rows = db.query("SELECT * FROM t").unwrap();
1120
+ assert_eq!(rows.len(), 0);
1121
+ }
1122
+
1060
1123
  #[test]
1061
1124
  fn query_invalid_sql_returns_error() {
1062
1125
  let db = Db::new().unwrap();
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes