Skip to content

CREATE ROLE IF NOT EXISTS <name> silently creates role named 'IF' instead (parser parses IF as role name) #120

Description

@emanzx

Tested against: origin/main @ a178aa5b6b0b260d105962c928f07304360b7b30 (fetched 2026-05-20)

Severity: Medium (breaks idempotent DDL patterns; PostgreSQL idiom standard)

CREATE ROLE IF NOT EXISTS <name> parses IF as the role name. Same parser-bug family as CREATE TENANT IF NOT EXISTS (filed separately).

Reproduction

-- First attempt: parser treats 'IF' as role name and CREATES role named 'IF'
CREATE ROLE IF NOT EXISTS mae8_admin;
-- → (silent success on first invocation; role 'IF' created)

-- Second attempt: now role 'IF' exists, error reveals the misparse:
CREATE ROLE IF NOT EXISTS mae8_admin;
-- → ERROR: bad request: role 'IF' already exists

The error message "role 'IF' already exists" is the dead giveaway — the parser is grabbing IF as the role identifier and ignoring everything after it.

Operational impact

Idempotent bootstrap scripts (the standard PostgreSQL pattern for cluster setup, where you re-run DDL safely) break here. Each CREATE ROLE IF NOT EXISTS X either creates a role named 'IF' (first time) or errors (subsequent times) — never creates a role named X. Combined with bug #lockout-app-errors, repeatedly trying CREATE ROLE IF NOT EXISTS in a script will lock out the superuser after 5 attempts.

CREATE COLLECTION IF NOT EXISTS and CREATE DATABASE IF NOT EXISTS appear to work correctly per spot check — so the parser handles this idiom for collections + databases but misses it for roles + tenants + users.

Suggested fix

Add IF NOT EXISTS to the CREATE ROLE grammar. Same pattern as CREATE COLLECTION / CREATE DATABASE that already work. Extend the fix to CREATE TENANT + CREATE USER in the same patch.

Workaround

Omit IF NOT EXISTS from CREATE ROLE statements; handle the "already exists" error in scripts. Also: scripts that did CREATE ROLE IF NOT EXISTS X on a fresh deployment have likely created a phantom 'IF' role on every such deployment — operators should grep audit logs for CREATE ROLE IF events.

Context

Caught during mae8 v2 Phase 0 bootstrap (2026-05-20). Full bug catalog: /home/system/rnd/mae8/docs/origin_bugs_2026-05-20.md (this is Bug 3 from that file).

Activity

  1. farhan-syah commented on May 22, 2026

    @farhan-syah
    Member

    Fixed across 86e8ea9 (CREATE ROLE) + 6c477bc (CREATE USER) + 9fa43da (TenantSelector AST) + 86a6023 (regression tests).

    Root cause

    The pgwire auth-DDL surface dispatched CREATE statements by string-prefix match and parsed them by whitespace tokenization, taking a fixed positional token as the object name. It did not recognize the IF NOT EXISTS clause, so CREATE ROLE IF NOT EXISTS mae8_admin parsed IF as the role name.

    Changes

    • CREATE ROLE IF NOT EXISTS <name> — clause recognized; first call creates <name>, a subsequent call is a no-op success (not ERROR: role 'IF' already exists). Idempotent bootstrap scripts work.
    • CREATE TENANT IF NOT EXISTS <name> — same fix (issue CREATE TENANT IF NOT EXISTS <name> silently creates tenant named 'IF' instead #119).
    • CREATE USER IF NOT EXISTS <name> — same fix, completing the suggested scope of this issue. The AuthStmt::CreateUser AST node now carries an if_not_exists flag; the handler treats re-creation of an existing user as a no-op.
    • A shared IF [NOT] EXISTS clause-stripping helper is applied uniformly across the whole auth-DDL surface — CREATE and DROP for ROLE, TENANT, SERVICE ACCOUNT, and USER all honor the clause now.

    Plain CREATE ROLE X / CREATE USER X still error on a genuine duplicate; only the IF NOT EXISTS form is idempotent.

    Regression coverage added: create_role_if_not_exists_names_real_role, create_user_if_not_exists_names_real_user, create_user_if_not_exists_is_idempotent, and the IF NOT EXISTS parser tests.

  2. added
    type:bugA defect — broken, incorrect, or lost data
    sev:1-criticalData loss, corruption, security, or crash; no workaround
    area:sqlParser, planner, SQL semantics
    on Jul 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:sqlParser, planner, SQL semanticssev:1-criticalData loss, corruption, security, or crash; no workaroundtype:bugA defect — broken, incorrect, or lost data

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions