Skip to content

WIP: command_tag_format — protocol-level command tag negotiation via _pq_#16

Open
NikolayS wants to merge 3 commits intomasterfrom
command-tag-omit-oid
Open

WIP: command_tag_format — protocol-level command tag negotiation via _pq_#16
NikolayS wants to merge 3 commits intomasterfrom
command-tag-omit-oid

Conversation

@NikolayS
Copy link
Owner

@NikolayS NikolayS commented Mar 11, 2026

(live hacking session: https://www.youtube.com/watch?v=VKuxQZlvd8E)

Adds a new protocol-level feature _pq_.command_tag_format that allows clients to negotiate richer command completion tags at connection time.

The problem

Every INSERT returns INSERT 0 N — the 0 is a vestigial OID field, hardcoded to zero since PG12 dropped table OIDs. It wastes bytes and confuses users, but changing the wire format breaks old clients.

Solution: protocol negotiation

New clients send _pq_.command_tag_format in the startup packet. Old clients never send it, so they always get legacy format. Zero breakage.

Formats

Format Output Example
legacy INSERT 0 N (default) INSERT 0 1
verbose INSERT tablename N INSERT users 1
fqn INSERT schema.tablename N INSERT public.users 1

verbose and fqn also work for UPDATE, DELETE, and MERGE.

Safety guarantees

Scenario Result
Old client + new server (no _pq_) INSERT 0 1 — always safe
New client + old server (_pq_ sent) silently ignored
SET command_tag_format = 'verbose' ERROR: cannot be changed
options=-ccommand_tag_format=verbose FATAL: cannot be changed

The GUC is PGC_INTERNAL — only the _pq_ startup packet path can set it. SET, options=-c, ALTER SYSTEM, and postgresql.conf are all blocked.

Implementation (8 files, ~115 insertions)

  • cmdtag.h / cmdtag.c — format constants, format-aware BuildQueryCompletionString
  • pquery.c — populates relation name from executor for verbose/fqn
  • backend_startup.c_pq_ handler, stores in Port->pq_command_tag_format
  • libpq-be.h — new Port field for deferred protocol option
  • postinit.c — deferred application via SetConfigOption(PGC_INTERNAL, PGC_S_OVERRIDE)
  • guc_parameters.dat / guc_tables.c — GUC registration with GUC_REPORT

NOT changed: fe-exec.c (libpq)

Old protocol is completely untouched. New-protocol-aware clients handle the new format themselves.

Evolution

  1. v1: command_tag_omit_oid — simple bool GUC (PGC_USERSET) + libpq patch
  2. v2: command_tag_format — enum with 4 modes, _pq_ negotiation, GUC_REPORT
  3. v3 (current): protocol-only — PGC_INTERNAL, removed modern mode, reverted libpq, separate Port field + PGC_S_OVERRIDE deferred application

Test results

See comment with full test evidence — 8/8 scenarios pass with both stock PG17 psql and raw _pq_ startup packet test.

options => 'client_message_level_options',
},


Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this extra line is not needed

@NikolayS NikolayS force-pushed the command-tag-omit-oid branch from a89112b to 14e3dea Compare March 11, 2026 17:37
Adds a new enum GUC command_tag_format (PGC_USERSET) with four modes:
- legacy: INSERT 0 N (default, backward compatible)
- modern: INSERT N (drops legacy OID field)
- verbose: INSERT tablename N (shows relation name)
- fqn: INSERT schema.tablename N (fully qualified)

Tested output:
  legacy:  INSERT 0 1
  modern:  INSERT 1
  verbose: INSERT users 1
  fqn:     INSERT myschema.users 1

Supports SET LOCAL for per-transaction control.
libpq PQcmdTuples() handles all formats gracefully.

Note: verbose/fqn currently only populated for INSERT (UPDATE/DELETE
show count only — relname propagation for those pending).
@NikolayS NikolayS force-pushed the command-tag-omit-oid branch from 14e3dea to 0e8ea91 Compare March 11, 2026 17:41
- Server handles _pq_.command_tag_format in startup parameters,
  mapping it to the command_tag_format GUC
- Added GUC_REPORT flag so server auto-reports format changes to client
- Client can negotiate format at connection time via:
    psql 'options=-ccommand_tag_format=modern'
- Old client + new server: server uses legacy default (safe)
- New client + old server: _pq_ option silently ignored (safe)
- Mid-session SET also works and is reported to client

Tested: three connections with different formats confirmed working.
- Remove 'modern' mode; keep legacy/verbose/fqn
- Change GUC context from PGC_USERSET to PGC_INTERNAL: blocks SET,
  options=-c, ALTER SYSTEM, and postgresql.conf
- Store _pq_.command_tag_format in Port->pq_command_tag_format
  (not guc_options) to isolate from regular GUC path
- Apply deferred in process_startup_options() via PGC_S_OVERRIDE
- Revert libpq fe-exec.c: old protocol completely untouched

Safety guarantees:
  Old client + new server (no _pq_)    → INSERT 0 N (always safe)
  New client + old server (_pq_ sent)  → silently ignored
  SET command_tag_format               → ERROR: cannot be changed
  options=-ccommand_tag_format=verbose → FATAL: cannot be changed
  _pq_.command_tag_format=verbose      → INSERT tablename N (works)

Tested with stock PG17 psql (old client) and raw _pq_ startup
packet (Python socket test).
@NikolayS
Copy link
Owner Author

Test Evidence: Protocol-Only command_tag_format

Tested on PG19devel (commit b6eb8dde6be + 3 patch commits), Hetzner CCX33 VM.

Old Client (stock PG17.9 psql) + New Server

--- Default connection: must get INSERT 0 N ---
$ /usr/lib/postgresql/17/bin/psql -U postgres -h /tmp -c "INSERT INTO proto_test VALUES (1);"
INSERT 0 1

--- options=-c verbose: must be REJECTED ---
$ /usr/lib/postgresql/17/bin/psql -U postgres -h /tmp "options=-ccommand_tag_format=verbose" -c "SELECT 1;"
psql: error: connection to server on socket "/tmp/.s.PGSQL.5432" failed: FATAL:  parameter "command_tag_format" cannot be changed

--- SET: must be REJECTED ---
$ /usr/lib/postgresql/17/bin/psql -U postgres -h /tmp -c "SET command_tag_format = 'verbose';"
ERROR:  parameter "command_tag_format" cannot be changed

Old client is fully protected — cannot accidentally enable a format its libpq can't parse.

New Protocol (_pq_ startup packet, raw Python socket test)

# test_pq_startup.py — sends _pq_.command_tag_format in startup packet

=== Testing with _pq_.command_tag_format=default ===
  GUC_REPORT: command_tag_format = legacy
  CommandComplete: INSERT 0 1

=== Testing with _pq_.command_tag_format=verbose ===
  GUC_REPORT: command_tag_format = verbose
  CommandComplete: INSERT proto_test 1

=== Testing with _pq_.command_tag_format=fqn ===
  GUC_REPORT: command_tag_format = fqn
  CommandComplete: INSERT public.proto_test 1

=== Testing with _pq_.command_tag_format=legacy ===
  GUC_REPORT: command_tag_format = legacy
  CommandComplete: INSERT 0 1

=== Testing with _pq_.command_tag_format=modern ===
  ERROR: FATALinvalid value for parameter "command_tag_format": "modern"
  HINT: Available values: legacy, verbose, fqn.

Summary

Scenario Result Status
Old PG17 psql, default INSERT 0 1
Old PG17 psql, options=-c verbose FATAL: cannot be changed
Old PG17 psql, SET verbose ERROR: cannot be changed
_pq_ no param (default) INSERT 0 1
_pq_ verbose INSERT proto_test 1
_pq_ fqn INSERT public.proto_test 1
_pq_ legacy INSERT 0 1
_pq_ modern (removed) FATAL: invalid value

All 8 test cases pass. Old protocol untouched (fe-exec.c has zero changes).

@NikolayS NikolayS changed the title feat: add command_tag_omit_oid GUC to drop legacy OID from INSERT tag WIP: feat: add command_tag_omit_oid GUC to drop legacy OID from INSERT tag Mar 11, 2026
@NikolayS NikolayS changed the title WIP: feat: add command_tag_omit_oid GUC to drop legacy OID from INSERT tag feat: command_tag_format — protocol-level command tag negotiation via _pq_ Mar 11, 2026
@NikolayS NikolayS changed the title feat: command_tag_format — protocol-level command tag negotiation via _pq_ WIP: feat: command_tag_format — protocol-level command tag negotiation via _pq_ Mar 11, 2026
@NikolayS NikolayS changed the title WIP: feat: command_tag_format — protocol-level command tag negotiation via _pq_ command_tag_format — protocol-level command tag negotiation via _pq_ Mar 11, 2026
@NikolayS NikolayS changed the title command_tag_format — protocol-level command tag negotiation via _pq_ WIP: command_tag_format — protocol-level command tag negotiation via _pq_ Mar 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant