sqlrite 0.6.0__tar.gz → 0.8.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (141) hide show
  1. {sqlrite-0.6.0 → sqlrite-0.8.0}/Cargo.lock +7 -7
  2. {sqlrite-0.6.0 → sqlrite-0.8.0}/Cargo.toml +2 -2
  3. {sqlrite-0.6.0 → sqlrite-0.8.0}/PKG-INFO +1 -1
  4. {sqlrite-0.6.0 → sqlrite-0.8.0}/README.md +7 -4
  5. {sqlrite-0.6.0 → sqlrite-0.8.0}/desktop/package.json +1 -1
  6. {sqlrite-0.6.0 → sqlrite-0.8.0}/docs/architecture.md +2 -3
  7. {sqlrite-0.6.0 → sqlrite-0.8.0}/docs/design-decisions.md +19 -0
  8. {sqlrite-0.6.0 → sqlrite-0.8.0}/docs/sql-engine.md +44 -2
  9. {sqlrite-0.6.0 → sqlrite-0.8.0}/docs/supported-sql.md +78 -16
  10. {sqlrite-0.6.0 → sqlrite-0.8.0}/pyproject.toml +1 -1
  11. {sqlrite-0.6.0 → sqlrite-0.8.0}/sdk/python/Cargo.toml +1 -1
  12. {sqlrite-0.6.0 → sqlrite-0.8.0}/sqlrite-ask/Cargo.toml +1 -1
  13. sqlrite-0.8.0/src/sql/agg.rs +543 -0
  14. {sqlrite-0.6.0 → sqlrite-0.8.0}/src/sql/executor.rs +1799 -130
  15. {sqlrite-0.6.0 → sqlrite-0.8.0}/src/sql/mod.rs +1 -0
  16. sqlrite-0.8.0/src/sql/parser/select.rs +679 -0
  17. sqlrite-0.6.0/src/sql/parser/select.rs +0 -234
  18. {sqlrite-0.6.0 → sqlrite-0.8.0}/.github/workflows/ci.yml +0 -0
  19. {sqlrite-0.6.0 → sqlrite-0.8.0}/.github/workflows/release-pr.yml +0 -0
  20. {sqlrite-0.6.0 → sqlrite-0.8.0}/.github/workflows/release.yml +0 -0
  21. {sqlrite-0.6.0 → sqlrite-0.8.0}/.github/workflows/rust.yml +0 -0
  22. {sqlrite-0.6.0 → sqlrite-0.8.0}/.gitignore +0 -0
  23. {sqlrite-0.6.0 → sqlrite-0.8.0}/CLAUDE.md +0 -0
  24. {sqlrite-0.6.0 → sqlrite-0.8.0}/CODE_OF_CONDUCT.md +0 -0
  25. {sqlrite-0.6.0 → sqlrite-0.8.0}/LICENSE +0 -0
  26. {sqlrite-0.6.0 → sqlrite-0.8.0}/MAINTAINERS +0 -0
  27. {sqlrite-0.6.0 → sqlrite-0.8.0}/Makefile +0 -0
  28. {sqlrite-0.6.0 → sqlrite-0.8.0}/desktop/index.html +0 -0
  29. {sqlrite-0.6.0 → sqlrite-0.8.0}/desktop/package-lock.json +0 -0
  30. {sqlrite-0.6.0 → sqlrite-0.8.0}/desktop/src/App.svelte +0 -0
  31. {sqlrite-0.6.0 → sqlrite-0.8.0}/desktop/src/app.css +0 -0
  32. {sqlrite-0.6.0 → sqlrite-0.8.0}/desktop/src/main.ts +0 -0
  33. {sqlrite-0.6.0 → sqlrite-0.8.0}/desktop/src/vite-env.d.ts +0 -0
  34. {sqlrite-0.6.0 → sqlrite-0.8.0}/desktop/svelte.config.js +0 -0
  35. {sqlrite-0.6.0 → sqlrite-0.8.0}/desktop/tsconfig.json +0 -0
  36. {sqlrite-0.6.0 → sqlrite-0.8.0}/desktop/vite.config.ts +0 -0
  37. {sqlrite-0.6.0 → sqlrite-0.8.0}/docs/_index.md +0 -0
  38. {sqlrite-0.6.0 → sqlrite-0.8.0}/docs/ask-backend-examples.md +0 -0
  39. {sqlrite-0.6.0 → sqlrite-0.8.0}/docs/ask.md +0 -0
  40. {sqlrite-0.6.0 → sqlrite-0.8.0}/docs/desktop.md +0 -0
  41. {sqlrite-0.6.0 → sqlrite-0.8.0}/docs/embedding.md +0 -0
  42. {sqlrite-0.6.0 → sqlrite-0.8.0}/docs/file-format.md +0 -0
  43. {sqlrite-0.6.0 → sqlrite-0.8.0}/docs/fts.md +0 -0
  44. {sqlrite-0.6.0 → sqlrite-0.8.0}/docs/getting-started.md +0 -0
  45. {sqlrite-0.6.0 → sqlrite-0.8.0}/docs/mcp.md +0 -0
  46. {sqlrite-0.6.0 → sqlrite-0.8.0}/docs/pager.md +0 -0
  47. {sqlrite-0.6.0 → sqlrite-0.8.0}/docs/phase-7-plan.md +0 -0
  48. {sqlrite-0.6.0 → sqlrite-0.8.0}/docs/phase-8-plan.md +0 -0
  49. {sqlrite-0.6.0 → sqlrite-0.8.0}/docs/release-plan.md +0 -0
  50. {sqlrite-0.6.0 → sqlrite-0.8.0}/docs/release-secrets.md +0 -0
  51. {sqlrite-0.6.0 → sqlrite-0.8.0}/docs/roadmap.md +0 -0
  52. {sqlrite-0.6.0 → sqlrite-0.8.0}/docs/smoke-test.md +0 -0
  53. {sqlrite-0.6.0 → sqlrite-0.8.0}/docs/storage-model.md +0 -0
  54. {sqlrite-0.6.0 → sqlrite-0.8.0}/docs/usage.md +0 -0
  55. {sqlrite-0.6.0 → sqlrite-0.8.0}/examples/README.md +0 -0
  56. {sqlrite-0.6.0 → sqlrite-0.8.0}/examples/c/Makefile +0 -0
  57. {sqlrite-0.6.0 → sqlrite-0.8.0}/examples/c/hello.c +0 -0
  58. {sqlrite-0.6.0 → sqlrite-0.8.0}/examples/go/go.mod +0 -0
  59. {sqlrite-0.6.0 → sqlrite-0.8.0}/examples/go/hello.go +0 -0
  60. {sqlrite-0.6.0 → sqlrite-0.8.0}/examples/hybrid-retrieval/README.md +0 -0
  61. {sqlrite-0.6.0 → sqlrite-0.8.0}/examples/hybrid-retrieval/hybrid_retrieval.rs +0 -0
  62. {sqlrite-0.6.0 → sqlrite-0.8.0}/examples/nodejs/hello.mjs +0 -0
  63. {sqlrite-0.6.0 → sqlrite-0.8.0}/examples/python/hello.py +0 -0
  64. {sqlrite-0.6.0 → sqlrite-0.8.0}/examples/rust/quickstart.rs +0 -0
  65. {sqlrite-0.6.0 → sqlrite-0.8.0}/examples/wasm/Makefile +0 -0
  66. {sqlrite-0.6.0 → sqlrite-0.8.0}/examples/wasm/index.html +0 -0
  67. {sqlrite-0.6.0 → sqlrite-0.8.0}/examples/wasm/server.mjs +0 -0
  68. {sqlrite-0.6.0 → sqlrite-0.8.0}/images/SQLRite - Desktop.png +0 -0
  69. {sqlrite-0.6.0 → sqlrite-0.8.0}/images/SQLRite Data Structures.png +0 -0
  70. {sqlrite-0.6.0 → sqlrite-0.8.0}/images/SQLRite Simple SQL Execution High Level Diagram.png +0 -0
  71. {sqlrite-0.6.0 → sqlrite-0.8.0}/images/SQLRite Simple SQL INSERT Execution High Level Diagram (Insert Row).png +0 -0
  72. {sqlrite-0.6.0 → sqlrite-0.8.0}/images/SQLRite Simple SQL INSERT Execution High Level Diagram.png +0 -0
  73. {sqlrite-0.6.0 → sqlrite-0.8.0}/images/SQLRite_logo.png +0 -0
  74. {sqlrite-0.6.0 → sqlrite-0.8.0}/images/architecture.png +0 -0
  75. {sqlrite-0.6.0 → sqlrite-0.8.0}/rust-toolchain.toml +0 -0
  76. {sqlrite-0.6.0 → sqlrite-0.8.0}/samples/AST.delete.example +0 -0
  77. {sqlrite-0.6.0 → sqlrite-0.8.0}/samples/AST.insert.exemple +0 -0
  78. {sqlrite-0.6.0 → sqlrite-0.8.0}/samples/AST.select.example +0 -0
  79. {sqlrite-0.6.0 → sqlrite-0.8.0}/samples/AST.update.example +0 -0
  80. {sqlrite-0.6.0 → sqlrite-0.8.0}/samples/CREATE TABLE sqlrite_schema.sql +0 -0
  81. {sqlrite-0.6.0 → sqlrite-0.8.0}/samples/CREATE_TABLE with duplicate.sql +0 -0
  82. {sqlrite-0.6.0 → sqlrite-0.8.0}/samples/CREATE_TABLE.sql +0 -0
  83. {sqlrite-0.6.0 → sqlrite-0.8.0}/samples/INSERT.sql +0 -0
  84. {sqlrite-0.6.0 → sqlrite-0.8.0}/scripts/bump-version.sh +0 -0
  85. {sqlrite-0.6.0 → sqlrite-0.8.0}/sdk/go/README.md +0 -0
  86. {sqlrite-0.6.0 → sqlrite-0.8.0}/sdk/go/ask.go +0 -0
  87. {sqlrite-0.6.0 → sqlrite-0.8.0}/sdk/go/ask_test.go +0 -0
  88. {sqlrite-0.6.0 → sqlrite-0.8.0}/sdk/go/conn.go +0 -0
  89. {sqlrite-0.6.0 → sqlrite-0.8.0}/sdk/go/go.mod +0 -0
  90. {sqlrite-0.6.0 → sqlrite-0.8.0}/sdk/go/rows.go +0 -0
  91. {sqlrite-0.6.0 → sqlrite-0.8.0}/sdk/go/sqlrite.go +0 -0
  92. {sqlrite-0.6.0 → sqlrite-0.8.0}/sdk/go/sqlrite_test.go +0 -0
  93. {sqlrite-0.6.0 → sqlrite-0.8.0}/sdk/go/stmt.go +0 -0
  94. {sqlrite-0.6.0 → sqlrite-0.8.0}/sdk/python/README.md +0 -0
  95. {sqlrite-0.6.0 → sqlrite-0.8.0}/sdk/python/src/lib.rs +0 -0
  96. {sqlrite-0.6.0 → sqlrite-0.8.0}/sdk/python/tests/test_ask.py +0 -0
  97. {sqlrite-0.6.0 → sqlrite-0.8.0}/sdk/python/tests/test_sqlrite.py +0 -0
  98. {sqlrite-0.6.0 → sqlrite-0.8.0}/sqlrite-ask/README.md +0 -0
  99. {sqlrite-0.6.0 → sqlrite-0.8.0}/sqlrite-ask/src/lib.rs +0 -0
  100. {sqlrite-0.6.0 → sqlrite-0.8.0}/sqlrite-ask/src/prompt.rs +0 -0
  101. {sqlrite-0.6.0 → sqlrite-0.8.0}/sqlrite-ask/src/provider/anthropic.rs +0 -0
  102. {sqlrite-0.6.0 → sqlrite-0.8.0}/sqlrite-ask/src/provider/mock.rs +0 -0
  103. {sqlrite-0.6.0 → sqlrite-0.8.0}/sqlrite-ask/src/provider/mod.rs +0 -0
  104. {sqlrite-0.6.0 → sqlrite-0.8.0}/sqlrite-ask/tests/anthropic_http.rs +0 -0
  105. {sqlrite-0.6.0 → sqlrite-0.8.0}/src/ask/mod.rs +0 -0
  106. {sqlrite-0.6.0 → sqlrite-0.8.0}/src/ask/schema.rs +0 -0
  107. {sqlrite-0.6.0 → sqlrite-0.8.0}/src/connection.rs +0 -0
  108. {sqlrite-0.6.0 → sqlrite-0.8.0}/src/error.rs +0 -0
  109. {sqlrite-0.6.0 → sqlrite-0.8.0}/src/lib.rs +0 -0
  110. {sqlrite-0.6.0 → sqlrite-0.8.0}/src/main.rs +0 -0
  111. {sqlrite-0.6.0 → sqlrite-0.8.0}/src/meta_command/mod.rs +0 -0
  112. {sqlrite-0.6.0 → sqlrite-0.8.0}/src/repl/mod.rs +0 -0
  113. {sqlrite-0.6.0 → sqlrite-0.8.0}/src/sql/db/database.rs +0 -0
  114. {sqlrite-0.6.0 → sqlrite-0.8.0}/src/sql/db/mod.rs +0 -0
  115. {sqlrite-0.6.0 → sqlrite-0.8.0}/src/sql/db/secondary_index.rs +0 -0
  116. {sqlrite-0.6.0 → sqlrite-0.8.0}/src/sql/db/table.rs +0 -0
  117. {sqlrite-0.6.0 → sqlrite-0.8.0}/src/sql/fts/bm25.rs +0 -0
  118. {sqlrite-0.6.0 → sqlrite-0.8.0}/src/sql/fts/mod.rs +0 -0
  119. {sqlrite-0.6.0 → sqlrite-0.8.0}/src/sql/fts/posting_list.rs +0 -0
  120. {sqlrite-0.6.0 → sqlrite-0.8.0}/src/sql/fts/tokenizer.rs +0 -0
  121. {sqlrite-0.6.0 → sqlrite-0.8.0}/src/sql/hnsw.rs +0 -0
  122. {sqlrite-0.6.0 → sqlrite-0.8.0}/src/sql/pager/allocator.rs +0 -0
  123. {sqlrite-0.6.0 → sqlrite-0.8.0}/src/sql/pager/cell.rs +0 -0
  124. {sqlrite-0.6.0 → sqlrite-0.8.0}/src/sql/pager/file.rs +0 -0
  125. {sqlrite-0.6.0 → sqlrite-0.8.0}/src/sql/pager/freelist.rs +0 -0
  126. {sqlrite-0.6.0 → sqlrite-0.8.0}/src/sql/pager/fts_cell.rs +0 -0
  127. {sqlrite-0.6.0 → sqlrite-0.8.0}/src/sql/pager/header.rs +0 -0
  128. {sqlrite-0.6.0 → sqlrite-0.8.0}/src/sql/pager/hnsw_cell.rs +0 -0
  129. {sqlrite-0.6.0 → sqlrite-0.8.0}/src/sql/pager/index_cell.rs +0 -0
  130. {sqlrite-0.6.0 → sqlrite-0.8.0}/src/sql/pager/interior_page.rs +0 -0
  131. {sqlrite-0.6.0 → sqlrite-0.8.0}/src/sql/pager/mod.rs +0 -0
  132. {sqlrite-0.6.0 → sqlrite-0.8.0}/src/sql/pager/overflow.rs +0 -0
  133. {sqlrite-0.6.0 → sqlrite-0.8.0}/src/sql/pager/page.rs +0 -0
  134. {sqlrite-0.6.0 → sqlrite-0.8.0}/src/sql/pager/pager.rs +0 -0
  135. {sqlrite-0.6.0 → sqlrite-0.8.0}/src/sql/pager/table_page.rs +0 -0
  136. {sqlrite-0.6.0 → sqlrite-0.8.0}/src/sql/pager/varint.rs +0 -0
  137. {sqlrite-0.6.0 → sqlrite-0.8.0}/src/sql/pager/wal.rs +0 -0
  138. {sqlrite-0.6.0 → sqlrite-0.8.0}/src/sql/parser/create.rs +0 -0
  139. {sqlrite-0.6.0 → sqlrite-0.8.0}/src/sql/parser/insert.rs +0 -0
  140. {sqlrite-0.6.0 → sqlrite-0.8.0}/src/sql/parser/mod.rs +0 -0
  141. {sqlrite-0.6.0 → sqlrite-0.8.0}/src/sql/tokenizer.rs +0 -0
@@ -3817,7 +3817,7 @@ dependencies = [
3817
3817
 
3818
3818
  [[package]]
3819
3819
  name = "sqlrite-ask"
3820
- version = "0.6.0"
3820
+ version = "0.8.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.6.0"
3831
+ version = "0.8.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.6.0"
3843
+ version = "0.8.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.6.0"
3860
+ version = "0.8.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.6.0"
3870
+ version = "0.8.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.6.0"
3881
+ version = "0.8.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.6.0"
3891
+ version = "0.8.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.6.0"
30
+ version = "0.8.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.6.0", path = "sqlrite-ask", optional = true }
141
+ sqlrite-ask = { version = "0.8.0", path = "sqlrite-ask", optional = true }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sqlrite
3
- Version: 0.6.0
3
+ Version: 0.8.0
4
4
  Classifier: Development Status :: 3 - Alpha
5
5
  Classifier: Intended Audience :: Developers
6
6
  Classifier: License :: OSI Approved :: MIT License
@@ -157,7 +157,7 @@ sqlrite> DELETE FROM users WHERE age < 30;
157
157
  | `CREATE TABLE` | `PRIMARY KEY`, `UNIQUE`, `NOT NULL`; duplicate-column detection; types `INTEGER`/`INT`/`BIGINT`/`SMALLINT`, `TEXT`/`VARCHAR`, `REAL`/`FLOAT`/`DOUBLE`/`DECIMAL`, `BOOLEAN`. Auto-creates `sqlrite_autoindex_<table>_<col>` for every PK + UNIQUE column |
158
158
  | `CREATE [UNIQUE] INDEX` | Single-column, named indexes; `IF NOT EXISTS`; persists as a dedicated cell-based B-Tree. INTEGER + TEXT columns only |
159
159
  | `INSERT INTO` | Explicit column list required; auto-ROWID for `INTEGER PRIMARY KEY`; multi-row `VALUES (…), (…)`; UNIQUE enforcement; clean type errors (no panics); NULL padding for omitted columns |
160
- | `SELECT` | `*` or column list; `WHERE`; single-column `ORDER BY [ASC\|DESC]`; `LIMIT n`. `WHERE col = literal` probes an index when one exists |
160
+ | `SELECT` | `*` or column list with optional `AS alias`; `WHERE`; `DISTINCT`; `GROUP BY col[, col …]`; aggregate projections `COUNT(*)` / `COUNT([DISTINCT] col)` / `SUM` / `AVG` / `MIN` / `MAX`; `[INNER\|LEFT OUTER\|RIGHT OUTER\|FULL OUTER] JOIN ... ON ...` with table aliases and qualified `t.col` references; single-column `ORDER BY [ASC\|DESC]` (also resolves alias and aggregate display names); `LIMIT n`. `WHERE col = literal` probes an index when one exists |
161
161
  | `UPDATE` | Multi-column `SET`; `WHERE`; UNIQUE + type enforcement; arithmetic in assignments (`SET age = age + 1`) |
162
162
  | `DELETE` | `WHERE` predicate or full-table delete |
163
163
  | `BEGIN` / `COMMIT` / `ROLLBACK` | Real transactions, snapshot-based; WAL-backed commit; single-level (no savepoints); auto-rollback if `COMMIT`'s disk write fails |
@@ -166,12 +166,14 @@ Expressions in `WHERE` and `UPDATE`'s `SET` RHS:
166
166
 
167
167
  - Comparisons — `=`, `<>`, `<`, `<=`, `>`, `>=`
168
168
  - Null tests — `IS NULL`, `IS NOT NULL`
169
+ - Pattern matching — `LIKE`, `NOT LIKE`, `ILIKE` (`%` and `_` wildcards, `\`-escaped literals; case-insensitive ASCII to match SQLite's default)
170
+ - Set membership — `IN (list)`, `NOT IN (list)` (literal lists only; subquery form is not supported yet)
169
171
  - Logical — `AND`, `OR`, `NOT` (SQL three-valued logic; NULL-as-false in `WHERE`)
170
172
  - Arithmetic — `+`, `-`, `*`, `/`, `%` (integer ops stay integer; any `REAL` promotes to `f64`; divide/modulo by zero is a clean error)
171
173
  - String concat — `||`
172
174
  - Literals — integer + real numbers, `'single-quoted strings'`, `TRUE` / `FALSE`, `NULL`; parentheses for grouping
173
175
 
174
- **Not yet supported** (common ones): joins, subqueries, CTEs, `GROUP BY` / aggregates, `DISTINCT`, `LIKE` / `IN`, expressions in the projection list, column aliases, `OFFSET`, multi-column `ORDER BY`, savepoints, `ALTER TABLE`, `DROP TABLE`, `DROP INDEX`. The [full list with context](docs/supported-sql.md#not-yet-supported) lives in the reference.
176
+ **Not yet supported** (common ones): subqueries, CTEs, `HAVING`, `LIKE ESCAPE '<char>'`, `IN (subquery)`, `DISTINCT` on `SUM`/`AVG`/`MIN`/`MAX`, GROUP BY on expressions, expressions in the projection list, `OFFSET`, multi-column `ORDER BY`, savepoints, `JOIN ... USING`, `NATURAL JOIN`, `CROSS JOIN`, comma joins, aggregates / DISTINCT / GROUP BY *over* JOIN results. The [full list with context](docs/supported-sql.md#not-yet-supported) lives in the reference.
175
177
 
176
178
  #### Meta commands
177
179
 
@@ -227,7 +229,8 @@ The project is staged in phases, each independently shippable. A finished phase
227
229
  - [x] Parsing via `sqlparser` (SQLite dialect); typed `SQLRiteError` via `thiserror`
228
230
  - [x] `CREATE TABLE` with `PRIMARY KEY`, `UNIQUE`, `NOT NULL`; duplicate-column detection; in-memory `BTreeMap` indexes on PK/UNIQUE columns
229
231
  - [x] `INSERT` with auto-ROWID for `INTEGER PRIMARY KEY`, UNIQUE enforcement, NULL padding for missing columns
230
- - [x] `SELECT` — projection, `WHERE`, `ORDER BY`, `LIMIT` (single-table, no joins yet)
232
+ - [x] `SELECT` — projection, `WHERE`, `ORDER BY`, `LIMIT`
233
+ - [x] `JOIN` — `INNER`, `LEFT OUTER`, `RIGHT OUTER`, `FULL OUTER` with `ON` (SQLR-5)
231
234
  - [x] `UPDATE ... SET ... WHERE ...` with type + UNIQUE enforcement at write time
232
235
  - [x] `DELETE ... WHERE ...`
233
236
  - [x] Expression evaluator: `=`/`<>`/`<`/`<=`/`>`/`>=`, `AND`/`OR`/`NOT`, arithmetic `+`/`-`/`*`/`/`/`%`, string concat `||`, NULL-as-false in `WHERE`
@@ -310,7 +313,7 @@ Lockstep versioning — one dispatch bumps every product to the same `vX.Y.Z`. T
310
313
 
311
314
  **Possible extras** *(no committed phase)*
312
315
  - Joins (`INNER`, `LEFT OUTER`, `CROSS` — SQLite does not support `RIGHT`/`FULL OUTER`)
313
- - `GROUP BY`, aggregates (`COUNT`, `SUM`, `AVG`, ...), `DISTINCT`, `LIKE`, `IN`
316
+ - `HAVING`, `IN (subquery)`, `BETWEEN`, `GLOB` / `REGEXP`, `GROUP_CONCAT`, window functions
314
317
  - Composite and expression indexes (with cost analysis)
315
318
  - Alternate storage engines — LSM/SSTable for write-heavy workloads alongside the B-Tree
316
319
  - Benchmarks against SQLite
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "sqlrite-desktop-frontend",
3
3
  "private": true,
4
- "version": "0.6.0",
4
+ "version": "0.8.0",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "dev": "vite",
@@ -136,9 +136,8 @@ Steps 1–7 are purely in-memory; step 8 is the only disk contact, and after the
136
136
 
137
137
  The roadmap has shipped far enough that the original "deliberately missing" list mostly turned into shipped features. What's still left:
138
138
 
139
- - **No query optimizer** beyond the bounded-heap top-k pass for KNN (Phase 7c) and the HNSW probe shortcut (7d.2). Equality-on-PK probes are direct; everything else is a table scan.
140
- - **No joins.** `INNER` / `LEFT OUTER` / `CROSS` are parsed but rejected by the executor. On the "possible extras" list in [roadmap.md](roadmap.md).
141
- - **No aggregates.** `COUNT(*)` / `SUM` / `AVG` / `GROUP BY` aren't implemented yet — the parser accepts them but the executor errors. Phase 8 candidate alongside FTS.
139
+ - **No query optimizer** beyond the bounded-heap top-k pass for KNN (Phase 7c) and the HNSW probe shortcut (7d.2). Equality-on-PK probes are direct; everything else is a table scan. Joins use plain nested-loop (O(N×M) per join level); hash / merge joins on equi-join shapes are a future increment.
140
+ - **Aggregates / GROUP BY / DISTINCT over joined results.** The single-table aggregator is wired against one rowid stream; the multi-table join executor produces joined rows but doesn't yet feed them through the aggregator. Surfaces as a clean `NotImplemented` at parse time. The single-table aggregation path (SQLR-3) is fully shipped.
142
141
  - **No network layer.** SQLRite is embedded-only. The closest thing is the [`sqlrite-mcp`](mcp.md) server, which is stdio (not network). A real wire protocol isn't on the roadmap.
143
142
  - **No streaming row cursor.** `Rows` is currently backed by an eager `Vec` (Phase 5a). The `Rows::next` API is shaped to support a real cursor — the swap is deferred to **5a.2**.
144
143
 
@@ -156,6 +156,25 @@ Decisions are grouped by the engine layer they concern: parser, storage, concurr
156
156
 
157
157
  ---
158
158
 
159
+ ### 14a. Implement RIGHT OUTER and FULL OUTER joins (SQLR-5)
160
+
161
+ **Decision.** SQLRite supports the full quartet — `INNER`, `LEFT OUTER`, `RIGHT OUTER`, `FULL OUTER` — via [`execute_select_rows_joined`](../src/sql/executor.rs). SQLite ships only `INNER` and `LEFT OUTER`; SQLite users typically rewrite a `RIGHT JOIN` as a `LEFT JOIN` with the operands swapped, and a `FULL JOIN` as a `UNION` of `LEFT` and a back-anti-`LEFT`.
162
+
163
+ **Why.** Once the executor has a multi-table scope (the `RowScope` trait), the per-flavor difference is just NULL-padding policy on top of one shared nested-loop driver:
164
+
165
+ - `INNER`: drop unmatched on both sides
166
+ - `LEFT OUTER`: keep unmatched left, NULL the right
167
+ - `RIGHT OUTER`: keep unmatched right, NULL the left
168
+ - `FULL OUTER`: do both
169
+
170
+ Adding the missing two flavors costs ~30 lines in the join driver and a `right_matched: Vec<bool>` to track unmatched right rows across the accumulator. That's much cheaper than the rewrite-by-hand experience SQLite users get, and removes the pedagogical "why doesn't this work" stumble — the whole project's premise is "implement these things to learn how they work", and `RIGHT` / `FULL` are interesting precisely because most engines support them and SQLite's choice not to is itself a design conversation.
171
+
172
+ **Cost.** Slightly more code in the join driver and the doc burden of explaining we diverge from SQLite. The single-table fast path is unchanged, so SQLite users hitting this engine without joins see no behavioral difference. The implementation is plain nested-loop (O(N×M) per join level) with no hash / merge optimization — same complexity as the LEFT path; both flavors will benefit equally when that work lands later.
173
+
174
+ **Cross-reference.** Implementation in [`src/sql/executor.rs`](../src/sql/executor.rs) (`execute_select_rows_joined`); per-flavor table in [`docs/supported-sql.md`](supported-sql.md#join-semantics-sqlr-5).
175
+
176
+ ---
177
+
159
178
  ### 14. Deterministic page-number ordering when saving
160
179
 
161
180
  **Decision.** [`save_database`](../src/sql/pager/mod.rs) sorts table names alphabetically before writing. Same DB contents → same bytes at same page numbers, every time.
@@ -45,11 +45,15 @@ The `sqlparser` AST is designed to cover every SQL dialect, so its types are hug
45
45
  |---|---|---|
46
46
  | `Statement::CreateTable(CreateTable)` | `CreateQuery { table_name, columns: Vec<ParsedColumn> }` | [`create.rs`](../src/sql/parser/create.rs) |
47
47
  | `Statement::Insert(Insert)` | `InsertQuery { table_name, columns, rows }` | [`insert.rs`](../src/sql/parser/insert.rs) |
48
- | `Statement::Query(_)` | `SelectQuery { table_name, projection, selection, order_by, limit }` | [`select.rs`](../src/sql/parser/select.rs) |
48
+ | `Statement::Query(_)` | `SelectQuery { table_name, table_alias, joins, projection, selection, order_by, limit, distinct, group_by }` | [`select.rs`](../src/sql/parser/select.rs) |
49
49
 
50
50
  `UPDATE` and `DELETE` don't have a dedicated internal struct — the executor pattern-matches the sqlparser types directly because there's less transformation needed.
51
51
 
52
- Each parser module also rejects features we don't implement with `SQLRiteError::NotImplemented` `JOIN`, `GROUP BY`, `HAVING`, `DISTINCT`, `OFFSET`, multi-table DELETE, tuple assignment targets, etc. These errors carry the feature name in the message so the user knows what isn't there.
52
+ `SelectQuery::projection` is now `Projection::All | Projection::Items(Vec<ProjectionItem>)`, where each item carries a `ProjectionKind::Column { qualifier, name }` (qualifier is `Some` for `t.col` shapes, used by JOIN execution to disambiguate) or `ProjectionKind::Aggregate(AggregateCall)` plus an optional `AS alias`. `AggregateCall` covers `COUNT(*)`, `COUNT([DISTINCT] col)`, `SUM` / `AVG` / `MIN` / `MAX` of a bare column. `group_by` is a `Vec<String>` of bare column names (empty = no GROUP BY); the parser validates that every non-aggregate projection item appears in `GROUP BY`.
53
+
54
+ `SelectQuery::joins` (SQLR-5) is a `Vec<JoinClause>` evaluated left-to-right by `execute_select_rows_joined`. Each clause carries a `JoinType` (`Inner` / `LeftOuter` / `RightOuter` / `FullOuter`), the right-table name + optional alias, and a required `ON` expression. Empty = single-table SELECT, the existing fast path with HNSW / FTS / bounded-heap optimizations.
55
+
56
+ Each parser module still rejects features we don't implement with `SQLRiteError::NotImplemented` — `JOIN ... USING`, `NATURAL JOIN`, `CROSS JOIN`, comma joins, aggregates / GROUP BY / DISTINCT over JOINs, `HAVING`, `DISTINCT ON (...)`, `GROUP BY` on expressions, `LIKE … ESCAPE '<char>'`, `IN (subquery)`, `OFFSET`, multi-table DELETE, tuple assignment targets, etc. These errors carry the feature name in the message so the user knows what isn't there.
53
57
 
54
58
  ## Statement dispatch
55
59
 
@@ -90,6 +94,9 @@ match query {
90
94
  |---|---|
91
95
  | Logical | `AND`, `OR`, `NOT` |
92
96
  | Comparison | `=`, `<>`, `<`, `<=`, `>`, `>=` |
97
+ | Null tests | `IS NULL`, `IS NOT NULL` |
98
+ | Pattern | `LIKE`, `NOT LIKE`, `ILIKE` (`%`, `_`, `\`-escape; case-insensitive ASCII) |
99
+ | Set | `IN (list)`, `NOT IN (list)` (literal lists only) |
93
100
  | Arithmetic | `+`, `-`, `*`, `/`, `%` |
94
101
  | String | `\|\|` |
95
102
  | Unary | `+`, `-`, `NOT` |
@@ -139,6 +146,41 @@ Returns top-k from the inverted index in `O(query-term-count × k log k)`. The p
139
146
 
140
147
  The full canonical FTS reference is in [`docs/fts.md`](fts.md).
141
148
 
149
+ ### Aggregation phase
150
+
151
+ When a SELECT contains an aggregate projection or a GROUP BY clause, the
152
+ rowid-shaped optimizations don't compose with grouping (every row
153
+ contributes to its group), so the executor takes a separate path:
154
+
155
+ 1. Filter by `WHERE` exactly as before — including the index-probe fast
156
+ path — to get the matching rowid set.
157
+ 2. For each matching rowid, derive a **group key** as a
158
+ `Vec<DistinctKey>` (one entry per `GROUP BY` column; empty key for
159
+ queries with aggregates but no `GROUP BY`).
160
+ 3. Update one `AggState` per (group, aggregate-projection-slot) —
161
+ `AggState` lives in [`src/sql/agg.rs`](../src/sql/agg.rs) and tracks
162
+ the SQLite numeric type rules (`SUM` stays `INTEGER` until a `REAL`
163
+ input or `i64` overflow promotes it; `AVG` is always `REAL`; `MIN`/`MAX`
164
+ reuse the executor's total order; `COUNT(DISTINCT col)` uses a
165
+ `HashSet<DistinctKey>`).
166
+ 4. Emit one output row per group, in projection order — bare-column
167
+ slots emit the captured group-key value, aggregate slots emit
168
+ `AggState::finalize()`.
169
+ 5. Apply DISTINCT (post-projection dedup), then ORDER BY (resolved
170
+ against the *output* row by alias, bare column name, or aggregate
171
+ display form), then LIMIT.
172
+
173
+ Aggregate function names (`COUNT`/`SUM`/`AVG`/`MIN`/`MAX`) used in WHERE
174
+ or any other scalar position get a friendly error redirecting the user
175
+ to the projection list (since `HAVING` isn't supported yet). DISTINCT
176
+ on `SUM`/`AVG`/`MIN`/`MAX` is rejected at parse time; only
177
+ `COUNT(DISTINCT col)` is in v1.
178
+
179
+ `LIKE` / `ILIKE` use a hand-rolled iterative two-pointer matcher in
180
+ `agg.rs::like_match` (no regex dep). `IN (list)` follows SQLite's
181
+ three-valued logic for NULL on either side, which collapses to "row
182
+ excluded" under WHERE's NULL-as-false rule.
183
+
142
184
  ## Two-pass pattern for UPDATE and DELETE
143
185
 
144
186
  Both `execute_update` and `execute_delete` use the same pattern to satisfy Rust's aliasing rules:
@@ -148,35 +148,93 @@ Hex literals, blob literals, and date/time functions are not supported.
148
148
  ## `SELECT`
149
149
 
150
150
  ```sql
151
- SELECT {* | col1, col2, ...}
152
- FROM <table>
151
+ SELECT [DISTINCT] {* | <projection_item>[, <projection_item>, ...]}
152
+ FROM <table> [AS <alias>]
153
+ [{INNER | LEFT [OUTER] | RIGHT [OUTER] | FULL [OUTER]} JOIN <table> [AS <alias>] ON <expr>]*
153
154
  [WHERE <expr>]
154
- [ORDER BY <col> [ASC|DESC]]
155
+ [GROUP BY <col>[, <col>, ...]]
156
+ [ORDER BY <expr> [ASC|DESC]]
155
157
  [LIMIT <non-negative-integer>];
156
158
  ```
157
159
 
160
+ `<projection_item>` is one of:
161
+
162
+ ```
163
+ <column> -- bare column reference
164
+ COUNT(*) -- counts every row, including all-NULL ones
165
+ COUNT([DISTINCT] <column>) -- counts non-NULL values, optionally deduping
166
+ {SUM | AVG | MIN | MAX}(<column>) -- aggregate over a single column
167
+ <projection_item> AS <alias> -- optional column alias
168
+ ```
169
+
158
170
  ### What works
159
171
 
160
- - **Projection**: `*` (all columns in declaration order) or a bare column list. Columns not declared on the table are rejected.
161
- - **`WHERE`**: any [expression](#expressions). Evaluated per row; NULL-as-false in WHERE context (three-valued logic collapsed to two-valued for filtering). Includes **`IS NULL`** / **`IS NOT NULL`** for explicit null tests.
162
- - **`ORDER BY`**: single sort key, `ASC` (default) or `DESC`. The sort key can be a bare column reference OR any expression — including function calls — so KNN queries like `ORDER BY vec_distance_l2(embedding, [...]) LIMIT k` work end-to-end *(Phase 7b)*. Sort key types must match; mixing `INTEGER` and `TEXT` across rows under a single `ORDER BY` is a runtime error.
163
- - **`LIMIT`**: non-negative integer literal. `LIMIT 0` is valid (returns zero rows).
172
+ - **Projection**: `*` (all columns in declaration order), a bare column list, or an explicit list mixing bare columns and aggregate calls. Each item can carry an optional `AS alias` (the alias becomes the output column header and is recognized by `ORDER BY`).
173
+ - **`WHERE`**: any [expression](#expressions). Evaluated per row; NULL-as-false in WHERE context (three-valued logic collapsed to two-valued for filtering). Includes **`IS NULL`** / **`IS NOT NULL`** for explicit null tests, **`LIKE` / `NOT LIKE` / `ILIKE`** for pattern matching, and **`IN (list) / NOT IN (list)`** for set-membership against literal lists.
174
+ - **`DISTINCT`**: `SELECT DISTINCT` deduplicates result rows after projection (and after aggregation, when both apply). `NULL` values compare equal to other `NULL`s for dedupe, matching SQL's DISTINCT semantic.
175
+ - **`GROUP BY`**: one or more bare column names. Every non-aggregate item in the projection must appear in the `GROUP BY` list (the parser rejects the violation with a clear message). `GROUP BY <col>` without any aggregate behaves like an implicit `DISTINCT <col>`.
176
+ - **Aggregates** (SQLR-3): `COUNT(*)`, `COUNT(col)`, `COUNT(DISTINCT col)`, `SUM(col)`, `AVG(col)`, `MIN(col)`, `MAX(col)`. `SUM` over an integer column stays `INTEGER` until a `REAL` input arrives or the running sum overflows `i64` (one-time promotion to `REAL`). `AVG` always returns `REAL` (or `NULL` on empty / all-NULL groups). `MIN` / `MAX` skip NULLs and use the same total order as `ORDER BY`. Aggregates over an empty table or empty group return `0` for `COUNT(*)` / `COUNT(col)` and `NULL` for the rest.
177
+ - **`ORDER BY`**: single sort key, `ASC` (default) or `DESC`. For non-aggregating queries the key is any expression — including function calls — so KNN queries like `ORDER BY vec_distance_l2(embedding, [...]) LIMIT k` work end-to-end *(Phase 7b)*. For aggregating queries the key resolves against the *output* row by name: a bare identifier matches an alias or a `GROUP BY` column, and a function call like `COUNT(*)` matches an aggregate projection by its canonical display form. Sort key types must match across rows.
178
+ - **`LIMIT`**: non-negative integer literal. `LIMIT 0` is valid (returns zero rows). When `DISTINCT` is in play, `LIMIT` is applied after deduplication so it counts unique rows.
179
+
180
+ ### `JOIN` semantics (SQLR-5)
181
+
182
+ Four flavors are supported, all with explicit `ON` conditions:
183
+
184
+ | Flavor | Keeps unmatched rows from… |
185
+ |---|---|
186
+ | `INNER JOIN` | …neither side. Only ON-matched pairs survive. |
187
+ | `LEFT [OUTER] JOIN` | …the left side; right-side columns become `NULL` for unmatched left rows. |
188
+ | `RIGHT [OUTER] JOIN` | …the right side; left-side columns become `NULL` for unmatched right rows. |
189
+ | `FULL [OUTER] JOIN` | …both sides, NULL-padded on the unmatched side. |
190
+
191
+ - **Engine choice:** SQLite ships only `INNER` and `LEFT OUTER`. SQLRite implements all four because the per-flavor differences boil down to NULL-padding policy on top of one shared nested-loop driver — adding `RIGHT` / `FULL` was effectively free once the executor had a multi-table scope. See [`docs/design-decisions.md`](design-decisions.md) for the rationale.
192
+ - **Aliases:** `FROM customers AS c INNER JOIN orders AS o ON c.id = o.customer_id`. When an alias is supplied the original table name leaves scope (SQL standard) — qualifier resolution uses the alias.
193
+ - **Qualified column references:** `<table>.<col>` and `<alias>.<col>` resolve to that specific side. Bare `<col>` references must resolve to exactly one in-scope table; ambiguous references error with a "qualify it as `<table>.col`" hint.
194
+ - **Output of `SELECT *`** over a join is every column of every in-scope table, in source order. Duplicate header names are permitted (SQLite-style). Disambiguate with explicit `SELECT t.col AS t_col, u.col AS u_col`.
195
+ - **Multi-join** chains left-fold: `A JOIN B ON ... JOIN C ON ...` evaluates as `(A ⨝ B) ⨝ C`. Each new clause sees every prior alias / table in its `ON` expression.
196
+ - **Self-joins** require an alias on at least one side: `FROM nodes AS p INNER JOIN nodes AS c ON p.id = c.parent_id`. Without one, you get a `duplicate table reference` error so qualifiers stay unambiguous.
197
+ - **`WHERE` runs after joins.** A `WHERE right.col IS NULL` filter on a `LEFT JOIN` correctly returns left rows with no match (the standard "anti-join via outer-join" idiom).
198
+ - **`ORDER BY` and `LIMIT`** apply to the fully joined row stream.
199
+ - **Algorithm:** plain nested-loop join, O(N×M) per join level. Adequate for an embedded learning database; hash / merge joins on equi-join shapes are a future optimization.
200
+
201
+ #### What's not supported in JOINs
202
+
203
+ - `JOIN ... USING (col)` and `NATURAL JOIN` — explicit `ON` only. (Both are deferred — `USING` is straightforward but adds a column-resolution rule we haven't needed yet.)
204
+ - `CROSS JOIN` (write `INNER JOIN ... ON true` instead) and comma-separated FROM lists.
205
+ - Aggregates / `GROUP BY` / `DISTINCT` *over* a join. The single-table aggregator is wired against one rowid stream; rewiring it for joined rows is a separate increment. Surfaces as a clean `NotImplemented` at parse time.
206
+ - `fts_match` / `bm25_score` inside a JOIN expression. They need to look up an FTS index by column, which is single-table-bound today. Use them on a single-table SELECT first, or fold the FTS lookup into the FROM side.
164
207
 
165
208
  ### Index probing
166
209
 
167
- The executor includes a tiny optimizer: if the `WHERE` is exactly `<indexed_col> = <literal>` or `<literal> = <indexed_col>`, it probes the index and scans only matching rows. Mixed predicates (`WHERE a = 1 AND b > 2`), range predicates (`WHERE a > 1`), and OR-combined predicates fall back to a full table scan.
210
+ The executor includes a tiny optimizer: if the `WHERE` is exactly `<indexed_col> = <literal>` or `<literal> = <indexed_col>`, it probes the index and scans only matching rows. Mixed predicates (`WHERE a = 1 AND b > 2`), range predicates (`WHERE a > 1`), and OR-combined predicates fall back to a full table scan. Aggregating queries (`GROUP BY` / aggregate functions) skip the rowid-shape optimizations (HNSW / FTS / bounded-heap top-k) since every matching row contributes to its group.
211
+
212
+ ### `LIKE` semantics
213
+
214
+ - `%` matches any (possibly empty) char sequence; `_` matches exactly one char. `\` escapes the next character so `\%` matches a literal percent. Outside `\%` / `\_` / `\\`, a backslash is itself a literal — matching SQLite's loose default.
215
+ - Case folding is **ASCII-only and on by default**, mirroring SQLite's default `PRAGMA case_sensitive_like = OFF`. `LIKE 'a%'` matches both `Apple` and `apple`. Non-ASCII characters compare by code point (no Unicode case folding).
216
+ - `LIKE … ESCAPE '<char>'` is not supported. `LIKE ANY (...)` is not supported.
217
+ - `NULL LIKE 'pattern'` evaluates to `NULL`; in a `WHERE` that excludes the row.
218
+
219
+ ### `IN` semantics
220
+
221
+ - Only the literal-list form is supported: `WHERE x IN (1, 2, 3)` and `WHERE x NOT IN (...)`.
222
+ - Three-valued logic: if the LHS is `NULL`, the result is `NULL`; if the RHS list contains a `NULL` and no other entry matches, the result is `NULL`. In a `WHERE` both cases collapse to "row excluded", matching SQLite.
223
+ - `IN (subquery)`, `IN UNNEST(...)`, and `BETWEEN` are not supported yet.
168
224
 
169
225
  ### What doesn't work
170
226
 
171
- - **Joins** of any kind (`INNER`, `LEFT OUTER`, `CROSS`, comma-join)
227
+ - **`CROSS JOIN`**, **comma-separated FROM lists**, **`NATURAL JOIN`**, **`JOIN ... USING (col)`** — explicit `INNER` / `LEFT` / `RIGHT` / `FULL OUTER JOIN ... ON ...` only (see [JOIN semantics](#join-semantics-sqlr-5))
228
+ - **Aggregates** / **`GROUP BY`** / **`DISTINCT`** over a JOIN — pipe through a subquery once subqueries land
172
229
  - **Subqueries**, CTEs (`WITH`), views
173
- - **`GROUP BY`**, aggregate functions (`COUNT`, `SUM`, `AVG`, `MIN`, `MAX`), `HAVING`
174
- - **`DISTINCT`**
175
- - **`LIKE`**, **`IN`**, `BETWEEN`
176
- - **Expressions in the projection list** (`SELECT age + 1 FROM users`) projection is bare column references only
177
- - **Multi-column `ORDER BY`**, `NULLS FIRST/LAST` (single sort key only; the sort key itself can be an expression as of Phase 7b)
230
+ - **`HAVING`** pre-aggregation `WHERE` works; post-aggregation filtering does not yet
231
+ - **`DISTINCT`** on `SUM` / `AVG` / `MIN` / `MAX` (only `COUNT(DISTINCT col)` is supported)
232
+ - **`GROUP BY` on expressions** — bare column names only in v1
233
+ - **`LIKE ESCAPE '<char>'`**, **`IN (subquery)`**, **`BETWEEN`**, **`GLOB`**, **`REGEXP`**
234
+ - **Expressions in the projection list** beyond aggregate calls (`SELECT age + 1 FROM users` is still rejected; aggregates are the one allowed expression form)
235
+ - **Multi-column `ORDER BY`**, `NULLS FIRST/LAST` (single sort key only)
178
236
  - **`OFFSET`**
179
- - **Column aliases** (`SELECT name AS n FROM users`)
237
+ - **Window functions** (`OVER (...)`, `FILTER (WHERE ...)`, `WITHIN GROUP`)
180
238
 
181
239
  Any of the above reaches the executor as a parsed AST node that execution doesn't handle, producing either `NotImplemented` or a more specific error (e.g., `joins are not supported`).
182
240
 
@@ -286,6 +344,9 @@ Expressions work inside `WHERE` (both in `SELECT`, `UPDATE`, `DELETE`) and on th
286
344
  | Category | Operators |
287
345
  |---|---|
288
346
  | Comparison | `=`, `<>`, `<`, `<=`, `>`, `>=` |
347
+ | Null tests | `IS NULL`, `IS NOT NULL` |
348
+ | Pattern | `LIKE`, `NOT LIKE`, `ILIKE` (`%`, `_`, `\`-escape; case-insensitive ASCII) |
349
+ | Set | `IN (list)`, `NOT IN (list)` (literal lists only) |
289
350
  | Logical | `AND`, `OR`, `NOT` |
290
351
  | Arithmetic | `+`, `-`, `*`, `/`, `%` |
291
352
  | String | `\|\|` (concatenation) |
@@ -490,7 +551,8 @@ A REPL launched with `sqlrite --readonly foo.sqlrite` (or `sqlrite::open_databas
490
551
  For context when you hit `NotImplemented`. See [Roadmap](roadmap.md) for when these land:
491
552
 
492
553
  ### Joins & composition
493
- - `INNER` / `LEFT OUTER` / `RIGHT OUTER` / `CROSS JOIN`, comma joins
554
+ - `INNER` / `LEFT OUTER` / `RIGHT OUTER` / `FULL OUTER JOIN ... ON ...` — **supported** (SQLR-5)
555
+ - `CROSS JOIN`, comma joins, `NATURAL JOIN`, `JOIN ... USING` — not yet
494
556
  - Subqueries (scalar, `IN (SELECT ...)`, correlated)
495
557
  - CTEs (`WITH`), recursive CTEs
496
558
  - Views (`CREATE VIEW`)
@@ -4,7 +4,7 @@ build-backend = "maturin"
4
4
 
5
5
  [project]
6
6
  name = "sqlrite"
7
- version = "0.6.0"
7
+ version = "0.8.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.6.0"
3
+ version = "0.8.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.6.0"
13
+ version = "0.8.0"
14
14
  authors = ["Joao Henrique Machado Silva <joaoh82@gmail.com>"]
15
15
  edition = "2024"
16
16
  rust-version = "1.85"