Applying Query Rules in the PostgreSQL Extended Query Protocol
Starting with ProxySQL v3.0.3, the PostgreSQL module introduces support for the Extended Query Protocol.
This protocol consists of multiple message types — Parse, Bind, Describe, Execute, Close, and Sync — which require special handling when applying query rules.
The implementation includes specific logic to determine how these rules are evaluated and applied throughout the extended query message flow.
Query Routing Behavior
Host Group Selection: The first message in the extended query pipeline that triggers query processing determines which host group will handle the entire query sequence.
Connection Persistence: Once a backend connection is established, it remains consistent for all messages in the extended query pipeline (until Sync message)
Fallback: If no matching query rule exists, queries are routed to the default host group
Important: Bind and Close Statement Handling
- No Query Processing: Bind and Close messages do NOT trigger query processing or hostgroup selection
- Deferred Routing: If Bind or Close is the first message in a sequence, the NEXT message that triggers query processing (Parse, Execute, Describe) will determine the hostgroup
Query Rules Application
The following query rule actions are applied at different stages of the extended query flow:
Parse Message Stage
These actions are evaluated and applied when the Parse packet is received:
Messages That Can Determine Hostgroup (if first in pipeline)
- Parse
- Describe
- Execute
Parse Message Processing
These actions are applied during Parse message phase:
- Query Rewrite – SQL text can be modified before parsing (
pgsql_query_rules -> replace_pattern) - Max Packet Size – If a packet is larger than
pgsql-max_allowed_packetvalue, reject the query. - Block Query – Reject query with defined error message (
pgsql_query_rules -> error_msg)
Execute Message Processing
These actions are applied during the Execute phase:
- OK Message – Return success response to client without executing query on backend server (
pgsql_query_rules -> OK_msg)
Sequence 1: Standard Extended Query
sequenceDiagram
participant C as Client
participant P as ProxySQL
participant B as Backend (Persistent Connection → Selected Hostgroup)
C->>P: Parse (SELECT ... WHERE user_id=$1)
C->>P: Bind (param=123)
C->>P: Describe
C->>P: Execute
C->>P: Sync
Note over P: Apply query rules<br>• Hostgroup selection<br>• Rewrite/Block/Max packet check<br>• Backend connection established
P->>B: Forward Parse (SELECT ... WHERE user_id=$1)
B->>P: ParseComplete
P->>C: ParseComplete
Note over P: No query processing<br>Deferred if first message
P->>P: Bind Queued (param=123)
Note over P: OK_msg check
P->>B: Forward Queued Bind + Describe + Execute
B->>P: BindComplete + ParamDescription, RowDescription + DataRow + CommandComplete
P->>C: BindComplete + ParamDescription, RowDescription + DataRow + CommandComplete
P->>B: Forward Sync
B->>P: ReadyForQuery
P->>C: ReadyForQuery
Sequence 2: Bind as First Message
sequenceDiagram
participant C as Client
participant P as ProxySQL
participant B as Backend (Persistent Connection → Selected Hostgroup)
C->>P: Bind (with prepared statement)
C->>P: Execute
C->>P: Sync
Note over P: No query processing<br>Hostgroup not selected yet
P->>P: Queue Bind
Note over P: Query rules applied <br>• Hostgroup selected here <br>• Backend connection established
P->>B: Forward queued Bind + Execute
B->>P: BindComplete + Results + CommandComplete
P->>C: BindComplete + Results + CommandComplete
P->>B: Forward Sync
B->>P: ReadyForQuery
P->>C: ReadyForQuery
Sequence 3: Multi-Statement Pipeline
sequenceDiagram
participant C as Client
participant P as ProxySQL
participant B as Backend (Persistent Connection → HG2)
C->>P: Parse (UPDATE users SET last_login=$1 WHERE id=$2)
C->>P: Parse (SELECT * FROM users WHERE id=$1)
C->>P: Bind (for UPDATE)
C->>P: Execute (for UPDATE)
C->>P: Bind (for SELECT)
C->>P: Execute (for SELECT)
C->>P: Sync
Note over P: • Hostgroup selected → HG2<br>• Backend connection established
P->>B: Forward Parse (for UPDATE)
B->>P: ParsesComplete (for UPDATE)
P->>C: ParsesComplete (for UPDATE)
Note over P: Would normally route to HG1,<br> but uses same backend connection
P->>B: Forward Parse (for SELECT)
B->>P: ParsesComplete (for SELECT)
P->>C: ParsesComplete (for SELECT)
Note over P: No query processing
P->>P: Queue Bind (for UPDATE)
P->>B: Forward Queued Bind + Execute (for UPDATE)
B->>P: BindComplete + CommandComplete (for UPDATE)
P->>C: BindComplete + CommandComplete (for UPDATE)
Note over P: No query processing
P->>P: Queue Bind (for SELECT)
P->>B: Forward Queued Bind + Execute (for SELECT)
B->>P: BindComplete + RowDescription + RowData + CommandComplete (for SELECT)
P->>C: BindComplete + RowDescription + RowData + CommandComplete (for SELECT)
P->>B: Forward Sync
B->>P: ReadyForQuery
P->>C: ReadyForQuery