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

  1. Point the view definition at the underlying base tables instead of the view name
  2. Create the new view under a different name, drop the old view, then rename
  3. 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

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


AI-assisted analysis of prestodb/presto@55bb57d202 (2026-09-04). Data as JSON: /api/errors/cdfffa5e1a008218. Report an issue: GitHub.