Added
mcpg.identifiers— one escape-based SQL identifier quoter (quote_identifier/ensure_identifier).
It doubles embedded double quotes ("→"") so any name PostgreSQL accepts as a delimited
identifier can be spliced safely, and rejects only the values that are not addressable identifiers at
all: the empty string, an embedded NUL, and names over PostgreSQL's 63-byte limit (which the server
would silently truncate). It replaces the copy-pasted[A-Za-z_][A-Za-z0-9_]*regex that ~20 modules
each carried, where that regex was doing double duty as both the injection guard and (accidentally)
a blanket ban on every hyphen / space / mixed-case / quoted name. Covered by an adversarial test suite
(tests/unit/test_identifiers.py).
Fixed
-
Tools rejected valid PostgreSQL object names that need delimited-identifier quoting
(#329). The reported case wasdump_database: a real
schema such asadm-pgbenchfailed withShellError: invalid schema name: 'adm-pgbench'before
pg_dumpwas ever spawned.pg_dump --schematakes a pattern (psql\drules) — an unquoted
*/?/[is a wildcard and bare letters fold to lowercase — so eachschemasentry is now
encoded as a double-quoted literal pattern (_encode_schema_pattern);copy_table_between_databases
encodes its--tablethe same way. The access-mode requirement (unrestricted+MCPG_ALLOW_SHELL)
and theDumpResultcontract are unchanged.A sweep fixed the same class of over-restriction across the in-process SQL tools, which now quote
viamcpg.identifiersinstead of validating against the plain-identifier allowlist:
export_table/import_csv/import_json/import_vectors(data movement), the pgvector suites
(vector_ops,vector_tuning,rag_efficiency,pg_search,turboquant),textsearch,
composite,rlsandtenancy/configroles (SET LOCAL ROLE),logical_replication,
migrations,test_data/test_row_factory,redis_fdw,listen(LISTEN/NOTIFY channels), and
timescaledb(whosecreate_hypertable/ policy calls take the relation as aregclasstext
argument, so the quoted relation is wrapped in a single-quoted literal with both layers escaped).
Hyphens, spaces, mixed case, and embedded quotes now work and are safely escaped everywhere; only
empty / NUL / overlong names are rejected.Left deliberately strict, because the name is not being used as a PostgreSQL identifier there:
the Apache AGE graph tools (graph,graph_projection,cypher,graph_diagram), where labels
follow AGE's own naming rules; the ORM/query-builder code generators (prisma,drizzle,diesel,
ecto,ent,jooq,sqlc,sqlalchemy_export), where the name becomes an identifier in generated
Go/Rust/Elixir/Python source; and SQL/PGQ property-graph names (pgq).textsearchalso keeps the
strict allowlist for the one value it embeds in a string literal (aregconfigname).