Cursor.execute now branches on DESCRIBE response's nfields:
- nfields > 0 → SELECT path (cursor lifecycle: CURNAME+NFETCH+...)
- nfields == 0 → DDL/DML path (just SQ_EXECUTE then SQ_RELEASE)
Examples that work end-to-end against the dev container:
cur.execute('CREATE TEMP TABLE t (id INTEGER, name VARCHAR(50))')
cur.execute("INSERT INTO t VALUES (1, 'hello')") # rowcount=1
cur.execute("UPDATE t SET name = 'new' WHERE id = 1")
cur.execute('DELETE FROM t WHERE id = 1')
Plus full mix: CREATE → 5 INSERTs → SELECT WHERE → DELETE WHERE → SELECT
(see tests/test_dml.py::test_full_dml_cycle_in_one_connection).
Three protocol findings during this push, documented in DECISION_LOG.md:
1. SQ_INSERTDONE (=94) is METADATA, not execution. It arrives in BOTH
the DESCRIBE response (PREPARE phase) AND the EXECUTE response for
literal-value INSERTs. The PREPARE-phase SQ_INSERTDONE carries the
serial values that WILL be assigned IF you execute. The EXECUTE-
phase SQ_INSERTDONE confirms execution. My initial assumption was
"PREPARE-phase INSERTDONE means already-executed" — wrong. Skipping
SQ_EXECUTE made the row not persist (SELECT returned []). Lesson:
optimization-looking responses may not be what they look like —
always verify with a follow-up SELECT.
2. SQ_INSERTDONE wire format: 18 bytes (10 byte longint serial8 + 8
byte bigint bigserial). Per IfxSqli.receiveInsertDone line 2347.
We read-and-discard for now; Phase 5+ surfaces as Cursor.lastrowid.
3. Transactions: commit() and rollback() are 2-byte messages.
SQ_CMMTWORK=19 + SQ_EOT for commit; SQ_RBWORK=20 + SQ_EOT for
rollback. Server responds with SQ_DONE+SQ_EOT in logged databases,
or SQ_ERR sqlcode=-255 ("Not in transaction") in unlogged databases
like sysmaster. Wire machinery is implemented; full transaction
testing needs a logged DB (use ``stores_demo`` from the dev image).
Module changes:
src/informix_db/cursors.py:
- execute() branches on nfields (SELECT path vs DDL/DML path)
- new _execute_dml() does just EXECUTE + RELEASE
- new _build_execute_pdu() emits the 8-byte SQ_ID(EXECUTE)+EOT
- _read_describe_response() and _drain_to_eot() handle SQ_INSERTDONE
src/informix_db/connections.py:
- commit() / rollback() now functional — send the SQ_CMMTWORK /
SQ_RBWORK PDU and drain the response
Tests: 40 unit + 24 integration (6 new DML tests) = 64 total, all
green, ruff clean. New tests cover:
- CREATE TEMP TABLE
- INSERT (rowcount=1, persists, SELECT shows it)
- UPDATE WHERE (specific row changed)
- DELETE WHERE (specific row removed)
- Full mixed cycle (CREATE + 5 INSERTs + SELECT + DELETE + SELECT)
- commit() in unlogged DB raises OperationalError sqlcode=-255
Captured wire artifacts kept for future debugging:
docs/CAPTURES/16-py-insert-literal.socat.log
docs/CAPTURES/17-py-insert-select.socat.log
71 lines
6.3 KiB
Plaintext
71 lines
6.3 KiB
Plaintext
2026/05/04 07:59:51 socat[386534] N listening on AF=2 0.0.0.0:9090
|
|
2026/05/04 07:59:51 socat[386534] N accepting connection from AF=2 127.0.0.1:43524 on AF=2 127.0.0.1:9090
|
|
2026/05/04 07:59:51 socat[386534] N opening connection to 127.0.0.1:9088
|
|
2026/05/04 07:59:51 socat[386534] N opening connection to AF=2 127.0.0.1:9088
|
|
2026/05/04 07:59:51 socat[386534] N successfully connected from local address AF=2 127.0.0.1:36412
|
|
2026/05/04 07:59:51 socat[386534] N successfully connected to 127.0.0.1:9088
|
|
2026/05/04 07:59:51 socat[386534] N starting data transfer loop with FDs [6,6] and [5,5]
|
|
> 2026/05/04 07:59:51.858392 length=384 from=0 to=383
|
|
01 80 01 3c 00 00 00 64 00 65 00 00 00 3d 00 06 49 45 45 45 4d 00 00 6c 73 71 6c 65 78 65 63 00 00 00 00 00 00 06 39 2e 32 38 30 00 00 0c 52 44 53 23 52 30 30 30 30 30 30 00 00 05 73 71 6c 69 00 00 00 01 3c 00 00 00 00 00 00 00 00 00 01 00 09 69 6e 66 6f 72 6d 69 78 00 00 07 69 6e 34 6d 69 78 00 6f 6c 00 00 00 00 00 00 00 00 00 3d 74 6c 69 74 63 70 00 00 00 00 00 01 00 68 00 0b 00 00 00 03 00 09 69 6e 66 6f 72 6d 69 78 00 00 00 00 00 00 00 00 00 00 00 00 6a 00 06 00 07 44 42 50 41 54 48 00 00 02 2e 00 00 0e 43 4c 49 45 4e 54 5f 4c 4f 43 41 4c 45 00 00 0d 65 6e 5f 55 53 2e 38 38 35 39 2d 31 00 00 11 43 4c 4e 54 5f 50 41 4d 5f 43 41 50 41 42 4c 45 00 00 02 31 00 00 07 44 42 44 41 54 45 00 00 06 59 34 4d 44 2d 00 00 0c 49 46 58 5f 55 50 44 44 45 53 43 00 00 02 31 00 00 09 4e 4f 44 45 46 44 41 43 00 00 03 6e 6f 00 00 6b 00 00 00 00 00 05 e5 f2 00 00 00 00 00 0b 72 70 6d 2d 62 75 6c 6c 65 74 00 00 00 00 29 2f 68 6f 6d 65 2f 72 70 6d 2f 63 6c 61 75 64 65 2f 69 6e 66 6f 72 6d 69 78 2f 70 79 74 68 6f 6e 2d 6c 69 62 72 61 72 79 00 00 74 00 20 00 00 00 00 00 00 00 00 00 16 69 6e 66 6f 72 6d 69 78 2d 64 62 40 70 69 64 33 38 36 35 34 36 00 00 7f
|
|
< 2026/05/04 07:59:51.870350 length=276 from=0 to=275
|
|
01 14 02 3c 10 00 00 64 00 65 00 00 00 3d 00 06 49 45 45 45 49 00 00 6c 73 72 76 69 6e 66 78 00 00 00 00 00 00 2f 49 42 4d 20 49 6e 66 6f 72 6d 69 78 20 44 79 6e 61 6d 69 63 20 53 65 72 76 65 72 20 56 65 72 73 69 6f 6e 20 31 35 2e 30 2e 31 2e 30 2e 33 00 00 07 73 65 72 69 61 6c 00 00 09 69 6e 66 6f 72 6d 69 78 00 00 00 01 3c 00 00 00 00 00 00 00 00 00 00 00 00 00 00 6f 6e 00 00 00 00 00 00 00 00 00 3d 73 6f 63 74 63 70 00 00 00 00 00 00 00 66 00 00 00 00 00 00 00 00 00 00 00 14 00 00 00 6b 00 00 00 00 00 00 03 1a 00 00 00 00 00 0d 32 33 32 37 63 34 33 35 34 65 61 38 00 00 00 00 0f 2f 68 6f 6d 65 2f 69 6e 66 6f 72 6d 69 78 00 00 6e 00 04 00 00 00 00 00 74 00 33 00 00 00 c8 00 00 00 c8 00 29 2f 6f 70 74 2f 69 62 6d 2f 69 6e 66 6f 72 6d 69 78 2f 76 31 35 2e 30 2e 31 2e 30 2e 33 2f 62 69 6e 2f 6f 6e 69 6e 69 74 00 00 7f
|
|
> 2026/05/04 07:59:51.870669 length=14 from=384 to=397
|
|
00 7e 00 08 ff fc 7f fc 3c 8c aa 97 00 0c
|
|
< 2026/05/04 07:59:51.870750 length=16 from=276 to=291
|
|
00 7e 00 09 bd be 9f fe 7f b7 ff ef ff 00 00 0c
|
|
> 2026/05/04 07:59:51.870820 length=48 from=398 to=445
|
|
00 51 00 06 00 26 00 0c 00 04 00 06 44 42 54 45 4d 50 00 04 2f 74 6d 70 00 0b 53 55 42 51 43 41 43 48 45 53 5a 00 00 02 31 30 00 00 00 00 00 0c
|
|
< 2026/05/04 07:59:51.870908 length=2 from=292 to=293
|
|
00 0c
|
|
> 2026/05/04 07:59:51.870956 length=18 from=446 to=463
|
|
00 24 00 09 73 79 73 6d 61 73 74 65 72 00 00 00 00 0c
|
|
< 2026/05/04 07:59:51.871136 length=28 from=294 to=321
|
|
00 0f 00 15 00 00 00 00 00 00 00 00 00 00 00 00 00 37 00 00 00 01 00 00 00 01 00 0c
|
|
> 2026/05/04 07:59:51.871182 length=66 from=464 to=529
|
|
00 02 00 00 00 00 00 33 43 52 45 41 54 45 20 54 45 4d 50 20 54 41 42 4c 45 20 74 31 20 28 69 64 20 49 4e 54 45 47 45 52 2c 20 6e 61 6d 65 20 56 41 52 43 48 41 52 28 35 30 29 29 00 00 16 00 31 00 0c
|
|
< 2026/05/04 07:59:51.871313 length=46 from=322 to=367
|
|
00 08 00 2d 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0f 00 10 00 00 00 00 00 00 00 00 00 00 00 00 00 37 00 00 00 01 00 00 00 01 00 0c
|
|
> 2026/05/04 07:59:51.871374 length=8 from=530 to=537
|
|
00 04 00 00 00 07 00 0c
|
|
< 2026/05/04 07:59:51.880065 length=28 from=368 to=395
|
|
00 0f 00 10 00 00 00 00 00 00 00 00 00 00 00 00 00 37 00 00 00 01 00 00 00 01 00 0c
|
|
> 2026/05/04 07:59:51.880164 length=8 from=538 to=545
|
|
00 04 00 00 00 0b 00 0c
|
|
< 2026/05/04 07:59:51.880243 length=2 from=396 to=397
|
|
00 0c
|
|
> 2026/05/04 07:59:51.880282 length=48 from=546 to=593
|
|
00 02 00 00 00 00 00 22 49 4e 53 45 52 54 20 49 4e 54 4f 20 74 31 20 56 41 4c 55 45 53 20 28 31 2c 20 27 68 65 6c 6c 6f 27 29 00 16 00 31 00 0c
|
|
< 2026/05/04 07:59:51.880440 length=66 from=398 to=463
|
|
00 08 00 06 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 5e 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0f 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 37 00 00 00 01 00 00 00 02 00 0c
|
|
> 2026/05/04 07:59:51.880669 length=8 from=594 to=601
|
|
00 04 00 00 00 0b 00 0c
|
|
< 2026/05/04 07:59:51.880736 length=2 from=464 to=465
|
|
00 0c
|
|
> 2026/05/04 07:59:51.880755 length=32 from=602 to=633
|
|
00 02 00 00 00 00 00 11 53 45 4c 45 43 54 20 69 64 20 46 52 4f 4d 20 74 31 00 00 16 00 31 00 0c
|
|
< 2026/05/04 07:59:51.880847 length=80 from=466 to=545
|
|
00 08 00 02 00 00 00 00 00 00 00 04 00 01 00 00 00 03 00 00 00 00 00 00 00 00 00 02 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 04 69 64 00 00 00 0f 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 37 00 00 00 01 00 00 00 02 00 0c
|
|
> 2026/05/04 07:59:51.880961 length=42 from=634 to=675
|
|
00 04 00 00 00 03 00 12 5f 69 66 78 63 30 30 30 30 30 30 30 30 30 30 30 30 31 00 06 00 04 00 00 00 09 00 00 10 00 00 00 00 0c
|
|
< 2026/05/04 07:59:51.881050 length=28 from=546 to=573
|
|
00 0f 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 37 00 00 00 01 00 00 00 02 00 0c
|
|
> 2026/05/04 07:59:51.881090 length=14 from=676 to=689
|
|
00 04 00 00 00 09 00 00 10 00 00 00 00 0c
|
|
< 2026/05/04 07:59:51.881130 length=28 from=574 to=601
|
|
00 0f 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 37 00 00 00 01 00 00 00 02 00 0c
|
|
> 2026/05/04 07:59:51.881169 length=8 from=690 to=697
|
|
00 04 00 00 00 0a 00 0c
|
|
< 2026/05/04 07:59:51.881207 length=2 from=602 to=603
|
|
00 0c
|
|
> 2026/05/04 07:59:51.881222 length=8 from=698 to=705
|
|
00 04 00 00 00 0b 00 0c
|
|
< 2026/05/04 07:59:51.881259 length=2 from=604 to=605
|
|
00 0c
|
|
> 2026/05/04 07:59:51.881282 length=2 from=706 to=707
|
|
00 38
|
|
< 2026/05/04 07:59:51.884736 length=2 from=606 to=607
|
|
00 38
|
|
2026/05/04 07:59:51 socat[386534] N socket 1 (fd 6) is at EOF
|
|
2026/05/04 07:59:51 socat[386534] N socket 2 (fd 5) is at EOF
|
|
2026/05/04 07:59:51 socat[386534] N exiting with status 0
|