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.
- {sqlrite-0.2.0 → sqlrite-0.3.0}/.github/workflows/release.yml +17 -12
- {sqlrite-0.2.0 → sqlrite-0.3.0}/Cargo.lock +7 -7
- {sqlrite-0.2.0 → sqlrite-0.3.0}/Cargo.toml +2 -2
- {sqlrite-0.2.0 → sqlrite-0.3.0}/PKG-INFO +1 -1
- {sqlrite-0.2.0 → sqlrite-0.3.0}/desktop/package.json +1 -1
- {sqlrite-0.2.0 → sqlrite-0.3.0}/docs/supported-sql.md +76 -7
- {sqlrite-0.2.0 → sqlrite-0.3.0}/pyproject.toml +1 -1
- {sqlrite-0.2.0 → sqlrite-0.3.0}/sdk/python/Cargo.toml +1 -1
- {sqlrite-0.2.0 → sqlrite-0.3.0}/sqlrite-ask/Cargo.toml +1 -1
- {sqlrite-0.2.0 → sqlrite-0.3.0}/src/sql/db/table.rs +282 -5
- {sqlrite-0.2.0 → sqlrite-0.3.0}/src/sql/executor.rs +246 -3
- {sqlrite-0.2.0 → sqlrite-0.3.0}/src/sql/mod.rs +700 -3
- {sqlrite-0.2.0 → sqlrite-0.3.0}/src/sql/pager/mod.rs +254 -1
- sqlrite-0.3.0/src/sql/parser/create.rs +368 -0
- sqlrite-0.2.0/src/sql/parser/create.rs +0 -241
- {sqlrite-0.2.0 → sqlrite-0.3.0}/.github/workflows/ci.yml +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/.github/workflows/release-pr.yml +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/.github/workflows/rust.yml +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/.gitignore +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/CLAUDE.md +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/CODE_OF_CONDUCT.md +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/LICENSE +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/MAINTAINERS +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/Makefile +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/README.md +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/desktop/index.html +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/desktop/package-lock.json +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/desktop/src/App.svelte +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/desktop/src/app.css +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/desktop/src/main.ts +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/desktop/src/vite-env.d.ts +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/desktop/svelte.config.js +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/desktop/tsconfig.json +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/desktop/vite.config.ts +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/docs/_index.md +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/docs/architecture.md +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/docs/ask-backend-examples.md +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/docs/ask.md +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/docs/design-decisions.md +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/docs/desktop.md +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/docs/embedding.md +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/docs/file-format.md +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/docs/fts.md +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/docs/getting-started.md +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/docs/mcp.md +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/docs/pager.md +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/docs/phase-7-plan.md +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/docs/phase-8-plan.md +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/docs/release-plan.md +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/docs/release-secrets.md +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/docs/roadmap.md +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/docs/smoke-test.md +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/docs/sql-engine.md +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/docs/storage-model.md +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/docs/usage.md +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/examples/README.md +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/examples/c/Makefile +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/examples/c/hello.c +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/examples/go/go.mod +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/examples/go/hello.go +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/examples/hybrid-retrieval/README.md +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/examples/hybrid-retrieval/hybrid_retrieval.rs +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/examples/nodejs/hello.mjs +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/examples/python/hello.py +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/examples/rust/quickstart.rs +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/examples/wasm/Makefile +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/examples/wasm/index.html +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/examples/wasm/server.mjs +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/images/SQLRite - Desktop.png +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/images/SQLRite Data Structures.png +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/images/SQLRite Simple SQL Execution High Level Diagram.png +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/images/SQLRite Simple SQL INSERT Execution High Level Diagram (Insert Row).png +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/images/SQLRite Simple SQL INSERT Execution High Level Diagram.png +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/images/SQLRite_logo.png +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/images/architecture.png +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/rust-toolchain.toml +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/samples/AST.delete.example +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/samples/AST.insert.exemple +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/samples/AST.select.example +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/samples/AST.update.example +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/samples/CREATE TABLE sqlrite_schema.sql +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/samples/CREATE_TABLE with duplicate.sql +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/samples/CREATE_TABLE.sql +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/samples/INSERT.sql +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/scripts/bump-version.sh +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/sdk/go/README.md +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/sdk/go/ask.go +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/sdk/go/ask_test.go +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/sdk/go/conn.go +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/sdk/go/go.mod +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/sdk/go/rows.go +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/sdk/go/sqlrite.go +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/sdk/go/sqlrite_test.go +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/sdk/go/stmt.go +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/sdk/python/README.md +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/sdk/python/src/lib.rs +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/sdk/python/tests/test_ask.py +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/sdk/python/tests/test_sqlrite.py +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/sqlrite-ask/README.md +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/sqlrite-ask/src/lib.rs +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/sqlrite-ask/src/prompt.rs +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/sqlrite-ask/src/provider/anthropic.rs +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/sqlrite-ask/src/provider/mock.rs +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/sqlrite-ask/src/provider/mod.rs +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/sqlrite-ask/tests/anthropic_http.rs +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/src/ask/mod.rs +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/src/ask/schema.rs +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/src/connection.rs +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/src/error.rs +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/src/lib.rs +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/src/main.rs +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/src/meta_command/mod.rs +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/src/repl/mod.rs +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/src/sql/db/database.rs +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/src/sql/db/mod.rs +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/src/sql/db/secondary_index.rs +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/src/sql/fts/bm25.rs +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/src/sql/fts/mod.rs +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/src/sql/fts/posting_list.rs +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/src/sql/fts/tokenizer.rs +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/src/sql/hnsw.rs +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/src/sql/pager/cell.rs +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/src/sql/pager/file.rs +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/src/sql/pager/fts_cell.rs +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/src/sql/pager/header.rs +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/src/sql/pager/hnsw_cell.rs +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/src/sql/pager/index_cell.rs +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/src/sql/pager/interior_page.rs +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/src/sql/pager/overflow.rs +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/src/sql/pager/page.rs +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/src/sql/pager/pager.rs +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/src/sql/pager/table_page.rs +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/src/sql/pager/varint.rs +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/src/sql/pager/wal.rs +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/src/sql/parser/insert.rs +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/src/sql/parser/mod.rs +0 -0
- {sqlrite-0.2.0 → sqlrite-0.3.0}/src/sql/parser/select.rs +0 -0
- {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
|
-
|
|
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
|
|
207
|
-
#
|
|
208
|
-
#
|
|
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
|
|
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
|
-
#
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
141
|
+
sqlrite-ask = { version = "0.3", path = "sqlrite-ask", optional = true }
|
|
@@ -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
|
|
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` (
|
|
409
|
-
- `
|
|
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
|
-
-
|
|
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.
|
|
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" }
|
|
@@ -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.
|
|
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::
|
|
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
|
|
330
|
-
///
|
|
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]"`)
|