Adds scroll/random-access methods on Cursor: * scroll(value, mode='relative'|'absolute') — PEP 249 compatible * fetch_first() / fetch_last() — jump to result-set ends * fetch_prior() — step backward (SQL-standard: from past-end yields the last row, matching JDBC ResultSet.previous() semantics) * fetch_absolute(n) — 0-indexed jump; negative n indexes from end * fetch_relative(n) — n-step from current position * rownumber property — current 0-indexed position Implementation: replaced _row_iter (single-pass iterator) with _row_index (random-access index) on the cursor. The result set is already materialized in _rows during execute(); scroll just repositions the index. No new wire protocol needed. For server-side scroll over genuinely huge result sets, SQ_SFETCH (tag 23) would be needed — JDBC has executeScrollFetch (line 3908) but we only need it if someone hits the in-memory materialization ceiling. Phase 18 if so. Out-of-range scroll raises IndexError per PEP 249. Invalid mode strings raise ProgrammingError. fetchall() now correctly returns only the rows from the current position to end (not all rows). 14 new integration tests in test_scroll_cursor.py covering: * fetchone advancing rownumber sequentially * fetch_first reset * fetch_last * fetch_prior including the past-end-to-last-row semantics * fetch_absolute with positive and negative indexes * fetch_relative * PEP 249 scroll(value, mode='relative'/'absolute') * IndexError on out-of-range * ProgrammingError on bad mode * Empty-result-set edge cases * fetchall after partial iteration Total: 69 unit + 177 integration = 246 tests.
62 lines
4.8 KiB
Markdown
62 lines
4.8 KiB
Markdown
# Changelog
|
|
|
|
All notable changes to `informix-db`. Versioning is [CalVer](https://calver.org/) — `YYYY.MM.DD` for date-based releases, `YYYY.MM.DD.N` for same-day post-releases per PEP 440.
|
|
|
|
## 2026.05.04.1 — Scroll cursors
|
|
|
|
### Added
|
|
|
|
- **Scroll cursor API** on `Cursor` (Phase 17):
|
|
- `cur.scroll(value, mode='relative'|'absolute')` — PEP 249 compatible
|
|
- `cur.fetch_first()` / `cur.fetch_last()` — jump to ends
|
|
- `cur.fetch_prior()` — backward step (SQL-standard semantics: from past-end yields the last row)
|
|
- `cur.fetch_absolute(n)` — 0-indexed jump; negative `n` indexes from the end
|
|
- `cur.fetch_relative(n)` — n-step from current position
|
|
- `cur.rownumber` — current 0-indexed position (None if before-first or no result set)
|
|
|
|
In-memory implementation — no new wire-protocol; the existing materialized result set in `cur._rows` is now indexed rather than iterated. For server-side scroll over huge result sets, `SQ_SFETCH` (tag 23) would be needed — Phase 18 if anyone hits the in-memory ceiling.
|
|
|
|
### Tests
|
|
|
|
14 new integration tests in `test_scroll_cursor.py`. Total: **69 unit + 177 integration = 246 tests**.
|
|
|
|
## 2026.05.04 — Library completion
|
|
|
|
The Phase 0 ambition — first pure-Python Informix SQLI driver — reaches feature completeness. Adds async, TLS, connection pool, smart-LOBs, fast-path RPC, composite UDTs.
|
|
|
|
### Added
|
|
|
|
- **Async API** (`informix_db.aio`) — `AsyncConnection`, `AsyncCursor`, `AsyncConnectionPool` for FastAPI / aiohttp / asyncio. Each blocking I/O call is offloaded to a worker thread via `asyncio.to_thread`; event loop never blocks.
|
|
- **Connection pool** (`informix_db.create_pool`) — thread-safe with min/max sizing, lazy growth, health-check on acquire, error-aware eviction.
|
|
- **TLS** — `tls=True` for self-signed dev servers, `tls=ssl.SSLContext` for production. Wrapping happens in `IfxSocket` so the rest of the protocol layer is unaware.
|
|
- **Smart-LOBs** (BLOB / CLOB) — full read/write end-to-end via `cursor.read_blob_column()` / `cursor.write_blob_column()` using the server's `lotofile` / `filetoblob` SQL functions intercepted at the `SQ_FILE` (98) protocol level.
|
|
- **Legacy in-row blobs** (BYTE / TEXT) — bind + read via the `SQ_BBIND` / `SQ_BLOB` / `SQ_FETCHBLOB` protocol family.
|
|
- **Fast-path RPC** (`Connection.fast_path_call`) — direct stored-procedure invocation bypassing PREPARE/EXECUTE; routine handles cached per-connection.
|
|
- **Composite UDT recognition** — `ROW`, `SET`, `MULTISET`, `LIST` columns return typed `RowValue` / `CollectionValue` wrappers exposing schema and raw bytes.
|
|
- **Type codecs** — `INTERVAL` (both DAY-TO-FRACTION and YEAR-TO-MONTH families), `DATETIME` (all qualifier ranges), `DECIMAL` / `MONEY` (BCD with sign+exp head byte and asymmetric base-100 complement for negatives), `DATE`, `BOOL`, all integer / float widths, `CHAR` / `VARCHAR` / `LVARCHAR`.
|
|
- **Transactions** — implicit `SQ_BEGIN` before each transaction in non-ANSI logged DBs; transparent no-ops on unlogged DBs.
|
|
- **PEP 249 exception hierarchy** — server `SQLCODE` mapped to the right exception class (`IntegrityError` for duplicate-key violations, `ProgrammingError` for syntax errors, etc.).
|
|
|
|
### Documentation
|
|
|
|
- [`README.md`](README.md) — overview and quick-start
|
|
- [`docs/USAGE.md`](docs/USAGE.md) — practical recipes and migration guide
|
|
- [`docs/PROTOCOL_NOTES.md`](docs/PROTOCOL_NOTES.md) — byte-level wire-format reference
|
|
- [`docs/DECISION_LOG.md`](docs/DECISION_LOG.md) — phase-by-phase architectural decisions, with the *why* preserved
|
|
- [`docs/JDBC_NOTES.md`](docs/JDBC_NOTES.md) — index into the decompiled IBM JDBC reference
|
|
- [`docs/CAPTURES/`](docs/CAPTURES/) — annotated socat hex-dump captures
|
|
|
|
### Test coverage
|
|
|
|
232 tests total: **69 unit + 163 integration**. Unit tests run with no external dependencies; integration tests run against the IBM Informix Developer Edition Docker image.
|
|
|
|
### Known gaps (deferred)
|
|
|
|
- **Full ROW/COLLECTION recursive parsing**: Phase 12 ships type recognition + raw-bytes wrapper. Parsing the textual representation into typed Python tuples/sets/lists is deferred — most workloads can use SQL projections (`SELECT row_col.fieldname FROM tbl`) instead.
|
|
- **UDT parameter encoding for fast-path**: scalar params/returns work; passing a 72-byte BLOB locator as a UDT param requires extending the SQ_BIND encoder with the extended_owner/extended_name preamble for type > 18.
|
|
- **Native async I/O**: Phase 16 ships a thread-pool wrapper that's functionally equivalent for typical FastAPI workloads. Native async (asyncpg-style transport abstraction) would be Phase 17 if a real workload needs it.
|
|
|
|
## 2026.05.02 — Phase 1: connection lifecycle
|
|
|
|
Initial release. `connect()` / `close()` works end-to-end. Cursor / execute / fetch arrived in Phase 2 (subsequent commits within the same session).
|