lora-python 0.7.0__tar.gz → 0.8.0__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 (250) hide show
  1. {lora_python-0.7.0 → lora_python-0.8.0}/Cargo.lock +26 -15
  2. {lora_python-0.7.0 → lora_python-0.8.0}/Cargo.toml +11 -10
  3. {lora_python-0.7.0 → lora_python-0.8.0}/PKG-INFO +32 -1
  4. {lora_python-0.7.0 → lora_python-0.8.0}/README.md +31 -0
  5. {lora_python-0.7.0 → lora_python-0.8.0}/crates/bindings/lora-python/README.md +31 -0
  6. {lora_python-0.7.0 → lora_python-0.8.0}/crates/bindings/lora-python/src/lib.rs +50 -1
  7. {lora_python-0.7.0 → lora_python-0.8.0}/crates/bindings/lora-python/src/to_python.rs +64 -1
  8. lora_python-0.8.0/crates/bindings/lora-python/tests/test_explain_profile.py +78 -0
  9. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-compiler/src/lib.rs +3 -0
  10. lora_python-0.8.0/crates/lora-compiler/src/plan_tree.rs +303 -0
  11. lora_python-0.8.0/crates/lora-database/src/database/compile.rs +53 -0
  12. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-database/src/database/execute.rs +0 -36
  13. lora_python-0.8.0/crates/lora-database/src/database/explain.rs +57 -0
  14. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-database/src/database/mod.rs +43 -0
  15. lora_python-0.8.0/crates/lora-database/src/database/profile.rs +121 -0
  16. lora_python-0.8.0/crates/lora-database/src/explain.rs +97 -0
  17. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-database/src/lib.rs +3 -0
  18. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-database/src/transaction.rs +84 -0
  19. lora_python-0.8.0/crates/lora-database/src/wal/archive/format.rs +151 -0
  20. lora_python-0.8.0/crates/lora-database/src/wal/archive/lock.rs +208 -0
  21. lora_python-0.8.0/crates/lora-database/src/wal/archive/platform.rs +49 -0
  22. lora_python-0.8.0/crates/lora-database/src/wal/archive/worker.rs +47 -0
  23. lora_python-0.8.0/crates/lora-database/src/wal/archive/workspace.rs +210 -0
  24. lora_python-0.8.0/crates/lora-database/src/wal/archive.rs +182 -0
  25. lora_python-0.8.0/crates/lora-database/tests/explain_profile.rs +231 -0
  26. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-executor/src/lib.rs +2 -0
  27. lora_python-0.8.0/crates/lora-executor/src/profile.rs +126 -0
  28. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-executor/src/pull/aggregate.rs +1 -1
  29. lora_python-0.8.0/crates/lora-executor/src/pull/columns.rs +50 -0
  30. lora_python-0.8.0/crates/lora-executor/src/pull/context.rs +33 -0
  31. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-executor/src/pull/expand.rs +1 -1
  32. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-executor/src/pull/filter.rs +1 -1
  33. lora_python-0.8.0/crates/lora-executor/src/pull/hydration.rs +59 -0
  34. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-executor/src/pull/mod.rs +25 -13
  35. lora_python-0.8.0/crates/lora-executor/src/pull/mutable.rs +298 -0
  36. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-executor/src/pull/optional.rs +1 -1
  37. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-executor/src/pull/path.rs +1 -1
  38. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-executor/src/pull/projection.rs +1 -1
  39. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-executor/src/pull/scan.rs +1 -1
  40. lora_python-0.8.0/crates/lora-executor/src/pull/shape.rs +50 -0
  41. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-executor/src/pull/sort.rs +1 -1
  42. lora_python-0.8.0/crates/lora-executor/src/pull/source.rs +72 -0
  43. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-executor/src/pull/tests.rs +1 -1
  44. lora_python-0.8.0/crates/lora-executor/src/pull/traits.rs +444 -0
  45. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-executor/src/pull/union.rs +1 -1
  46. {lora_python-0.7.0 → lora_python-0.8.0}/pyproject.toml +1 -1
  47. lora_python-0.7.0/crates/lora-database/src/wal/archive.rs +0 -803
  48. lora_python-0.7.0/crates/lora-executor/src/pull/traits.rs +0 -1001
  49. {lora_python-0.7.0 → lora_python-0.8.0}/LICENSE +0 -0
  50. {lora_python-0.7.0 → lora_python-0.8.0}/crates/bindings/lora-python/.gitignore +0 -0
  51. {lora_python-0.7.0 → lora_python-0.8.0}/crates/bindings/lora-python/Cargo.toml +0 -0
  52. {lora_python-0.7.0 → lora_python-0.8.0}/crates/bindings/lora-python/LICENSE +0 -0
  53. {lora_python-0.7.0 → lora_python-0.8.0}/crates/bindings/lora-python/build.rs +0 -0
  54. {lora_python-0.7.0 → lora_python-0.8.0}/crates/bindings/lora-python/examples/async_demo.py +0 -0
  55. {lora_python-0.7.0 → lora_python-0.8.0}/crates/bindings/lora-python/examples/basic.py +0 -0
  56. {lora_python-0.7.0 → lora_python-0.8.0}/crates/bindings/lora-python/src/errors.rs +0 -0
  57. {lora_python-0.7.0 → lora_python-0.8.0}/crates/bindings/lora-python/src/from_python.rs +0 -0
  58. {lora_python-0.7.0 → lora_python-0.8.0}/crates/bindings/lora-python/tests/test_async.py +0 -0
  59. {lora_python-0.7.0 → lora_python-0.8.0}/crates/bindings/lora-python/tests/test_sync.py +0 -0
  60. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-analyzer/Cargo.toml +0 -0
  61. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-analyzer/src/analyzer/clauses.rs +0 -0
  62. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-analyzer/src/analyzer/expressions.rs +0 -0
  63. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-analyzer/src/analyzer/mod.rs +0 -0
  64. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-analyzer/src/analyzer/patterns.rs +0 -0
  65. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-analyzer/src/analyzer/state.rs +0 -0
  66. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-analyzer/src/analyzer/tests.rs +0 -0
  67. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-analyzer/src/errors.rs +0 -0
  68. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-analyzer/src/lib.rs +0 -0
  69. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-analyzer/src/resolved.rs +0 -0
  70. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-analyzer/src/scope.rs +0 -0
  71. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-analyzer/src/symbols.rs +0 -0
  72. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-analyzer/tests/error_messages.rs +0 -0
  73. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-ast/Cargo.toml +0 -0
  74. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-ast/src/ast.rs +0 -0
  75. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-ast/src/lib.rs +0 -0
  76. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-compiler/Cargo.toml +0 -0
  77. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-compiler/src/logical.rs +0 -0
  78. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-compiler/src/optimizer.rs +0 -0
  79. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-compiler/src/pattern.rs +0 -0
  80. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-compiler/src/physical.rs +0 -0
  81. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-compiler/src/planner.rs +0 -0
  82. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-database/Cargo.toml +0 -0
  83. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-database/benches/advanced_benchmarks.rs +0 -0
  84. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-database/benches/concurrent_benchmarks.rs +0 -0
  85. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-database/benches/engine_benchmarks.rs +0 -0
  86. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-database/benches/fixtures.rs +0 -0
  87. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-database/benches/perf_smoke_baseline.json +0 -0
  88. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-database/benches/perf_smoke_benchmarks.rs +0 -0
  89. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-database/benches/scale_benchmarks.rs +0 -0
  90. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-database/benches/temporal_spatial_benchmarks.rs +0 -0
  91. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-database/benches/wal_benchmarks.rs +0 -0
  92. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-database/src/database/builder.rs +0 -0
  93. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-database/src/database/graph_api.rs +0 -0
  94. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-database/src/database/occ.rs +0 -0
  95. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-database/src/database/pull_mode.rs +0 -0
  96. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-database/src/database/replay.rs +0 -0
  97. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-database/src/database/stream.rs +0 -0
  98. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-database/src/database/write_guard.rs +0 -0
  99. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-database/src/error.rs +0 -0
  100. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-database/src/named.rs +0 -0
  101. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-database/src/plan_cache.rs +0 -0
  102. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-database/src/snapshot/json.rs +0 -0
  103. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-database/src/snapshot/mod.rs +0 -0
  104. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-database/src/snapshot/store.rs +0 -0
  105. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-database/src/stream.rs +0 -0
  106. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-database/src/wal/admin.rs +0 -0
  107. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-database/src/wal/mod.rs +0 -0
  108. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-database/src/wal/write_scope.rs +0 -0
  109. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-database/tests/advanced_queries.rs +0 -0
  110. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-database/tests/aggregation.rs +0 -0
  111. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-database/tests/backend_stub.rs +0 -0
  112. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-database/tests/binary.rs +0 -0
  113. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-database/tests/create.rs +0 -0
  114. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-database/tests/error_messages.rs +0 -0
  115. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-database/tests/errors.rs +0 -0
  116. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-database/tests/expressions.rs +0 -0
  117. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-database/tests/functions_extended.rs +0 -0
  118. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-database/tests/invariants.rs +0 -0
  119. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-database/tests/managed_snapshots.rs +0 -0
  120. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-database/tests/match.rs +0 -0
  121. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-database/tests/merge.rs +0 -0
  122. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-database/tests/ordering.rs +0 -0
  123. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-database/tests/parameters.rs +0 -0
  124. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-database/tests/parser.rs +0 -0
  125. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-database/tests/paths.rs +0 -0
  126. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-database/tests/projection.rs +0 -0
  127. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-database/tests/scale.rs +0 -0
  128. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-database/tests/seeds.rs +0 -0
  129. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-database/tests/snapshot.rs +0 -0
  130. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-database/tests/spatial.rs +0 -0
  131. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-database/tests/temporal.rs +0 -0
  132. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-database/tests/test_helpers.rs +0 -0
  133. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-database/tests/transactions.rs +0 -0
  134. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-database/tests/types_advanced.rs +0 -0
  135. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-database/tests/union.rs +0 -0
  136. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-database/tests/update.rs +0 -0
  137. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-database/tests/vectors.rs +0 -0
  138. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-database/tests/wal.rs +0 -0
  139. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-database/tests/where_clause.rs +0 -0
  140. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-database/tests/with.rs +0 -0
  141. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-executor/Cargo.toml +0 -0
  142. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-executor/src/errors.rs +0 -0
  143. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-executor/src/eval/binops.rs +0 -0
  144. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-executor/src/eval/errors.rs +0 -0
  145. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-executor/src/eval/expr.rs +0 -0
  146. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-executor/src/eval/functions.rs +0 -0
  147. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-executor/src/eval/mod.rs +0 -0
  148. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-executor/src/eval/point.rs +0 -0
  149. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-executor/src/eval/vector.rs +0 -0
  150. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-executor/src/executor/helpers.rs +0 -0
  151. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-executor/src/executor/immutable.rs +0 -0
  152. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-executor/src/executor/mod.rs +0 -0
  153. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-executor/src/executor/mutable.rs +0 -0
  154. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-executor/src/value.rs +0 -0
  155. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-executor/tests/error_messages.rs +0 -0
  156. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-parser/Cargo.toml +0 -0
  157. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-parser/src/cypher.pest +0 -0
  158. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-parser/src/errors.rs +0 -0
  159. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-parser/src/lib.rs +0 -0
  160. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-parser/src/parser/clauses.rs +0 -0
  161. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-parser/src/parser/expressions.rs +0 -0
  162. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-parser/src/parser/literals.rs +0 -0
  163. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-parser/src/parser/mod.rs +0 -0
  164. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-parser/src/parser/patterns.rs +0 -0
  165. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-parser/src/parser/query.rs +0 -0
  166. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-parser/src/parser/tests.rs +0 -0
  167. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-parser/src/parser/util.rs +0 -0
  168. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-parser/tests/error_messages.rs +0 -0
  169. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-snapshot/Cargo.toml +0 -0
  170. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-snapshot/src/body.rs +0 -0
  171. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-snapshot/src/codec.rs +0 -0
  172. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-snapshot/src/columnar.rs +0 -0
  173. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-snapshot/src/envelope.rs +0 -0
  174. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-snapshot/src/errors.rs +0 -0
  175. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-snapshot/src/format.rs +0 -0
  176. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-snapshot/src/lib.rs +0 -0
  177. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-snapshot/src/options.rs +0 -0
  178. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-snapshot/src/tests.rs +0 -0
  179. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-snapshot/src/transform.rs +0 -0
  180. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-snapshot/src/view.rs +0 -0
  181. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-snapshot/tests/error_messages.rs +0 -0
  182. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-store/Cargo.toml +0 -0
  183. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-store/src/lib.rs +0 -0
  184. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-store/src/lock_table.rs +0 -0
  185. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-store/src/memory/graph.rs +0 -0
  186. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-store/src/memory/impls.rs +0 -0
  187. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-store/src/memory/mod.rs +0 -0
  188. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-store/src/memory/property_index.rs +0 -0
  189. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-store/src/memory/snapshot.rs +0 -0
  190. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-store/src/memory/tests.rs +0 -0
  191. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-store/src/mutation.rs +0 -0
  192. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-store/src/snapshot.rs +0 -0
  193. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-store/src/traits.rs +0 -0
  194. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-store/src/types/binary/mod.rs +0 -0
  195. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-store/src/types/binary/tests.rs +0 -0
  196. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-store/src/types/binary/traits.rs +0 -0
  197. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-store/src/types/binary/types.rs +0 -0
  198. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-store/src/types/graph.rs +0 -0
  199. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-store/src/types/mod.rs +0 -0
  200. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-store/src/types/property_value.rs +0 -0
  201. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-store/src/types/spatial/distance.rs +0 -0
  202. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-store/src/types/spatial/mod.rs +0 -0
  203. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-store/src/types/spatial/point.rs +0 -0
  204. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-store/src/types/spatial/srid.rs +0 -0
  205. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-store/src/types/spatial/tests.rs +0 -0
  206. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-store/src/types/temporal/calendar.rs +0 -0
  207. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-store/src/types/temporal/date.rs +0 -0
  208. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-store/src/types/temporal/datetime.rs +0 -0
  209. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-store/src/types/temporal/duration.rs +0 -0
  210. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-store/src/types/temporal/format.rs +0 -0
  211. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-store/src/types/temporal/mod.rs +0 -0
  212. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-store/src/types/temporal/parsing.rs +0 -0
  213. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-store/src/types/temporal/time.rs +0 -0
  214. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-store/src/types/vector/build.rs +0 -0
  215. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-store/src/types/vector/mod.rs +0 -0
  216. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-store/src/types/vector/similarity.rs +0 -0
  217. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-store/src/types/vector/tests.rs +0 -0
  218. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-store/src/types/vector/types.rs +0 -0
  219. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-store/tests/error_messages.rs +0 -0
  220. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-wal/Cargo.toml +0 -0
  221. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-wal/src/codec/decode.rs +0 -0
  222. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-wal/src/codec/encode.rs +0 -0
  223. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-wal/src/codec/format.rs +0 -0
  224. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-wal/src/codec/mod.rs +0 -0
  225. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-wal/src/codec/tests.rs +0 -0
  226. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-wal/src/config.rs +0 -0
  227. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-wal/src/dir.rs +0 -0
  228. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-wal/src/errors.rs +0 -0
  229. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-wal/src/lib.rs +0 -0
  230. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-wal/src/lock.rs +0 -0
  231. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-wal/src/lsn.rs +0 -0
  232. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-wal/src/record.rs +0 -0
  233. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-wal/src/recorder/errors.rs +0 -0
  234. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-wal/src/recorder/mirror.rs +0 -0
  235. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-wal/src/recorder/mod.rs +0 -0
  236. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-wal/src/recorder/recorder.rs +0 -0
  237. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-wal/src/recorder/tests.rs +0 -0
  238. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-wal/src/replay.rs +0 -0
  239. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-wal/src/segment.rs +0 -0
  240. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-wal/src/testing.rs +0 -0
  241. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-wal/src/wal/group_flusher.rs +0 -0
  242. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-wal/src/wal/mod.rs +0 -0
  243. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-wal/src/wal/tests.rs +0 -0
  244. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-wal/src/wal/wal.rs +0 -0
  245. {lora_python-0.7.0 → lora_python-0.8.0}/crates/lora-wal/tests/error_messages.rs +0 -0
  246. {lora_python-0.7.0 → lora_python-0.8.0}/python/lora_python/__init__.py +0 -0
  247. {lora_python-0.7.0 → lora_python-0.8.0}/python/lora_python/_async.py +0 -0
  248. {lora_python-0.7.0 → lora_python-0.8.0}/python/lora_python/_native.pyi +0 -0
  249. {lora_python-0.7.0 → lora_python-0.8.0}/python/lora_python/py.typed +0 -0
  250. {lora_python-0.7.0 → lora_python-0.8.0}/python/lora_python/types.py +0 -0
@@ -797,7 +797,7 @@ checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
797
797
 
798
798
  [[package]]
799
799
  name = "lora-analyzer"
800
- version = "0.7.0"
800
+ version = "0.8.0"
801
801
  dependencies = [
802
802
  "lora-ast",
803
803
  "lora-parser",
@@ -807,14 +807,22 @@ dependencies = [
807
807
 
808
808
  [[package]]
809
809
  name = "lora-ast"
810
- version = "0.7.0"
810
+ version = "0.8.0"
811
811
  dependencies = [
812
812
  "smallvec 2.0.0-alpha.12",
813
813
  ]
814
814
 
815
+ [[package]]
816
+ name = "lora-binding-buffer"
817
+ version = "0.8.0"
818
+ dependencies = [
819
+ "lora-database",
820
+ "lora-store",
821
+ ]
822
+
815
823
  [[package]]
816
824
  name = "lora-compiler"
817
- version = "0.7.0"
825
+ version = "0.8.0"
818
826
  dependencies = [
819
827
  "lora-analyzer",
820
828
  "lora-ast",
@@ -822,7 +830,7 @@ dependencies = [
822
830
 
823
831
  [[package]]
824
832
  name = "lora-database"
825
- version = "0.7.0"
833
+ version = "0.8.0"
826
834
  dependencies = [
827
835
  "anyhow",
828
836
  "arc-swap",
@@ -844,7 +852,7 @@ dependencies = [
844
852
 
845
853
  [[package]]
846
854
  name = "lora-executor"
847
- version = "0.7.0"
855
+ version = "0.8.0"
848
856
  dependencies = [
849
857
  "lora-analyzer",
850
858
  "lora-ast",
@@ -859,9 +867,10 @@ dependencies = [
859
867
 
860
868
  [[package]]
861
869
  name = "lora-ffi"
862
- version = "0.7.0"
870
+ version = "0.8.0"
863
871
  dependencies = [
864
872
  "anyhow",
873
+ "lora-binding-buffer",
865
874
  "lora-database",
866
875
  "lora-store",
867
876
  "serde",
@@ -870,9 +879,10 @@ dependencies = [
870
879
 
871
880
  [[package]]
872
881
  name = "lora-node"
873
- version = "0.7.0"
882
+ version = "0.8.0"
874
883
  dependencies = [
875
884
  "anyhow",
885
+ "lora-binding-buffer",
876
886
  "lora-database",
877
887
  "lora-store",
878
888
  "napi",
@@ -884,7 +894,7 @@ dependencies = [
884
894
 
885
895
  [[package]]
886
896
  name = "lora-parser"
887
- version = "0.7.0"
897
+ version = "0.8.0"
888
898
  dependencies = [
889
899
  "lora-ast",
890
900
  "pest",
@@ -895,7 +905,7 @@ dependencies = [
895
905
 
896
906
  [[package]]
897
907
  name = "lora-python"
898
- version = "0.7.0"
908
+ version = "0.8.0"
899
909
  dependencies = [
900
910
  "anyhow",
901
911
  "lora-database",
@@ -907,7 +917,7 @@ dependencies = [
907
917
 
908
918
  [[package]]
909
919
  name = "lora-server"
910
- version = "0.7.0"
920
+ version = "0.8.0"
911
921
  dependencies = [
912
922
  "anyhow",
913
923
  "axum",
@@ -921,7 +931,7 @@ dependencies = [
921
931
 
922
932
  [[package]]
923
933
  name = "lora-snapshot"
924
- version = "0.7.0"
934
+ version = "0.8.0"
925
935
  dependencies = [
926
936
  "argon2",
927
937
  "bincode",
@@ -936,7 +946,7 @@ dependencies = [
936
946
 
937
947
  [[package]]
938
948
  name = "lora-store"
939
- version = "0.7.0"
949
+ version = "0.8.0"
940
950
  dependencies = [
941
951
  "bincode",
942
952
  "crc32fast",
@@ -948,7 +958,7 @@ dependencies = [
948
958
 
949
959
  [[package]]
950
960
  name = "lora-wal"
951
- version = "0.7.0"
961
+ version = "0.8.0"
952
962
  dependencies = [
953
963
  "crc32fast",
954
964
  "lora-store",
@@ -958,11 +968,12 @@ dependencies = [
958
968
 
959
969
  [[package]]
960
970
  name = "lora-wasm"
961
- version = "0.7.0"
971
+ version = "0.8.0"
962
972
  dependencies = [
963
973
  "anyhow",
964
974
  "console_error_panic_hook",
965
975
  "js-sys",
976
+ "lora-binding-buffer",
966
977
  "lora-database",
967
978
  "lora-store",
968
979
  "serde",
@@ -973,7 +984,7 @@ dependencies = [
973
984
 
974
985
  [[package]]
975
986
  name = "lora_ruby"
976
- version = "0.7.0"
987
+ version = "0.8.0"
977
988
  dependencies = [
978
989
  "anyhow",
979
990
  "lora-database",
@@ -4,7 +4,7 @@ resolver = "2"
4
4
 
5
5
  [workspace.package]
6
6
  edition = "2021"
7
- version = "0.7.0"
7
+ version = "0.8.0"
8
8
  license = "BUSL-1.1"
9
9
  authors = ["LoraDB, Inc."]
10
10
  repository = "https://github.com/lora-db/lora"
@@ -15,15 +15,16 @@ rust-version = "1.87"
15
15
  # Internal crates — versions are kept in lockstep with [workspace.package].version
16
16
  # by `scripts/sync-versions.mjs`. Both `path` and `version` are set so that
17
17
  # `cargo publish` works (crates.io cannot resolve path-only deps).
18
- lora-ast = { path = "crates/lora-ast", version = "=0.7.0" }
19
- lora-parser = { path = "crates/lora-parser", version = "=0.7.0" }
20
- lora-analyzer = { path = "crates/lora-analyzer", version = "=0.7.0" }
21
- lora-compiler = { path = "crates/lora-compiler", version = "=0.7.0" }
22
- lora-store = { path = "crates/lora-store", version = "=0.7.0" }
23
- lora-snapshot = { path = "crates/lora-snapshot", version = "=0.7.0", default-features = false }
24
- lora-wal = { path = "crates/lora-wal", version = "=0.7.0" }
25
- lora-executor = { path = "crates/lora-executor", version = "=0.7.0" }
26
- lora-database = { path = "crates/lora-database", version = "=0.7.0" }
18
+ lora-ast = { path = "crates/lora-ast", version = "=0.8.0" }
19
+ lora-parser = { path = "crates/lora-parser", version = "=0.8.0" }
20
+ lora-analyzer = { path = "crates/lora-analyzer", version = "=0.8.0" }
21
+ lora-compiler = { path = "crates/lora-compiler", version = "=0.8.0" }
22
+ lora-store = { path = "crates/lora-store", version = "=0.8.0" }
23
+ lora-snapshot = { path = "crates/lora-snapshot", version = "=0.8.0", default-features = false }
24
+ lora-wal = { path = "crates/lora-wal", version = "=0.8.0" }
25
+ lora-executor = { path = "crates/lora-executor", version = "=0.8.0" }
26
+ lora-database = { path = "crates/lora-database", version = "=0.8.0" }
27
+ lora-binding-buffer = { path = "crates/bindings/lora-binding-buffer", version = "=0.8.0" }
27
28
 
28
29
  # External crates.
29
30
  anyhow = "1"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lora-python
3
- Version: 0.7.0
3
+ Version: 0.8.0
4
4
  Classifier: Programming Language :: Rust
5
5
  Classifier: Programming Language :: Python :: 3
6
6
  Classifier: Programming Language :: Python :: 3.8
@@ -76,6 +76,37 @@ persistent = Database.create("app", {"database_dir": "./data"}) # persistent: .
76
76
  If you want persistence, pass a database name and `database_dir` to
77
77
  `Database.create(...)` or `Database(...)`.
78
78
 
79
+ ## Explain & Profile
80
+
81
+ `db.explain()` and `db.profile()` are first-class methods alongside
82
+ `db.execute()`. They are intentionally separate calls — neither
83
+ routes through `execute()` — so plan inspection and runtime metrics
84
+ must be requested explicitly.
85
+
86
+ ```python
87
+ plan = db.explain(
88
+ "MATCH (p:Person) WHERE p.name = $name RETURN p",
89
+ {"name": "Alice"},
90
+ )
91
+ print(plan["shape"]) # "readOnly"
92
+ print(plan["tree"]["operator"]) # top-most operator label
93
+
94
+ profile = db.profile(
95
+ "MATCH (p:Person) WHERE p.name = $name RETURN p",
96
+ {"name": "Alice"},
97
+ )
98
+ print(profile["metrics"]["total_elapsed_ns"])
99
+ print(profile["metrics"]["per_operator"]) # per-step inclusive timing
100
+ ```
101
+
102
+ `explain()` never invokes the executor — calling it on a mutating
103
+ query (`CREATE`, `MERGE`, `SET`, `DELETE`, `REMOVE`) leaves the graph
104
+ untouched.
105
+
106
+ > **`profile()` executes the query for real.** Mutating queries
107
+ > produce the same side effects as `execute()`. Use `explain()` to
108
+ > inspect a mutating plan without running it.
109
+
79
110
  ## Async usage (non-blocking)
80
111
 
81
112
  ```python
@@ -46,6 +46,37 @@ persistent = Database.create("app", {"database_dir": "./data"}) # persistent: .
46
46
  If you want persistence, pass a database name and `database_dir` to
47
47
  `Database.create(...)` or `Database(...)`.
48
48
 
49
+ ## Explain & Profile
50
+
51
+ `db.explain()` and `db.profile()` are first-class methods alongside
52
+ `db.execute()`. They are intentionally separate calls — neither
53
+ routes through `execute()` — so plan inspection and runtime metrics
54
+ must be requested explicitly.
55
+
56
+ ```python
57
+ plan = db.explain(
58
+ "MATCH (p:Person) WHERE p.name = $name RETURN p",
59
+ {"name": "Alice"},
60
+ )
61
+ print(plan["shape"]) # "readOnly"
62
+ print(plan["tree"]["operator"]) # top-most operator label
63
+
64
+ profile = db.profile(
65
+ "MATCH (p:Person) WHERE p.name = $name RETURN p",
66
+ {"name": "Alice"},
67
+ )
68
+ print(profile["metrics"]["total_elapsed_ns"])
69
+ print(profile["metrics"]["per_operator"]) # per-step inclusive timing
70
+ ```
71
+
72
+ `explain()` never invokes the executor — calling it on a mutating
73
+ query (`CREATE`, `MERGE`, `SET`, `DELETE`, `REMOVE`) leaves the graph
74
+ untouched.
75
+
76
+ > **`profile()` executes the query for real.** Mutating queries
77
+ > produce the same side effects as `execute()`. Use `explain()` to
78
+ > inspect a mutating plan without running it.
79
+
49
80
  ## Async usage (non-blocking)
50
81
 
51
82
  ```python
@@ -46,6 +46,37 @@ persistent = Database.create("app", {"database_dir": "./data"}) # persistent: .
46
46
  If you want persistence, pass a database name and `database_dir` to
47
47
  `Database.create(...)` or `Database(...)`.
48
48
 
49
+ ## Explain & Profile
50
+
51
+ `db.explain()` and `db.profile()` are first-class methods alongside
52
+ `db.execute()`. They are intentionally separate calls — neither
53
+ routes through `execute()` — so plan inspection and runtime metrics
54
+ must be requested explicitly.
55
+
56
+ ```python
57
+ plan = db.explain(
58
+ "MATCH (p:Person) WHERE p.name = $name RETURN p",
59
+ {"name": "Alice"},
60
+ )
61
+ print(plan["shape"]) # "readOnly"
62
+ print(plan["tree"]["operator"]) # top-most operator label
63
+
64
+ profile = db.profile(
65
+ "MATCH (p:Person) WHERE p.name = $name RETURN p",
66
+ {"name": "Alice"},
67
+ )
68
+ print(profile["metrics"]["total_elapsed_ns"])
69
+ print(profile["metrics"]["per_operator"]) # per-step inclusive timing
70
+ ```
71
+
72
+ `explain()` never invokes the executor — calling it on a mutating
73
+ query (`CREATE`, `MERGE`, `SET`, `DELETE`, `REMOVE`) leaves the graph
74
+ untouched.
75
+
76
+ > **`profile()` executes the query for real.** Mutating queries
77
+ > produce the same side effects as `execute()`. Use `explain()` to
78
+ > inspect a mutating plan without running it.
79
+
49
80
  ## Async usage (non-blocking)
50
81
 
51
82
  ```python
@@ -42,7 +42,8 @@ use from_python::{
42
42
  py_statements_to_transaction, PyDatabaseOpenOptions,
43
43
  };
44
44
  use to_python::{
45
- lora_value_to_py, row_arrays_to_py, row_to_py_dict, snapshot_info_to_meta, snapshot_meta_to_py,
45
+ lora_value_to_py, query_plan_to_py, query_profile_to_py, row_arrays_to_py, row_to_py_dict,
46
+ snapshot_info_to_meta, snapshot_meta_to_py,
46
47
  };
47
48
 
48
49
  // ============================================================================
@@ -185,6 +186,54 @@ impl Database {
185
186
  Ok(out)
186
187
  }
187
188
 
189
+ /// Compile a query and return its plan as a `dict` without
190
+ /// executing it.
191
+ ///
192
+ /// Mutating queries (`CREATE`, `MERGE`, `SET`, `DELETE`, `REMOVE`)
193
+ /// leave the graph untouched. Errors surface with the same
194
+ /// `LoraQueryError` shape as `execute()`.
195
+ #[pyo3(signature = (query, params=None))]
196
+ fn explain<'py>(
197
+ &self,
198
+ py: Python<'py>,
199
+ query: String,
200
+ params: Option<&Bound<'py, PyAny>>,
201
+ ) -> PyResult<Bound<'py, PyDict>> {
202
+ let params_map = match params {
203
+ Some(p) if !p.is_none() => Some(py_object_to_params(p)?),
204
+ _ => None,
205
+ };
206
+ let db = self.inner()?;
207
+ let plan = py
208
+ .allow_threads(move || db.explain(&query, params_map))
209
+ .map_err(lora_query_err_from_anyhow)?;
210
+ query_plan_to_py(py, &plan)
211
+ }
212
+
213
+ /// Execute a query and return the plan plus runtime metrics as a
214
+ /// `dict`.
215
+ ///
216
+ /// **PROFILE executes the query for real.** Mutating queries are
217
+ /// persisted exactly as in `execute()`. Use `explain()` to inspect
218
+ /// a mutating plan without running it.
219
+ #[pyo3(signature = (query, params=None))]
220
+ fn profile<'py>(
221
+ &self,
222
+ py: Python<'py>,
223
+ query: String,
224
+ params: Option<&Bound<'py, PyAny>>,
225
+ ) -> PyResult<Bound<'py, PyDict>> {
226
+ let params_map = match params {
227
+ Some(p) if !p.is_none() => Some(py_object_to_params(p)?),
228
+ _ => None,
229
+ };
230
+ let db = self.inner()?;
231
+ let prof = py
232
+ .allow_threads(move || db.profile(&query, params_map))
233
+ .map_err(lora_query_err_from_anyhow)?;
234
+ query_profile_to_py(py, &prof)
235
+ }
236
+
188
237
  /// Return an iterator over result rows. The query is materialized by
189
238
  /// `execute()` first, then Python consumes the row list lazily.
190
239
  #[pyo3(signature = (query, params=None))]
@@ -8,7 +8,9 @@
8
8
  use pyo3::prelude::*;
9
9
  use pyo3::types::{PyBytes, PyDict, PyList};
10
10
 
11
- use lora_database::{LoraValue, Row, SnapshotInfo, SnapshotMeta};
11
+ use lora_database::{
12
+ LoraValue, PlanTreeNode, QueryPlan, QueryProfile, Row, SnapshotInfo, SnapshotMeta,
13
+ };
12
14
  use lora_store::{LoraBinary, LoraPoint, LoraVector, VectorValues};
13
15
 
14
16
  pub(crate) fn snapshot_info_to_meta(info: SnapshotInfo) -> SnapshotMeta {
@@ -64,6 +66,67 @@ pub(crate) fn row_to_py_dict<'py>(py: Python<'py>, row: &Row) -> PyResult<Bound<
64
66
  Ok(out)
65
67
  }
66
68
 
69
+ pub(crate) fn query_plan_to_py<'py>(
70
+ py: Python<'py>,
71
+ plan: &QueryPlan,
72
+ ) -> PyResult<Bound<'py, PyDict>> {
73
+ let out = PyDict::new_bound(py);
74
+ out.set_item("query", &plan.query)?;
75
+ out.set_item("shape", plan.shape.as_str())?;
76
+ let cols = PyList::new_bound(py, plan.result_columns.iter().map(|c| c.as_str()));
77
+ out.set_item("result_columns", cols)?;
78
+ out.set_item("tree", plan_tree_node_to_py(py, &plan.tree.root)?)?;
79
+ Ok(out)
80
+ }
81
+
82
+ pub(crate) fn query_profile_to_py<'py>(
83
+ py: Python<'py>,
84
+ profile: &QueryProfile,
85
+ ) -> PyResult<Bound<'py, PyDict>> {
86
+ let out = PyDict::new_bound(py);
87
+ out.set_item("plan", query_plan_to_py(py, &profile.plan)?)?;
88
+
89
+ let metrics = PyDict::new_bound(py);
90
+ metrics.set_item("total_elapsed_ns", profile.metrics.total_elapsed_ns)?;
91
+ metrics.set_item("total_rows", profile.metrics.total_rows)?;
92
+ metrics.set_item("mutated", profile.metrics.mutated)?;
93
+
94
+ let per_op = PyDict::new_bound(py);
95
+ for (id, op) in &profile.metrics.per_operator {
96
+ let entry = PyDict::new_bound(py);
97
+ entry.set_item("rows", op.rows)?;
98
+ entry.set_item("db_hits", op.db_hits)?;
99
+ entry.set_item("elapsed_ns", op.elapsed_ns)?;
100
+ entry.set_item("next_calls", op.next_calls)?;
101
+ per_op.set_item(*id as u64, entry)?;
102
+ }
103
+ metrics.set_item("per_operator", per_op)?;
104
+
105
+ out.set_item("metrics", metrics)?;
106
+ Ok(out)
107
+ }
108
+
109
+ fn plan_tree_node_to_py<'py>(py: Python<'py>, node: &PlanTreeNode) -> PyResult<Bound<'py, PyDict>> {
110
+ let out = PyDict::new_bound(py);
111
+ out.set_item("id", node.id as u64)?;
112
+ out.set_item("operator", &node.operator)?;
113
+ let details = PyDict::new_bound(py);
114
+ for (k, v) in &node.details {
115
+ details.set_item(k, v)?;
116
+ }
117
+ out.set_item("details", details)?;
118
+ match node.estimated_rows {
119
+ Some(r) => out.set_item("estimated_rows", r)?,
120
+ None => out.set_item("estimated_rows", py.None())?,
121
+ }
122
+ let children = PyList::empty_bound(py);
123
+ for child in &node.children {
124
+ children.append(plan_tree_node_to_py(py, child)?)?;
125
+ }
126
+ out.set_item("children", children)?;
127
+ Ok(out)
128
+ }
129
+
67
130
  pub(crate) fn lora_value_to_py<'py>(
68
131
  py: Python<'py>,
69
132
  value: &LoraValue,
@@ -0,0 +1,78 @@
1
+ """Tests for Database.explain and Database.profile."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import pytest
6
+
7
+ from lora_python import Database, LoraQueryError
8
+
9
+
10
+ def test_explain_does_not_execute_mutating_query() -> None:
11
+ db = Database.create()
12
+ plan = db.explain("CREATE (:Foo {n: 1})")
13
+ assert plan["shape"] == "mutating"
14
+ assert db.node_count == 0
15
+
16
+
17
+ def test_explain_returns_plan_tree() -> None:
18
+ db = Database.create()
19
+ db.execute("CREATE (:Person {name: 'Alice'})")
20
+ plan = db.explain("MATCH (p:Person) RETURN p")
21
+ assert plan["shape"] == "readOnly"
22
+ assert plan["result_columns"] == ["p"]
23
+ assert plan["query"] == "MATCH (p:Person) RETURN p"
24
+ assert "operator" in plan["tree"]
25
+
26
+
27
+ def test_explain_with_params_forwarded() -> None:
28
+ db = Database.create()
29
+ db.execute("CREATE (:Person {name: 'Alice'})")
30
+ plan = db.explain(
31
+ "MATCH (p:Person) WHERE p.name = $name RETURN p",
32
+ {"name": "Alice"},
33
+ )
34
+ assert plan["shape"] == "readOnly"
35
+
36
+
37
+ def test_explain_surfaces_parse_error_like_execute() -> None:
38
+ db = Database.create()
39
+ with pytest.raises(LoraQueryError) as exec_exc:
40
+ db.execute("INVALID")
41
+ with pytest.raises(LoraQueryError) as explain_exc:
42
+ db.explain("INVALID")
43
+ # Both errors share the LORA_PARSE prefix.
44
+ assert str(exec_exc.value).split(":")[0] == str(explain_exc.value).split(":")[0]
45
+
46
+
47
+ def test_profile_executes_mutating_query() -> None:
48
+ db = Database.create()
49
+ profile = db.profile("CREATE (:Foo {n: 1}) RETURN 1 AS one")
50
+ assert profile["metrics"]["mutated"] is True
51
+ assert profile["metrics"]["total_rows"] == 1
52
+ assert db.node_count == 1
53
+
54
+
55
+ def test_profile_reports_per_operator_timing() -> None:
56
+ db = Database.create()
57
+ for name in ["Alice", "Bob", "Carol", "Dave"]:
58
+ db.execute(f"CREATE (:Person {{name: '{name}'}})")
59
+ profile = db.profile(
60
+ "MATCH (p:Person) WHERE p.name <> 'Bob' RETURN p.name AS name"
61
+ )
62
+ assert profile["metrics"]["total_rows"] == 3
63
+ assert profile["metrics"]["mutated"] is False
64
+ per_op = profile["metrics"]["per_operator"]
65
+ assert len(per_op) > 0
66
+ for op_id, op in per_op.items():
67
+ assert op["next_calls"] > 0
68
+
69
+
70
+ def test_profile_with_params_forwarded() -> None:
71
+ db = Database.create()
72
+ db.execute("CREATE (:Person {name: 'Alice'})")
73
+ db.execute("CREATE (:Person {name: 'Bob'})")
74
+ profile = db.profile(
75
+ "MATCH (p:Person) WHERE p.name = $name RETURN p",
76
+ {"name": "Alice"},
77
+ )
78
+ assert profile["metrics"]["total_rows"] == 1
@@ -1,10 +1,13 @@
1
1
  pub mod logical;
2
2
  pub mod optimizer;
3
3
  pub mod physical;
4
+ pub mod plan_tree;
4
5
 
5
6
  mod pattern;
6
7
  mod planner;
7
8
 
9
+ pub use plan_tree::{plan_tree_from_compiled, PlanTree, PlanTreeNode};
10
+
8
11
  pub use logical::{
9
12
  Aggregation, Argument, Create, Delete, Expand, Filter, Limit, LogicalOp, LogicalPlan, Merge,
10
13
  NodeByPropertyScan, NodeScan, OptionalMatch, PathBuild, PlanNodeId, Projection, Remove, Set,