Technical Analysis: REFRESH MATERIALIZED VIEW CONCURRENTLY + WITH NO DATA Error Checking Location
Core Problem
The thread raises a question about PostgreSQL's internal architecture regarding where semantic validation of utility command options should occur — specifically whether conflicting options in REFRESH MATERIALIZED VIEW CONCURRENTLY ... WITH NO DATA should be detected at parse time (in gram.y) or deferred to execution time (in RefreshMatViewByOid()).
Currently, PostgreSQL allows the parser to accept the combination of CONCURRENTLY and WITH NO DATA without complaint, constructing a valid RefreshMatViewStmt node with both n->concurrent = true and n->skipData = true. The error is only raised later during execution in RefreshMatViewByOid() (in matview.c), which checks:
if (stmt->concurrent && stmt->skipData)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("REFRESH options CONCURRENTLY and WITH NO DATA cannot be used together")));
Why This Matters Architecturally
This question touches on a fundamental PostgreSQL design principle: the separation of concerns between parsing, analysis, rewriting, and execution phases.
The Case for Parser-Level Checking
- Earlier error detection gives better user feedback (syntax errors are reported before planning/execution overhead)
- The conflict is purely syntactic — it doesn't depend on catalog state, permissions, or runtime conditions
- It could be caught in
gram.ywith a simple conditional check when building theRefreshMatViewStmtnode
The Case for Executor-Level Checking (Current Behavior)
- PostgreSQL's parser (
gram.y) is intentionally kept thin — it handles syntax recognition and AST construction, not semantic validation - The grammar file is already extremely large and complex; adding semantic checks increases maintenance burden
- Many utility commands follow this same convention: the parser builds the statement node, and validity is checked in the utility execution path (often in
ProcessUtilityor the specific command handler) - This approach keeps validation logic co-located with execution logic, making it easier to maintain and modify
- For prepared statements or plan caching scenarios, conditions might theoretically change between parse and execute (though not applicable in this specific case)
PostgreSQL's Established Convention
The existing behavior is deliberate and consistent with PostgreSQL conventions. The project generally follows these principles:
gram.yhandles pure syntax — is the SQL grammatically valid?- Parse analysis (
analyze.c,parse_*.c) handles semantic validation that can be done without catalog access or with minimal catalog access - Utility command execution handles option validation that is specific to the command's semantics
For utility statements specifically, many similar checks are deferred:
CREATE INDEX CONCURRENTLYon a temp table (checked at execution)VACUUM FULLcombined with certain options (checked at execution)- Various
ALTER TABLEsub-command conflicts (checked at execution)
Technical Implications of Moving the Check
If one were to move this check to the parser, it would involve adding something like:
/* In gram.y, in the RefreshMatViewStmt production */
if (n->concurrent && n->skipData)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("REFRESH options CONCURRENTLY and WITH NO DATA cannot be used together")));
However, this would:
- Be inconsistent with how other utility commands handle similar conflicts
- Make the error reporting location inconsistent if someone later adds another way to trigger the same check (e.g., via an extension or protocol-level command)
- Potentially complicate future work if the restriction is ever relaxed
Conclusion
The current placement is an intentional architectural choice. PostgreSQL's utility command infrastructure consistently defers semantic validation of option combinations to the executor phase, keeping the parser focused on syntactic correctness. This is not a bug or oversight — it's a deliberate design pattern that prioritizes maintainability and consistency across the codebase.