From 146d52f7360029e2fe1706fd9f1bf7b91ca23423 Mon Sep 17 00:00:00 2001 From: Christopher Date: Sat, 1 Aug 2026 00:19:25 +0200 Subject: [PATCH] docs: add a Turso extras guide page Verified recipes for using turso-only features with generated code: materialized views via a view shim, CDC through a declared turso_cdc table, BEGIN CONCURRENT with the pyturso mvcc gotchas, and enum-like CREATE DOMAIN columns mapped to StrEnum via a type override. --- docs/content/docs/guide/_index.md | 4 +- docs/content/docs/guide/drivers.md | 2 + docs/content/docs/guide/naming.md | 2 +- .../docs/guide/sqlite-type-conversion.md | 2 +- docs/content/docs/guide/turso-extras.md | 137 ++++++++++++++++++ 5 files changed, 143 insertions(+), 4 deletions(-) create mode 100644 docs/content/docs/guide/turso-extras.md diff --git a/docs/content/docs/guide/_index.md b/docs/content/docs/guide/_index.md index dd6ee34..5cfdd7a 100644 --- a/docs/content/docs/guide/_index.md +++ b/docs/content/docs/guide/_index.md @@ -42,6 +42,6 @@ Start with the essentials, then dip into feature pages as you need them: {{< /cards >}} The remaining pages - enums, type overrides, converters, working with JSON, -docstrings, SQLite type conversion, and naming - cover specific features and can -be read in any order. The [Reference](/docs/reference) section holds the +docstrings, SQLite type conversion, Turso extras, and naming - cover specific +features and can be read in any order. The [Reference](/docs/reference) section holds the canonical option, type-mapping, and driver-support tables. diff --git a/docs/content/docs/guide/drivers.md b/docs/content/docs/guide/drivers.md index 4311aee..ef27c98 100644 --- a/docs/content/docs/guide/drivers.md +++ b/docs/content/docs/guide/drivers.md @@ -158,6 +158,8 @@ asyncio variant. - Unlike the `sqlite3` module, no connection flags are needed - the generated code converts values inline, and the observable Python types match the SQLite drivers exactly. +- Turso-only features (materialized views, CDC, concurrent writes, domains) + work with generated code too - see [Turso extras](/docs/guide/turso-extras). ```python import turso diff --git a/docs/content/docs/guide/naming.md b/docs/content/docs/guide/naming.md index d2b1d9c..c291f57 100644 --- a/docs/content/docs/guide/naming.md +++ b/docs/content/docs/guide/naming.md @@ -3,7 +3,7 @@ title: Naming and identifiers description: >- How SQL identifiers become Python names: singularized model classes, field naming, initialisms, and reserved-word escaping. weight: 100 -prev: /docs/guide/sqlite-type-conversion +prev: /docs/guide/turso-extras --- SQL identifiers are not always valid Python names, and SQL naming conventions are diff --git a/docs/content/docs/guide/sqlite-type-conversion.md b/docs/content/docs/guide/sqlite-type-conversion.md index a179571..9a183a6 100644 --- a/docs/content/docs/guide/sqlite-type-conversion.md +++ b/docs/content/docs/guide/sqlite-type-conversion.md @@ -4,7 +4,7 @@ description: >- How the SQLite drivers convert dates, decimals, booleans, and blobs: adapters, converters, PARSE_DECLTYPES, speedups, and how turso differs. weight: 90 prev: /docs/guide/docstrings -next: /docs/guide/naming +next: /docs/guide/turso-extras --- SQLite only stores a handful of native types, so dates, decimals, booleans and diff --git a/docs/content/docs/guide/turso-extras.md b/docs/content/docs/guide/turso-extras.md new file mode 100644 index 0000000..0298999 --- /dev/null +++ b/docs/content/docs/guide/turso-extras.md @@ -0,0 +1,137 @@ +--- +title: Turso extras +description: >- + Use Turso's features beyond SQLite with generated code: materialized views, change data capture, concurrent writes, and enum-like domains via overrides. +weight: 95 +prev: /docs/guide/sqlite-type-conversion +next: /docs/guide/naming +--- + +Turso adds features SQLite does not have. sqlc's sqlite parser rejects their +DDL (`CREATE MATERIALIZED VIEW`, `CREATE DOMAIN`, `BEGIN CONCURRENT`), but +they still work with generated code. The pattern: + +{{< callout type="info" >}} + **Shim the shape in the sqlc schema, create the real object at runtime.** + sqlc only needs column names and types to generate; the driver does not care + what the object actually is. +{{< /callout >}} + +Everything on this page was verified against pyturso 0.7.1. Experimental +features are enabled per connection: +`turso.connect("app.db", experimental_features="views,custom_types")`. + +## Materialized views + +Give sqlc a plain `CREATE VIEW` with the same columns; it generates a model +and query functions from it. At runtime, create the real materialized view: + +```sql +-- sqlc schema shim +CREATE VIEW user_stats AS +SELECT status, count(*) AS n +FROM users +GROUP BY status; + +-- name: GetUserStats :many +SELECT status, n FROM user_stats ORDER BY status; +``` + +```python +conn = turso.connect("app.db", experimental_features="views") +conn.execute("CREATE MATERIALIZED VIEW user_stats AS SELECT status, count(*) AS n FROM users GROUP BY status") + +stats = queries.get_user_stats(conn)() # incrementally maintained by turso +``` + +## Change data capture + +Turso writes changes to a normal `turso_cdc` table. Declare it in the sqlc +schema and query the change feed with generated functions; only the enabling +`PRAGMA` has to stay in runtime code (sqlc drops `PRAGMA` queries): + +```sql +CREATE TABLE turso_cdc +( + change_id integer PRIMARY KEY NOT NULL, + change_time integer NOT NULL, + change_txn_id integer, + change_type integer NOT NULL, -- 1 insert, 0 update, -1 delete + table_name text NOT NULL, + id integer NOT NULL, + before blob, + after blob, + updates blob +); + +-- name: ListChanges :many +SELECT change_id, change_type, table_name, id FROM turso_cdc ORDER BY change_id; +``` + +```python +conn.execute("PRAGMA capture_data_changes_conn('full')") +queries.create_user(conn, id_=1, name="ada") +changes = queries.list_changes(conn)() # typed rows, one per change +``` + +## Concurrent writes (MVCC) + +Generated functions never manage transactions, so they run unmodified inside +`BEGIN CONCURRENT` / `COMMIT`: + +```python +conn = turso.connect("app.db", experimental_features="mvcc") +conn.execute("PRAGMA journal_mode = 'mvcc'").fetchall() + +conn.execute("BEGIN CONCURRENT") +queries.create_user(conn, id_=1, name="ada") +conn.execute("COMMIT") +``` + +{{< callout type="warning" >}} + Two gotchas: pyturso needs `experimental_features="mvcc"` at connect time - + the pragma alone is not enough - and the pragma only takes effect once its + result row is **fetched** (the `.fetchall()` above). Without either you get + "Concurrent transaction mode is only supported when MVCC is enabled". +{{< /callout >}} + +## Enum-like domains + +Turso has no enum type, but a `CREATE DOMAIN` with a `CHECK` on a `STRICT` +table enforces the value set database-side. Shim the column as `text` for +sqlc and map it to your own `enum.StrEnum` with a +[type override](/docs/guide/type-overrides) - the same experience the plugin's +[PostgreSQL enums](/docs/guide/enums) provide: + +```python +# app_enums.py +class Status(enum.StrEnum): + ACTIVE = "active" + BANNED = "banned" +``` + +```yaml +overrides: + - column: users.status + py_type: + import: app_enums + package: Status + type: Status +``` + +```python +conn = turso.connect("app.db", experimental_features="custom_types") +conn.execute("CREATE DOMAIN status_d AS text CHECK (value IN ('active', 'banned'))") +conn.execute("CREATE TABLE users (id integer PRIMARY KEY NOT NULL, name text NOT NULL, status status_d NOT NULL) STRICT") + +queries.create_user(conn, id_=1, name="ada", status=Status.ACTIVE) +user = queries.get_user(conn, id_=1) +assert user.status is Status.ACTIVE # a real enum member +conn.execute("INSERT INTO users VALUES (2, 'x', 'nonsense')") # IntegrityError +``` + +{{< callout type="info" >}} + All of these Turso features are experimental and pre-1.0 - the SQL surface + and the pyturso flags may change. The generated code is agnostic to them; + only your runtime setup would need updating. +{{< /callout >}}