{"record":{"id":"6657717248a7cce7","repo":"prestodb/presto","slug":"view-is-stale","errorCode":"VIEW_IS_STALE","errorMessage":"View '%s' is stale; it must be re-created","messagePattern":"View '(.+?)' is stale; it must be re-created","errorType":"error_code","errorClass":"SemanticException","httpStatus":null,"severity":"error","filePath":"presto-main-base/src/main/java/com/facebook/presto/sql/analyzer/StatementAnalyzer.java","lineNumber":2669,"sourceCode":"            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);\n            analysis.unregisterTableForView();\n\n            if (savedViewAccessorWhereClause.isPresent()) {\n                analysis.clearViewAccessorWhereClause();\n            }\n\n            if (isViewStale(view.getColumns(), descriptor.getVisibleFields())) {\n                throw new SemanticException(VIEW_IS_STALE, table, \"View '%s' is stale; it must be re-created\", name);\n            }\n\n            // Derive the type of the view from the stored definition, not from the analysis of the underlying query.\n            // This is needed in case the underlying table(s) changed and the query in the view now produces types that\n            // are implicitly coercible to the declared view types.\n            List<Field> outputFields = view.getColumns().stream()\n                    .map(column -> Field.newQualified(\n                            table.getLocation(),\n                            table.getName(),\n                            Optional.of(column.getName()),\n                            column.getType(),\n                            false,\n                            Optional.of(name),\n                            Optional.of(column.getName()),\n                            false))\n                    .collect(toImmutableList());\n\n            // Propagate source columns from the view's underlying query to the view's output fields.","sourceCodeStart":2651,"sourceCodeEnd":2687,"githubUrl":"https://github.com/prestodb/presto/blob/55bb57d202de3b926896fa966c2c4a44c779634e/presto-main-base/src/main/java/com/facebook/presto/sql/analyzer/StatementAnalyzer.java#L2651-L2687","documentation":"A stored view's declared column list must still match the fields its query currently produces. If underlying tables changed (columns dropped, renamed, reordered, or type-incompatibly altered), isViewStale detects a mismatch between the persisted view columns and the live descriptor fields, and Presto throws VIEW_IS_STALE telling the user to recreate the view.","triggerScenarios":"SELECT * FROM stale_view after an underlying table lost or renamed a column, or produced a different visible field set than recorded in the ViewDefinition. Detected in view analysis by comparing view.getColumns() with descriptor.getVisibleFields().","commonSituations":"ALTER TABLE ... DROP/RENAME COLUMN on a table feeding a view; schema evolution in Hive/Iceberg/Delta after view creation; restoring a table from backup with an older schema; cross-cluster replication lag changing column sets.","solutions":["DROP VIEW and re-CREATE VIEW (or CREATE OR REPLACE VIEW) so the stored column list matches the new underlying schema","Check what changed: compare SHOW COLUMNS FROM view output against the underlying table's current columns","Pin the view to explicit column names instead of SELECT * to make breakage explicit and controlled","Coordinate schema migrations: update dependent views in the same migration as the base table change"],"exampleFix":"// before\n-- underlying table dropped column 'extra'; view still declares it\nCREATE VIEW my_view AS SELECT a, extra FROM base;\n// after\nDROP VIEW my_view;\nCREATE VIEW my_view AS SELECT a FROM base;","handlingStrategy":"validation","validationCode":"-- compare view columns against current base-table columns before querying\nDESCRIBE my_view;\nDESCRIBE base_table; -- field sets must match the stored view definition","typeGuard":null,"tryCatchPattern":"try {\n    return session.execute(\"SELECT * FROM my_view\");\n} catch (SemanticException e) {\n    if (e.getCode() == VIEW_IS_STALE) {\n        // recreate the view: DROP VIEW my_view; CREATE VIEW my_view AS ...\n    }\n    throw e;\n}","preventionTips":["Update dependent views in the same migration that alters base tables","Avoid SELECT * in view definitions; name columns explicitly","Validate schema compatibility in CI by running view queries after migrations","Version-control view DDL and re-apply after any base-table schema change"],"tags":["sql","view","stale-schema","schema-evolution"],"backgroundTag":"view-is-stale-schema-changed","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"}