10 Commits

Author SHA1 Message Date
9616ddbc0a Fix LVARCHAR tuple framing; DATETIME fractions on bind (2026.08.31)
More data corruption from the same field report as 2026.05.08.2. Anyone
with LVARCHAR columns should upgrade: 2026.08.27 is not safe for them.

Two independent errors in the LVARCHAR envelope, both shifting every
column selected after it.

1. A phantom pad byte. We appended an even-byte pad when the value
   length was odd. There is no pad. Wire capture (12.10), INT8 /
   LVARCHAR / INT8:
     00 01 00 00 07 d1 00 00 00 00   a = INT8 2001
     00                              null indicator
     00 00 00 0b                     length 11
     50 61 63 6b 61 67 65 52 6f 6f 74   "PackageRoot" (odd)
     00 01 00 00 00 0a 00 00 00 00   b = INT8 10, starts immediately

2. A missing length on NULL. We returned as soon as the indicator said
   NULL, leaving its 4-byte length unread. The length belongs to the
   envelope and is always present:
     'odd' -> 00 | 00 00 00 03 | 6f 64 64    8 bytes
     NULL  -> 01 | 00 00 00 00               5 bytes
     ''    -> 00 | 00 00 00 00               5 bytes
   NULL and empty string differ only in the indicator byte.

Reported symptom: INT8 10 decoding as 2560 (the same value shifted one
byte left), strings losing their first character, and IndexError or
"INT8 payload too short" once the drift ran off the payload. The
reporter isolated it by reordering columns in the projection — right
when first, wrong when later. That's the fingerprint of positional
drift and a genuinely good diagnostic.

Missed by 247 tests because the only LVARCHAR fixture was 'lv value':
8 characters, even, never NULL. Neither faulty branch ever ran. Same
gap shape as last time — code paths covered, the data reaching them
not. New tests vary parity (0,1,2,3,11,255,256), cover NULL and empty
separately, always place a column AFTER the LVARCHAR, and rotate the
projection through every position.

Separately, DATETIME lost sub-second precision on INSERT. The encoder
emitted YEAR TO SECOND unconditionally, so binding a datetime with
microseconds into a FRACTION(n) column stored zeros, silently. Reads
were always right, so it only went missing on the way in. Now widens
to FRACTION(5) when microsecond is non-zero and keeps the original
encoding otherwise, so the well-exercised path stays byte-identical.
Binding into a narrower column still truncates server-side.

Also: server_version reported 9.56 for a 12.10 server, which reads like
a client-SDK version. The login response only carries the internal
protocol version, and documenting that didn't make the name less
misleading. server_version now returns the release via one cached
DBINFO query; server_version_internal returns the raw login string.

281/281 integration on 15, 14.10 and 12.10; 123 unit tests.
2026-08-31 14:26:17 -06:00
85d3a8f7d7 Docs site: correct APIs that don't exist, document what does
Auditing the docs site against the actual module surface — prompted by
the same field report as 2026.05.08.2 — turned up a partly fictional API.
Every claim below was checked by calling it, not by reading the source.

Documented but nonexistent, now removed:
  conn.transaction()        AttributeError (neither sync nor async)
  conn.autocommit           never existed; set via connect(autocommit=)
  cursor.lastrowid          never existed
  cursor.read_clob_column   never existed
  cursor.write_clob_column  never existed

A copy-pasteable example in reference/types.md raised TypeError:
IntervalYM takes a single total month count, not (years=, months=).

False descriptions corrected: RowValue was described as "named-tuple-like
with .name-accessible fields" and CollectionValue as "iterable" with
"indexed access". Neither is true — both are opaque wrappers over raw
bytes plus a schema string. The docs now say so and point at SQL
projection as the way to get fields today.

Replacements are the real idioms, taken from passing tests:
  CLOBs        -> write_blob_column(..., clob=True); read returns bytes
  SERIAL value -> SELECT DBINFO('sqlca.sqlerrd1')
  transactions -> commit()/rollback() in try/except

Also added: INT8/SERIAL8/BIGSERIAL to the type table (with why INT8 is
not BIGINT), server_capabilities + server_version, the scrollable-cursor
methods, and the note that server_version reports the internal protocol
version (12.10 says 9.56, 14.10 says 9.59).

Updated the test-count claim to 400+ across three server versions,
replacing "integration tests run against 15.0.1.0.3DE".

Added a checker pass over every conn.*/cursor.* reference in the docs;
the only remaining unresolved names are deliberate mentions of IfxPy
APIs we don't implement. Site builds clean.
2026-08-27 09:29:02 -06:00
2d83ed7b45 Wire-dump: lift overflow-x: auto to base rule (tablet-portrait fix)
Previously the horizontal-scroll fallback only applied at ≤640px; between
641px and 799px (tablet portrait, narrow desktop 2-col), the hero stayed
2-column so the wire-dump column could still be too narrow for the
~564px hex content, and overflow: hidden silently clipped the right side.

Lifting overflow-x: auto to all widths means: (a) any width where the
column is wider than the hex, content displays normally with no scroll;
(b) any width where the column is narrower, content becomes
horizontally-scrollable inside the dump. y stays hidden to keep the
typed-out animation's unrevealed lines clipped below the fold.
2026-05-08 06:07:43 -06:00
dc7b9bfd94 Mobile: fix hero overflow at ≤640px, shrink wire-dump, hide ASCII column
Root cause: hero's grid items inherited min-width: auto, so the wire-dump's
white-space: pre hex lines (~564px wide content) forced the hero column to
that width, propagating up to the page and causing 193px of horizontal
overflow at narrow viewports.

Fix:
- min-width: 0 on .ifx-hero, .ifx-hero__copy, .ifx-hero__visual (lets
  grid items shrink below content's intrinsic min-width)
- overflow-x: auto on .ifx-wiredump (contains residual hex overflow
  inside the dump, not on the page)
- Font shrunk 0.78rem → 0.62rem on mobile, ASCII column hidden, padding
  tightened — readable hex without horizontal scroll inside the dump
- Eyebrow flex-wrap: wrap so the No-libcrypt suffix wraps cleanly
- Title floor 2rem → 1.75rem at 7.5vw for narrow screens
- Tightened CTA padding, install command word-break: break-all
2026-05-08 06:01:47 -06:00
ab5be68738 Hero CTA: 'GitHub' button text → 'Source' (link already pointed at Gitea) 2026-05-08 05:44:11 -06:00
21c47385ae Prose rebrand: informix-db → informix-driver across docs site
Sweep all backticked + bold + table-cell + heading mentions of the
project's brand to match the PyPI distribution name and the docs domain.
Path references (`cd informix-db`, `git.supported.systems/.../informix-db`)
stay — those reference the actual Gitea repo directory which we did NOT
rename. Same with `import informix_db` (Python module name, separate
from distribution brand).

Also flip GitHub references to Gitea throughout the docs site:
- `github.com/rsp2k/informix-db/blob/main/X` → Gitea `/src/branch/main/X`
- `github.com/rsp2k/informix-db/tree/main/X` → Gitea same path
- `github.com/rsp2k/informix-db` (plain) → Gitea
- Hero "GitHub" CTA button → Gitea source URL
- Social icon: `github` → `seti:git` (generic git icon, not octocat)

Net result: zero stale GitHub references, brand consistency matches what
users `pip install`.
2026-05-08 05:43:07 -06:00
b67e6d008b Homepage frontmatter title: informix-db → informix-driver (browser tab consistency) 2026-05-08 05:01:22 -06:00
073c7ed513 Site header: informix-db → informix-driver
Match what users see when they `pip install informix-driver`. Also fix
two broken social links: PyPI was pointing at the now-404 informix-db
project, GitHub link was pointing at github.com/rsp2k/informix-db
(repo doesn't exist there — source lives on Gitea). Rewired to:

- Source (Gitea): git.supported.systems/warehack.ing/informix-db
- PyPI: pypi.org/project/informix-driver
- editLink: Gitea's _edit/branch/main URL pattern

Logo SVG aria-label + title also updated for accessibility consistency.
2026-05-08 05:00:36 -06:00
24feabd21b Rename PyPI distribution: informix-db → informix-driver
PyPI rejected `informix-db` as too similar to legacy `informixdb` (v2.5,
2008). Renamed distribution to `informix-driver`. Import name stays
`informix_db` — same separation Pillow uses with `import PIL`.

Updated:
- pyproject.toml [project].name
- README pip-install command + brief explanation note
- docs-site quickstart, vs-ifxpy, wtf, Hero install commands

First PyPI release: pypi.org/project/informix-driver/2026.5.8/
2026-05-08 04:17:57 -06:00
86070e4688 Add docs-site: Astro + Starlight at informix-db.warehack.ing
22 pages across Diataxis quadrants (start / how-to / reference / explain).
Custom amber-on-charcoal theme, wire-dump hero animation, Supported
Systems footer badge. caddy-docker-proxy deployment with prod + dev
profiles, Makefile with prod/dev/down/logs/local targets.
2026-05-08 03:23:22 -06:00