sqlrite 0.2.0__tar.gz → 0.3.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 (138) hide show
  1. {sqlrite-0.2.0 → sqlrite-0.3.0}/.github/workflows/release.yml +17 -12
  2. {sqlrite-0.2.0 → sqlrite-0.3.0}/Cargo.lock +7 -7
  3. {sqlrite-0.2.0 → sqlrite-0.3.0}/Cargo.toml +2 -2
  4. {sqlrite-0.2.0 → sqlrite-0.3.0}/PKG-INFO +1 -1
  5. {sqlrite-0.2.0 → sqlrite-0.3.0}/desktop/package.json +1 -1
  6. {sqlrite-0.2.0 → sqlrite-0.3.0}/docs/supported-sql.md +76 -7
  7. {sqlrite-0.2.0 → sqlrite-0.3.0}/pyproject.toml +1 -1
  8. {sqlrite-0.2.0 → sqlrite-0.3.0}/sdk/python/Cargo.toml +1 -1
  9. {sqlrite-0.2.0 → sqlrite-0.3.0}/sqlrite-ask/Cargo.toml +1 -1
  10. {sqlrite-0.2.0 → sqlrite-0.3.0}/src/sql/db/table.rs +282 -5
  11. {sqlrite-0.2.0 → sqlrite-0.3.0}/src/sql/executor.rs +246 -3
  12. {sqlrite-0.2.0 → sqlrite-0.3.0}/src/sql/mod.rs +700 -3
  13. {sqlrite-0.2.0 → sqlrite-0.3.0}/src/sql/pager/mod.rs +254 -1
  14. sqlrite-0.3.0/src/sql/parser/create.rs +368 -0
  15. sqlrite-0.2.0/src/sql/parser/create.rs +0 -241
  16. {sqlrite-0.2.0 → sqlrite-0.3.0}/.github/workflows/ci.yml +0 -0
  17. {sqlrite-0.2.0 → sqlrite-0.3.0}/.github/workflows/release-pr.yml +0 -0
  18. {sqlrite-0.2.0 → sqlrite-0.3.0}/.github/workflows/rust.yml +0 -0
  19. {sqlrite-0.2.0 → sqlrite-0.3.0}/.gitignore +0 -0
  20. {sqlrite-0.2.0 → sqlrite-0.3.0}/CLAUDE.md +0 -0
  21. {sqlrite-0.2.0 → sqlrite-0.3.0}/CODE_OF_CONDUCT.md +0 -0
  22. {sqlrite-0.2.0 → sqlrite-0.3.0}/LICENSE +0 -0
  23. {sqlrite-0.2.0 → sqlrite-0.3.0}/MAINTAINERS +0 -0
  24. {sqlrite-0.2.0 → sqlrite-0.3.0}/Makefile +0 -0
  25. {sqlrite-0.2.0 → sqlrite-0.3.0}/README.md +0 -0
  26. {sqlrite-0.2.0 → sqlrite-0.3.0}/desktop/index.html +0 -0
  27. {sqlrite-0.2.0 → sqlrite-0.3.0}/desktop/package-lock.json +0 -0
  28. {sqlrite-0.2.0 → sqlrite-0.3.0}/desktop/src/App.svelte +0 -0
  29. {sqlrite-0.2.0 → sqlrite-0.3.0}/desktop/src/app.css +0 -0
  30. {sqlrite-0.2.0 → sqlrite-0.3.0}/desktop/src/main.ts +0 -0
  31. {sqlrite-0.2.0 → sqlrite-0.3.0}/desktop/src/vite-env.d.ts +0 -0
  32. {sqlrite-0.2.0 → sqlrite-0.3.0}/desktop/svelte.config.js +0 -0
  33. {sqlrite-0.2.0 → sqlrite-0.3.0}/desktop/tsconfig.json +0 -0
  34. {sqlrite-0.2.0 → sqlrite-0.3.0}/desktop/vite.config.ts +0 -0
  35. {sqlrite-0.2.0 → sqlrite-0.3.0}/docs/_index.md +0 -0
  36. {sqlrite-0.2.0 → sqlrite-0.3.0}/docs/architecture.md +0 -0
  37. {sqlrite-0.2.0 → sqlrite-0.3.0}/docs/ask-backend-examples.md +0 -0
  38. {sqlrite-0.2.0 → sqlrite-0.3.0}/docs/ask.md +0 -0
  39. {sqlrite-0.2.0 → sqlrite-0.3.0}/docs/design-decisions.md +0 -0
  40. {sqlrite-0.2.0 → sqlrite-0.3.0}/docs/desktop.md +0 -0
  41. {sqlrite-0.2.0 → sqlrite-0.3.0}/docs/embedding.md +0 -0
  42. {sqlrite-0.2.0 → sqlrite-0.3.0}/docs/file-format.md +0 -0
  43. {sqlrite-0.2.0 → sqlrite-0.3.0}/docs/fts.md +0 -0
  44. {sqlrite-0.2.0 → sqlrite-0.3.0}/docs/getting-started.md +0 -0
  45. {sqlrite-0.2.0 → sqlrite-0.3.0}/docs/mcp.md +0 -0
  46. {sqlrite-0.2.0 → sqlrite-0.3.0}/docs/pager.md +0 -0
  47. {sqlrite-0.2.0 → sqlrite-0.3.0}/docs/phase-7-plan.md +0 -0
  48. {sqlrite-0.2.0 → sqlrite-0.3.0}/docs/phase-8-plan.md +0 -0
  49. {sqlrite-0.2.0 → sqlrite-0.3.0}/docs/release-plan.md +0 -0
  50. {sqlrite-0.2.0 → sqlrite-0.3.0}/docs/release-secrets.md +0 -0
  51. {sqlrite-0.2.0 → sqlrite-0.3.0}/docs/roadmap.md +0 -0
  52. {sqlrite-0.2.0 → sqlrite-0.3.0}/docs/smoke-test.md +0 -0
  53. {sqlrite-0.2.0 → sqlrite-0.3.0}/docs/sql-engine.md +0 -0
  54. {sqlrite-0.2.0 → sqlrite-0.3.0}/docs/storage-model.md +0 -0
  55. {sqlrite-0.2.0 → sqlrite-0.3.0}/docs/usage.md +0 -0
  56. {sqlrite-0.2.0 → sqlrite-0.3.0}/examples/README.md +0 -0
  57. {sqlrite-0.2.0 → sqlrite-0.3.0}/examples/c/Makefile +0 -0
  58. {sqlrite-0.2.0 → sqlrite-0.3.0}/examples/c/hello.c +0 -0
  59. {sqlrite-0.2.0 → sqlrite-0.3.0}/examples/go/go.mod +0 -0
  60. {sqlrite-0.2.0 → sqlrite-0.3.0}/examples/go/hello.go +0 -0
  61. {sqlrite-0.2.0 → sqlrite-0.3.0}/examples/hybrid-retrieval/README.md +0 -0
  62. {sqlrite-0.2.0 → sqlrite-0.3.0}/examples/hybrid-retrieval/hybrid_retrieval.rs +0 -0
  63. {sqlrite-0.2.0 → sqlrite-0.3.0}/examples/nodejs/hello.mjs +0 -0
  64. {sqlrite-0.2.0 → sqlrite-0.3.0}/examples/python/hello.py +0 -0
  65. {sqlrite-0.2.0 → sqlrite-0.3.0}/examples/rust/quickstart.rs +0 -0
  66. {sqlrite-0.2.0 → sqlrite-0.3.0}/examples/wasm/Makefile +0 -0
  67. {sqlrite-0.2.0 → sqlrite-0.3.0}/examples/wasm/index.html +0 -0
  68. {sqlrite-0.2.0 → sqlrite-0.3.0}/examples/wasm/server.mjs +0 -0
  69. {sqlrite-0.2.0 → sqlrite-0.3.0}/images/SQLRite - Desktop.png +0 -0
  70. {sqlrite-0.2.0 → sqlrite-0.3.0}/images/SQLRite Data Structures.png +0 -0
  71. {sqlrite-0.2.0 → sqlrite-0.3.0}/images/SQLRite Simple SQL Execution High Level Diagram.png +0 -0
  72. {sqlrite-0.2.0 → sqlrite-0.3.0}/images/SQLRite Simple SQL INSERT Execution High Level Diagram (Insert Row).png +0 -0
  73. {sqlrite-0.2.0 → sqlrite-0.3.0}/images/SQLRite Simple SQL INSERT Execution High Level Diagram.png +0 -0
  74. {sqlrite-0.2.0 → sqlrite-0.3.0}/images/SQLRite_logo.png +0 -0
  75. {sqlrite-0.2.0 → sqlrite-0.3.0}/images/architecture.png +0 -0
  76. {sqlrite-0.2.0 → sqlrite-0.3.0}/rust-toolchain.toml +0 -0
  77. {sqlrite-0.2.0 → sqlrite-0.3.0}/samples/AST.delete.example +0 -0
  78. {sqlrite-0.2.0 → sqlrite-0.3.0}/samples/AST.insert.exemple +0 -0
  79. {sqlrite-0.2.0 → sqlrite-0.3.0}/samples/AST.select.example +0 -0
  80. {sqlrite-0.2.0 → sqlrite-0.3.0}/samples/AST.update.example +0 -0
  81. {sqlrite-0.2.0 → sqlrite-0.3.0}/samples/CREATE TABLE sqlrite_schema.sql +0 -0
  82. {sqlrite-0.2.0 → sqlrite-0.3.0}/samples/CREATE_TABLE with duplicate.sql +0 -0
  83. {sqlrite-0.2.0 → sqlrite-0.3.0}/samples/CREATE_TABLE.sql +0 -0
  84. {sqlrite-0.2.0 → sqlrite-0.3.0}/samples/INSERT.sql +0 -0
  85. {sqlrite-0.2.0 → sqlrite-0.3.0}/scripts/bump-version.sh +0 -0
  86. {sqlrite-0.2.0 → sqlrite-0.3.0}/sdk/go/README.md +0 -0
  87. {sqlrite-0.2.0 → sqlrite-0.3.0}/sdk/go/ask.go +0 -0
  88. {sqlrite-0.2.0 → sqlrite-0.3.0}/sdk/go/ask_test.go +0 -0
  89. {sqlrite-0.2.0 → sqlrite-0.3.0}/sdk/go/conn.go +0 -0
  90. {sqlrite-0.2.0 → sqlrite-0.3.0}/sdk/go/go.mod +0 -0
  91. {sqlrite-0.2.0 → sqlrite-0.3.0}/sdk/go/rows.go +0 -0
  92. {sqlrite-0.2.0 → sqlrite-0.3.0}/sdk/go/sqlrite.go +0 -0
  93. {sqlrite-0.2.0 → sqlrite-0.3.0}/sdk/go/sqlrite_test.go +0 -0
  94. {sqlrite-0.2.0 → sqlrite-0.3.0}/sdk/go/stmt.go +0 -0
  95. {sqlrite-0.2.0 → sqlrite-0.3.0}/sdk/python/README.md +0 -0
  96. {sqlrite-0.2.0 → sqlrite-0.3.0}/sdk/python/src/lib.rs +0 -0
  97. {sqlrite-0.2.0 → sqlrite-0.3.0}/sdk/python/tests/test_ask.py +0 -0
  98. {sqlrite-0.2.0 → sqlrite-0.3.0}/sdk/python/tests/test_sqlrite.py +0 -0
  99. {sqlrite-0.2.0 → sqlrite-0.3.0}/sqlrite-ask/README.md +0 -0
  100. {sqlrite-0.2.0 → sqlrite-0.3.0}/sqlrite-ask/src/lib.rs +0 -0
  101. {sqlrite-0.2.0 → sqlrite-0.3.0}/sqlrite-ask/src/prompt.rs +0 -0
  102. {sqlrite-0.2.0 → sqlrite-0.3.0}/sqlrite-ask/src/provider/anthropic.rs +0 -0
  103. {sqlrite-0.2.0 → sqlrite-0.3.0}/sqlrite-ask/src/provider/mock.rs +0 -0
  104. {sqlrite-0.2.0 → sqlrite-0.3.0}/sqlrite-ask/src/provider/mod.rs +0 -0
  105. {sqlrite-0.2.0 → sqlrite-0.3.0}/sqlrite-ask/tests/anthropic_http.rs +0 -0
  106. {sqlrite-0.2.0 → sqlrite-0.3.0}/src/ask/mod.rs +0 -0
  107. {sqlrite-0.2.0 → sqlrite-0.3.0}/src/ask/schema.rs +0 -0
  108. {sqlrite-0.2.0 → sqlrite-0.3.0}/src/connection.rs +0 -0
  109. {sqlrite-0.2.0 → sqlrite-0.3.0}/src/error.rs +0 -0
  110. {sqlrite-0.2.0 → sqlrite-0.3.0}/src/lib.rs +0 -0
  111. {sqlrite-0.2.0 → sqlrite-0.3.0}/src/main.rs +0 -0
  112. {sqlrite-0.2.0 → sqlrite-0.3.0}/src/meta_command/mod.rs +0 -0
  113. {sqlrite-0.2.0 → sqlrite-0.3.0}/src/repl/mod.rs +0 -0
  114. {sqlrite-0.2.0 → sqlrite-0.3.0}/src/sql/db/database.rs +0 -0
  115. {sqlrite-0.2.0 → sqlrite-0.3.0}/src/sql/db/mod.rs +0 -0
  116. {sqlrite-0.2.0 → sqlrite-0.3.0}/src/sql/db/secondary_index.rs +0 -0
  117. {sqlrite-0.2.0 → sqlrite-0.3.0}/src/sql/fts/bm25.rs +0 -0
  118. {sqlrite-0.2.0 → sqlrite-0.3.0}/src/sql/fts/mod.rs +0 -0
  119. {sqlrite-0.2.0 → sqlrite-0.3.0}/src/sql/fts/posting_list.rs +0 -0
  120. {sqlrite-0.2.0 → sqlrite-0.3.0}/src/sql/fts/tokenizer.rs +0 -0
  121. {sqlrite-0.2.0 → sqlrite-0.3.0}/src/sql/hnsw.rs +0 -0
  122. {sqlrite-0.2.0 → sqlrite-0.3.0}/src/sql/pager/cell.rs +0 -0
  123. {sqlrite-0.2.0 → sqlrite-0.3.0}/src/sql/pager/file.rs +0 -0
  124. {sqlrite-0.2.0 → sqlrite-0.3.0}/src/sql/pager/fts_cell.rs +0 -0
  125. {sqlrite-0.2.0 → sqlrite-0.3.0}/src/sql/pager/header.rs +0 -0
  126. {sqlrite-0.2.0 → sqlrite-0.3.0}/src/sql/pager/hnsw_cell.rs +0 -0
  127. {sqlrite-0.2.0 → sqlrite-0.3.0}/src/sql/pager/index_cell.rs +0 -0
  128. {sqlrite-0.2.0 → sqlrite-0.3.0}/src/sql/pager/interior_page.rs +0 -0
  129. {sqlrite-0.2.0 → sqlrite-0.3.0}/src/sql/pager/overflow.rs +0 -0
  130. {sqlrite-0.2.0 → sqlrite-0.3.0}/src/sql/pager/page.rs +0 -0
  131. {sqlrite-0.2.0 → sqlrite-0.3.0}/src/sql/pager/pager.rs +0 -0
  132. {sqlrite-0.2.0 → sqlrite-0.3.0}/src/sql/pager/table_page.rs +0 -0
  133. {sqlrite-0.2.0 → sqlrite-0.3.0}/src/sql/pager/varint.rs +0 -0
  134. {sqlrite-0.2.0 → sqlrite-0.3.0}/src/sql/pager/wal.rs +0 -0
  135. {sqlrite-0.2.0 → sqlrite-0.3.0}/src/sql/parser/insert.rs +0 -0
  136. {sqlrite-0.2.0 → sqlrite-0.3.0}/src/sql/parser/mod.rs +0 -0
  137. {sqlrite-0.2.0 → sqlrite-0.3.0}/src/sql/parser/select.rs +0 -0
  138. {sqlrite-0.2.0 → sqlrite-0.3.0}/src/sql/tokenizer.rs +0 -0
@@ -152,7 +152,14 @@ jobs:
152
152
  # click Approve before this job actually runs).
153
153
  publish-crate:
154
154
  name: Publish sqlrite crate to crates.io
155
- needs: [detect, tag-all]
155
+ # Engine depends on sqlrite-ask (post-v0.1.19 dep-direction flip), so
156
+ # publish-ask must complete first — otherwise crates.io rejects the
157
+ # engine publish with "failed to select a version for the requirement
158
+ # `sqlrite-ask = "^X.Y"`". This was masked through 0.1.x because old
159
+ # sqlrite-ask versions were already on crates.io and the engine's
160
+ # version requirement (`^0.1`) matched them; the v0.2.0 cut surfaced
161
+ # the latent bug.
162
+ needs: [detect, tag-all, publish-ask]
156
163
  if: needs.detect.outputs.should_release == 'true'
157
164
  runs-on: ubuntu-latest
158
165
  environment: release
@@ -203,9 +210,11 @@ jobs:
203
210
 
204
211
  # ---------------------------------------------------------------------------
205
212
  # Step 3a': publish the `sqlrite-ask` crate (Phase 7g.1) — natural-
206
- # language → SQL adapter built on top of the engine. Same shape as
207
- # `publish-crate` above; separate job so a registry hiccup on one
208
- # doesn't block the other and re-runs are surgical.
213
+ # language → SQL adapter. Since the v0.1.19 dep-direction flip,
214
+ # sqlrite-ask is dep-free of sqlrite-engine it's a pure-string-in /
215
+ # string-out adapter. The engine depends on IT, not the other way
216
+ # around. So this job runs FIRST in the publish chain; publish-crate
217
+ # waits on it.
209
218
  #
210
219
  # Crate name on crates.io: `sqlrite-ask`. Library name (the `use`
211
220
  # path): `sqlrite_ask`. No alias-renaming this time — the short
@@ -213,7 +222,7 @@ jobs:
213
222
  # for why the engine had to rename).
214
223
  publish-ask:
215
224
  name: Publish sqlrite-ask crate to crates.io
216
- needs: [detect, tag-all, publish-crate]
225
+ needs: [detect, tag-all]
217
226
  if: needs.detect.outputs.should_release == 'true'
218
227
  runs-on: ubuntu-latest
219
228
  environment: release
@@ -230,13 +239,9 @@ jobs:
230
239
  env:
231
240
  CARGO_REGISTRY_TOKEN: ${{ secrets.CRATES_IO_TOKEN }}
232
241
  # `--no-verify` mirrors `publish-crate` — Release-PR CI
233
- # already validated this commit.
234
- #
235
- # `needs: [..., publish-crate]` is load-bearing: sqlrite-ask
236
- # depends on sqlrite-engine, and crates.io rejects publishes
237
- # whose path-deps haven't yet resolved to a published version
238
- # at the same number. Sequencing makes the dep visible by the
239
- # time we publish.
242
+ # already validated this commit. sqlrite-ask has no
243
+ # SQLRite-internal path-deps after the v0.1.19 dep-direction
244
+ # flip, so this job is unblocked the moment `tag-all` lands.
240
245
  run: cargo publish -p sqlrite-ask --no-verify
241
246
 
242
247
  - name: GitHub Release
@@ -3817,7 +3817,7 @@ dependencies = [
3817
3817
 
3818
3818
  [[package]]
3819
3819
  name = "sqlrite-ask"
3820
- version = "0.2.0"
3820
+ version = "0.3.0"
3821
3821
  dependencies = [
3822
3822
  "serde",
3823
3823
  "serde_json",
@@ -3828,7 +3828,7 @@ dependencies = [
3828
3828
 
3829
3829
  [[package]]
3830
3830
  name = "sqlrite-desktop"
3831
- version = "0.2.0"
3831
+ version = "0.3.0"
3832
3832
  dependencies = [
3833
3833
  "serde",
3834
3834
  "serde_json",
@@ -3840,7 +3840,7 @@ dependencies = [
3840
3840
 
3841
3841
  [[package]]
3842
3842
  name = "sqlrite-engine"
3843
- version = "0.2.0"
3843
+ version = "0.3.0"
3844
3844
  dependencies = [
3845
3845
  "clap",
3846
3846
  "env_logger",
@@ -3857,7 +3857,7 @@ dependencies = [
3857
3857
 
3858
3858
  [[package]]
3859
3859
  name = "sqlrite-ffi"
3860
- version = "0.2.0"
3860
+ version = "0.3.0"
3861
3861
  dependencies = [
3862
3862
  "cbindgen",
3863
3863
  "serde",
@@ -3867,7 +3867,7 @@ dependencies = [
3867
3867
 
3868
3868
  [[package]]
3869
3869
  name = "sqlrite-mcp"
3870
- version = "0.2.0"
3870
+ version = "0.3.0"
3871
3871
  dependencies = [
3872
3872
  "clap",
3873
3873
  "libc",
@@ -3878,7 +3878,7 @@ dependencies = [
3878
3878
 
3879
3879
  [[package]]
3880
3880
  name = "sqlrite-nodejs"
3881
- version = "0.2.0"
3881
+ version = "0.3.0"
3882
3882
  dependencies = [
3883
3883
  "napi",
3884
3884
  "napi-build",
@@ -3888,7 +3888,7 @@ dependencies = [
3888
3888
 
3889
3889
  [[package]]
3890
3890
  name = "sqlrite-python"
3891
- version = "0.2.0"
3891
+ version = "0.3.0"
3892
3892
  dependencies = [
3893
3893
  "pyo3",
3894
3894
  "sqlrite-engine",
@@ -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.2.0"
30
+ version = "0.3.0"
31
31
  authors = ["Joao Henrique Machado Silva <joaoh82@gmail.com>"]
32
32
  edition = "2024"
33
33
  rust-version = "1.85"
@@ -138,4 +138,4 @@ fs2 = { version = "0.4", optional = true }
138
138
  # crate publishes to crates.io, and a path-only dep without a
139
139
  # version field fails the manifest verification step. See PR #58
140
140
  # retrospective in docs/roadmap.md.
141
- sqlrite-ask = { version = "0.2", path = "sqlrite-ask", optional = true }
141
+ sqlrite-ask = { version = "0.3", path = "sqlrite-ask", optional = true }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sqlrite
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Classifier: Development Status :: 3 - Alpha
5
5
  Classifier: Intended Audience :: Developers
6
6
  Classifier: License :: OSI Approved :: MIT License
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "sqlrite-desktop-frontend",
3
3
  "private": true,
4
- "version": "0.2.0",
4
+ "version": "0.3.0",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "dev": "vite",
@@ -8,12 +8,14 @@ If you're looking for _how_ to use SQLRite (REPL flow, meta-commands, history, e
8
8
 
9
9
  | Statement | Supported today |
10
10
  |---|---|
11
- | [`CREATE TABLE`](#create-table) | Columns with `PRIMARY KEY` / `UNIQUE` / `NOT NULL`; typed columns; auto-indexes on constrained columns |
11
+ | [`CREATE TABLE`](#create-table) | Columns with `PRIMARY KEY` / `UNIQUE` / `NOT NULL` / `DEFAULT <literal>`; typed columns; auto-indexes on constrained columns |
12
12
  | [`CREATE [UNIQUE] INDEX`](#create-index) | Single-column named indexes, `IF NOT EXISTS`, persisted as cell-based B-Trees |
13
- | [`INSERT INTO`](#insert-into) | Auto-ROWID, UNIQUE/PK enforcement, clean type errors, NULL padding |
13
+ | [`INSERT INTO`](#insert-into) | Auto-ROWID, UNIQUE/PK enforcement, clean type errors, NULL/DEFAULT padding |
14
14
  | [`SELECT`](#select) | `*` or column list, `WHERE`, single-column `ORDER BY`, `LIMIT`; index probing on `col = literal` |
15
15
  | [`UPDATE`](#update) | Multi-column `SET`, `WHERE`, arithmetic RHS, type + UNIQUE enforcement |
16
16
  | [`DELETE`](#delete) | `WHERE` predicate or whole-table |
17
+ | [`ALTER TABLE`](#alter-table) | `RENAME TO`, `RENAME COLUMN`, `ADD COLUMN`, `DROP COLUMN` (one operation per statement) |
18
+ | [`DROP TABLE`](#drop-table) / [`DROP INDEX`](#drop-index) | `IF EXISTS`; single target; auto-indexes refused for `DROP INDEX` |
17
19
  | [`BEGIN`](#transactions) / [`COMMIT`](#transactions) / [`ROLLBACK`](#transactions) | Snapshot-based; single-level; WAL-backed commit; auto-rollback on COMMIT disk failure |
18
20
 
19
21
  Statements the parser accepts (because sqlparser understands them in the SQLite dialect) but SQLRite doesn't execute yet return `SQL Statement not supported yet`. The [Not yet supported](#not-yet-supported) section below enumerates the common ones.
@@ -41,12 +43,12 @@ CREATE TABLE <name> (<col> <type> [column_constraint]* [, ...]);
41
43
 
42
44
  - `PRIMARY KEY` — one column per table; the column **must** be `INTEGER` and gets auto-ROWID behavior (omitted on INSERT → auto-assigned). Auto-creates an index named `sqlrite_autoindex_<table>_<column>`.
43
45
  - `UNIQUE` — enforced at INSERT/UPDATE time. Auto-creates an index with the same naming scheme.
44
- - `NOT NULL` — rejects NULL at INSERT/UPDATE. Omitted columns on INSERT are NULL by default, so a `NOT NULL` without an INSERT-time value is an error.
46
+ - `NOT NULL` — rejects NULL at INSERT/UPDATE. Omitted columns on INSERT are NULL by default (or pick up the column's `DEFAULT`, if any), so a `NOT NULL` without an INSERT-time value or DEFAULT is an error.
47
+ - `DEFAULT <literal>` — value substituted when the column is omitted from an INSERT. Accepts integer / real / text / boolean / NULL literals (and unary `+` / `-` on numerics). Function-call defaults like `CURRENT_TIMESTAMP` and other non-literal expressions are rejected at CREATE TABLE time. Explicit `INSERT ... VALUES (..., NULL, ...)` is preserved as NULL — the default only fires for omitted columns (matches SQLite).
45
48
 
46
49
  ### What's **not** enforced at CREATE TABLE time
47
50
 
48
51
  - **Table-level constraints** (`PRIMARY KEY (col1, col2)`, `FOREIGN KEY`, `CHECK`, `UNIQUE (col1, col2)`) are parsed but ignored.
49
- - **`DEFAULT` values** are parsed but ignored.
50
52
  - **Multi-column `PRIMARY KEY`** — only single-column PKs work; a composite PK is accepted by the parser but treated as no PK.
51
53
 
52
54
  ### Errors returned
@@ -208,6 +210,72 @@ DELETE FROM <table> [WHERE <expr>];
208
210
 
209
211
  ---
210
212
 
213
+ ## `ALTER TABLE`
214
+
215
+ ```sql
216
+ ALTER TABLE [IF EXISTS] <table> RENAME TO <new_table>;
217
+ ALTER TABLE [IF EXISTS] <table> RENAME COLUMN <old_col> TO <new_col>;
218
+ ALTER TABLE [IF EXISTS] <table> ADD COLUMN <col_def>;
219
+ ALTER TABLE [IF EXISTS] <table> DROP COLUMN <col>;
220
+ ```
221
+
222
+ One operation per statement (SQLite-style). `ALTER TABLE foo RENAME TO bar, ADD COLUMN x ...` is rejected — issue separate statements instead.
223
+
224
+ ### `RENAME TO`
225
+
226
+ - Reserved-name rejection: cannot rename to `sqlrite_master`.
227
+ - Errors if the target name is already a table.
228
+ - Auto-indexes whose names embed the old table name (`sqlrite_autoindex_<old>_<col>`) are renamed in lockstep so the schema catalog stays consistent. Explicit indexes carry their user-given name unchanged.
229
+
230
+ ### `RENAME COLUMN`
231
+
232
+ - Errors if the old column doesn't exist or the new name already exists in the table.
233
+ - Re-keys the row storage and updates every dependent index (auto + explicit, secondary / HNSW / FTS) — including auto-index name regeneration.
234
+ - Renaming the PRIMARY KEY column is allowed; the table's `primary_key` pointer follows the new name.
235
+
236
+ ### `ADD COLUMN`
237
+
238
+ - The column definition reuses the same parser that handles CREATE TABLE columns: same types, same `NOT NULL` / `DEFAULT` semantics.
239
+ - **Rejected:** `PRIMARY KEY` and `UNIQUE` constraints on the added column. Both would require backfilling the column under uniqueness constraints against existing rows; that path will land alongside multi-column UNIQUE.
240
+ - **`NOT NULL` on a non-empty table requires `DEFAULT`.** Without one there's no value to backfill existing rowids with. Same rule SQLite applies.
241
+ - With a `DEFAULT`, every existing rowid is backfilled with the default value at ADD COLUMN time. Without a `DEFAULT`, existing rowids read as NULL for the new column.
242
+
243
+ ### `DROP COLUMN`
244
+
245
+ - **Rejected:** dropping the PRIMARY KEY column.
246
+ - **Rejected:** dropping the only remaining column (degenerate table).
247
+ - Cascades to every dependent index (auto + explicit, secondary / HNSW / FTS) on the dropped column.
248
+ - `CASCADE` / `RESTRICT` modifiers are accepted by the parser and ignored — SQLite has no real distinction here either.
249
+
250
+ ---
251
+
252
+ ## `DROP TABLE`
253
+
254
+ ```sql
255
+ DROP TABLE [IF EXISTS] <table>;
256
+ ```
257
+
258
+ - Single target per statement. `DROP TABLE a, b, c;` is parsed but rejected with a NotImplemented error.
259
+ - Reserved-name rejection: `DROP TABLE sqlrite_master` errors with the same message `CREATE TABLE` uses.
260
+ - All indexes attached to the table (auto, explicit, HNSW, FTS) disappear with the table — they live inside the `Table` struct and ride along.
261
+ - Without `IF EXISTS`, dropping a table that doesn't exist errors. With it, that's a benign 0-tables-dropped no-op.
262
+ - **Disk pages are orphaned, not freed.** SQLRite has no free-list yet — the file size doesn't shrink until a future `VACUUM`. The behavior is safe (orphan pages are unreachable from `sqlrite_master` after reopen) but means a write-heavy schema churn won't reclaim space until VACUUM lands.
263
+
264
+ ---
265
+
266
+ ## `DROP INDEX`
267
+
268
+ ```sql
269
+ DROP INDEX [IF EXISTS] <index_name>;
270
+ ```
271
+
272
+ - Single target per statement.
273
+ - Walks every table searching for an index with the given name across the secondary B-Tree, HNSW, and FTS index families.
274
+ - **Refuses to drop auto-indexes.** `sqlrite_autoindex_*` names are constraint-bound to the column they index — the only way to remove them is to drop the underlying column or table. Same rule SQLite enforces for its `sqlite_autoindex_*` indexes.
275
+ - `IF EXISTS` makes a missing index a benign no-op.
276
+
277
+ ---
278
+
211
279
  ## Expressions
212
280
 
213
281
  Expressions work inside `WHERE` (both in `SELECT`, `UPDATE`, `DELETE`) and on the right-hand side of `UPDATE`'s `SET`.
@@ -405,11 +473,12 @@ For context when you hit `NotImplemented`. See [Roadmap](roadmap.md) for when th
405
473
  - Built-in functions (`LENGTH`, `UPPER`, `LOWER`, `COALESCE`, `IFNULL`, date/time, `printf`, …)
406
474
 
407
475
  ### DDL
408
- - `ALTER TABLE` (add column, rename column, rename table)
409
- - `DROP TABLE`, `DROP INDEX`
476
+ - `ALTER TABLE` extras: multi-operation (`ALTER TABLE foo RENAME TO bar, ADD COLUMN x ...`), `ALTER COLUMN ... SET / DROP DEFAULT`, `ALTER COLUMN ... TYPE`
477
+ - `ADD COLUMN` constraint extras: `PRIMARY KEY` and `UNIQUE` on the added column (would need backfill + uniqueness against existing rows)
478
+ - `DROP TABLE` / `DROP INDEX` extras: multi-target (`DROP TABLE a, b, c;`)
410
479
  - `CREATE VIEW`, `CREATE TRIGGER`
411
480
  - Table-level constraints (composite PK, composite UNIQUE, `FOREIGN KEY`, `CHECK`)
412
- - Column defaults (`DEFAULT <value>`)
481
+ - Non-literal `DEFAULT` expressions (`CURRENT_TIMESTAMP`, function calls, column references)
413
482
  - Composite / multi-column indexes
414
483
 
415
484
  ### Transactions
@@ -4,7 +4,7 @@ build-backend = "maturin"
4
4
 
5
5
  [project]
6
6
  name = "sqlrite"
7
- version = "0.2.0"
7
+ version = "0.3.0"
8
8
  description = "Python bindings for SQLRite — a small, embeddable SQLite clone written in Rust."
9
9
  authors = [{ name = "Joao Henrique Machado Silva", email = "joaoh82@gmail.com" }]
10
10
  license = { text = "MIT" }
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "sqlrite-python"
3
- version = "0.2.0"
3
+ version = "0.3.0"
4
4
  authors = ["Joao Henrique Machado Silva <joaoh82@gmail.com>"]
5
5
  edition = "2024"
6
6
  rust-version = "1.85"
@@ -10,7 +10,7 @@
10
10
  # Published to crates.io as `sqlrite-ask`. Joins the lockstep release
11
11
  # wave (`sqlrite-ask-vX.Y.Z` tag) — see `docs/release-plan.md`.
12
12
  name = "sqlrite-ask"
13
- version = "0.2.0"
13
+ version = "0.3.0"
14
14
  authors = ["Joao Henrique Machado Silva <joaoh82@gmail.com>"]
15
15
  edition = "2024"
16
16
  rust-version = "1.85"
@@ -2,7 +2,7 @@ use crate::error::{Result, SQLRiteError};
2
2
  use crate::sql::db::secondary_index::{IndexOrigin, SecondaryIndex};
3
3
  use crate::sql::fts::PostingList;
4
4
  use crate::sql::hnsw::HnswIndex;
5
- use crate::sql::parser::create::CreateQuery;
5
+ use crate::sql::parser::create::{CreateQuery, ParsedColumn};
6
6
  use std::collections::{BTreeMap, HashMap};
7
7
  use std::fmt;
8
8
  use std::sync::{Arc, Mutex};
@@ -207,12 +207,13 @@ impl Table {
207
207
  if col.is_pk {
208
208
  primary_key = col_name.to_string();
209
209
  }
210
- table_cols.push(Column::new(
210
+ table_cols.push(Column::with_default(
211
211
  col_name.to_string(),
212
212
  col.datatype.to_string(),
213
213
  col.is_pk,
214
214
  col.not_null,
215
215
  col.is_unique,
216
+ col.default.clone(),
216
217
  ));
217
218
 
218
219
  let dt = DataType::new(col.datatype.to_string());
@@ -326,13 +327,235 @@ impl Table {
326
327
  }
327
328
 
328
329
  /// Finds a secondary index by its own name (e.g., `sqlrite_autoindex_users_email`
329
- /// or a user-provided CREATE INDEX name). Used by Phase 3e.2 to look up
330
- /// explicit indexes when DROP INDEX lands.
331
- #[allow(dead_code)]
330
+ /// or a user-provided CREATE INDEX name). Used by DROP INDEX and the
331
+ /// rename helpers below.
332
332
  pub fn index_by_name(&self, name: &str) -> Option<&SecondaryIndex> {
333
333
  self.secondary_indexes.iter().find(|i| i.name == name)
334
334
  }
335
335
 
336
+ /// Renames a column in place. Updates row storage, the `Column`
337
+ /// metadata, every secondary / HNSW / FTS index whose `column_name`
338
+ /// matches, the `primary_key` pointer if the renamed column is the
339
+ /// PK, and any auto-index name that embedded the old column name.
340
+ ///
341
+ /// Caller-side validation (table existence, source-column existence
342
+ /// at the surface level, IF EXISTS) lives in the executor; this
343
+ /// method enforces the column-level invariants that have to be
344
+ /// checked under the `Table` borrow anyway.
345
+ pub fn rename_column(&mut self, old: &str, new: &str) -> Result<()> {
346
+ if !self.columns.iter().any(|c| c.column_name == old) {
347
+ return Err(SQLRiteError::General(format!(
348
+ "column '{old}' does not exist in table '{}'",
349
+ self.tb_name
350
+ )));
351
+ }
352
+ if old != new && self.columns.iter().any(|c| c.column_name == new) {
353
+ return Err(SQLRiteError::General(format!(
354
+ "column '{new}' already exists in table '{}'",
355
+ self.tb_name
356
+ )));
357
+ }
358
+ if old == new {
359
+ return Ok(());
360
+ }
361
+
362
+ for col in self.columns.iter_mut() {
363
+ if col.column_name == old {
364
+ col.column_name = new.to_string();
365
+ }
366
+ }
367
+
368
+ // Re-key the per-column row map.
369
+ {
370
+ let mut rows = self.rows.lock().expect("rows mutex poisoned");
371
+ if let Some(storage) = rows.remove(old) {
372
+ rows.insert(new.to_string(), storage);
373
+ }
374
+ }
375
+
376
+ if self.primary_key == old {
377
+ self.primary_key = new.to_string();
378
+ }
379
+
380
+ let table_name = self.tb_name.clone();
381
+ for idx in self.secondary_indexes.iter_mut() {
382
+ if idx.column_name == old {
383
+ idx.column_name = new.to_string();
384
+ if idx.origin == IndexOrigin::Auto
385
+ && idx.name == SecondaryIndex::auto_name(&table_name, old)
386
+ {
387
+ idx.name = SecondaryIndex::auto_name(&table_name, new);
388
+ }
389
+ }
390
+ }
391
+ for entry in self.hnsw_indexes.iter_mut() {
392
+ if entry.column_name == old {
393
+ entry.column_name = new.to_string();
394
+ }
395
+ }
396
+ for entry in self.fts_indexes.iter_mut() {
397
+ if entry.column_name == old {
398
+ entry.column_name = new.to_string();
399
+ }
400
+ }
401
+
402
+ Ok(())
403
+ }
404
+
405
+ /// Appends a new column to this table from a parsed column spec.
406
+ /// The new column's row storage is allocated empty; existing rowids
407
+ /// read NULL for the new column unless `parsed.default` is set, in
408
+ /// which case those rowids are backfilled with the default value.
409
+ ///
410
+ /// Rejects PK / UNIQUE on the added column (would require
411
+ /// backfill-with-uniqueness-check against existing rows). Rejects
412
+ /// NOT NULL without DEFAULT on a non-empty table — same rule SQLite
413
+ /// applies, and necessary because we have no other backfill source.
414
+ pub fn add_column(&mut self, parsed: ParsedColumn) -> Result<()> {
415
+ if self.contains_column(parsed.name.clone()) {
416
+ return Err(SQLRiteError::General(format!(
417
+ "column '{}' already exists in table '{}'",
418
+ parsed.name, self.tb_name
419
+ )));
420
+ }
421
+ if parsed.is_pk {
422
+ return Err(SQLRiteError::General(
423
+ "cannot ADD COLUMN with PRIMARY KEY constraint on existing table".to_string(),
424
+ ));
425
+ }
426
+ if parsed.is_unique {
427
+ return Err(SQLRiteError::General(
428
+ "cannot ADD COLUMN with UNIQUE constraint on existing table".to_string(),
429
+ ));
430
+ }
431
+ let table_has_rows = self
432
+ .columns
433
+ .first()
434
+ .map(|c| {
435
+ self.rows
436
+ .lock()
437
+ .expect("rows mutex poisoned")
438
+ .get(&c.column_name)
439
+ .map(|r| r.rowids().len())
440
+ .unwrap_or(0)
441
+ > 0
442
+ })
443
+ .unwrap_or(false);
444
+ if parsed.not_null && parsed.default.is_none() && table_has_rows {
445
+ return Err(SQLRiteError::General(format!(
446
+ "cannot ADD COLUMN '{}' NOT NULL without DEFAULT to a non-empty table",
447
+ parsed.name
448
+ )));
449
+ }
450
+
451
+ let new_column = Column::with_default(
452
+ parsed.name.clone(),
453
+ parsed.datatype.clone(),
454
+ parsed.is_pk,
455
+ parsed.not_null,
456
+ parsed.is_unique,
457
+ parsed.default.clone(),
458
+ );
459
+
460
+ // Allocate empty row storage for the new column. Mirrors the
461
+ // dispatch in `Table::new` so the new column behaves identically
462
+ // to one declared at CREATE TABLE time.
463
+ let row_storage = match &new_column.datatype {
464
+ DataType::Integer => Row::Integer(BTreeMap::new()),
465
+ DataType::Real => Row::Real(BTreeMap::new()),
466
+ DataType::Text => Row::Text(BTreeMap::new()),
467
+ DataType::Bool => Row::Bool(BTreeMap::new()),
468
+ DataType::Vector(_dim) => Row::Vector(BTreeMap::new()),
469
+ DataType::Json => Row::Text(BTreeMap::new()),
470
+ DataType::Invalid | DataType::None => Row::None,
471
+ };
472
+ {
473
+ let mut rows = self.rows.lock().expect("rows mutex poisoned");
474
+ rows.insert(parsed.name.clone(), row_storage);
475
+ }
476
+
477
+ // Backfill existing rowids with the default value, if any.
478
+ // NULL defaults are a no-op — a missing key in the BTreeMap reads
479
+ // as NULL anyway. Type mismatches were caught at `parse_one_column`
480
+ // time when the DEFAULT was evaluated against the declared
481
+ // datatype; reaching the `_` arm here would indicate a bug.
482
+ if let Some(default) = &parsed.default {
483
+ let existing_rowids = self.rowids();
484
+ let mut rows = self.rows.lock().expect("rows mutex poisoned");
485
+ let storage = rows.get_mut(&parsed.name).expect("just inserted");
486
+ match (storage, default) {
487
+ (Row::Integer(tree), Value::Integer(v)) => {
488
+ let v32 = *v as i32;
489
+ for rowid in existing_rowids {
490
+ tree.insert(rowid, v32);
491
+ }
492
+ }
493
+ (Row::Real(tree), Value::Real(v)) => {
494
+ let v32 = *v as f32;
495
+ for rowid in existing_rowids {
496
+ tree.insert(rowid, v32);
497
+ }
498
+ }
499
+ (Row::Text(tree), Value::Text(v)) => {
500
+ for rowid in existing_rowids {
501
+ tree.insert(rowid, v.clone());
502
+ }
503
+ }
504
+ (Row::Bool(tree), Value::Bool(v)) => {
505
+ for rowid in existing_rowids {
506
+ tree.insert(rowid, *v);
507
+ }
508
+ }
509
+ (_, Value::Null) => {} // no-op
510
+ (storage_ref, _) => {
511
+ return Err(SQLRiteError::Internal(format!(
512
+ "DEFAULT type does not match column storage for '{}': storage variant {:?}, default {:?}",
513
+ parsed.name,
514
+ std::mem::discriminant(storage_ref),
515
+ default
516
+ )));
517
+ }
518
+ }
519
+ }
520
+
521
+ self.columns.push(new_column);
522
+ Ok(())
523
+ }
524
+
525
+ /// Removes a column from this table. Refuses to drop the PRIMARY KEY
526
+ /// column or the only remaining column. Cascades to every index
527
+ /// (auto, explicit, HNSW, FTS) that referenced the column.
528
+ pub fn drop_column(&mut self, name: &str) -> Result<()> {
529
+ if !self.contains_column(name.to_string()) {
530
+ return Err(SQLRiteError::General(format!(
531
+ "column '{name}' does not exist in table '{}'",
532
+ self.tb_name
533
+ )));
534
+ }
535
+ if self.primary_key == name {
536
+ return Err(SQLRiteError::General(format!(
537
+ "cannot drop primary key column '{name}'"
538
+ )));
539
+ }
540
+ if self.columns.len() == 1 {
541
+ return Err(SQLRiteError::General(format!(
542
+ "cannot drop the only column of table '{}'",
543
+ self.tb_name
544
+ )));
545
+ }
546
+
547
+ self.columns.retain(|c| c.column_name != name);
548
+ {
549
+ let mut rows = self.rows.lock().expect("rows mutex poisoned");
550
+ rows.remove(name);
551
+ }
552
+ self.secondary_indexes.retain(|i| i.column_name != name);
553
+ self.hnsw_indexes.retain(|i| i.column_name != name);
554
+ self.fts_indexes.retain(|i| i.column_name != name);
555
+
556
+ Ok(())
557
+ }
558
+
336
559
  /// Returns a `bool` informing if a `Column` with a specific name exists or not
337
560
  ///
338
561
  pub fn contains_column(&self, column: String) -> bool {
@@ -822,10 +1045,12 @@ impl Table {
822
1045
  for i in 0..column_names.len() {
823
1046
  let mut val = String::from("Null");
824
1047
  let key = &column_names[i];
1048
+ let mut column_supplied = false;
825
1049
 
826
1050
  if let Some(supplied_key) = cols.get(j) {
827
1051
  if supplied_key == &column_names[i] {
828
1052
  val = values[j].to_string();
1053
+ column_supplied = true;
829
1054
  j += 1;
830
1055
  } else if self.primary_key == column_names[i] {
831
1056
  // PK already stored in the auto-assign branch above.
@@ -835,6 +1060,17 @@ impl Table {
835
1060
  continue;
836
1061
  }
837
1062
 
1063
+ // Column was omitted from the INSERT column list. Substitute its
1064
+ // DEFAULT literal if one was declared at CREATE TABLE time;
1065
+ // otherwise it stays as the "Null" sentinel set above. SQLite
1066
+ // semantics: an *explicit* NULL is preserved as NULL — the
1067
+ // default only fires for omitted columns.
1068
+ if !column_supplied {
1069
+ if let Some(default) = &self.columns[i].default {
1070
+ val = default.to_default_insert_string();
1071
+ }
1072
+ }
1073
+
838
1074
  // Step 1: write into row storage and compute the typed Value
839
1075
  // we'll hand to the secondary index (if any).
840
1076
  let typed_value: Option<Value> = {
@@ -1129,15 +1365,36 @@ pub struct Column {
1129
1365
  pub is_pk: bool,
1130
1366
  pub not_null: bool,
1131
1367
  pub is_unique: bool,
1368
+ /// Literal value to substitute when this column is omitted from an
1369
+ /// INSERT. Restricted to literal expressions at CREATE TABLE time.
1370
+ /// `None` means "no DEFAULT declared"; an INSERT that omits the column
1371
+ /// gets `Value::Null` instead.
1372
+ pub default: Option<Value>,
1132
1373
  }
1133
1374
 
1134
1375
  impl Column {
1376
+ /// Builds a `Column` without a `DEFAULT` clause. Existing call sites
1377
+ /// (catalog-table setup, test fixtures) keep working unchanged.
1135
1378
  pub fn new(
1136
1379
  name: String,
1137
1380
  datatype: String,
1138
1381
  is_pk: bool,
1139
1382
  not_null: bool,
1140
1383
  is_unique: bool,
1384
+ ) -> Self {
1385
+ Self::with_default(name, datatype, is_pk, not_null, is_unique, None)
1386
+ }
1387
+
1388
+ /// Builds a `Column` with an optional `DEFAULT` literal. Used by the
1389
+ /// CREATE TABLE / `parse_create_sql` paths that propagate user-supplied
1390
+ /// defaults from `ParsedColumn`.
1391
+ pub fn with_default(
1392
+ name: String,
1393
+ datatype: String,
1394
+ is_pk: bool,
1395
+ not_null: bool,
1396
+ is_unique: bool,
1397
+ default: Option<Value>,
1141
1398
  ) -> Self {
1142
1399
  let dt = DataType::new(datatype);
1143
1400
  Column {
@@ -1146,6 +1403,7 @@ impl Column {
1146
1403
  is_pk,
1147
1404
  not_null,
1148
1405
  is_unique,
1406
+ default,
1149
1407
  }
1150
1408
  }
1151
1409
  }
@@ -1275,6 +1533,25 @@ impl Value {
1275
1533
  Value::Null => String::from("NULL"),
1276
1534
  }
1277
1535
  }
1536
+
1537
+ /// Renders this value in the same stringly format that
1538
+ /// [`crate::sql::parser::insert::InsertQuery::new`] produces for INSERT
1539
+ /// values, so a DEFAULT can be substituted into the existing
1540
+ /// `insert_row` parse pipeline without a parallel typed path.
1541
+ ///
1542
+ /// The differences from [`Self::to_display_string`] that matter:
1543
+ /// - `NULL` renders as the `"Null"` sentinel that `insert_row` matches.
1544
+ /// - Text stays unquoted (the insert pipeline strips quotes upstream).
1545
+ pub fn to_default_insert_string(&self) -> String {
1546
+ match self {
1547
+ Value::Integer(v) => v.to_string(),
1548
+ Value::Text(s) => s.clone(),
1549
+ Value::Real(f) => f.to_string(),
1550
+ Value::Bool(b) => b.to_string(),
1551
+ Value::Vector(v) => format_vector_for_display(v),
1552
+ Value::Null => String::from("Null"),
1553
+ }
1554
+ }
1278
1555
  }
1279
1556
 
1280
1557
  /// Parse a bracket-array literal like `"[0.1, 0.2, 0.3]"` (or `"[1, 2, 3]"`)