prestodb/presto · error · SemanticException
VIEW_IS_RECURSIVE
VIEW_IS_RECURSIVE
Error message
Statement would create a recursive view
What it means
When analyzing a view body, Presto rejects a CREATE OR REPLACE VIEW whose target name equals a table being referenced inside the view definition. Creating a replace-view that selects from itself would define infinite recursion, so the analyzer compares the statement's view name to the referenced QualifiedObjectName and throws VIEW_IS_RECURSIVE.
Source
Thrown at presto-main-base/src/main/java/com/facebook/presto/sql/analyzer/StatementAnalyzer.java:2643
columnMetadata.getType(),
columnMetadata.isHidden(),
Optional.of(tableName),
Optional.of(columnMetadata.getName()),
false);
fields.add(field);
}
return createAndAssignScope(table, scope, fields.build());
}
private Scope processView(Table table, Optional<Scope> scope, QualifiedObjectName name, Optional<ViewDefinition> optionalView)
{
Statement statement = analysis.getStatement();
if (statement instanceof CreateView) {
CreateView viewStatement = (CreateView) statement;
QualifiedObjectName viewNameFromStatement = createQualifiedObjectName(session, viewStatement, viewStatement.getName(), metadata);
if (viewStatement.isReplace() && viewNameFromStatement.equals(name)) {
throw new SemanticException(VIEW_IS_RECURSIVE, table, "Statement would create a recursive view");
}
}
if (analysis.hasTableInView(table)) {
throw new SemanticException(VIEW_IS_RECURSIVE, table, "View is recursive");
}
ViewDefinition view = optionalView.get();
analysis.getViewDefinitionReferences().addViewDefinitionReference(name, view);
Optional<Expression> savedViewAccessorWhereClause = analysis.getCurrentQuerySpecification()
.flatMap(QuerySpecification::getWhere);
savedViewAccessorWhereClause.ifPresent(analysis::setViewAccessorWhereClause);
Query query = parseView(view.getOriginalSql(), name, table);
analysis.registerNamedQuery(table, query, true);
analysis.registerTableForView(table);
RelationType descriptor = analyzeView(query, name, view.getCatalog(), view.getSchema(), view.getOwner(), table);View on GitHub (pinned to 55bb57d202)
Solutions
- Point the view definition at the underlying base tables instead of the view name
- Create the new view under a different name, drop the old view, then rename
- Split the logic: materialize intermediate results into a table or non-recursive view first
Example fix
// before CREATE OR REPLACE VIEW v AS SELECT * FROM v; // after CREATE OR REPLACE VIEW v AS SELECT * FROM base_table;
Defensive patterns
Strategy: validation
Validate before calling
-- ensure the body does not select from the view being replaced SELECT * FROM system.metadata.views WHERE name = 'v'; -- rewrite the body against base tables before CREATE OR REPLACE
Try / catch
try {
session.execute(createOrReplaceSql);
} catch (SemanticException e) {
if (e.getCode() == VIEW_IS_RECURSIVE) {
throw new IllegalStateException("CREATE OR REPLACE references itself; point body at base tables");
}
throw e;
} Prevention
- Never reference the view's own name in a CREATE OR REPLACE body
- When overwriting a view, derive the new body from base tables, not the old view
- Track view->table dependencies in migration tooling to catch self-references
When it happens
Trigger: CREATE OR REPLACE VIEW v AS SELECT * FROM v; — isReplace() is true and the referenced table name equals the view being created. Only the replace path performs this name-equality check; plain CREATE is caught by the hasTableInView path.
Common situations: Overwriting an existing view with a definition that still references the old view of the same name; renaming mishaps where a base table was dropped and a view of the same name now shadows it.
Related errors
- MATERIALIZED_VIEW_IS_RECURSIVE
- MUST_BE_AGGREGATE_OR_GROUP_BY
- NESTED_AGGREGATION
- NESTED_WINDOW
- MUST_BE_AGGREGATION_FUNCTION
AI-assisted analysis of prestodb/presto@55bb57d202 (2026-09-04).
Data as JSON: /api/errors/cdfffa5e1a008218.
Report an issue: GitHub.