JET Wire Protocol — Specification

A PostgreSQL-compatible wire protocol for the MS Access / Jet thin TCP endpoint, incorporating MariaDB features where the Jet engine supports them.

1. Design Philosophy

The JET wire protocol adopts PostgreSQL's v3 message-oriented structure as its foundation: every message is a single type byte followed by a 4-byte big-endian length (including the length word itself), then the payload. This framing is simple, self-describing, and used identically by both sides — client to server and server to client.

Where PostgreSQL's protocol defines features the Jet/ACE engine cannot support (parse trees, portals, COPY, LISTEN/NOTIFY, logical replication), those message types are omitted or repurposed. Where MariaDB's protocol adds capabilities that are relevant to Jet (multi-statement batches, connection-level statistics, mid-session user switching, compression), those are incorporated as extension messages.

Golden rule: The protocol defines what goes over the wire. The worker thread maps protocol messages to DAO calls. The Jet engine never sees the protocol — it only sees SQL strings and Recordset/Execute operations via DAO.

2. Message Framing

Byte Content ───── ─────── 0 Message type (single ASCII byte or extended byte) 1..4 Length: total message length in bytes, including this 4-byte length word (big-endian int32). Minimum value is 4. 5..N-1 Payload (length - 4 bytes) The length includes the 4-byte length word itself. A message with length=4 is an empty message (no payload).

2.1 Message Type Space

PostgreSQL uses ASCII characters as message type identifiers. JET follows the same convention, with the letter J (0x4A) reserved as a prefix byte for JET-specific extension messages that have no PostgreSQL equivalent.

RangeUsage
A..Z, a..zStandard messages (compatible with PostgreSQL v3 where applicable)
J + second byteJET extension messages (two-byte type: J prefix + subtype). Examples: JS = Statistics, JU = ChangeUser, JM = MultiStatement

2.2 Multi-Packet Messages (MySQL Borrowing)

PostgreSQL assumes a single message fits in one TCP segment (practical limit ~1 GB with int32 length). MySQL's protocol handles >16 MB payloads by splitting across packets with a sequence number. JET adopts PostgreSQL's single-message model — the int32 length field allows up to 2 GB per message, more than sufficient for Jet/ACE result sets. No multi-packet splitting is needed.

3. Connection Lifecycle

Client Server │ │ │──── TCP connect ────────────────────>│ │ │ │ (optional) │ │──── SSLRequest ────────────────────>│ │<──── 'S' (yes) or 'N' (no) ────────│ │──── TLS handshake ─────────────────>│ │ │ │──── StartupMessage ────────────────>│ │ (user, database, options) │ │ │ │<──── AuthenticationRequest ────────│ │──── AuthenticationResponse ────────>│ │ (may repeat for SASL) │ │ │ │<──── AuthenticationOk ─────────────│ │<──── ParameterStatus* ─────────────│ (server_version, │<──── BackendKeyData ───────────────│ server_encoding, │<──── ReadyForQuery ────────────────│ jet_version, etc.) │ │ │──── Query / Parse+Bind+Execute ────>│ │<──── results / ReadyForQuery ──────│ │ ... (loop) ... │ │ │ │──── Terminate ─────────────────────>│ │ (socket closes) │

3.1 SSL Negotiation

Identical to PostgreSQL. The client sends an 8-byte SSLRequest message (int32 length=8, int32 magic=80877103). The server responds with S to upgrade or N to refuse. On S, a TLS handshake begins immediately.

3.2 StartupMessage

Identical to PostgreSQL's v3 StartupMessage. Protocol version 3.0 (int32 = 196608). Key-value pairs follow: user (required), database (path to .accdb/.mdb file, required), dbpassword (optional — .accdb/.mdb file password, tried only if the database is password-protected), options (Jet-specific: --jet-exclusive=false, --jet-systemdb=path).

Int32 Protocol version: 196608 (3.0) String "user" '\0' "username" '\0' String "database" '\0' "C:\data\myapp.accdb" '\0' String "dbpassword" '\0' "file-password" '\0' (optional) String ... additional parameters ... '\0' Terminator byte

3.3 Authentication

JET supports three authentication methods, using PostgreSQL's AuthenticationRequest message format (int32 type code):

CodeMethodDAO Mapping
0AuthenticationOkProceed to ReadyForQuery
3AuthenticationCleartextPasswordClient sends password in cleartext; server validates against workspace user list or Jet system database
10AuthenticationSASL (SCRAM-SHA-256)Full SCRAM exchange (same as PG). Server stores SCRAM verifiers per user.

Jet/ACE's native user-level security (workgroup file, user/group permissions) maps to the wire protocol's user identity. The Workspace is created with that user name:

' DAO equivalent in worker thread Set workspace = DBEngine.CreateWorkspace( _ "Session_" & threadId, _ startup_user, _ password_from_auth, _ dbUseJet) Set database = workspace.OpenDatabase(startup_database)

3.4 ParameterStatus

After authentication, the server sends ParameterStatus messages for server metadata. Standard PG keys plus JET-specific keys:

KeyValue
server_version"16.0.0"
server_version_num"160000"
server_encoding"UTF8"
client_encoding"UTF8"
DateStyle"ISO, MDY"
integer_datetimes"on"
standard_conforming_strings"on"
jet_version"ACE 16.0.0"
jet_databaseFull path to the open .accdb/.mdb file
jet_collating_orderActive collating order (e.g. "General", "General_CI_AS")
jet_ansi_mode"on" or "off" (ANSI-92 Query Mode)

4. Simple Query Protocol

Identical to PostgreSQL's simple query flow: client sends Query, server executes it, sends back result sets and ReadyForQuery. A single Query message may contain multiple SQL statements separated by semicolons — the server processes each in sequence, sending a result descriptor + rows (or CommandComplete) for each, then a single ReadyForQuery at the end.

4.1 Query Execution Flow

Client Server │ │ │──── Query (SQL string) ────────────>│ │ │ │ For each result set: │ │<──── RowDescription ───────────────│ Column metadata │<──── DataRow* ─────────────────────│ Zero or more rows │<──── CommandComplete ──────────────│ "SELECT N" or "INSERT 0 M" │ │ │ Or on error: │ │<──── ErrorResponse ────────────────│ │ │ │<──── ReadyForQuery ────────────────│ Transaction state: I/T/E

4.2 SQL Dialect

The JET endpoint accepts Jet SQL (Access SQL). The worker does not translate between dialects. A PostgreSQL psql client connecting to this endpoint must send Jet SQL, not PostgreSQL SQL. See §8 for schema introspection helpers that bridge the gap.

⚠ Jet SQL differences vs PostgreSQL — DAO-imposed restrictions:

4.3 Result Set Messages — DAO Mappings

PG MessageContentDAO Source
RowDescription (0x54)Int16 column count, then per col: name (string), table OID (0), column attr (int16), type OID (int32), type size (int16), type modifier (int32), format code (int16=0 for text)rs.Fields(i).Name, rs.Fields(i).Type mapped to PG OID (see §6), rs.Fields(i).Size
DataRow (0x44)Int16 column count, then per col: int32 length + value (or -1 for NULL)rs.Fields(i).Value formatted as text; IsNull(rs.Fields(i).Value) → length = -1
CommandComplete (0x43)Tag string: "SELECT N" or "INSERT 0 M" etc.rs.RecordCount (for SELECT), database.RecordsAffected (for DML)
EmptyQueryResponse (0x49)Sent when the SQL string is empty
ReadyForQuery (0x5A)1-byte transaction state: I (idle), T (in transaction), E (failed)Tracked from BeginTrans/CommitTrans/Rollback state

RowDescription — Detailed Format

Int16 Number of columns (N) ── For each column (N times): String Column name (null-terminated) Int32 Table OID (always 0 — Jet has no system OIDs) Int16 Column attribute number (always 0) Int32 Type OID (mapped from DAO DataTypeEnum, see §6) Int16 Type size (from rs.Fields(i).Size; -1 for variable-length) Int32 Type modifier (-1 for none) Int16 Format code (0 = text, 1 = binary; always 0 in simple protocol)

DataRow — Detailed Format

Int16 Number of columns (must match RowDescription) ── For each column: Int32 Value length in bytes (-1 for NULL) Byte[N] Value as UTF-8 text (absent if length = -1)

5. Extended Query Protocol

PostgreSQL's extended query protocol uses a three-phase model: Parse (prepare a statement), Bind (supply parameters), Execute (run it). DAO supports this through its QueryDef object, which is a saved or temporary parameterized query. JET maps the PG extended protocol onto QueryDef operations.

Client Server (DAO) │ │ │ Prepare phase: │ │──── Parse (stmt_name, sql, types) ─>│ │ qd = database.CreateQueryDef("") │ qd.SQL = sql │ stmt_cache[stmt_name] = qd │<──── ParseComplete ────────────────│ │ │ │ Bind phase: │ │──── Bind (portal, stmt_name, │ │ param_formats, params, │ │ result_formats) ──────────>│ │<──── BindComplete ─────────────────│ │ │ │ Describe phase (optional): │ │──── Describe (portal) ─────────────>│ │ For each param in qd.Parameters: │ create temp rs with WHERE 1=0 │ to get field types │<──── RowDescription ───────────────│ │ │ │ Execute phase: │ │──── Execute (portal, max_rows) ────>│ │ qd.Parameters(i).Value = param │ rs = qd.OpenRecordset() │ // or qd.Execute for action queries │<──── DataRow* ─────────────────────│ │<──── CommandComplete ──────────────│ │<──── PortalSuspended ──────────────│ (if more rows exist) │ │ │ Close / cleanup: │ │──── Close (portal/statement) ──────>│ │ rs.Close(); qd.Close() │<──── CloseComplete ────────────────│ │ │ │──── Sync ──────────────────────────>│ │<──── ReadyForQuery ────────────────│

5.1 Statement Names and Portals

Statement name maps to a DAO QueryDef object. An unnamed statement ("") creates a temporary QueryDef scoped to the current parse cycle. Named statements persist in a per-connection statement cache — the DAO equivalent of PostgreSQL's prepared statement hash table.

Portal maps to an open DAO Recordset. The portal name identifies an in-progress cursor. Only a single portal is active at a time (matching DAO's limitation — a QueryDef can only have one open Recordset).

5.2 Parameter Binding

DAO QueryDefs support named parameters using the PARAMETERS declaration or positional ? placeholders. JET uses the MariaDB-style positional parameter model (matching ? placeholders) rather than PostgreSQL's $1 syntax, because Jet SQL uses ?:

' Jet SQL parameter syntax SELECT * FROM Customers WHERE City = ? AND Balance > ? ' Mapped via DAO: Set qd = database.CreateQueryDef("") qd.SQL = "SELECT * FROM Customers WHERE City = ? AND Balance > ?" qd.Parameters(0).Value = "London" qd.Parameters(1).Value = 500 Set rs = qd.OpenRecordset()

Unlike PostgreSQL's extended query protocol, which sends parameter types in the Parse message, JET infers parameter types from the SQL (the Jet engine does this internally when the QueryDef is created). The client may still send type OIDs in the Parse message for forward compatibility, but the server ignores them.

5.3 Extended Query Messages — DAO Mappings

MessageTypeDAO Action
ParsePdatabase.CreateQueryDef("") → set .SQL = sql → store in cache
BindBSet qd.Parameters(i).Value for each bound parameter
Describe (S)DOpen a dummy Recordset (WHERE 1=0) to get column metadata without fetching rows
Describe (P)DReturn RowDescription for the portal's current result shape
ExecuteEqd.OpenRecordset() (SELECT) or qd.Execute() (DML)
Close (S)Cqd.Close() — release cached QueryDef
Close (P)Crs.Close() — release portal Recordset
SyncSCommit any pending work; send ReadyForQuery
FlushHFlush server output buffer to client (identical to PG)

6. Type Mapping — DAO to PostgreSQL OIDs

The protocol uses PostgreSQL OIDs in RowDescription messages so that standard PG clients can interpret column types. The worker thread maps DAO DataTypeEnum values to the closest PG type OID.

DAO DataTypeEnumValuePG OIDPG Type NameNotes
dbBoolean116bool
dbByte221int20–255
dbInteger323int4±2^15 (Access Integer = 16-bit)
dbLong423int4±2^31 (Access Long Integer = 32-bit)
dbCurrency5790moneyFixed-point, 4 decimal places
dbSingle6700float4
dbDouble7701float8
dbDate81082date
dbTime221083time
dbBinary917byteaHex-encoded in text mode
dbText1025text
dbMemo1225textMemo fields map to text (PG has no 64K limit)
dbLongBinary1117byteaOLE Object / Attachment binary
dbGUID152950uuidReplication ID / GUID
dbBigInt1620int8ACE only (Access 2007+)
dbDecimal201700numericACE only
dbVarBinary20417byteaACE only
dbDateTimeExt1011114timestampACE extended datetime

7. JET Extension Messages (MariaDB-Inspired)

These messages use the J prefix byte followed by a subtype character. They have no PostgreSQL equivalent but add capabilities relevant to the Jet engine or to database administration.

7.1 JET_STATISTICS — Server Statistics

Inspired by: MariaDB's COM_STATISTICS. Returns server status information.

Client → Server: 'JS' (2 bytes: J-prefix + subtype) Server → Client: DataRow message with these columns: Field Value ───── ───── jet_version ACE version string database_path Full path to open database database_size File size in bytes connection_count Active connections uptime_seconds Server uptime workspace_count Active DAO Workspace objects recordset_count Open DAO Recordset objects total_queries Queries executed since startup total_errors Errors since startup jet_locks_held Active Jet page locks

7.2 JET_CHANGE_USER — Mid-Session User Switch

Inspired by: MariaDB's COM_CHANGE_USER. Resets the connection state and re-authenticates as a different user, without closing the TCP connection.

Client → Server: 'JU' + payload: String New username String New password (or empty for re-auth challenge) String New database path (or empty to keep current) Server → Client: AuthenticationRequest (re-enters auth flow) → AuthenticationOk → ReadyForQuery Effect: The worker thread closes the current Workspace and Database, calls CoUninitialize/CoInitialize to reset COM state, creates a new Workspace as the new user, opens the specified (or same) database. All cached QueryDefs and open Recordsets are discarded.

7.3 JET_MULTI_STATEMENT — Multi-Statement Mode

Inspired by: MariaDB's COM_SET_OPTION with MYSQL_OPTION_MULTI_STATEMENTS_ON. Toggles whether a single Query message may contain multiple SQL statements separated by semicolons.

Client → Server: 'JM' + 1 byte: 0x01 = enable multi-statement mode 0x00 = disable multi-statement mode (default: enabled) Server → Client: CommandComplete ("SET") When disabled: only the first statement in a Query message is executed. Surplus statements are silently ignored. When enabled (default): all statements are executed in order, each producing its own result set sequence.

7.4 JET_BEGIN_TRANS, JET_COMMIT, JET_ROLLBACK — Explicit Transaction Control

While PostgreSQL and MariaDB accept BEGIN/COMMIT/ROLLBACK as SQL statements, Jet SQL has its own transaction syntax (BeginTrans/CommitTrans/Rollback in DAO, or BEGIN TRANSACTION in Jet SQL). JET provides these as protocol-level messages for completeness and to avoid dialect confusion.

Client → Server: 'JB' = BeginTrans 'JC' = CommitTrans 'JR' = Rollback Server → Client: CommandComplete ("BEGIN" / "COMMIT" / "ROLLBACK") DAO equivalent: workspace.BeginTrans() workspace.CommitTrans() workspace.Rollback() Nesting: Jet supports up to 5 nested transaction levels. Each BeginTrans increments the level; CommitTrans/Rollback decrements. ReadyForQuery reports 'T' while in transaction, 'I' when idle.

7.5 JET_SET_DATABASE — Switch Database

Inspired by: PostgreSQL's no mid-session database switch (must reconnect) vs. MySQL's USE database. JET provides this as a protocol message since Jet/ACE can have multiple open databases per workspace.

Client → Server: 'JD' + database_path (string) Server → Client: CommandComplete ("SET") Effect: The worker opens the new database on the current workspace. The previous database is NOT closed — Jet allows multiple open databases. Subsequent queries execute against the newly-set default database. ParameterStatus "jet_database" is updated.

7.6 JET_PING — Connection Keepalive

Inspired by: MariaDB's COM_PING. Checks if the connection is alive without executing a query.

Client → Server: 'JP' Server → Client: CommandComplete ("PING") If the server process is alive but the Jet engine is unresponsive (e.g. database corruption), an ErrorResponse is returned instead.

7.7 JET_COMPRESS — Compression Toggle

Inspired by: MariaDB's compressed protocol (mysql_compress packet header). Toggles zlib compression on the connection.

Client → Server: 'JZ' + 1 byte: 0x01 = enable compression 0x00 = disable compression Server → Client: CommandComplete ("SET") (if accepted; ErrorResponse if rejected, e.g. already compressed) When enabled, all subsequent protocol messages are wrapped in a compressed frame: 4-byte: compressed payload length 4-byte: uncompressed payload length (0 if not compressed) N-byte: compressed or uncompressed payload A compressed frame may contain multiple protocol messages. Compression is per-message-direction: client compression and server compression are negotiated independently.
Client responsibility: After sending a JET_COMPRESS toggle, the client must read and consume the server's CommandComplete response before enabling local compression and before sending any further messages. Sending data to a server whose compression state has changed without first draining the acknowledgement desynchronises the protocol — the server's CommandComplete sits unread on the socket and the next client message lands in the wrong compression context.

8. Schema Introspection — INFORMATION_SCHEMA Bridge

PostgreSQL clients expect to discover schema by querying information_schema or pg_catalog. Jet/ACE has no equivalent system catalog. However, DAO provides collections that expose the same data. The worker thread intercepts specific SQL patterns and returns results from DAO collections rather than executing the SQL against Jet.

8.1 Intercepted Queries

Client SQLWorker Action (DAO)
SELECT * FROM information_schema.tables WHERE table_schema = 'public'Iterate database.TableDefs; filter out system tables (names starting with MSys or ~); return name, table_type
SELECT * FROM information_schema.columns WHERE table_name = 'X'Iterate database.TableDefs("X").Fields; return name, data_type, character_maximum_length, is_nullable, ordinal_position
SELECT * FROM pg_catalog.pg_tablesSame as information_schema.tables above
SELECT * FROM pg_catalog.pg_classReturn minimal stub: only entries for user tables, with relname, relkind='r'
SELECT * FROM pg_catalog.pg_attribute WHERE attrelid = ...Return column metadata from TableDef.Fields
SELECT * FROM pg_catalog.pg_typeReturn the type mapping table from §6 above as a static result set
SELECT current_database()Return the database path from the open Database object
SELECT version()Return the jet_version ParameterStatus string
SHOW search_path / SHOW all / any SHOWReturn ParameterStatus values (PG-compatible SHOW)
SELECT * FROM MSysObjects WHERE type=1Pass through to Jet (direct system table access if permissions allow)

8.2 QueryDefs as "Views"

DAO's QueryDefs collection contains saved SELECT queries, action queries, and parameter queries. These are exposed through information_schema.views and can be queried like tables:

' DAO exposes saved queries as QueryDefs For Each qd In database.QueryDefs If (qd.Attributes And dbQueryDefHidden) = 0 Then ' Expose as a view in information_schema.views End If Next

9. Error Handling

JET uses PostgreSQL's ErrorResponse and NoticeResponse message formats. The field type codes are identical to PG's.

Field CodeMeaningSource
SSeverity"ERROR", "FATAL", "PANIC"
CCode (SQLSTATE)Mapped from Jet error numbers (see below)
MMessageErr.Description from DAO Errors collection
DDetailAdditional DAO error info (if multiple errors in collection)
HHintServer-generated hint (e.g. "Check that the table exists")
PPositionCharacter position in the SQL where the error was detected
RRoutineDAO method that triggered the error (e.g. "OpenRecordset")

Jet Error → SQLSTATE Mapping

Jet Error Description SQLSTATE ────────── ─────────── ──────── 3010 Table already exists 42P07 (duplicate_table) 3011 Could not find object 42P01 (undefined_table) 3013 Duplicate index 42P10 (duplicate_column) 3022 Duplicate primary key 23505 (unique_violation) 3078 Table in use 55P03 (lock_not_available) 3085 Invalid field name 42703 (undefined_column) 3376 Table not found 42P01 (undefined_table) 3420 Invalid data type 42804 (datatype_mismatch) 3464 Data type mismatch 42804 (datatype_mismatch)

10. Message Type Registry — Complete List

TypeNameDirectionDescription
Connection Phase
SSLRequestC→S8-byte SSL negotiation request
StartupMessageC→SProtocol version + key-value params
RAuthenticationRequestS→CAuth method + challenge data
pAuthenticationResponseC→SPassword / SASL response
SParameterStatusS→CServer parameter key=value
KBackendKeyDataS→CProcess ID + secret key
ZReadyForQueryS→CTransaction state byte (I/T/E)
Simple Query
QQueryC→SSQL string
TRowDescriptionS→CColumn metadata for result set
DDataRowS→COne row of data
CCommandCompleteS→CCompletion tag
IEmptyQueryResponseS→CEmpty SQL string received
Extended Query
PParseC→SStatement name + SQL + param types
BBindC→SPortal + statement + params + formats
EExecuteC→SPortal + max rows
DDescribeC→SPortal or statement descriptor request
CCloseC→SClose portal or statement
SSyncC→SCommit pending; return to ready state
HFlushC→SFlush server output buffer
1ParseCompleteS→C
2BindCompleteS→C
3CloseCompleteS→C
sPortalSuspendedS→CExecute reached max_rows before EOF
nNoDataS→CDescribe on a non-SELECT statement
Error & Notice
EErrorResponseS→CError with field codes
NNoticeResponseS→CWarning/info with field codes
Copy (Stub — not supported by Jet)
GCopyInResponseS→CAlways returns ErrorResponse ("COPY not supported")
HCopyOutResponseS→CAlways returns ErrorResponse
Termination
XTerminateC→SClient disconnecting
Cancel
CancelRequestC→SSent on separate connection; 16-byte message
JET Extensions (J-prefix, two bytes)
JSJET_STATISTICSC→SRequest server statistics
JUJET_CHANGE_USERC→SSwitch user mid-session
JMJET_MULTI_STATEMENTC→SToggle multi-statement mode
JBJET_BEGIN_TRANSC→SBegin transaction
JCJET_COMMITC→SCommit transaction
JRJET_ROLLBACKC→SRollback transaction
JDJET_SET_DATABASEC→SSwitch active database
JPJET_PINGC→SConnection keepalive
JZJET_COMPRESSC→SToggle compression

11. Message Flow Examples

11.1 Simple SELECT

Client Server │ │ │──── Query: "SELECT * FROM Customers │ │ WHERE City='London'" ──────────>│ │ Set rs = database.OpenRecordset(sql) │ (rs is dbOpenSnapshot for read-only) │<──── RowDescription ───────────────│ From rs.Fields │ CustomerID → int4 (DAO dbLong → OID 23) │ CompanyName → text (DAO dbText → OID 25) │ City → text │ Balance → money (DAO dbCurrency → OID 790) │<──── DataRow ───────────────────────│ rs.Fields values │ "1" "Acme Ltd" "London" "1500.0000" │<──── DataRow ───────────────────────│ │ "5" "Beta Co" "London" "3200.5000" │<──── CommandComplete ("SELECT 2") ──│ rs.RecordCount │<──── ReadyForQuery (I) ─────────────│ idle

11.2 Extended Query with Parameters

Client Server │ │ │──── Parse ("" , "SELECT * FROM │ │ Customers WHERE City=?", []) ──>│ │ Set qd = database.CreateQueryDef("") │ qd.SQL = sql │ cache[""] = qd │<──── ParseComplete ─────────────────│ │ │ │──── Bind ("" , "" , [0], │ │ [["London"]], [0]) ────────────>│ │ qd.Parameters(0).Value = "London" │<──── BindComplete ──────────────────│ │ │ │──── Describe (P, "") ──────────────>│ │ ' Open temp rs to get field info │<──── RowDescription ───────────────│ │ │ │──── Execute ("" , 0) ──────────────>│ │ Set rs = qd.OpenRecordset() │<──── DataRow ───────────────────────│ Row 1 │<──── DataRow ───────────────────────│ Row 2 │<──── CommandComplete ("SELECT 2") ──│ │<──── PortalSuspended ───────────────│ (no more rows; Execute complete) │ │ │──── Close (P, "") ─────────────────>│ rs.Close() │<──── CloseComplete ─────────────────│ │──── Sync ──────────────────────────>│ │<──── ReadyForQuery (I) ─────────────│

12. Implementation Notes

12.1 Thread Model

12.2 Statement Cache

Named statements (from Parse with a non-empty statement name) are stored in a per-connection hash map: name → QueryDef object. The unnamed statement ("") is a single slot reused by each Parse. The cache is cleared on Sync with an error, on JET_CHANGE_USER, and on disconnect.

12.3 Transaction State Tracking

The ReadyForQuery transaction indicator is derived from DAO's transaction state:

12.4 DAO Error Collection Handling

DAO's DBEngine.Errors collection may contain multiple errors for a single operation. The first error (Errors(0)) is the primary error and maps to the wire protocol's ErrorResponse. Subsequent errors are concatenated into the Detail field. Example:

S: ERROR C: 42P01 M: The Microsoft Jet database engine could not find the input table or query 'MissingTable'. D: Error 3078: The Microsoft Jet database engine cannot find 'MissingTable'. Make sure the object exists and that you spell its name correctly. R: OpenRecordset

12.5 Large Result Sets — Fetch-Size Borrowing from MariaDB

PostgreSQL's extended query protocol supports Execute with a max_rows parameter to limit rows per fetch. JET adopts this directly — the worker calls rs.MoveNext() up to max_rows times, then sends PortalSuspended. The client can re-Execute to fetch the next batch.

For the simple query protocol, JET sends all rows at once. For very large result sets, the worker should stream: send RowDescription, then send DataRow messages in batches of 100, flushing the socket buffer between batches. This prevents the worker from buffering the entire result set in memory.

Streaming rule: For SELECT queries via the simple protocol, the worker opens the Recordset (dbOpenForwardOnly), sends RowDescription immediately, then iterates rs.MoveNext() → build DataRowsend() directly. The Recordset cursor and the socket write buffer advance in lockstep. No client-visible pagination — just TCP-level backpressure.

13. Compatibility Summary

FeaturePG v3MariaDBJETNotes
Message framing✓ (different)✓ (PG style)Type byte + int32 length
SSL/TLSPG-style SSLRequest
SCRAM-SHA-256
Cleartext password
Simple query
Multi-statement✓ (in Query)✓ (toggle)✓ (both)Default on; JET_MULTI_STATEMENT toggle
Extended query✓ (different)✓ (PG style)Parse/Bind/Execute → QueryDef
Named statementsCached QueryDefs
Portals / cursors✓ (single)Max 1 portal per connection
Binary result format— (text only)DAO provides values as variants; text is simplest
COPYJet has no COPY equivalent; stub error
LISTEN/NOTIFYJet has no pub/sub; stub error
Pipeline modeCould be added; not in v1
CancelSeparate cancel connection
Change userJET_CHANGE_USER
StatisticsJET_STATISTICS
PingJET_PING
Mid-session DB switch✓ (USE)JET_SET_DATABASE
CompressionJET_COMPRESS (zlib)
information_schema✓ (different)✓ (emulated)Mapped from DAO collections
pg_catalog✓ (stub)Minimal pg_class/pg_attribute/pg_type