sqlrite 0.1.15__tar.gz → 0.1.17__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 (110) hide show
  1. {sqlrite-0.1.15 → sqlrite-0.1.17}/.github/workflows/release.yml +69 -1
  2. {sqlrite-0.1.15 → sqlrite-0.1.17}/Cargo.lock +161 -5
  3. {sqlrite-0.1.15 → sqlrite-0.1.17}/Cargo.toml +9 -1
  4. {sqlrite-0.1.15 → sqlrite-0.1.17}/PKG-INFO +59 -1
  5. {sqlrite-0.1.15 → sqlrite-0.1.17}/README.md +36 -4
  6. {sqlrite-0.1.15 → sqlrite-0.1.17}/desktop/package.json +1 -1
  7. {sqlrite-0.1.15 → sqlrite-0.1.17}/docs/embedding.md +47 -0
  8. {sqlrite-0.1.15 → sqlrite-0.1.17}/docs/phase-7-plan.md +10 -6
  9. {sqlrite-0.1.15 → sqlrite-0.1.17}/docs/release-plan.md +12 -3
  10. {sqlrite-0.1.15 → sqlrite-0.1.17}/docs/roadmap.md +2 -2
  11. {sqlrite-0.1.15 → sqlrite-0.1.17}/docs/supported-sql.md +33 -0
  12. {sqlrite-0.1.15 → sqlrite-0.1.17}/pyproject.toml +1 -1
  13. {sqlrite-0.1.15 → sqlrite-0.1.17}/scripts/bump-version.sh +7 -5
  14. {sqlrite-0.1.15 → sqlrite-0.1.17}/sdk/go/README.md +58 -0
  15. {sqlrite-0.1.15 → sqlrite-0.1.17}/sdk/python/Cargo.toml +1 -1
  16. {sqlrite-0.1.15 → sqlrite-0.1.17}/sdk/python/README.md +58 -0
  17. {sqlrite-0.1.15 → sqlrite-0.1.17}/src/sql/db/table.rs +50 -1
  18. {sqlrite-0.1.15 → sqlrite-0.1.17}/src/sql/executor.rs +292 -0
  19. {sqlrite-0.1.15 → sqlrite-0.1.17}/src/sql/mod.rs +228 -0
  20. {sqlrite-0.1.15 → sqlrite-0.1.17}/src/sql/pager/mod.rs +55 -0
  21. {sqlrite-0.1.15 → sqlrite-0.1.17}/src/sql/parser/create.rs +5 -0
  22. {sqlrite-0.1.15 → sqlrite-0.1.17}/.github/workflows/ci.yml +0 -0
  23. {sqlrite-0.1.15 → sqlrite-0.1.17}/.github/workflows/release-pr.yml +0 -0
  24. {sqlrite-0.1.15 → sqlrite-0.1.17}/.github/workflows/rust.yml +0 -0
  25. {sqlrite-0.1.15 → sqlrite-0.1.17}/.gitignore +0 -0
  26. {sqlrite-0.1.15 → sqlrite-0.1.17}/CODE_OF_CONDUCT.md +0 -0
  27. {sqlrite-0.1.15 → sqlrite-0.1.17}/LICENSE +0 -0
  28. {sqlrite-0.1.15 → sqlrite-0.1.17}/MAINTAINERS +0 -0
  29. {sqlrite-0.1.15 → sqlrite-0.1.17}/Makefile +0 -0
  30. {sqlrite-0.1.15 → sqlrite-0.1.17}/desktop/index.html +0 -0
  31. {sqlrite-0.1.15 → sqlrite-0.1.17}/desktop/package-lock.json +0 -0
  32. {sqlrite-0.1.15 → sqlrite-0.1.17}/desktop/src/App.svelte +0 -0
  33. {sqlrite-0.1.15 → sqlrite-0.1.17}/desktop/src/app.css +0 -0
  34. {sqlrite-0.1.15 → sqlrite-0.1.17}/desktop/src/main.ts +0 -0
  35. {sqlrite-0.1.15 → sqlrite-0.1.17}/desktop/src/vite-env.d.ts +0 -0
  36. {sqlrite-0.1.15 → sqlrite-0.1.17}/desktop/svelte.config.js +0 -0
  37. {sqlrite-0.1.15 → sqlrite-0.1.17}/desktop/tsconfig.json +0 -0
  38. {sqlrite-0.1.15 → sqlrite-0.1.17}/desktop/vite.config.ts +0 -0
  39. {sqlrite-0.1.15 → sqlrite-0.1.17}/docs/_index.md +0 -0
  40. {sqlrite-0.1.15 → sqlrite-0.1.17}/docs/architecture.md +0 -0
  41. {sqlrite-0.1.15 → sqlrite-0.1.17}/docs/design-decisions.md +0 -0
  42. {sqlrite-0.1.15 → sqlrite-0.1.17}/docs/desktop.md +0 -0
  43. {sqlrite-0.1.15 → sqlrite-0.1.17}/docs/file-format.md +0 -0
  44. {sqlrite-0.1.15 → sqlrite-0.1.17}/docs/getting-started.md +0 -0
  45. {sqlrite-0.1.15 → sqlrite-0.1.17}/docs/pager.md +0 -0
  46. {sqlrite-0.1.15 → sqlrite-0.1.17}/docs/release-secrets.md +0 -0
  47. {sqlrite-0.1.15 → sqlrite-0.1.17}/docs/smoke-test.md +0 -0
  48. {sqlrite-0.1.15 → sqlrite-0.1.17}/docs/sql-engine.md +0 -0
  49. {sqlrite-0.1.15 → sqlrite-0.1.17}/docs/storage-model.md +0 -0
  50. {sqlrite-0.1.15 → sqlrite-0.1.17}/docs/usage.md +0 -0
  51. {sqlrite-0.1.15 → sqlrite-0.1.17}/examples/README.md +0 -0
  52. {sqlrite-0.1.15 → sqlrite-0.1.17}/examples/c/Makefile +0 -0
  53. {sqlrite-0.1.15 → sqlrite-0.1.17}/examples/c/hello.c +0 -0
  54. {sqlrite-0.1.15 → sqlrite-0.1.17}/examples/go/go.mod +0 -0
  55. {sqlrite-0.1.15 → sqlrite-0.1.17}/examples/go/hello.go +0 -0
  56. {sqlrite-0.1.15 → sqlrite-0.1.17}/examples/nodejs/hello.mjs +0 -0
  57. {sqlrite-0.1.15 → sqlrite-0.1.17}/examples/python/hello.py +0 -0
  58. {sqlrite-0.1.15 → sqlrite-0.1.17}/examples/rust/quickstart.rs +0 -0
  59. {sqlrite-0.1.15 → sqlrite-0.1.17}/examples/wasm/Makefile +0 -0
  60. {sqlrite-0.1.15 → sqlrite-0.1.17}/examples/wasm/index.html +0 -0
  61. {sqlrite-0.1.15 → sqlrite-0.1.17}/images/SQLRite - Desktop.png +0 -0
  62. {sqlrite-0.1.15 → sqlrite-0.1.17}/images/SQLRite Data Structures.png +0 -0
  63. {sqlrite-0.1.15 → sqlrite-0.1.17}/images/SQLRite Simple SQL Execution High Level Diagram.png +0 -0
  64. {sqlrite-0.1.15 → sqlrite-0.1.17}/images/SQLRite Simple SQL INSERT Execution High Level Diagram (Insert Row).png +0 -0
  65. {sqlrite-0.1.15 → sqlrite-0.1.17}/images/SQLRite Simple SQL INSERT Execution High Level Diagram.png +0 -0
  66. {sqlrite-0.1.15 → sqlrite-0.1.17}/images/SQLRite_logo.png +0 -0
  67. {sqlrite-0.1.15 → sqlrite-0.1.17}/images/architecture.png +0 -0
  68. {sqlrite-0.1.15 → sqlrite-0.1.17}/rust-toolchain.toml +0 -0
  69. {sqlrite-0.1.15 → sqlrite-0.1.17}/samples/AST.delete.example +0 -0
  70. {sqlrite-0.1.15 → sqlrite-0.1.17}/samples/AST.insert.exemple +0 -0
  71. {sqlrite-0.1.15 → sqlrite-0.1.17}/samples/AST.select.example +0 -0
  72. {sqlrite-0.1.15 → sqlrite-0.1.17}/samples/AST.update.example +0 -0
  73. {sqlrite-0.1.15 → sqlrite-0.1.17}/samples/CREATE TABLE sqlrite_schema.sql +0 -0
  74. {sqlrite-0.1.15 → sqlrite-0.1.17}/samples/CREATE_TABLE with duplicate.sql +0 -0
  75. {sqlrite-0.1.15 → sqlrite-0.1.17}/samples/CREATE_TABLE.sql +0 -0
  76. {sqlrite-0.1.15 → sqlrite-0.1.17}/samples/INSERT.sql +0 -0
  77. {sqlrite-0.1.15 → sqlrite-0.1.17}/sdk/go/conn.go +0 -0
  78. {sqlrite-0.1.15 → sqlrite-0.1.17}/sdk/go/go.mod +0 -0
  79. {sqlrite-0.1.15 → sqlrite-0.1.17}/sdk/go/rows.go +0 -0
  80. {sqlrite-0.1.15 → sqlrite-0.1.17}/sdk/go/sqlrite.go +0 -0
  81. {sqlrite-0.1.15 → sqlrite-0.1.17}/sdk/go/sqlrite_test.go +0 -0
  82. {sqlrite-0.1.15 → sqlrite-0.1.17}/sdk/go/stmt.go +0 -0
  83. {sqlrite-0.1.15 → sqlrite-0.1.17}/sdk/python/src/lib.rs +0 -0
  84. {sqlrite-0.1.15 → sqlrite-0.1.17}/sdk/python/tests/test_sqlrite.py +0 -0
  85. {sqlrite-0.1.15 → sqlrite-0.1.17}/src/connection.rs +0 -0
  86. {sqlrite-0.1.15 → sqlrite-0.1.17}/src/error.rs +0 -0
  87. {sqlrite-0.1.15 → sqlrite-0.1.17}/src/lib.rs +0 -0
  88. {sqlrite-0.1.15 → sqlrite-0.1.17}/src/main.rs +0 -0
  89. {sqlrite-0.1.15 → sqlrite-0.1.17}/src/meta_command/mod.rs +0 -0
  90. {sqlrite-0.1.15 → sqlrite-0.1.17}/src/repl/mod.rs +0 -0
  91. {sqlrite-0.1.15 → sqlrite-0.1.17}/src/sql/db/database.rs +0 -0
  92. {sqlrite-0.1.15 → sqlrite-0.1.17}/src/sql/db/mod.rs +0 -0
  93. {sqlrite-0.1.15 → sqlrite-0.1.17}/src/sql/db/secondary_index.rs +0 -0
  94. {sqlrite-0.1.15 → sqlrite-0.1.17}/src/sql/hnsw.rs +0 -0
  95. {sqlrite-0.1.15 → sqlrite-0.1.17}/src/sql/pager/cell.rs +0 -0
  96. {sqlrite-0.1.15 → sqlrite-0.1.17}/src/sql/pager/file.rs +0 -0
  97. {sqlrite-0.1.15 → sqlrite-0.1.17}/src/sql/pager/header.rs +0 -0
  98. {sqlrite-0.1.15 → sqlrite-0.1.17}/src/sql/pager/hnsw_cell.rs +0 -0
  99. {sqlrite-0.1.15 → sqlrite-0.1.17}/src/sql/pager/index_cell.rs +0 -0
  100. {sqlrite-0.1.15 → sqlrite-0.1.17}/src/sql/pager/interior_page.rs +0 -0
  101. {sqlrite-0.1.15 → sqlrite-0.1.17}/src/sql/pager/overflow.rs +0 -0
  102. {sqlrite-0.1.15 → sqlrite-0.1.17}/src/sql/pager/page.rs +0 -0
  103. {sqlrite-0.1.15 → sqlrite-0.1.17}/src/sql/pager/pager.rs +0 -0
  104. {sqlrite-0.1.15 → sqlrite-0.1.17}/src/sql/pager/table_page.rs +0 -0
  105. {sqlrite-0.1.15 → sqlrite-0.1.17}/src/sql/pager/varint.rs +0 -0
  106. {sqlrite-0.1.15 → sqlrite-0.1.17}/src/sql/pager/wal.rs +0 -0
  107. {sqlrite-0.1.15 → sqlrite-0.1.17}/src/sql/parser/insert.rs +0 -0
  108. {sqlrite-0.1.15 → sqlrite-0.1.17}/src/sql/parser/mod.rs +0 -0
  109. {sqlrite-0.1.15 → sqlrite-0.1.17}/src/sql/parser/select.rs +0 -0
  110. {sqlrite-0.1.15 → sqlrite-0.1.17}/src/sql/tokenizer.rs +0 -0
@@ -126,6 +126,7 @@ jobs:
126
126
  TAGS=(
127
127
  "sqlrite-v$V"
128
128
  "sqlrite-ffi-v$V"
129
+ "sqlrite-ask-v$V"
129
130
  "sqlrite-desktop-v$V"
130
131
  "sqlrite-py-v$V"
131
132
  "sqlrite-node-v$V"
@@ -199,6 +200,73 @@ jobs:
199
200
  See the umbrella release [v${{ needs.detect.outputs.version }}](../../releases/tag/v${{ needs.detect.outputs.version }}) for the full changelog.
200
201
  generate_release_notes: true
201
202
 
203
+ # ---------------------------------------------------------------------------
204
+ # Step 3a': publish the `sqlrite-ask` crate (Phase 7g.1) — natural-
205
+ # language → SQL adapter built on top of the engine. Same shape as
206
+ # `publish-crate` above; separate job so a registry hiccup on one
207
+ # doesn't block the other and re-runs are surgical.
208
+ #
209
+ # Crate name on crates.io: `sqlrite-ask`. Library name (the `use`
210
+ # path): `sqlrite_ask`. No alias-renaming this time — the short
211
+ # name was available unlike `sqlrite` (see Phase 6d retrospective
212
+ # for why the engine had to rename).
213
+ publish-ask:
214
+ name: Publish sqlrite-ask crate to crates.io
215
+ needs: [detect, tag-all, publish-crate]
216
+ if: needs.detect.outputs.should_release == 'true'
217
+ runs-on: ubuntu-latest
218
+ environment: release
219
+ steps:
220
+ - uses: actions/checkout@v4
221
+
222
+ - uses: dtolnay/rust-toolchain@stable
223
+
224
+ - uses: Swatinem/rust-cache@v2
225
+ with:
226
+ shared-key: publish-ask
227
+
228
+ - name: cargo publish
229
+ env:
230
+ CARGO_REGISTRY_TOKEN: ${{ secrets.CRATES_IO_TOKEN }}
231
+ # `--no-verify` mirrors `publish-crate` — Release-PR CI
232
+ # already validated this commit.
233
+ #
234
+ # `needs: [..., publish-crate]` is load-bearing: sqlrite-ask
235
+ # depends on sqlrite-engine, and crates.io rejects publishes
236
+ # whose path-deps haven't yet resolved to a published version
237
+ # at the same number. Sequencing makes the dep visible by the
238
+ # time we publish.
239
+ run: cargo publish -p sqlrite-ask --no-verify
240
+
241
+ - name: GitHub Release
242
+ uses: softprops/action-gh-release@v2
243
+ with:
244
+ tag_name: sqlrite-ask-v${{ needs.detect.outputs.version }}
245
+ name: sqlrite-ask v${{ needs.detect.outputs.version }}
246
+ body: |
247
+ Published to crates.io: https://crates.io/crates/sqlrite-ask/${{ needs.detect.outputs.version }}
248
+
249
+ Natural-language → SQL adapter for SQLRite. Anthropic-first; OpenAI / Ollama follow-ups.
250
+
251
+ ```toml
252
+ [dependencies]
253
+ sqlrite-engine = "${{ needs.detect.outputs.version }}"
254
+ sqlrite-ask = "${{ needs.detect.outputs.version }}"
255
+ ```
256
+
257
+ ```rust
258
+ use sqlrite::Connection;
259
+ use sqlrite_ask::{AskConfig, ConnectionAskExt};
260
+
261
+ let conn = Connection::open("foo.sqlrite")?;
262
+ let cfg = AskConfig::from_env()?; // SQLRITE_LLM_API_KEY etc.
263
+ let resp = conn.ask("How many users are over 30?", &cfg)?;
264
+ println!("Generated SQL: {}", resp.sql);
265
+ ```
266
+
267
+ See the umbrella release [v${{ needs.detect.outputs.version }}](../../releases/tag/v${{ needs.detect.outputs.version }}) for the full changelog.
268
+ generate_release_notes: true
269
+
202
270
  # ---------------------------------------------------------------------------
203
271
  # Step 3b: build `libsqlrite_c` for each supported platform and
204
272
  # upload the tarballs to the `sqlrite-ffi-v<V>` GitHub Release.
@@ -1223,7 +1291,7 @@ jobs:
1223
1291
  # config if we add one later.
1224
1292
  finalize:
1225
1293
  name: Finalize umbrella release
1226
- needs: [detect, publish-crate, publish-ffi, publish-desktop, publish-python, publish-nodejs, publish-wasm, publish-go]
1294
+ needs: [detect, publish-crate, publish-ask, publish-ffi, publish-desktop, publish-python, publish-nodejs, publish-wasm, publish-go]
1227
1295
  if: needs.detect.outputs.should_release == 'true'
1228
1296
  runs-on: ubuntu-latest
1229
1297
  steps:
@@ -106,6 +106,12 @@ dependencies = [
106
106
  "object",
107
107
  ]
108
108
 
109
+ [[package]]
110
+ name = "ascii"
111
+ version = "1.1.0"
112
+ source = "registry+https://github.com/rust-lang/crates.io-index"
113
+ checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16"
114
+
109
115
  [[package]]
110
116
  name = "atk"
111
117
  version = "0.18.2"
@@ -396,6 +402,12 @@ dependencies = [
396
402
  "windows-link 0.2.1",
397
403
  ]
398
404
 
405
+ [[package]]
406
+ name = "chunked_transfer"
407
+ version = "1.5.0"
408
+ source = "registry+https://github.com/rust-lang/crates.io-index"
409
+ checksum = "6e4de3bc4ea267985becf712dc6d9eed8b04c953b3fcfb339ebc87acd9804901"
410
+
399
411
  [[package]]
400
412
  name = "clap"
401
413
  version = "4.6.1"
@@ -1557,6 +1569,12 @@ version = "1.10.1"
1557
1569
  source = "registry+https://github.com/rust-lang/crates.io-index"
1558
1570
  checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
1559
1571
 
1572
+ [[package]]
1573
+ name = "httpdate"
1574
+ version = "1.0.3"
1575
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1576
+ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
1577
+
1560
1578
  [[package]]
1561
1579
  name = "hyper"
1562
1580
  version = "1.9.0"
@@ -3267,6 +3285,20 @@ dependencies = [
3267
3285
  "windows-sys 0.60.2",
3268
3286
  ]
3269
3287
 
3288
+ [[package]]
3289
+ name = "ring"
3290
+ version = "0.17.14"
3291
+ source = "registry+https://github.com/rust-lang/crates.io-index"
3292
+ checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
3293
+ dependencies = [
3294
+ "cc",
3295
+ "cfg-if",
3296
+ "getrandom 0.2.17",
3297
+ "libc",
3298
+ "untrusted",
3299
+ "windows-sys 0.52.0",
3300
+ ]
3301
+
3270
3302
  [[package]]
3271
3303
  name = "rustc-hash"
3272
3304
  version = "2.1.2"
@@ -3295,6 +3327,41 @@ dependencies = [
3295
3327
  "windows-sys 0.61.2",
3296
3328
  ]
3297
3329
 
3330
+ [[package]]
3331
+ name = "rustls"
3332
+ version = "0.23.40"
3333
+ source = "registry+https://github.com/rust-lang/crates.io-index"
3334
+ checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b"
3335
+ dependencies = [
3336
+ "log",
3337
+ "once_cell",
3338
+ "ring",
3339
+ "rustls-pki-types",
3340
+ "rustls-webpki",
3341
+ "subtle",
3342
+ "zeroize",
3343
+ ]
3344
+
3345
+ [[package]]
3346
+ name = "rustls-pki-types"
3347
+ version = "1.14.1"
3348
+ source = "registry+https://github.com/rust-lang/crates.io-index"
3349
+ checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9"
3350
+ dependencies = [
3351
+ "zeroize",
3352
+ ]
3353
+
3354
+ [[package]]
3355
+ name = "rustls-webpki"
3356
+ version = "0.103.13"
3357
+ source = "registry+https://github.com/rust-lang/crates.io-index"
3358
+ checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e"
3359
+ dependencies = [
3360
+ "ring",
3361
+ "rustls-pki-types",
3362
+ "untrusted",
3363
+ ]
3364
+
3298
3365
  [[package]]
3299
3366
  name = "rustversion"
3300
3367
  version = "1.0.22"
@@ -3511,6 +3578,7 @@ version = "1.0.149"
3511
3578
  source = "registry+https://github.com/rust-lang/crates.io-index"
3512
3579
  checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
3513
3580
  dependencies = [
3581
+ "indexmap 2.14.0",
3514
3582
  "itoa",
3515
3583
  "memchr",
3516
3584
  "serde",
@@ -3734,9 +3802,21 @@ dependencies = [
3734
3802
  "recursive",
3735
3803
  ]
3736
3804
 
3805
+ [[package]]
3806
+ name = "sqlrite-ask"
3807
+ version = "0.1.17"
3808
+ dependencies = [
3809
+ "serde",
3810
+ "serde_json",
3811
+ "sqlrite-engine",
3812
+ "thiserror 2.0.18",
3813
+ "tiny_http",
3814
+ "ureq",
3815
+ ]
3816
+
3737
3817
  [[package]]
3738
3818
  name = "sqlrite-desktop"
3739
- version = "0.1.15"
3819
+ version = "0.1.17"
3740
3820
  dependencies = [
3741
3821
  "serde",
3742
3822
  "serde_json",
@@ -3748,7 +3828,7 @@ dependencies = [
3748
3828
 
3749
3829
  [[package]]
3750
3830
  name = "sqlrite-engine"
3751
- version = "0.1.15"
3831
+ version = "0.1.17"
3752
3832
  dependencies = [
3753
3833
  "clap",
3754
3834
  "env_logger",
@@ -3757,13 +3837,14 @@ dependencies = [
3757
3837
  "prettytable-rs",
3758
3838
  "rustyline",
3759
3839
  "rustyline-derive",
3840
+ "serde_json",
3760
3841
  "sqlparser",
3761
3842
  "thiserror 2.0.18",
3762
3843
  ]
3763
3844
 
3764
3845
  [[package]]
3765
3846
  name = "sqlrite-ffi"
3766
- version = "0.1.15"
3847
+ version = "0.1.17"
3767
3848
  dependencies = [
3768
3849
  "cbindgen",
3769
3850
  "sqlrite-engine",
@@ -3771,7 +3852,7 @@ dependencies = [
3771
3852
 
3772
3853
  [[package]]
3773
3854
  name = "sqlrite-nodejs"
3774
- version = "0.1.15"
3855
+ version = "0.1.17"
3775
3856
  dependencies = [
3776
3857
  "napi",
3777
3858
  "napi-build",
@@ -3781,7 +3862,7 @@ dependencies = [
3781
3862
 
3782
3863
  [[package]]
3783
3864
  name = "sqlrite-python"
3784
- version = "0.1.15"
3865
+ version = "0.1.17"
3785
3866
  dependencies = [
3786
3867
  "pyo3",
3787
3868
  "sqlrite-engine",
@@ -3861,6 +3942,12 @@ version = "0.11.1"
3861
3942
  source = "registry+https://github.com/rust-lang/crates.io-index"
3862
3943
  checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
3863
3944
 
3945
+ [[package]]
3946
+ name = "subtle"
3947
+ version = "2.6.1"
3948
+ source = "registry+https://github.com/rust-lang/crates.io-index"
3949
+ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
3950
+
3864
3951
  [[package]]
3865
3952
  name = "swift-rs"
3866
3953
  version = "1.0.7"
@@ -4371,6 +4458,18 @@ dependencies = [
4371
4458
  "time-core",
4372
4459
  ]
4373
4460
 
4461
+ [[package]]
4462
+ name = "tiny_http"
4463
+ version = "0.12.0"
4464
+ source = "registry+https://github.com/rust-lang/crates.io-index"
4465
+ checksum = "389915df6413a2e74fb181895f933386023c71110878cd0825588928e64cdc82"
4466
+ dependencies = [
4467
+ "ascii",
4468
+ "chunked_transfer",
4469
+ "httpdate",
4470
+ "log",
4471
+ ]
4472
+
4374
4473
  [[package]]
4375
4474
  name = "tinystr"
4376
4475
  version = "0.8.3"
@@ -4694,6 +4793,30 @@ version = "0.2.4"
4694
4793
  source = "registry+https://github.com/rust-lang/crates.io-index"
4695
4794
  checksum = "7264e107f553ccae879d21fbea1d6724ac785e8c3bfc762137959b5802826ef3"
4696
4795
 
4796
+ [[package]]
4797
+ name = "untrusted"
4798
+ version = "0.9.0"
4799
+ source = "registry+https://github.com/rust-lang/crates.io-index"
4800
+ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
4801
+
4802
+ [[package]]
4803
+ name = "ureq"
4804
+ version = "2.12.1"
4805
+ source = "registry+https://github.com/rust-lang/crates.io-index"
4806
+ checksum = "02d1a66277ed75f640d608235660df48c8e3c19f3b4edb6a263315626cc3c01d"
4807
+ dependencies = [
4808
+ "base64 0.22.1",
4809
+ "flate2",
4810
+ "log",
4811
+ "once_cell",
4812
+ "rustls",
4813
+ "rustls-pki-types",
4814
+ "serde",
4815
+ "serde_json",
4816
+ "url",
4817
+ "webpki-roots 0.26.11",
4818
+ ]
4819
+
4697
4820
  [[package]]
4698
4821
  name = "url"
4699
4822
  version = "2.5.8"
@@ -4998,6 +5121,24 @@ dependencies = [
4998
5121
  "system-deps",
4999
5122
  ]
5000
5123
 
5124
+ [[package]]
5125
+ name = "webpki-roots"
5126
+ version = "0.26.11"
5127
+ source = "registry+https://github.com/rust-lang/crates.io-index"
5128
+ checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9"
5129
+ dependencies = [
5130
+ "webpki-roots 1.0.7",
5131
+ ]
5132
+
5133
+ [[package]]
5134
+ name = "webpki-roots"
5135
+ version = "1.0.7"
5136
+ source = "registry+https://github.com/rust-lang/crates.io-index"
5137
+ checksum = "52f5ee44c96cf55f1b349600768e3ece3a8f26010c05265ab73f945bb1a2eb9d"
5138
+ dependencies = [
5139
+ "rustls-pki-types",
5140
+ ]
5141
+
5001
5142
  [[package]]
5002
5143
  name = "webview2-com"
5003
5144
  version = "0.38.2"
@@ -5228,6 +5369,15 @@ dependencies = [
5228
5369
  "windows-targets 0.42.2",
5229
5370
  ]
5230
5371
 
5372
+ [[package]]
5373
+ name = "windows-sys"
5374
+ version = "0.52.0"
5375
+ source = "registry+https://github.com/rust-lang/crates.io-index"
5376
+ checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
5377
+ dependencies = [
5378
+ "windows-targets 0.52.6",
5379
+ ]
5380
+
5231
5381
  [[package]]
5232
5382
  name = "windows-sys"
5233
5383
  version = "0.59.0"
@@ -5722,6 +5872,12 @@ dependencies = [
5722
5872
  "synstructure",
5723
5873
  ]
5724
5874
 
5875
+ [[package]]
5876
+ name = "zeroize"
5877
+ version = "1.8.2"
5878
+ source = "registry+https://github.com/rust-lang/crates.io-index"
5879
+ checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
5880
+
5725
5881
  [[package]]
5726
5882
  name = "zerotrie"
5727
5883
  version = "0.2.4"
@@ -27,7 +27,7 @@ resolver = "3"
27
27
  # `package =` key so the import name stays `sqlrite` internally:
28
28
  # sqlrite = { package = "sqlrite-engine", path = "…" }
29
29
  name = "sqlrite-engine"
30
- version = "0.1.15"
30
+ version = "0.1.17"
31
31
  authors = ["Joao Henrique Machado Silva <joaoh82@gmail.com>"]
32
32
  edition = "2024"
33
33
  rust-version = "1.85"
@@ -82,6 +82,14 @@ log = "0.4"
82
82
  sqlparser = "0.61"
83
83
  thiserror = "2.0"
84
84
  prettytable-rs = "0.10"
85
+ # Phase 7e: JSON column type. `serde_json` powers both the validation
86
+ # step at INSERT time (parse-and-discard to confirm the text is valid
87
+ # JSON) and the path extraction inside the json_extract / json_type
88
+ # / json_array_length / json_object_keys SQL functions. `preserve_order`
89
+ # keeps object keys in insertion order so json_object_keys output is
90
+ # stable; without it, BTreeMap-backed Maps would alphabetically sort,
91
+ # which surprises callers re-serializing the same JSON.
92
+ serde_json = { version = "1", features = ["preserve_order"] }
85
93
 
86
94
  # CLI-only deps (feature-gated). `optional = true` + the `cli`
87
95
  # feature above means these only land in the dep graph when
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sqlrite
3
- Version: 0.1.15
3
+ Version: 0.1.17
4
4
  Classifier: Development Status :: 3 - Alpha
5
5
  Classifier: Intended Audience :: Developers
6
6
  Classifier: License :: OSI Approved :: MIT License
@@ -87,6 +87,64 @@ conn = sqlrite.connect_read_only("foo.sqlrite")
87
87
  # connections on the same file coexist (shared OS lock).
88
88
  ```
89
89
 
90
+ ### Vector columns + KNN (Phase 7a–7d)
91
+
92
+ The engine ships with a fixed-dimension `VECTOR(N)` storage class and three distance functions (`vec_distance_l2`, `vec_distance_cosine`, `vec_distance_dot`). Plain `cursor.execute(...)` is all you need from Python — values come back as Python lists of floats.
93
+
94
+ ```python
95
+ cur.execute("CREATE TABLE docs (id INTEGER PRIMARY KEY, embedding VECTOR(384))")
96
+ cur.execute("INSERT INTO docs (id, embedding) VALUES (1, [0.1, 0.2, ..., 0.0])") # 384 floats
97
+ cur.execute("""
98
+ SELECT id FROM docs
99
+ ORDER BY vec_distance_l2(embedding, [0.1, 0.2, ..., 0.0])
100
+ LIMIT 10
101
+ """)
102
+ for row in cur:
103
+ print(row)
104
+ ```
105
+
106
+ For larger collections, build an HNSW index — the executor will use it automatically when the `WHERE`/`ORDER BY` shape matches:
107
+
108
+ ```python
109
+ cur.execute("CREATE INDEX idx_docs_emb ON docs USING hnsw (embedding)")
110
+ ```
111
+
112
+ ### JSON columns (Phase 7e)
113
+
114
+ `JSON` (and `JSONB` as an alias) columns store text, validated at INSERT/UPDATE time. Read with `json_extract` / `json_type` / `json_array_length` / `json_object_keys`. Path subset: `$`, `.key`, `[N]`, chained.
115
+
116
+ ```python
117
+ cur.execute("CREATE TABLE events (id INTEGER PRIMARY KEY, payload JSON)")
118
+ cur.execute(
119
+ "INSERT INTO events (payload) VALUES "
120
+ "('{\"user\": {\"name\": \"alice\"}, \"score\": 42}')"
121
+ )
122
+ cur.execute(
123
+ "SELECT json_extract(payload, '$.user.name'), json_type(payload, '$.score') FROM events"
124
+ )
125
+ print(cur.fetchall()) # [('alice', 'integer')]
126
+ ```
127
+
128
+ > `json_object_keys` returns a JSON-array text rather than a table-valued result (set-returning functions aren't supported yet).
129
+
130
+ ### Natural-language → SQL (Phase 7g — *coming soon*)
131
+
132
+ The Phase 7g.2-7g.8 wave adds a Python-native `conn.ask()` that wraps the new `sqlrite-ask` Rust crate via PyO3. Today it's available only from Rust ([`sqlrite-ask` on crates.io](https://crates.io/crates/sqlrite-ask)); the Python wrapper lands in 7g.4 and will look like:
133
+
134
+ ```python
135
+ # 7g.4 preview — not yet released
136
+ import sqlrite
137
+
138
+ conn = sqlrite.connect("foo.sqlrite")
139
+ cfg = sqlrite.AskConfig.from_env() # SQLRITE_LLM_API_KEY etc.
140
+ resp = conn.ask("How many users are over 30?", cfg)
141
+ print(resp.sql) # "SELECT COUNT(*) FROM users WHERE age > 30"
142
+ print(resp.explanation) # "Counts users over the age threshold."
143
+
144
+ # Convenience:
145
+ rows = conn.ask_run("How many users are over 30?", cfg).fetchall()
146
+ ```
147
+
90
148
  ## API surface
91
149
 
92
150
  | Function / Method | Purpose |
@@ -158,6 +158,32 @@ Expressions in `WHERE` and `UPDATE`'s `SET` RHS:
158
158
  | `.read FILENAME` | later |
159
159
  | `.ast QUERY` | later |
160
160
 
161
+ #### Natural-language → SQL (`sqlrite-ask`)
162
+
163
+ *Phase 7g.1.* The companion crate [`sqlrite-ask`](sqlrite-ask/) turns a natural-language question into a SQL query against your database, using the [Anthropic API](https://docs.anthropic.com/) for the actual generation.
164
+
165
+ ```toml
166
+ [dependencies]
167
+ sqlrite-engine = "0.1"
168
+ sqlrite-ask = "0.1"
169
+ ```
170
+
171
+ ```rust
172
+ use sqlrite::Connection;
173
+ use sqlrite_ask::{AskConfig, ConnectionAskExt};
174
+
175
+ let conn = Connection::open("foo.sqlrite")?;
176
+ let cfg = AskConfig::from_env()?; // SQLRITE_LLM_API_KEY etc.
177
+ let resp = conn.ask("How many users are over 30?", &cfg)?;
178
+ println!("Generated SQL: {}", resp.sql);
179
+ println!("Why: {}", resp.explanation);
180
+ // Caller decides whether to run resp.sql — the library deliberately doesn't.
181
+ ```
182
+
183
+ **Defaults:** `claude-sonnet-4-6`, `max_tokens: 1024`, schema dump cached for 5 minutes via Anthropic prompt caching (configurable to 1h or off via `AskConfig::cache_ttl`). Bring your own API key — set `SQLRITE_LLM_API_KEY` or pass it on `AskConfig`.
184
+
185
+ Per-product `ask()` wrappers (`.ask` REPL command, desktop "Ask" button, `conn.ask()` in the Python / Node / Go SDKs, and the MCP `ask` tool) ship in **7g.2-7g.8** as follow-up sub-phases. WASM gets a JS-callback shape so the API key never enters the browser. See [`docs/phase-7-plan.md`](docs/phase-7-plan.md) §7g for the full surface plan.
186
+
161
187
  ### Roadmap
162
188
 
163
189
  The project is staged in phases, each independently shippable. A finished phase is committed to `main` before the next one starts.
@@ -236,10 +262,16 @@ Lockstep versioning — one dispatch bumps every product to the same `vX.Y.Z`. T
236
262
  - [ ] macOS Apple Developer ID cert → `codesign` + `notarytool` in `tauri-action`
237
263
  - [ ] Windows code-signing cert → `signtool` in `tauri-action`
238
264
 
239
- **Phase 7 — AI-era extensions** *(research)*
240
- - [ ] Vector / embedding column type with an ANN index
241
- - [ ] Natural-language SQL front-end that emits queries against this engine
242
- - [ ] Other agent-era ideas as they emerge
265
+ **Phase 7 — AI-era extensions** *(in progress — full plan in [`docs/phase-7-plan.md`](docs/phase-7-plan.md))*
266
+ - [x] **7a `VECTOR(N)` column type** *(v0.1.10)*: dense f32 vectors with bracket-array literal syntax (`[0.1, 0.2, ...]`); file format bumped to v4
267
+ - [x] **7b Distance functions** *(v0.1.11)*: `vec_distance_l2/cosine/dot` + `ORDER BY <expr> LIMIT k` so KNN queries work end-to-end
268
+ - [x] **7c — Bounded-heap top-k optimization** *(v0.1.12)*
269
+ - [x] **7d — HNSW ANN index** *(v0.1.13–15)*: `CREATE INDEX … USING hnsw (col)`; recall@10 ≥ 0.95 at default `M=16, ef_construction=200, ef_search=50`; persisted as a `KIND_HNSW` cell tree
270
+ - [x] **7e — JSON column type + path queries** *(v0.1.16)*: `JSON` / `JSONB` columns stored as canonical text; `json_extract` / `json_type` / `json_array_length` / `json_object_keys`; `$.key`, `[N]`, chained JSONPath subset
271
+ - [x] **7g.1 — `sqlrite-ask` crate** *(this wave)*: foundational natural-language → SQL via the [Anthropic API](https://docs.anthropic.com/) (Sonnet 4.6 by default), prompt-cached schema dump, sync `ureq` HTTP. Public surface: `sqlrite_ask::ask(conn, q, &cfg)` or `conn.ask(q, &cfg)` via `ConnectionAskExt`.
272
+ - [ ] **7g.2-7g.8** — per-product `ask()` adapters: REPL `.ask`, desktop "Ask" button, Python/Node/Go/WASM SDKs, MCP `ask` tool
273
+ - [ ] **7h** — MCP server adapter (`sqlrite-mcp` binary)
274
+ - [ ] *(deferred to Phase 8)* Full-text search with BM25 + hybrid retrieval
243
275
 
244
276
  **Possible extras** *(no committed phase)*
245
277
  - Joins (`INNER`, `LEFT OUTER`, `CROSS` — SQLite does not support `RIGHT`/`FULL OUTER`)
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "sqlrite-desktop-frontend",
3
3
  "private": true,
4
- "version": "0.1.15",
4
+ "version": "0.1.17",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "dev": "vite",
@@ -71,6 +71,53 @@ See [Phase 4f notes in roadmap.md](roadmap.md) for the snapshot semantics and th
71
71
  - **Parameter binding.** `stmt.query(&[&"alice"])` is the intended shape but the current implementation takes no arguments — use string interpolation for now. Parameter binding lands with the cursor refactor.
72
72
  - **Cursor abstraction.** The Pager still eagerly loads every row at open time; `Rows` today wraps an in-memory `Vec`. Phase 5a's follow-up refactor streams rows through the B-Tree on demand — same public API, much lower memory for big SELECTs.
73
73
 
74
+ ### Natural-language → SQL via `sqlrite-ask` (Phase 7g.1)
75
+
76
+ The companion crate [`sqlrite-ask`](../sqlrite-ask/) layers a natural-language → SQL helper on top of the engine. Engine stays pure-SQL with no HTTP / TLS / async deps; anyone who doesn't need `ask()` can ignore the crate entirely.
77
+
78
+ ```toml
79
+ [dependencies]
80
+ sqlrite-engine = "0.1"
81
+ sqlrite-ask = "0.1"
82
+ ```
83
+
84
+ ```rust
85
+ use sqlrite::Connection;
86
+ use sqlrite_ask::{AskConfig, AskResponse, ConnectionAskExt};
87
+
88
+ let conn = Connection::open("foo.sqlrite")?;
89
+ let cfg = AskConfig::from_env()?; // reads SQLRITE_LLM_API_KEY etc.
90
+ let resp: AskResponse = conn.ask("How many users are over 30?", &cfg)?;
91
+
92
+ println!("Generated SQL: {}", resp.sql);
93
+ println!("Rationale: {}", resp.explanation);
94
+ println!("Tokens: in={}, out={}, cache_hit={}",
95
+ resp.usage.input_tokens,
96
+ resp.usage.output_tokens,
97
+ resp.usage.cache_read_input_tokens);
98
+
99
+ // Caller decides whether to execute the generated SQL — the library
100
+ // does NOT auto-execute. SDK convenience wrappers (Python's
101
+ // `conn.ask_run()`, Node's `db.askRun()`, etc.) add a one-shot
102
+ // generate-and-execute helper, but the default Rust API is
103
+ // "generate, return, let me decide".
104
+ let mut conn = conn; // need &mut for execute
105
+ let _ = conn.execute(&resp.sql)?;
106
+ ```
107
+
108
+ **What `sqlrite-ask` provides:**
109
+
110
+ - `ask(conn, question, config) -> AskResponse` — free function entry point.
111
+ - `Connection::ask(question, config)` via the `ConnectionAskExt` trait — same flow, method syntax.
112
+ - `AskConfig::from_env()` — reads `SQLRITE_LLM_PROVIDER` / `_API_KEY` / `_MODEL` / `_MAX_TOKENS` / `_CACHE_TTL` with sensible defaults.
113
+ - `AnthropicProvider` — sync `ureq` POST to `https://api.anthropic.com/v1/messages`. The crate is provider-pluggable (a `Provider` trait + `Request` / `Response` types), with OpenAI and Ollama follow-ups planned for later 7g sub-phases.
114
+ - Schema-aware prompt construction — walks `Database.tables` deterministically (alphabetical) and emits a `<schema>...</schema>` block with `cache_control: ephemeral`, so the schema dump is served from Anthropic's prompt cache after the first call.
115
+ - Output parser tolerant to fenced JSON (`` ```json … ``` ``) or JSON-with-leading-prose, in addition to strict JSON.
116
+
117
+ **Defaults:** `claude-sonnet-4-6`, `max_tokens: 1024`, 5-minute prompt-cache TTL. Configurable via `AskConfig`.
118
+
119
+ **Per-product wrappers ship in 7g.2-7g.8** — REPL `.ask`, desktop "Ask" button, Python/Node/Go SDKs `conn.ask()`, WASM SDK with a JS-callback shape (so the API key stays out of the browser tab), and the MCP `ask` tool.
120
+
74
121
  ## The C FFI (Phase 5b)
75
122
 
76
123
  The `sqlrite-ffi/` crate wraps the Rust API in a C ABI that every non-Rust SDK binds against. Build the shared library:
@@ -170,14 +170,14 @@ SELECT id, title FROM docs ORDER BY embedding <-> [0.1, ...] LIMIT 10;
170
170
 
171
171
  ---
172
172
 
173
- ### 7e — JSON column type + path queries
173
+ ### 7e — JSON column type + path queries
174
174
 
175
- **What.** New `JSON` data type. Store as bincoded `serde_json::Value` (or as a parsed AST see open questions). Support a small set of extraction functions:
175
+ **What.** New `JSON` data type. Stored as canonical UTF-8 text and validated at INSERT/UPDATE time via `serde_json::from_str`. The four path-extraction functions parse on demand:
176
176
 
177
177
  - `json_extract(col, '$.path')` — returns the value at the path, NULL if absent
178
- - `json_array_length(col, '$.path')` — array length, NULL for non-array
179
- - `json_object_keys(col, '$.path')` — TEXT array of keys, NULL for non-object
180
- - `json_type(col, '$.path')` — `'null'`, `'bool'`, `'number'`, `'string'`, `'array'`, `'object'`
178
+ - `json_array_length(col, '$.path')` — array length, NULL for non-array, errors for non-array-with-path-resolved
179
+ - `json_object_keys(col, '$.path')` — JSON-array text of keys (see scope-correction note in Q3 below; SQLite's set-returning shape requires features we don't have)
180
+ - `json_type(col, '$.path')` — `'null'` / `'true'` / `'false'` / `'integer'` / `'real'` / `'text'` / `'array'` / `'object'` (matches SQLite JSON1 conventions)
181
181
 
182
182
  **Why this matters for AI-era specifically.** LLM tool-call outputs are JSON. RAG citation arrays are JSON. Agent scratchpads are JSON. Storing them as TEXT and re-parsing on every query is wasteful.
183
183
 
@@ -249,7 +249,7 @@ let rows = conn.execute(&resp.sql)?;
249
249
 
250
250
  **Layered design.** The work splits into one library layer + several thin adapters:
251
251
 
252
- - **7g.1 — `sqlrite-ask` crate (foundational, ~400 LOC).** New separate crate (not feature-gated on the engine) so the engine stays pure-SQL with no HTTP / async deps. Owns: provider adapters (Anthropic / OpenAI / Ollama), prompt construction, schema introspection helper that walks `sqlrite_master`, the `AskResponse` type, configuration loading from env or a passed config struct. Depends on `sqlrite-engine` for the schema introspection.
252
+ - **✅ 7g.1 — `sqlrite-ask` crate (foundational, ~750 LOC code + tests).** New separate crate (not feature-gated on the engine) so the engine stays pure-SQL with no HTTP / async deps. Owns: provider adapters (Anthropic in 7g.1; OpenAI / Ollama follow-ups), prompt construction, schema introspection helper that walks `Database.tables` directly (typed walk — cheaper + more robust than reflecting through `sqlrite_master`), the `AskResponse` type, configuration loading from env (`SQLRITE_LLM_PROVIDER` / `_API_KEY` / `_MODEL` / `_MAX_TOKENS` / `_CACHE_TTL`) or a passed config struct. Depends on `sqlrite-engine` for the schema introspection. Public API: `ask()` free function, `ConnectionAskExt::ask` trait extension on `sqlrite::Connection`, `AskConfig::from_env()`, `AskResponse { sql, explanation, usage }`. Default model `claude-sonnet-4-6` per the cost-quality NL→SQL sweet spot. Sync `ureq` HTTP (rejected reqwest::blocking — pulls tokio in even on the blocking path); JSON request/response shapes hand-rolled in serde_json (~120 LOC) — there is no official Anthropic Rust SDK, and rolling our own matches Q5's "build it yourself" theme. Schema dump goes inside a `<schema>...</schema>` block with `cache_control: ephemeral` so repeat asks against the same DB hit Anthropic's prompt cache (5-min TTL default; 1-hour TTL via `CacheTtl::OneHour`). Output parsing is tolerant — strict JSON, fenced JSON, or JSON-with-leading-prose all parse — because real LLM output drifts even with strict prompts. 30 tests pass (26 unit + 4 integration via `tiny_http` localhost mock).
253
253
  - **7g.2 — REPL `.ask` (~80 LOC).** Thin. Calls `sqlrite-ask`, prints the generated SQL, prompts `Y/n`, runs if confirmed. Most of the file is the rustyline integration.
254
254
  - **7g.3 — Desktop UI (~150 LOC).** New "Ask" button + prompt input + inline SQL preview in the editor. Calls into `sqlrite-ask` from a Tauri command; the command lives in `desktop/src-tauri/`. Schema introspection runs server-side; HTTP call also server-side (so the API key stays in the app process, not the webview).
255
255
  - **7g.4 — Python SDK (~80 LOC).** PyO3 wrapper around `sqlrite-ask`. `Connection.ask(question)` returns a Python object with `.sql` and `.explanation`. `Connection.ask_run(question)` is the convenience that calls `execute` after.
@@ -378,6 +378,10 @@ Q1–Q10 were resolved by the project owner on 2026-04-26. Each question keeps i
378
378
  ### Q3. JSON storage format
379
379
 
380
380
  > **Decided: bincoded `serde_json::Value`** for the MVP. JSON indexing remains a future phase.
381
+ >
382
+ > **Scope correction (2026-04-28, during 7e implementation):** Q3's "bincoded `Value`" answer was settled before remembering that bincode was removed from the engine in Phase 3c (cell-based encoding replaced it). Rather than re-add bincode for one column type, **7e ships JSON-as-canonical-text** — same as SQLite's JSON1 extension. INSERT/UPDATE call `serde_json::from_str` to validate; the four `json_*` functions re-parse on demand. Trade-off: ~2× storage vs. binary, plus per-call parse overhead — both acceptable for MVP and consistent with SQLite's choice. JSONB-style binary indexing remains a future-phase optimization, but doesn't block 7e.
383
+ >
384
+ > One additional 7e divergence from the original plan: `json_object_keys` is supposed to be a *table-valued function* (one row per key, like SQLite's). We don't yet support set-returning functions in the executor, so 7e returns the keys as a JSON-array text instead. Caller can iterate via `json_array_length` + `json_extract` indexing. Documented in `docs/supported-sql.md` so users see the divergence up front.
381
385
 
382
386
  - **bincoded `serde_json::Value`:** one-line implementation, fast read/write, opaque on disk.
383
387
  - **Parsed AST as cell-encoded structure:** more code, but lets us index into JSON without a full deserialize.