{"record":{"id":"0d43213ec3d18fde","repo":"apache/cassandra","slug":"forbidden-default-time-to-live-detected-for-a-mate","errorCode":null,"errorMessage":"Forbidden default_time_to_live detected for a materialized view. Data in a materialized view always expire at the same time than the corresponding data in the parent table. default_time_to_live must be set to zero, see CASSANDRA-12868 for more information","messagePattern":"Forbidden default_time_to_live detected for a materialized view\\. Data in a materialized view always expire at the same time than the corresponding data in the parent table\\. default_time_to_live must be set to zero, see CASSANDRA-12868 for more information","errorType":"validation","errorClass":"InvalidRequestException","httpStatus":null,"severity":"error","filePath":"src/java/org/apache/cassandra/cql3/statements/schema/AlterViewStatement.java","lineNumber":101,"sourceCode":"        }\n\n        attrs.validate();\n\n        // Guardrails on table properties\n        Guardrails.tableProperties.guard(attrs.updatedProperties(), attrs::removeProperty, state);\n\n        TableParams params = attrs.asAlteredTableParams(view.metadata.params);\n\n        if (params.gcGraceSeconds == 0)\n        {\n            throw ire(\"Cannot alter gc_grace_seconds of a materialized view to 0, since this \" +\n                      \"value is used to TTL undelivered updates. Setting gc_grace_seconds too \" +\n                      \"low might cause undelivered updates to expire before being replayed.\");\n        }\n\n        if (params.defaultTimeToLive > 0)\n        {\n            throw ire(\"Forbidden default_time_to_live detected for a materialized view. \" +\n                      \"Data in a materialized view always expire at the same time than \" +\n                      \"the corresponding data in the parent table. default_time_to_live \" +\n                      \"must be set to zero, see CASSANDRA-12868 for more information\");\n        }\n\n        ViewMetadata newView = view.copy(view.metadata.withSwapped(params));\n        return schema.withAddedOrUpdated(keyspace.withSwapped(keyspace.views.withSwapped(newView)));\n    }\n\n    SchemaChange schemaChangeEvent(KeyspacesDiff diff)\n    {\n        return new SchemaChange(Change.UPDATED, Target.TABLE, keyspaceName, viewName);\n    }\n\n    public void authorize(ClientState client)\n    {\n        ViewMetadata view = Schema.instance.getView(keyspaceName, viewName);\n        if (null != view)","sourceCodeStart":83,"sourceCodeEnd":119,"githubUrl":"https://github.com/apache/cassandra/blob/88fd0f6a0eaed8943f05ac9e8f947882b8ddc8f1/src/java/org/apache/cassandra/cql3/statements/schema/AlterViewStatement.java#L83-L119","documentation":"Materialized views must have default_time_to_live = 0 because view rows must expire at exactly the same time as the corresponding base-table rows; a view-level TTL would cause divergence (CASSANDRA-12868). AlterViewStatement.apply() throws when the altered params have defaultTimeToLive > 0.","triggerScenarios":"`ALTER MATERIALIZED VIEW <ks>.<view> WITH default_time_to_live = <n>` for any n > 0.","commonSituations":"Applying table-level TTL policies uniformly to views; attempting to 'fix' expiring data by adding TTL at the view level instead of the base table.","solutions":["Remove the default_time_to_live option from the ALTER MATERIALIZED VIEW statement","Set TTL on the base table instead (`ALTER TABLE ... WITH default_time_to_live = n` or per-row USING TTL), which propagates expiry to views","Keep/force default_time_to_live = 0 on the view"],"exampleFix":"// before\nALTER MATERIALIZED VIEW ks.v WITH default_time_to_live = 604800;\n\n// after\nALTER TABLE ks.base WITH default_time_to_live = 604800;","handlingStrategy":"validation","validationCode":"// Ensure no positive TTL is applied to views\nif (Number(opts.default_time_to_live) > 0) {\n  throw new Error('default_time_to_live must be 0 for materialized views (CASSANDRA-12868)');\n}","typeGuard":"function isViewSafeTtl(s) { const n = Number(s); return Number.isFinite(n) && n === 0; }","tryCatchPattern":"try {\n  session.execute(alterViewCql);\n} catch (e) {\n  if (/Forbidden default_time_to_live/.test(e.message)) {\n    // move the TTL to the base table instead\n  } else throw e;\n}","preventionTips":["Only set default_time_to_live on base tables, never views","Set row TTL with USING TTL on writes to the base table","Filter out TTL options in view-alteration builders","Remember view expiry mirrors the base table by design"],"tags":["cassandra","cql","materialized-view","ttl","guardrails"],"backgroundTag":"invalid-config-value","analyzedSha":"88fd0f6a0eaed8943f05ac9e8f947882b8ddc8f1","analyzedAt":"2026-09-10T07:29:22.284Z","contentChangedAt":"2026-09-10T07:29:22.284Z","schemaVersion":2},"datasetVersion":"2026-09-14T21:17:11.552Z"}