{"record":{"id":"cdfffa5e1a008218","repo":"prestodb/presto","slug":"view-is-recursive","errorCode":"VIEW_IS_RECURSIVE","errorMessage":"Statement would create a recursive view","messagePattern":"Statement would create a recursive view","errorType":"error_code","errorClass":"SemanticException","httpStatus":null,"severity":"error","filePath":"presto-main-base/src/main/java/com/facebook/presto/sql/analyzer/StatementAnalyzer.java","lineNumber":2643,"sourceCode":"                        columnMetadata.getType(),\n                        columnMetadata.isHidden(),\n                        Optional.of(tableName),\n                        Optional.of(columnMetadata.getName()),\n                        false);\n                fields.add(field);\n            }\n\n            return createAndAssignScope(table, scope, fields.build());\n        }\n\n        private Scope processView(Table table, Optional<Scope> scope, QualifiedObjectName name, Optional<ViewDefinition> optionalView)\n        {\n            Statement statement = analysis.getStatement();\n            if (statement instanceof CreateView) {\n                CreateView viewStatement = (CreateView) statement;\n                QualifiedObjectName viewNameFromStatement = createQualifiedObjectName(session, viewStatement, viewStatement.getName(), metadata);\n                if (viewStatement.isReplace() && viewNameFromStatement.equals(name)) {\n                    throw new SemanticException(VIEW_IS_RECURSIVE, table, \"Statement would create a recursive view\");\n                }\n            }\n            if (analysis.hasTableInView(table)) {\n                throw new SemanticException(VIEW_IS_RECURSIVE, table, \"View is recursive\");\n            }\n            ViewDefinition view = optionalView.get();\n\n            analysis.getViewDefinitionReferences().addViewDefinitionReference(name, view);\n\n            Optional<Expression> savedViewAccessorWhereClause = analysis.getCurrentQuerySpecification()\n                    .flatMap(QuerySpecification::getWhere);\n            savedViewAccessorWhereClause.ifPresent(analysis::setViewAccessorWhereClause);\n\n            Query query = parseView(view.getOriginalSql(), name, table);\n\n            analysis.registerNamedQuery(table, query, true);\n            analysis.registerTableForView(table);\n            RelationType descriptor = analyzeView(query, name, view.getCatalog(), view.getSchema(), view.getOwner(), table);","sourceCodeStart":2625,"sourceCodeEnd":2661,"githubUrl":"https://github.com/prestodb/presto/blob/55bb57d202de3b926896fa966c2c4a44c779634e/presto-main-base/src/main/java/com/facebook/presto/sql/analyzer/StatementAnalyzer.java#L2625-L2661","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nCREATE OR REPLACE VIEW v AS SELECT * FROM v;\n// after\nCREATE OR REPLACE VIEW v AS SELECT * FROM base_table;","handlingStrategy":"validation","validationCode":"-- ensure the body does not select from the view being replaced\nSELECT * FROM system.metadata.views WHERE name = 'v';\n-- rewrite the body against base tables before CREATE OR REPLACE","typeGuard":null,"tryCatchPattern":"try {\n    session.execute(createOrReplaceSql);\n} catch (SemanticException e) {\n    if (e.getCode() == VIEW_IS_RECURSIVE) {\n        throw new IllegalStateException(\"CREATE OR REPLACE references itself; point body at base tables\");\n    }\n    throw e;\n}","preventionTips":["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"],"tags":["sql","view","recursion","semantic-analysis"],"backgroundTag":"recursive-view-definition","analyzedSha":"55bb57d202de3b926896fa966c2c4a44c779634e","analyzedAt":"2026-09-04T12:50:26.162Z","contentChangedAt":"2026-09-04T12:50:26.162Z","schemaVersion":2},"datasetVersion":"2026-09-11T21:17:09.523Z"}