pyturso 0.2.0rc15__tar.gz → 0.2.2__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 (179) hide show
  1. {pyturso-0.2.0rc15 → pyturso-0.2.2}/Cargo.lock +27 -27
  2. {pyturso-0.2.0rc15 → pyturso-0.2.2}/Cargo.toml +17 -17
  3. {pyturso-0.2.0rc15 → pyturso-0.2.2}/PKG-INFO +1 -1
  4. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/incremental/compiler.rs +25 -0
  5. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/incremental/filter_operator.rs +216 -0
  6. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/io/unix.rs +3 -2
  7. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/lib.rs +39 -9
  8. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/mvcc/database/mod.rs +14 -2
  9. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/mvcc/database/tests.rs +13 -0
  10. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/translate/index.rs +31 -2
  11. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/translate/insert.rs +7 -1
  12. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/vdbe/builder.rs +6 -0
  13. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/vdbe/execute.rs +90 -16
  14. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/vdbe/explain.rs +14 -0
  15. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/vdbe/insn.rs +13 -0
  16. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/vdbe/sorter.rs +1 -1
  17. {pyturso-0.2.0rc15 → pyturso-0.2.2}/bindings/python/Cargo.toml +0 -0
  18. {pyturso-0.2.0rc15 → pyturso-0.2.2}/bindings/python/build.rs +0 -0
  19. {pyturso-0.2.0rc15 → pyturso-0.2.2}/bindings/python/example.py +0 -0
  20. {pyturso-0.2.0rc15 → pyturso-0.2.2}/bindings/python/requirements-dev.txt +0 -0
  21. {pyturso-0.2.0rc15 → pyturso-0.2.2}/bindings/python/requirements.txt +0 -0
  22. {pyturso-0.2.0rc15 → pyturso-0.2.2}/bindings/python/src/errors.rs +0 -0
  23. {pyturso-0.2.0rc15 → pyturso-0.2.2}/bindings/python/src/lib.rs +0 -0
  24. {pyturso-0.2.0rc15 → pyturso-0.2.2}/bindings/python/tests/__init__.py +0 -0
  25. {pyturso-0.2.0rc15 → pyturso-0.2.2}/bindings/python/tests/test_database.py +0 -0
  26. {pyturso-0.2.0rc15 → pyturso-0.2.2}/bindings/python/turso/__init__.py +0 -0
  27. {pyturso-0.2.0rc15 → pyturso-0.2.2}/bindings/python/turso/py.typed +0 -0
  28. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/Cargo.toml +0 -0
  29. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/assert.rs +0 -0
  30. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/benches/benchmark.rs +0 -0
  31. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/benches/json_benchmark.rs +0 -0
  32. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/benches/mvcc_benchmark.rs +0 -0
  33. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/benches/tpc_h_benchmark.rs +0 -0
  34. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/build.rs +0 -0
  35. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/error.rs +0 -0
  36. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/ext/dynamic.rs +0 -0
  37. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/ext/mod.rs +0 -0
  38. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/ext/vtab_xconnect.rs +0 -0
  39. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/fast_lock.rs +0 -0
  40. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/function.rs +0 -0
  41. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/functions/datetime.rs +0 -0
  42. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/functions/mod.rs +0 -0
  43. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/functions/printf.rs +0 -0
  44. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/functions/strftime.rs +0 -0
  45. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/incremental/aggregate_operator.rs +0 -0
  46. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/incremental/cursor.rs +0 -0
  47. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/incremental/dbsp.rs +0 -0
  48. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/incremental/expr_compiler.rs +0 -0
  49. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/incremental/input_operator.rs +0 -0
  50. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/incremental/join_operator.rs +0 -0
  51. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/incremental/merge_operator.rs +0 -0
  52. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/incremental/mod.rs +0 -0
  53. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/incremental/operator.rs +0 -0
  54. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/incremental/persistence.rs +0 -0
  55. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/incremental/project_operator.rs +0 -0
  56. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/incremental/view.rs +0 -0
  57. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/info.rs +0 -0
  58. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/io/clock.rs +0 -0
  59. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/io/common.rs +0 -0
  60. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/io/generic.rs +0 -0
  61. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/io/io_uring.rs +0 -0
  62. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/io/memory.rs +0 -0
  63. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/io/mod.rs +0 -0
  64. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/io/vfs.rs +0 -0
  65. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/io/windows.rs +0 -0
  66. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/json/cache.rs +0 -0
  67. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/json/error.rs +0 -0
  68. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/json/jsonb.rs +0 -0
  69. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/json/mod.rs +0 -0
  70. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/json/ops.rs +0 -0
  71. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/json/path.rs +0 -0
  72. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/json/vtab.rs +0 -0
  73. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/mvcc/clock.rs +0 -0
  74. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/mvcc/cursor.rs +0 -0
  75. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/mvcc/database/checkpoint_state_machine.rs +0 -0
  76. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/mvcc/mod.rs +0 -0
  77. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/mvcc/persistent_storage/logical_log.rs +0 -0
  78. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/mvcc/persistent_storage/mod.rs +0 -0
  79. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/numeric/mod.rs +0 -0
  80. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/numeric/nonnan.rs +0 -0
  81. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/parameters.rs +0 -0
  82. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/pragma.rs +0 -0
  83. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/pseudo.rs +0 -0
  84. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/schema.rs +0 -0
  85. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/series.rs +0 -0
  86. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/state_machine.rs +0 -0
  87. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/storage/btree.rs +0 -0
  88. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/storage/buffer_pool.rs +0 -0
  89. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/storage/checksum.rs +0 -0
  90. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/storage/database.rs +0 -0
  91. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/storage/encryption.rs +0 -0
  92. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/storage/mod.rs +0 -0
  93. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/storage/page_cache.rs +0 -0
  94. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/storage/pager.rs +0 -0
  95. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/storage/slot_bitmap.rs +0 -0
  96. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/storage/sqlite3_ondisk.rs +0 -0
  97. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/storage/state_machines.rs +0 -0
  98. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/storage/wal.rs +0 -0
  99. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/time/internal.rs +0 -0
  100. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/time/mod.rs +0 -0
  101. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/translate/aggregation.rs +0 -0
  102. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/translate/alter.rs +0 -0
  103. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/translate/analyze.rs +0 -0
  104. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/translate/attach.rs +0 -0
  105. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/translate/collate.rs +0 -0
  106. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/translate/compound_select.rs +0 -0
  107. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/translate/delete.rs +0 -0
  108. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/translate/display.rs +0 -0
  109. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/translate/emitter.rs +0 -0
  110. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/translate/expr.rs +0 -0
  111. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/translate/group_by.rs +0 -0
  112. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/translate/integrity_check.rs +0 -0
  113. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/translate/logical.rs +0 -0
  114. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/translate/main_loop.rs +0 -0
  115. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/translate/mod.rs +0 -0
  116. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/translate/optimizer/OPTIMIZER.md +0 -0
  117. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/translate/optimizer/access_method.rs +0 -0
  118. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/translate/optimizer/constraints.rs +0 -0
  119. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/translate/optimizer/cost.rs +0 -0
  120. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/translate/optimizer/join.rs +0 -0
  121. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/translate/optimizer/lift_common_subexpressions.rs +0 -0
  122. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/translate/optimizer/mod.rs +0 -0
  123. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/translate/optimizer/order.rs +0 -0
  124. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/translate/order_by.rs +0 -0
  125. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/translate/plan.rs +0 -0
  126. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/translate/planner.rs +0 -0
  127. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/translate/pragma.rs +0 -0
  128. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/translate/result_row.rs +0 -0
  129. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/translate/rollback.rs +0 -0
  130. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/translate/schema.rs +0 -0
  131. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/translate/select.rs +0 -0
  132. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/translate/subquery.rs +0 -0
  133. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/translate/transaction.rs +0 -0
  134. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/translate/update.rs +0 -0
  135. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/translate/upsert.rs +0 -0
  136. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/translate/values.rs +0 -0
  137. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/translate/view.rs +0 -0
  138. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/translate/window.rs +0 -0
  139. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/types.rs +0 -0
  140. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/util.rs +0 -0
  141. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/uuid.rs +0 -0
  142. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/vdbe/likeop.rs +0 -0
  143. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/vdbe/metrics.rs +0 -0
  144. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/vdbe/mod.rs +0 -0
  145. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/vector/distance/euclidean.rs +0 -0
  146. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/vector/distance.rs +0 -0
  147. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/vector/mod.rs +0 -0
  148. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/vector/vector_types.rs +0 -0
  149. {pyturso-0.2.0rc15 → pyturso-0.2.2}/core/vtab.rs +0 -0
  150. {pyturso-0.2.0rc15 → pyturso-0.2.2}/extensions/core/Cargo.toml +0 -0
  151. {pyturso-0.2.0rc15 → pyturso-0.2.2}/extensions/core/README.md +0 -0
  152. {pyturso-0.2.0rc15 → pyturso-0.2.2}/extensions/core/build.rs +0 -0
  153. {pyturso-0.2.0rc15 → pyturso-0.2.2}/extensions/core/src/functions.rs +0 -0
  154. {pyturso-0.2.0rc15 → pyturso-0.2.2}/extensions/core/src/lib.rs +0 -0
  155. {pyturso-0.2.0rc15 → pyturso-0.2.2}/extensions/core/src/types.rs +0 -0
  156. {pyturso-0.2.0rc15 → pyturso-0.2.2}/extensions/core/src/vfs_modules.rs +0 -0
  157. {pyturso-0.2.0rc15 → pyturso-0.2.2}/extensions/core/src/vtabs.rs +0 -0
  158. {pyturso-0.2.0rc15 → pyturso-0.2.2}/macros/Cargo.toml +0 -0
  159. {pyturso-0.2.0rc15 → pyturso-0.2.2}/macros/src/ext/agg_derive.rs +0 -0
  160. {pyturso-0.2.0rc15 → pyturso-0.2.2}/macros/src/ext/match_ignore_ascii_case.rs +0 -0
  161. {pyturso-0.2.0rc15 → pyturso-0.2.2}/macros/src/ext/mod.rs +0 -0
  162. {pyturso-0.2.0rc15 → pyturso-0.2.2}/macros/src/ext/scalars.rs +0 -0
  163. {pyturso-0.2.0rc15 → pyturso-0.2.2}/macros/src/ext/vfs_derive.rs +0 -0
  164. {pyturso-0.2.0rc15 → pyturso-0.2.2}/macros/src/ext/vtab_derive.rs +0 -0
  165. {pyturso-0.2.0rc15 → pyturso-0.2.2}/macros/src/lib.rs +0 -0
  166. {pyturso-0.2.0rc15 → pyturso-0.2.2}/parser/Cargo.toml +0 -0
  167. {pyturso-0.2.0rc15 → pyturso-0.2.2}/parser/README.md +0 -0
  168. {pyturso-0.2.0rc15 → pyturso-0.2.2}/parser/benches/parser_benchmark.rs +0 -0
  169. {pyturso-0.2.0rc15 → pyturso-0.2.2}/parser/src/ast/check.rs +0 -0
  170. {pyturso-0.2.0rc15 → pyturso-0.2.2}/parser/src/ast/fmt.rs +0 -0
  171. {pyturso-0.2.0rc15 → pyturso-0.2.2}/parser/src/ast.rs +0 -0
  172. {pyturso-0.2.0rc15 → pyturso-0.2.2}/parser/src/error.rs +0 -0
  173. {pyturso-0.2.0rc15 → pyturso-0.2.2}/parser/src/lexer.rs +0 -0
  174. {pyturso-0.2.0rc15 → pyturso-0.2.2}/parser/src/lib.rs +0 -0
  175. {pyturso-0.2.0rc15 → pyturso-0.2.2}/parser/src/parser.rs +0 -0
  176. {pyturso-0.2.0rc15 → pyturso-0.2.2}/parser/src/token.rs +0 -0
  177. {pyturso-0.2.0rc15 → pyturso-0.2.2}/pyproject.toml +0 -0
  178. {pyturso-0.2.0rc15 → pyturso-0.2.2}/turso/__init__.py +0 -0
  179. {pyturso-0.2.0rc15 → pyturso-0.2.2}/turso/py.typed +0 -0
@@ -690,7 +690,7 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
690
690
 
691
691
  [[package]]
692
692
  name = "core_tester"
693
- version = "0.2.0-pre.15"
693
+ version = "0.2.2"
694
694
  dependencies = [
695
695
  "anyhow",
696
696
  "assert_cmd",
@@ -2278,7 +2278,7 @@ dependencies = [
2278
2278
 
2279
2279
  [[package]]
2280
2280
  name = "limbo_completion"
2281
- version = "0.2.0-pre.15"
2281
+ version = "0.2.2"
2282
2282
  dependencies = [
2283
2283
  "mimalloc",
2284
2284
  "turso_ext",
@@ -2286,7 +2286,7 @@ dependencies = [
2286
2286
 
2287
2287
  [[package]]
2288
2288
  name = "limbo_crypto"
2289
- version = "0.2.0-pre.15"
2289
+ version = "0.2.2"
2290
2290
  dependencies = [
2291
2291
  "blake3",
2292
2292
  "data-encoding",
@@ -2299,7 +2299,7 @@ dependencies = [
2299
2299
 
2300
2300
  [[package]]
2301
2301
  name = "limbo_csv"
2302
- version = "0.2.0-pre.15"
2302
+ version = "0.2.2"
2303
2303
  dependencies = [
2304
2304
  "csv",
2305
2305
  "mimalloc",
@@ -2309,7 +2309,7 @@ dependencies = [
2309
2309
 
2310
2310
  [[package]]
2311
2311
  name = "limbo_fuzzy"
2312
- version = "0.2.0-pre.15"
2312
+ version = "0.2.2"
2313
2313
  dependencies = [
2314
2314
  "mimalloc",
2315
2315
  "turso_ext",
@@ -2317,7 +2317,7 @@ dependencies = [
2317
2317
 
2318
2318
  [[package]]
2319
2319
  name = "limbo_ipaddr"
2320
- version = "0.2.0-pre.15"
2320
+ version = "0.2.2"
2321
2321
  dependencies = [
2322
2322
  "ipnetwork",
2323
2323
  "mimalloc",
@@ -2326,7 +2326,7 @@ dependencies = [
2326
2326
 
2327
2327
  [[package]]
2328
2328
  name = "limbo_percentile"
2329
- version = "0.2.0-pre.15"
2329
+ version = "0.2.2"
2330
2330
  dependencies = [
2331
2331
  "mimalloc",
2332
2332
  "turso_ext",
@@ -2334,7 +2334,7 @@ dependencies = [
2334
2334
 
2335
2335
  [[package]]
2336
2336
  name = "limbo_regexp"
2337
- version = "0.2.0-pre.15"
2337
+ version = "0.2.2"
2338
2338
  dependencies = [
2339
2339
  "mimalloc",
2340
2340
  "regex",
@@ -2343,7 +2343,7 @@ dependencies = [
2343
2343
 
2344
2344
  [[package]]
2345
2345
  name = "limbo_sim"
2346
- version = "0.2.0-pre.15"
2346
+ version = "0.2.2"
2347
2347
  dependencies = [
2348
2348
  "anyhow",
2349
2349
  "bitmaps",
@@ -2380,7 +2380,7 @@ dependencies = [
2380
2380
 
2381
2381
  [[package]]
2382
2382
  name = "limbo_sqlite_test_ext"
2383
- version = "0.2.0-pre.15"
2383
+ version = "0.2.2"
2384
2384
  dependencies = [
2385
2385
  "cc",
2386
2386
  ]
@@ -3116,7 +3116,7 @@ dependencies = [
3116
3116
 
3117
3117
  [[package]]
3118
3118
  name = "py-turso"
3119
- version = "0.2.0-pre.15"
3119
+ version = "0.2.2"
3120
3120
  dependencies = [
3121
3121
  "anyhow",
3122
3122
  "pyo3",
@@ -3843,7 +3843,7 @@ checksum = "d372029cb5195f9ab4e4b9aef550787dce78b124fcaee8d82519925defcd6f0d"
3843
3843
 
3844
3844
  [[package]]
3845
3845
  name = "sql_generation"
3846
- version = "0.2.0-pre.15"
3846
+ version = "0.2.2"
3847
3847
  dependencies = [
3848
3848
  "anarchist-readable-name-generator-lib 0.2.0",
3849
3849
  "anyhow",
@@ -4357,7 +4357,7 @@ dependencies = [
4357
4357
 
4358
4358
  [[package]]
4359
4359
  name = "turso"
4360
- version = "0.2.0-pre.15"
4360
+ version = "0.2.2"
4361
4361
  dependencies = [
4362
4362
  "rand 0.9.2",
4363
4363
  "rand_chacha 0.9.0",
@@ -4369,7 +4369,7 @@ dependencies = [
4369
4369
 
4370
4370
  [[package]]
4371
4371
  name = "turso-java"
4372
- version = "0.2.0-pre.15"
4372
+ version = "0.2.2"
4373
4373
  dependencies = [
4374
4374
  "jni",
4375
4375
  "thiserror 2.0.16",
@@ -4378,7 +4378,7 @@ dependencies = [
4378
4378
 
4379
4379
  [[package]]
4380
4380
  name = "turso_cli"
4381
- version = "0.2.0-pre.15"
4381
+ version = "0.2.2"
4382
4382
  dependencies = [
4383
4383
  "anyhow",
4384
4384
  "cfg-if",
@@ -4414,7 +4414,7 @@ dependencies = [
4414
4414
 
4415
4415
  [[package]]
4416
4416
  name = "turso_core"
4417
- version = "0.2.0-pre.15"
4417
+ version = "0.2.2"
4418
4418
  dependencies = [
4419
4419
  "aegis",
4420
4420
  "aes",
@@ -4473,7 +4473,7 @@ dependencies = [
4473
4473
 
4474
4474
  [[package]]
4475
4475
  name = "turso_dart"
4476
- version = "0.2.0-pre.15"
4476
+ version = "0.2.2"
4477
4477
  dependencies = [
4478
4478
  "flutter_rust_bridge",
4479
4479
  "turso_core",
@@ -4481,7 +4481,7 @@ dependencies = [
4481
4481
 
4482
4482
  [[package]]
4483
4483
  name = "turso_ext"
4484
- version = "0.2.0-pre.15"
4484
+ version = "0.2.2"
4485
4485
  dependencies = [
4486
4486
  "chrono",
4487
4487
  "getrandom 0.3.2",
@@ -4490,7 +4490,7 @@ dependencies = [
4490
4490
 
4491
4491
  [[package]]
4492
4492
  name = "turso_ext_tests"
4493
- version = "0.2.0-pre.15"
4493
+ version = "0.2.2"
4494
4494
  dependencies = [
4495
4495
  "env_logger 0.11.7",
4496
4496
  "lazy_static",
@@ -4501,7 +4501,7 @@ dependencies = [
4501
4501
 
4502
4502
  [[package]]
4503
4503
  name = "turso_macros"
4504
- version = "0.2.0-pre.15"
4504
+ version = "0.2.2"
4505
4505
  dependencies = [
4506
4506
  "proc-macro2",
4507
4507
  "quote",
@@ -4510,7 +4510,7 @@ dependencies = [
4510
4510
 
4511
4511
  [[package]]
4512
4512
  name = "turso_node"
4513
- version = "0.2.0-pre.15"
4513
+ version = "0.2.2"
4514
4514
  dependencies = [
4515
4515
  "chrono",
4516
4516
  "napi",
@@ -4523,7 +4523,7 @@ dependencies = [
4523
4523
 
4524
4524
  [[package]]
4525
4525
  name = "turso_parser"
4526
- version = "0.2.0-pre.15"
4526
+ version = "0.2.2"
4527
4527
  dependencies = [
4528
4528
  "bitflags 2.9.4",
4529
4529
  "criterion",
@@ -4539,7 +4539,7 @@ dependencies = [
4539
4539
 
4540
4540
  [[package]]
4541
4541
  name = "turso_sqlite3"
4542
- version = "0.2.0-pre.15"
4542
+ version = "0.2.2"
4543
4543
  dependencies = [
4544
4544
  "env_logger 0.11.7",
4545
4545
  "libc",
@@ -4552,7 +4552,7 @@ dependencies = [
4552
4552
 
4553
4553
  [[package]]
4554
4554
  name = "turso_stress"
4555
- version = "0.2.0-pre.15"
4555
+ version = "0.2.2"
4556
4556
  dependencies = [
4557
4557
  "anarchist-readable-name-generator-lib 0.1.2",
4558
4558
  "antithesis_sdk",
@@ -4568,7 +4568,7 @@ dependencies = [
4568
4568
 
4569
4569
  [[package]]
4570
4570
  name = "turso_sync_engine"
4571
- version = "0.2.0-pre.15"
4571
+ version = "0.2.2"
4572
4572
  dependencies = [
4573
4573
  "base64",
4574
4574
  "bytes",
@@ -4595,7 +4595,7 @@ dependencies = [
4595
4595
 
4596
4596
  [[package]]
4597
4597
  name = "turso_sync_js"
4598
- version = "0.2.0-pre.15"
4598
+ version = "0.2.2"
4599
4599
  dependencies = [
4600
4600
  "genawaiter",
4601
4601
  "napi",
@@ -4610,7 +4610,7 @@ dependencies = [
4610
4610
 
4611
4611
  [[package]]
4612
4612
  name = "turso_whopper"
4613
- version = "0.2.0-pre.15"
4613
+ version = "0.2.2"
4614
4614
  dependencies = [
4615
4615
  "anyhow",
4616
4616
  "clap",
@@ -8,29 +8,29 @@ exclude = [
8
8
  ]
9
9
 
10
10
  [workspace.package]
11
- version = "0.2.0-pre.15"
11
+ version = "0.2.2"
12
12
  authors = ["the Limbo authors"]
13
13
  edition = "2021"
14
14
  license = "MIT"
15
15
  repository = "https://github.com/tursodatabase/turso"
16
16
 
17
17
  [workspace.dependencies]
18
- turso = { path = "bindings/rust", version = "0.2.0-pre.15" }
19
- turso_node = { path = "bindings/javascript", version = "0.2.0-pre.15" }
20
- limbo_completion = { path = "extensions/completion", version = "0.2.0-pre.15" }
21
- turso_core = { path = "core", version = "0.2.0-pre.15" }
22
- turso_sync_engine = { path = "sync/engine", version = "0.2.0-pre.15" }
23
- limbo_crypto = { path = "extensions/crypto", version = "0.2.0-pre.15" }
24
- limbo_csv = { path = "extensions/csv", version = "0.2.0-pre.15" }
25
- turso_ext = { path = "extensions/core", version = "0.2.0-pre.15" }
26
- turso_ext_tests = { path = "extensions/tests", version = "0.2.0-pre.15" }
27
- limbo_ipaddr = { path = "extensions/ipaddr", version = "0.2.0-pre.15" }
28
- turso_macros = { path = "macros", version = "0.2.0-pre.15" }
29
- limbo_percentile = { path = "extensions/percentile", version = "0.2.0-pre.15" }
30
- limbo_regexp = { path = "extensions/regexp", version = "0.2.0-pre.15" }
31
- limbo_uuid = { path = "extensions/uuid", version = "0.2.0-pre.15" }
32
- turso_parser = { path = "parser", version = "0.2.0-pre.15" }
33
- limbo_fuzzy = { path = "extensions/fuzzy", version = "0.2.0-pre.15" }
18
+ turso = { path = "bindings/rust", version = "0.2.2" }
19
+ turso_node = { path = "bindings/javascript", version = "0.2.2" }
20
+ limbo_completion = { path = "extensions/completion", version = "0.2.2" }
21
+ turso_core = { path = "core", version = "0.2.2" }
22
+ turso_sync_engine = { path = "sync/engine", version = "0.2.2" }
23
+ limbo_crypto = { path = "extensions/crypto", version = "0.2.2" }
24
+ limbo_csv = { path = "extensions/csv", version = "0.2.2" }
25
+ turso_ext = { path = "extensions/core", version = "0.2.2" }
26
+ turso_ext_tests = { path = "extensions/tests", version = "0.2.2" }
27
+ limbo_ipaddr = { path = "extensions/ipaddr", version = "0.2.2" }
28
+ turso_macros = { path = "macros", version = "0.2.2" }
29
+ limbo_percentile = { path = "extensions/percentile", version = "0.2.2" }
30
+ limbo_regexp = { path = "extensions/regexp", version = "0.2.2" }
31
+ limbo_uuid = { path = "extensions/uuid", version = "0.2.2" }
32
+ turso_parser = { path = "parser", version = "0.2.2" }
33
+ limbo_fuzzy = { path = "extensions/fuzzy", version = "0.2.2" }
34
34
  sql_generation = { path = "sql_generation" }
35
35
  strum = { version = "0.26", features = ["derive"] }
36
36
  strum_macros = "0.26"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyturso
3
- Version: 0.2.0rc15
3
+ Version: 0.2.2
4
4
  Classifier: Development Status :: 3 - Alpha
5
5
  Classifier: Programming Language :: Python
6
6
  Classifier: Programming Language :: Python :: 3
@@ -2149,6 +2149,31 @@ impl DbspCompiler {
2149
2149
  ))
2150
2150
  }
2151
2151
  }
2152
+ LogicalExpr::IsNull { expr, negated } => {
2153
+ // Extract column index from the inner expression
2154
+ if let LogicalExpr::Column(col) = expr.as_ref() {
2155
+ let column_idx = schema
2156
+ .columns
2157
+ .iter()
2158
+ .position(|c| c.name == col.name)
2159
+ .ok_or_else(|| {
2160
+ LimboError::ParseError(format!(
2161
+ "Column '{}' not found in schema for IS NULL filter",
2162
+ col.name
2163
+ ))
2164
+ })?;
2165
+
2166
+ if *negated {
2167
+ Ok(FilterPredicate::IsNotNull { column_idx })
2168
+ } else {
2169
+ Ok(FilterPredicate::IsNull { column_idx })
2170
+ }
2171
+ } else {
2172
+ Err(LimboError::ParseError(
2173
+ "IS NULL/IS NOT NULL expects a column reference".to_string(),
2174
+ ))
2175
+ }
2176
+ }
2152
2177
  _ => Err(LimboError::ParseError(format!(
2153
2178
  "Unsupported filter expression: {expr:?}"
2154
2179
  ))),
@@ -39,6 +39,11 @@ pub enum FilterPredicate {
39
39
  /// Column <= Column comparisons
40
40
  ColumnLessThanOrEqual { left_idx: usize, right_idx: usize },
41
41
 
42
+ /// Column IS NULL check
43
+ IsNull { column_idx: usize },
44
+ /// Column IS NOT NULL check
45
+ IsNotNull { column_idx: usize },
46
+
42
47
  /// Logical AND of two predicates
43
48
  And(Box<FilterPredicate>, Box<FilterPredicate>),
44
49
  /// Logical OR of two predicates
@@ -214,6 +219,18 @@ impl FilterOperator {
214
219
  }
215
220
  false
216
221
  }
222
+ FilterPredicate::IsNull { column_idx } => {
223
+ if let Some(v) = values.get(*column_idx) {
224
+ return matches!(v, Value::Null);
225
+ }
226
+ false
227
+ }
228
+ FilterPredicate::IsNotNull { column_idx } => {
229
+ if let Some(v) = values.get(*column_idx) {
230
+ return !matches!(v, Value::Null);
231
+ }
232
+ false
233
+ }
217
234
  }
218
235
  }
219
236
  }
@@ -293,3 +310,202 @@ impl IncrementalOperator for FilterOperator {
293
310
  self.tracker = Some(tracker);
294
311
  }
295
312
  }
313
+
314
+ #[cfg(test)]
315
+ mod tests {
316
+ use super::*;
317
+ use crate::types::Text;
318
+
319
+ #[test]
320
+ fn test_is_null_predicate() {
321
+ let predicate = FilterPredicate::IsNull { column_idx: 1 };
322
+ let filter = FilterOperator::new(predicate);
323
+
324
+ // Test with NULL value
325
+ let values_with_null = vec![
326
+ Value::Integer(1),
327
+ Value::Null,
328
+ Value::Text(Text::from("test")),
329
+ ];
330
+ assert!(filter.evaluate_predicate(&values_with_null));
331
+
332
+ // Test with non-NULL value
333
+ let values_without_null = vec![
334
+ Value::Integer(1),
335
+ Value::Integer(42),
336
+ Value::Text(Text::from("test")),
337
+ ];
338
+ assert!(!filter.evaluate_predicate(&values_without_null));
339
+
340
+ // Test with different non-NULL types
341
+ let values_with_text = vec![
342
+ Value::Integer(1),
343
+ Value::Text(Text::from("not null")),
344
+ Value::Text(Text::from("test")),
345
+ ];
346
+ assert!(!filter.evaluate_predicate(&values_with_text));
347
+
348
+ let values_with_blob = vec![
349
+ Value::Integer(1),
350
+ Value::Blob(vec![1, 2, 3]),
351
+ Value::Text(Text::from("test")),
352
+ ];
353
+ assert!(!filter.evaluate_predicate(&values_with_blob));
354
+ }
355
+
356
+ #[test]
357
+ fn test_is_not_null_predicate() {
358
+ let predicate = FilterPredicate::IsNotNull { column_idx: 1 };
359
+ let filter = FilterOperator::new(predicate);
360
+
361
+ // Test with NULL value
362
+ let values_with_null = vec![
363
+ Value::Integer(1),
364
+ Value::Null,
365
+ Value::Text(Text::from("test")),
366
+ ];
367
+ assert!(!filter.evaluate_predicate(&values_with_null));
368
+
369
+ // Test with non-NULL value (Integer)
370
+ let values_with_integer = vec![
371
+ Value::Integer(1),
372
+ Value::Integer(42),
373
+ Value::Text(Text::from("test")),
374
+ ];
375
+ assert!(filter.evaluate_predicate(&values_with_integer));
376
+
377
+ // Test with non-NULL value (Text)
378
+ let values_with_text = vec![
379
+ Value::Integer(1),
380
+ Value::Text(Text::from("not null")),
381
+ Value::Text(Text::from("test")),
382
+ ];
383
+ assert!(filter.evaluate_predicate(&values_with_text));
384
+
385
+ // Test with non-NULL value (Blob)
386
+ let values_with_blob = vec![
387
+ Value::Integer(1),
388
+ Value::Blob(vec![1, 2, 3]),
389
+ Value::Text(Text::from("test")),
390
+ ];
391
+ assert!(filter.evaluate_predicate(&values_with_blob));
392
+ }
393
+
394
+ #[test]
395
+ fn test_is_null_with_and() {
396
+ // Test: column_0 = 1 AND column_1 IS NULL
397
+ let predicate = FilterPredicate::And(
398
+ Box::new(FilterPredicate::Equals {
399
+ column_idx: 0,
400
+ value: Value::Integer(1),
401
+ }),
402
+ Box::new(FilterPredicate::IsNull { column_idx: 1 }),
403
+ );
404
+ let filter = FilterOperator::new(predicate);
405
+
406
+ // Should match: column_0 = 1 AND column_1 IS NULL
407
+ let values_match = vec![
408
+ Value::Integer(1),
409
+ Value::Null,
410
+ Value::Text(Text::from("test")),
411
+ ];
412
+ assert!(filter.evaluate_predicate(&values_match));
413
+
414
+ // Should not match: column_0 = 2 AND column_1 IS NULL
415
+ let values_wrong_first = vec![
416
+ Value::Integer(2),
417
+ Value::Null,
418
+ Value::Text(Text::from("test")),
419
+ ];
420
+ assert!(!filter.evaluate_predicate(&values_wrong_first));
421
+
422
+ // Should not match: column_0 = 1 AND column_1 IS NOT NULL
423
+ let values_not_null = vec![
424
+ Value::Integer(1),
425
+ Value::Integer(42),
426
+ Value::Text(Text::from("test")),
427
+ ];
428
+ assert!(!filter.evaluate_predicate(&values_not_null));
429
+ }
430
+
431
+ #[test]
432
+ fn test_is_not_null_with_or() {
433
+ // Test: column_0 = 1 OR column_1 IS NOT NULL
434
+ let predicate = FilterPredicate::Or(
435
+ Box::new(FilterPredicate::Equals {
436
+ column_idx: 0,
437
+ value: Value::Integer(1),
438
+ }),
439
+ Box::new(FilterPredicate::IsNotNull { column_idx: 1 }),
440
+ );
441
+ let filter = FilterOperator::new(predicate);
442
+
443
+ // Should match: column_0 = 1 (regardless of column_1)
444
+ let values_first_matches = vec![
445
+ Value::Integer(1),
446
+ Value::Null,
447
+ Value::Text(Text::from("test")),
448
+ ];
449
+ assert!(filter.evaluate_predicate(&values_first_matches));
450
+
451
+ // Should match: column_1 IS NOT NULL (regardless of column_0)
452
+ let values_second_matches = vec![
453
+ Value::Integer(2),
454
+ Value::Integer(42),
455
+ Value::Text(Text::from("test")),
456
+ ];
457
+ assert!(filter.evaluate_predicate(&values_second_matches));
458
+
459
+ // Should not match: column_0 != 1 AND column_1 IS NULL
460
+ let values_no_match = vec![
461
+ Value::Integer(2),
462
+ Value::Null,
463
+ Value::Text(Text::from("test")),
464
+ ];
465
+ assert!(!filter.evaluate_predicate(&values_no_match));
466
+ }
467
+
468
+ #[test]
469
+ fn test_complex_null_predicates() {
470
+ // Test: (column_0 IS NULL OR column_1 IS NOT NULL) AND column_2 = 'test'
471
+ let predicate = FilterPredicate::And(
472
+ Box::new(FilterPredicate::Or(
473
+ Box::new(FilterPredicate::IsNull { column_idx: 0 }),
474
+ Box::new(FilterPredicate::IsNotNull { column_idx: 1 }),
475
+ )),
476
+ Box::new(FilterPredicate::Equals {
477
+ column_idx: 2,
478
+ value: Value::Text(Text::from("test")),
479
+ }),
480
+ );
481
+ let filter = FilterOperator::new(predicate);
482
+
483
+ // Should match: column_0 IS NULL, column_2 = 'test'
484
+ let values1 = vec![Value::Null, Value::Null, Value::Text(Text::from("test"))];
485
+ assert!(filter.evaluate_predicate(&values1));
486
+
487
+ // Should match: column_1 IS NOT NULL, column_2 = 'test'
488
+ let values2 = vec![
489
+ Value::Integer(1),
490
+ Value::Integer(42),
491
+ Value::Text(Text::from("test")),
492
+ ];
493
+ assert!(filter.evaluate_predicate(&values2));
494
+
495
+ // Should not match: column_2 != 'test'
496
+ let values3 = vec![
497
+ Value::Null,
498
+ Value::Integer(42),
499
+ Value::Text(Text::from("other")),
500
+ ];
501
+ assert!(!filter.evaluate_predicate(&values3));
502
+
503
+ // Should not match: column_0 IS NOT NULL AND column_1 IS NULL AND column_2 = 'test'
504
+ let values4 = vec![
505
+ Value::Integer(1),
506
+ Value::Null,
507
+ Value::Text(Text::from("test")),
508
+ ];
509
+ assert!(!filter.evaluate_predicate(&values4));
510
+ }
511
+ }
@@ -50,8 +50,9 @@ fn try_pwritev_raw(
50
50
  let mut iov_count = 0;
51
51
  for (i, b) in bufs.iter().enumerate().skip(start_idx).take(iov_len) {
52
52
  let s = b.as_slice();
53
- let ptr = if i == start_idx { &s[start_off..] } else { s }.as_ptr();
54
- let len = b.len();
53
+ let slice = if i == start_idx { &s[start_off..] } else { s };
54
+ let ptr = slice.as_ptr();
55
+ let len = slice.len();
55
56
 
56
57
  if let Some((last_ptr, last_len)) = last_end {
57
58
  // Check if this buffer is adjacent to the last
@@ -62,6 +62,7 @@ pub use io::{
62
62
  };
63
63
  use parking_lot::RwLock;
64
64
  use schema::Schema;
65
+ use std::cell::Cell;
65
66
  use std::{
66
67
  borrow::Cow,
67
68
  cell::RefCell,
@@ -217,7 +218,7 @@ pub struct Database {
217
218
  shared_wal: Arc<RwLock<WalFileShared>>,
218
219
  db_state: Arc<AtomicDbState>,
219
220
  init_lock: Arc<Mutex<()>>,
220
- open_flags: OpenFlags,
221
+ open_flags: Cell<OpenFlags>,
221
222
  builtin_syms: RwLock<SymbolTable>,
222
223
  opts: DatabaseOpts,
223
224
  n_connections: AtomicUsize,
@@ -231,7 +232,7 @@ impl fmt::Debug for Database {
231
232
  let mut debug_struct = f.debug_struct("Database");
232
233
  debug_struct
233
234
  .field("path", &self.path)
234
- .field("open_flags", &self.open_flags);
235
+ .field("open_flags", &self.open_flags.get());
235
236
 
236
237
  // Database state information
237
238
  let db_state_value = match self.db_state.get() {
@@ -407,6 +408,31 @@ impl Database {
407
408
  opts: DatabaseOpts,
408
409
  encryption_opts: Option<EncryptionOpts>,
409
410
  ) -> Result<Arc<Database>> {
411
+ // Currently, if a non-zero-sized WAL file exists, the database cannot be opened in MVCC mode.
412
+ // FIXME: this should initiate immediate checkpoint from WAL->DB in MVCC mode.
413
+ if opts.enable_mvcc
414
+ && std::path::Path::exists(std::path::Path::new(wal_path))
415
+ && std::path::Path::new(wal_path).metadata().unwrap().len() > 0
416
+ {
417
+ return Err(LimboError::InvalidArgument(format!(
418
+ "WAL file exists for database {path}, but MVCC is enabled. This is currently not supported. Open the database in non-MVCC mode and run PRAGMA wal_checkpoint(TRUNCATE) to truncate the WAL."
419
+ )));
420
+ }
421
+
422
+ // If a non-zero-sized logical log file exists, the database cannot be opened in non-MVCC mode,
423
+ // because the changes in the logical log would not be visible to the non-MVCC connections.
424
+ if !opts.enable_mvcc {
425
+ let db_path = std::path::Path::new(path);
426
+ let log_path = db_path.with_extension("db-log");
427
+ if std::path::Path::exists(log_path.as_path())
428
+ && log_path.as_path().metadata().unwrap().len() > 0
429
+ {
430
+ return Err(LimboError::InvalidArgument(format!(
431
+ "MVCC logical log file exists for database {path}, but MVCC is disabled. This is not supported. Open the database in MVCC mode and run PRAGMA wal_checkpoint(TRUNCATE) to truncate the logical log.",
432
+ )));
433
+ }
434
+ }
435
+
410
436
  let shared_wal = WalFileShared::open_shared_if_exists(&io, wal_path)?;
411
437
 
412
438
  let mv_store = if opts.enable_mvcc {
@@ -443,7 +469,7 @@ impl Database {
443
469
  db_file,
444
470
  builtin_syms: syms.into(),
445
471
  io: io.clone(),
446
- open_flags: flags,
472
+ open_flags: flags.into(),
447
473
  db_state: Arc::new(AtomicDbState::new(db_state)),
448
474
  init_lock: Arc::new(Mutex::new(())),
449
475
  opts,
@@ -574,7 +600,7 @@ impl Database {
574
600
  }
575
601
 
576
602
  pub fn is_readonly(&self) -> bool {
577
- self.open_flags.contains(OpenFlags::ReadOnly)
603
+ self.open_flags.get().contains(OpenFlags::ReadOnly)
578
604
  }
579
605
 
580
606
  /// If we do not have a physical WAL file, but we know the database file is initialized on disk,
@@ -1502,9 +1528,7 @@ impl Connection {
1502
1528
  db_opts: DatabaseOpts,
1503
1529
  io: Arc<dyn IO>,
1504
1530
  ) -> Result<Arc<Database>> {
1505
- let mut opts = OpenOptions::parse(uri)?;
1506
- // FIXME: for now, only support read only attach
1507
- opts.mode = OpenMode::ReadOnly;
1531
+ let opts = OpenOptions::parse(uri)?;
1508
1532
  let flags = opts.get_flags()?;
1509
1533
  let io = opts.vfs.map(Database::io_for_vfs).unwrap_or(Ok(io))?;
1510
1534
  let db = Database::open_file_with_flags(io.clone(), &opts.path, flags, db_opts, None)?;
@@ -2065,9 +2089,15 @@ impl Connection {
2065
2089
  .with_indexes(use_indexes)
2066
2090
  .with_views(use_views)
2067
2091
  .with_strict(use_strict);
2068
- let db = Self::from_uri_attached(path, db_opts, self.db.io.clone())?;
2092
+ let io: Arc<dyn IO> = if path.contains(":memory:") {
2093
+ Arc::new(MemoryIO::new())
2094
+ } else {
2095
+ Arc::new(PlatformIO::new()?)
2096
+ };
2097
+ let db = Self::from_uri_attached(path, db_opts, io)?;
2069
2098
  let pager = Arc::new(db.init_pager(None)?);
2070
-
2099
+ // FIXME: for now, only support read only attach
2100
+ db.open_flags.set(OpenFlags::ReadOnly);
2071
2101
  self.attached_databases.write().insert(alias, (db, pager));
2072
2102
 
2073
2103
  Ok(())