diff --git a/docs-site/src/content/docs/explain/async-strategy.mdx b/docs-site/src/content/docs/explain/async-strategy.mdx
index 7fc816b..cb6a75d 100644
--- a/docs-site/src/content/docs/explain/async-strategy.mdx
+++ b/docs-site/src/content/docs/explain/async-strategy.mdx
@@ -7,7 +7,7 @@ sidebar:
import { Aside } from '@astrojs/starlight/components';
-`informix-driver`'s async API (`from informix_db import aio`) is implemented by wrapping the sync core in a thread pool. Every `await conn.execute(...)` schedules the underlying sync `execute()` on the pool's executor.
+`informix-driver`'s async API (`from informix_db import aio`) is implemented by wrapping the sync core in a thread pool. Every `await cur.execute(...)` schedules the underlying sync `execute()` on the pool's executor.
This is a deliberate architectural choice from Phase 16. Here's the reasoning.
diff --git a/docs-site/src/content/docs/how-to/async-fastapi.mdx b/docs-site/src/content/docs/how-to/async-fastapi.mdx
index 9b8fc5f..e9d0f1a 100644
--- a/docs-site/src/content/docs/how-to/async-fastapi.mdx
+++ b/docs-site/src/content/docs/how-to/async-fastapi.mdx
@@ -64,13 +64,13 @@ This is a Phase 27 invariant: async cancellation cannot leak running workers ont
## Connection-level transactions
-For request-scoped transactions, use a context manager around the connection:
+For request-scoped transactions, commit on the way out and roll back on any exception:
```python
@app.post("/orders")
async def create_order(order: OrderIn, conn = Depends(get_conn)):
- async with conn.transaction():
- cur = await conn.cursor()
+ cur = await conn.cursor()
+ try:
await cur.execute(
"INSERT INTO orders VALUES (?, ?, ?)",
(order.id, order.customer_id, order.total),
@@ -79,7 +79,15 @@ async def create_order(order: OrderIn, conn = Depends(get_conn)):
"UPDATE inventory SET qty = qty - ? WHERE sku = ?",
(order.qty, order.sku),
)
+ await conn.commit()
+ except Exception:
+ await conn.rollback()
+ raise
return {"ok": True}
```
-The transaction commits on normal exit and rolls back on any exception, including `HTTPException`.
+`HTTPException` is an `Exception`, so raising one inside the block rolls back before FastAPI turns it into a response.
+
+
diff --git a/docs-site/src/content/docs/how-to/executemany.mdx b/docs-site/src/content/docs/how-to/executemany.mdx
index 0b7ad6c..e780f5d 100644
--- a/docs-site/src/content/docs/how-to/executemany.mdx
+++ b/docs-site/src/content/docs/how-to/executemany.mdx
@@ -85,7 +85,17 @@ with conn:
## Returning generated keys
-Informix's `SERIAL` columns are server-assigned. To get the IDs back, use a single-row insert per row and read `cur.lastrowid` — `executemany` doesn't return per-row IDs.
+Informix's `SERIAL` columns are server-assigned, and `executemany` doesn't return per-row IDs. To get an ID back, insert one row at a time and ask the server for the value it assigned:
+
+```python
+cur.execute("INSERT INTO orders (customer_id, total) VALUES (?, ?)", (7, 42.0))
+cur.execute("SELECT DBINFO('sqlca.sqlerrd1') FROM systables WHERE tabid = 1")
+new_id = cur.fetchone()[0]
+```
+
+`sqlca.sqlerrd1` is where Informix records the serial value from the most recent INSERT on this connection, so read it before running any other statement.
+
+There is no `cursor.lastrowid` — an earlier version of this page said there was.
For batch inserts that need the IDs, the idiomatic pattern is:
diff --git a/docs-site/src/content/docs/how-to/smart-lobs.mdx b/docs-site/src/content/docs/how-to/smart-lobs.mdx
index c583957..95a0256 100644
--- a/docs-site/src/content/docs/how-to/smart-lobs.mdx
+++ b/docs-site/src/content/docs/how-to/smart-lobs.mdx
@@ -34,25 +34,33 @@ The `BLOB_PLACEHOLDER` token in the SQL marks where the BLOB data goes. Other pa
## Reading a CLOB
+CLOBs use the same methods as BLOBs — there is no separate `read_clob_column`. `read_blob_column` returns `bytes` for both, so decode it with the encoding the column was written in:
+
```python
-text: str = cur.read_clob_column(
+raw: bytes = cur.read_blob_column(
"SELECT body FROM articles WHERE id = ?",
(42,),
)
+text: str = raw.decode("iso-8859-1") # or your DB_LOCALE's codec
```
-Returns a decoded `str` using the connection's `client_locale`.
+Returning `bytes` rather than `str` is deliberate: the driver transports the LOB over the `SQ_FILE` channel and never sees the column's declared character set, so guessing an encoding here would be exactly the kind of silent assumption that produces mojibake in one deployment and works fine in another.
## Writing a CLOB
+Pass `clob=True` so the write routes through `filetoclob` rather than `filetoblob`, and encode the text yourself:
+
```python
-cur.write_clob_column(
- "INSERT INTO articles VALUES (?, CLOB_PLACEHOLDER)",
- clob_data="long article text...",
- params=(42,),
+cur.write_blob_column(
+ "INSERT INTO articles VALUES (?, BLOB_PLACEHOLDER)",
+ "long article text… café résumé".encode("iso-8859-1"),
+ (42,),
+ clob=True,
)
```
+The placeholder token is `BLOB_PLACEHOLDER` for both BLOB and CLOB writes.
+
## Server-side prerequisites