Before:
cur.execute('SELECT COUNT(*) FROM systables')
cur.fetchone() # → (b'\xc2\x02\x00\x00\x00\x00\x00\x00\x00',) raw bytes
After:
cur.execute('SELECT COUNT(*) FROM systables')
cur.fetchone() # → (Decimal('276'),)
The trickiest decode of the project so far. IDS DECIMAL/MONEY wire format:
byte[0] = (sign << 7) | biased_exponent_base100
bit 7 = sign (1=positive, 0=negative)
bits 0-6 = (exponent + 64), XOR'd with 0x7F if negative
byte[1..] = digit-pair bytes (each 0..99 = two BCD digits)
if negative: asymmetric base-100 complement applied:
walk digits right→left, trailing zeros stay zero,
first non-zero subtracts from 100, rest from 99
Initial naive "99 - d for all digits" decoder gave artifacts like
-1234.559999 instead of -1234.56. The asymmetric complement rule
(from Decimal.decComplement line 447) is what makes negatives
round-trip exactly.
Width on the wire: per-column encoded_length packed as
(precision << 8) | scale; byte width = ceil(precision/2) + 1.
parse_tuple_payload uses this to slice DECIMAL columns correctly.
Tested cases all decode correctly:
COUNT(*) → Decimal('276')
SUM(tabid) → Decimal('55')
AVG(tabid) → Decimal('5.5')
1234.56::DECIMAL → Decimal('1234.56')
-1234.56::DECIMAL → Decimal('-1234.56')
-0.5::DECIMAL → Decimal('-0.5')
-99.99::DECIMAL → Decimal('-99.99')
-12345678.9::DECIMAL → Decimal('-12345678.9')
NULL → None
Encoder (_encode_decimal) is implemented but disabled — server rejects
the produced bytes (precision packing not quite right). Phase 6.x will
fix. Workaround: cast Decimal to float, or pass via SQL literal.
Module changes:
src/informix_db/converters.py:
+ decimal module import
+ _decode_decimal — full BCD decoder with asymmetric complement
+ _encode_decimal (Phase 6.x stub — present but unreached)
+ DECIMAL/MONEY added to DECODERS dispatch
src/informix_db/_resultset.py:
+ DECIMAL/MONEY width computation from encoded_length
Tests: 40 unit + 55 integration (8 new DECIMAL) = 95 total, all
green, ruff clean.
51 lines
5.7 KiB
Plaintext
51 lines
5.7 KiB
Plaintext
2026/05/04 11:14:20 socat[852378] N listening on AF=2 0.0.0.0:9090
|
|
2026/05/04 11:14:20 socat[852378] N accepting connection from AF=2 127.0.0.1:52034 on AF=2 127.0.0.1:9090
|
|
2026/05/04 11:14:20 socat[852378] N opening connection to 127.0.0.1:9088
|
|
2026/05/04 11:14:20 socat[852378] N opening connection to AF=2 127.0.0.1:9088
|
|
2026/05/04 11:14:20 socat[852378] N successfully connected from local address AF=2 127.0.0.1:60064
|
|
2026/05/04 11:14:20 socat[852378] N successfully connected to 127.0.0.1:9088
|
|
2026/05/04 11:14:20 socat[852378] N starting data transfer loop with FDs [6,6] and [5,5]
|
|
> 2026/05/04 11:14:20.597948 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 0d 01 bc 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 38 35 32 34 31 32 00 00 7f
|
|
< 2026/05/04 11:14:20.609620 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 11:14:20.609849 length=14 from=384 to=397
|
|
00 7e 00 08 ff fc 7f fc 3c 8c aa 97 00 0c
|
|
< 2026/05/04 11:14:20.609915 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 11:14:20.609941 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 11:14:20.610015 length=2 from=292 to=293
|
|
00 0c
|
|
> 2026/05/04 11:14:20.610030 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 11:14:20.610194 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 11:14:20.610232 length=140 from=464 to=603
|
|
00 02 00 00 00 00 00 7e 53 45 4c 45 43 54 20 31 32 33 34 2e 35 36 3a 3a 44 45 43 49 4d 41 4c 28 31 30 2c 32 29 2c 20 2d 31 32 33 34 2e 35 36 3a 3a 44 45 43 49 4d 41 4c 28 31 30 2c 32 29 2c 20 30 2e 35 3a 3a 44 45 43 49 4d 41 4c 28 31 30 2c 32 29 2c 20 2d 30 2e 35 3a 3a 44 45 43 49 4d 41 4c 28 31 30 2c 32 29 20 46 52 4f 4d 20 73 79 73 74 61 62 6c 65 73 20 57 48 45 52 45 20 74 61 62 69 64 20 3d 20 31 00 16 00 31 00 0c
|
|
< 2026/05/04 11:14:20.610502 length=210 from=322 to=531
|
|
00 08 00 02 00 00 00 00 00 00 00 18 00 04 00 00 00 2c 00 00 00 00 00 00 00 00 00 05 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0a 02 00 00 00 0b 00 00 00 06 00 05 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0a 02 00 00 00 16 00 00 00 0c 00 05 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0a 02 00 00 00 21 00 00 00 12 00 05 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0a 02 28 63 6f 6e 73 74 61 6e 74 29 00 28 63 6f 6e 73 74 61 6e 74 29 00 28 63 6f 6e 73 74 61 6e 74 29 00 28 63 6f 6e 73 74 61 6e 74 29 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 01 00 0c
|
|
> 2026/05/04 11:14:20.610703 length=42 from=604 to=645
|
|
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 11:14:20.610790 length=60 from=532 to=591
|
|
00 0e 00 00 00 00 00 18 c2 0c 22 38 00 00 3d 57 41 2c 00 00 c0 32 00 00 00 00 3f 32 00 00 00 00 00 0f 00 00 00 00 00 01 00 00 03 01 00 00 00 00 00 37 00 00 00 01 00 00 00 01 00 0c
|
|
> 2026/05/04 11:14:20.610864 length=14 from=646 to=659
|
|
00 04 00 00 00 09 00 00 10 00 00 00 00 0c
|
|
< 2026/05/04 11:14:20.610900 length=28 from=592 to=619
|
|
00 0f 00 00 00 00 00 01 00 00 03 01 00 00 00 00 00 37 00 00 00 01 00 00 00 01 00 0c
|
|
> 2026/05/04 11:14:20.610934 length=8 from=660 to=667
|
|
00 04 00 00 00 0a 00 0c
|
|
< 2026/05/04 11:14:20.610971 length=2 from=620 to=621
|
|
00 0c
|
|
> 2026/05/04 11:14:20.610985 length=8 from=668 to=675
|
|
00 04 00 00 00 0b 00 0c
|
|
< 2026/05/04 11:14:20.611019 length=2 from=622 to=623
|
|
00 0c
|
|
> 2026/05/04 11:14:20.611042 length=2 from=676 to=677
|
|
00 38
|
|
< 2026/05/04 11:14:20.611086 length=2 from=624 to=625
|
|
00 38
|
|
2026/05/04 11:14:20 socat[852378] N socket 1 (fd 6) is at EOF
|
|
2026/05/04 11:14:20 socat[852378] N socket 2 (fd 5) is at EOF
|
|
2026/05/04 11:14:20 socat[852378] N exiting with status 0
|