Opt-in via conn.cursor(scrollable=True). Opens the cursor with
SQ_SCROLL (24) before SQ_OPEN (6), keeps it open server-side, and
sends SQ_SFETCH (23) per scroll call instead of materializing the
result set up-front.
User-facing API is identical to Phase 17's in-memory scroll
(fetch_first/last/prior/absolute/relative, scroll, rownumber).
Only the internal mechanism differs:
| feature | default | scrollable=True
|-------------------|------------------|------------------
| memory | all rows | one row at a time
| round-trips/fetch | 0 (after NFETCH) | 1 per call
| cursor lifetime | closed after exec| open until close()
| best for | sequential iter | random access on
| huge result sets
Wire format (verified against JDBC ScrollProbe capture):
* SQ_SFETCH: [short SQ_ID=4][int 23][short scrolltype]
[int target][int bufSize=4096][short SQ_EOT]
scrolltype: 1=NEXT, 4=LAST, 6=ABSOLUTE
* SQ_SCROLL (24): emitted between CURNAME and SQ_OPEN
* SQ_TUPID (25): response tag with 1-indexed row position;
authoritative source for client-side position tracking
Position tracking uses the server's SQ_TUPID rather than client-
computed indexes. Total row count discovered lazily via SFETCH(LAST)
when negative absolute indexing requires it; cached in
_scroll_total_rows.
Trap on the way: initial SFETCH used SHORT for bufSize → server
hung silently. Same SHORT-vs-INT diagnostic pattern as Phase 4.x's
CURNAME+NFETCH. Captured JDBC trace, byte-diffed against ours,
found the mismatch (bufSize is INT in modern Informix per
isXPSVER8_40 / is2GBFetchBufferSupported).
Tests: 14 integration tests in test_scroll_cursor_server.py
covering lifecycle, sequential fetch, fetch_first/last/prior/
absolute/relative, negative indexing, scroll, empty result sets,
past-end, and random-access on a 100-row result set.
Total: 69 unit + 191 integration = 260 tests.
221 lines
8.1 KiB
Python
221 lines
8.1 KiB
Python
"""SQLI wire-protocol message-type constants.
|
|
|
|
Two distinct namespaces live here:
|
|
|
|
* ``SQ_*`` — the post-login SQLI message-type tags from
|
|
``com.informix.jdbc.IfxMessageTypes``. Each one is a 16-bit big-endian
|
|
short on the wire. See ``docs/PROTOCOL_NOTES.md`` §5 for categorization
|
|
by purpose.
|
|
* ``ASF_*``, ``SL_*``, ``PF_*`` — the connection-establishment markers
|
|
used inside the binary login PDU body, defined locally in
|
|
``com.informix.asf.Connection`` (NOT in ``IfxMessageTypes``). These
|
|
also appear on the wire as 16-bit shorts but they're a separate
|
|
namespace whose values may overlap numerically with ``SQ_*`` constants.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from enum import IntEnum
|
|
|
|
|
|
class MessageType(IntEnum):
|
|
"""SQLI post-login message-type tags. Wire format: 2 bytes, big-endian."""
|
|
|
|
# --- Statement execution ---
|
|
SQ_COMMAND = 1
|
|
SQ_PREPARE = 2
|
|
SQ_CURNAME = 3
|
|
SQ_ID = 4
|
|
SQ_BIND = 5
|
|
SQ_OPEN = 6
|
|
SQ_EXECUTE = 7
|
|
SQ_DESCRIBE = 8
|
|
SQ_NFETCH = 9
|
|
SQ_CLOSE = 10
|
|
SQ_RELEASE = 11
|
|
SQ_NDESCRIBE = 22 # numerical describe — request column metadata after a PREPARE/COMMAND
|
|
SQ_WANTDONE = 49 # request a SQ_DONE completion notification
|
|
# Phase 18: server-side scrollable cursor.
|
|
SQ_SFETCH = 23 # scroll-fetch: ``[short SFETCH][short scrolltype]
|
|
# [int target][short bufSize]``. scrolltype values
|
|
# per JDBC IfxSqli.getaRow: 1=NEXT, 4=LAST, 6=ABSOLUTE.
|
|
SQ_SCROLL = 24 # cursor-open modifier — emitted *before* SQ_OPEN
|
|
# to mark the cursor as scrollable. Server keeps
|
|
# the result set materialized for random access.
|
|
SQ_TUPID = 25 # server response tag carrying the row's 1-indexed
|
|
# position. Body: ``[int tupleId]``. Sent before
|
|
# SQ_TUPLE in scrollable-cursor responses.
|
|
|
|
# --- Per-PDU framing ---
|
|
SQ_EOT = 12 # end-of-transmission / flush marker; ends every PDU
|
|
SQ_ERR = 13 # error response from the server
|
|
SQ_TUPLE = 14 # one row of result data
|
|
SQ_DONE = 15 # statement / result-set completion
|
|
SQ_XACTSTAT = 99 # transaction-state event (logged DBs only). Body:
|
|
# ``[short xcEvent][short xcNewLevel][short xcOldLevel]``. See
|
|
# ``IfxSqli.receiveXactstat`` and the Phase 7 DECISION_LOG entry.
|
|
|
|
# --- Transactions ---
|
|
SQ_CMMTWORK = 19
|
|
SQ_RBWORK = 20
|
|
SQ_SVPOINT = 21
|
|
SQ_BEGIN = 35
|
|
SQ_DBOPEN = 36
|
|
SQ_DBCLOSE = 37
|
|
SQ_DBLIST = 26
|
|
|
|
# --- Connection lifecycle (post-login) ---
|
|
SQ_VERSION = 53
|
|
SQ_EXIT = 56 # client-initiated session terminate (also returned as ack)
|
|
SQ_INFO = 81 # info request, with sub-codes (see ``InfoSubtype`` below)
|
|
SQ_CONNECT = 112
|
|
SQ_SETCONN = 113
|
|
SQ_DISCONNECT = 114
|
|
SQ_PROTOCOLS = 126
|
|
|
|
# --- Auth (server-initiated challenge/response, e.g. PAM) ---
|
|
SQ_ACCEPT = 127
|
|
SQ_ACK = 128
|
|
SQ_CHALLENGE = 129
|
|
SQ_RESPONSE = 130
|
|
|
|
# --- Savepoints (newer) ---
|
|
SQ_SQLISETSVPT = 137
|
|
SQ_SQLIRELSVPT = 138
|
|
SQ_SQLIRBACKSVPT = 139
|
|
|
|
# --- XA (distributed transactions) — Phase 6+ ---
|
|
SQ_XROLLBACK = 65
|
|
SQ_XCLOSE = 66
|
|
SQ_XCOMMIT = 67
|
|
SQ_XEND = 68
|
|
SQ_XFORGET = 69
|
|
SQ_XPREPARE = 70
|
|
SQ_XRECOVER = 71
|
|
SQ_XSTART = 72
|
|
SQ_XERR = 73
|
|
SQ_XASTATE = 74
|
|
SQ_XOPEN = 82
|
|
|
|
# --- BLOB / LOB ---
|
|
# Phase 8 (BYTE/TEXT in-row blobs)
|
|
SQ_FETCHBLOB = 38
|
|
SQ_BLOB = 39
|
|
SQ_BBIND = 41
|
|
SQ_SBBIND = 52
|
|
SQ_FILE_READ = 106
|
|
SQ_FILE_WRITE = 107
|
|
# Phase 9+ (smart-LOB BLOB/CLOB)
|
|
SQ_LODATA = 97 # smart-LOB data transfer with sub-commands:
|
|
# 0=LO_READ, 1=LO_READWITHSEEK, 2=LO_WRITE.
|
|
# Body: [short subCom][short loFd][int length]
|
|
# [short bufSize=32000] (+ [int8 offset][short whence]
|
|
# for LO_READWITHSEEK). See IfxSqli.sendLoData line 4864.
|
|
SQ_GETROUTINE = 101 # request a routine handle by signature.
|
|
# Body: [byte isRoutineById][int sigLen]
|
|
# [sig bytes][pad if odd][short fparamFlag].
|
|
# Response is the same tag with body
|
|
# [short dbNameLen][dbName][int handle].
|
|
SQ_EXFPROUTINE = 102 # execute a fast-path routine with bound
|
|
# params. Body: [char dbName][int handle]
|
|
# [short paramCount][short fparamFlag]
|
|
# [SQ_BIND-format params].
|
|
SQ_FPROUTINE = 103 # response tag: fast-path return-value descriptor.
|
|
# Body: [short numReturns] then per return:
|
|
# [short type][maybe UDT info][short ind]
|
|
# [short prec][data].
|
|
SQ_FPARAM = 104 # parameter metadata for SQ_FPROUTINE
|
|
|
|
# --- RPC sub-protocol (range 200-205) — Phase 6+ ---
|
|
SQ_INVOKE = 200
|
|
SQ_REPLY = 201
|
|
SQ_EXCEPTION = 202
|
|
SQ_VERSION_REQ = 203
|
|
SQ_VERSION_REPLY = 204
|
|
SQ_AMFPARAM = 205
|
|
|
|
|
|
class InfoSubtype(IntEnum):
|
|
"""Sub-codes for the ``SQ_INFO`` message body."""
|
|
|
|
INFO_DONE = 0
|
|
INFO_REQUEST = 1
|
|
INFO_VERSION = 2
|
|
INFO_TYPE = 3
|
|
INFO_CAPABILITY = 4
|
|
INFO_DB = 5
|
|
INFO_ENV = 6
|
|
|
|
|
|
class LoginMarker(IntEnum):
|
|
"""Login-PDU section markers from ``com.informix.asf.Connection``.
|
|
|
|
These appear inside the binary login PDU as 16-bit shorts and tag
|
|
the structural sub-blocks (association header, capability section,
|
|
env vars, process info, etc.). Numerically distinct from ``MessageType``;
|
|
do not mix the two.
|
|
|
|
See ``docs/PROTOCOL_NOTES.md`` §3 for the full byte-by-byte layout.
|
|
"""
|
|
|
|
SQ_ASSOC = 100 # start of "association" record
|
|
SQ_ASCBINARY = 101 # binary-format indicator
|
|
SQ_ASCINITRESP = 102 # init response (error block follows in some paths)
|
|
SQ_ASCDBLIST = 103
|
|
SQ_ASCINITREQ = 104 # init request marker (paired with ASF_XCONNECT=11)
|
|
SQ_ASCENV = 106 # environment-vars sub-block
|
|
SQ_ASCPINFO = 107 # process-info sub-block
|
|
SQ_ASCBPARMS = 108 # binary parameters block
|
|
SQ_ASSOCBIND = 110
|
|
SQ_ASSOCRESP = 111
|
|
SQ_ASCMISC_60 = 116 # misc / AppName section
|
|
SQ_ASCEOT = 127 # end-of-PDU marker for the login
|
|
|
|
|
|
class SLHeader(IntEnum):
|
|
"""SL header constants for the 6-byte login envelope."""
|
|
|
|
# slType byte (offset 2 of SLheader)
|
|
SLTYPE_CONREQ = 1 # client → server: connection request
|
|
SLTYPE_CONACC = 2 # server → client: connection accepted
|
|
SLTYPE_CONREJ = 3 # server → client: connection rejected
|
|
SLTYPE_REDIRECT = 13 # server → client: redirect to another node
|
|
|
|
# slAttribute byte (offset 3 of SLheader): protocol version
|
|
PF_PROT_SQLI_0600 = 60 # SQLI protocol family 6.00 (current)
|
|
PF_PROT_SQLI_WITH_CSS = 61 # variant with column-storage support
|
|
|
|
|
|
class StmtOptions(IntEnum):
|
|
"""Bit flags packed into the login-PDU stmtoptions int."""
|
|
|
|
ASF_AMBIG_SEOL = 3 # bits 0+1 (always set)
|
|
ASF_GRPREF = 0x02000000 # bit 25 — sqlhosts group reference
|
|
ASF_TRUSTCTXT = 0x04000000 # bit 26 — trusted context
|
|
|
|
|
|
# Hardcoded constant strings emitted in the binary login PDU.
|
|
# These are NOT free parameters — they identify our wire client to the server
|
|
# and must match what IBM's JDBC driver sends, byte-for-byte.
|
|
|
|
APPL_TYPE = b"sqlexec\x00\x00\x00\x00\x00" # 12 bytes, fixed-width nul-padded
|
|
APPL_ID = b"sqli" # 4 bytes (length-prefix says 5 because of trailing nul)
|
|
PROT_SQLIOL = b"ol\x00\x00\x00\x00\x00\x00" # 8 bytes; SQLI/OL protocol identifier
|
|
NET_TLITCP = b"tlitcp\x00\x00" # 8 bytes; "TLI over TCP" network type
|
|
FLOAT_TYPE = b"IEEEM" # 5 bytes; declares we want IEEE 754 float encoding
|
|
CLIENT_VERSION = b"9.280" # 5 bytes; hardcoded by JDBC, NOT a real version
|
|
CLIENT_SERIAL = b"RDS#R000000" # 11 bytes; hardcoded marketing/license artifact
|
|
|
|
# Buffer / size constants from com.informix.asf.Connection
|
|
MAX_BUFF_SIZE = 32768
|
|
MIN_BUFF_SIZE = 140
|
|
STREAM_BUF_SIZE = 4096
|
|
PFCONREQ_BUF_SIZE = 2048
|
|
SL_HEADER_SIZE = 6
|
|
|
|
# UTYPE_INTERNET — declares we're connecting over IP (vs. shared-memory etc.)
|
|
UTYPE_INTERNET = 1
|
|
|
|
# ASF_XCONNECT — paired with SQ_ASCINITREQ in the login PDU init-request marker
|
|
ASF_XCONNECT = 11
|