prestodb/presto · error · PrestoException
INVALID_COLUMN_MASK
INVALID_COLUMN_MASK
Error message
Column mask for '%s.%s' is recursive
What it means
Column masks, like row filters, can reference other columns/tables and may nest. The analyzer tracks registered masks per (table, column, identity) and throws INVALID_COLUMN_MASK if applying a mask would re-enter the same mask, preventing infinite recursion during analysis.
Source
Thrown at presto-main-base/src/main/java/com/facebook/presto/sql/analyzer/StatementAnalyzer.java:5387
analysis.recordSubqueries(expression, expressionAnalysis);
Type actualType = expressionAnalysis.getType(expression);
if (!actualType.equals(BOOLEAN)) {
if (!metadata.getFunctionAndTypeManager().canCoerce(actualType, BOOLEAN)) {
throw new PrestoException(DATATYPE_MISMATCH, format("Expected row filter for '%s' to be of type BOOLEAN, but was %s", name, actualType), null);
}
analysis.addCoercion(expression, BOOLEAN, false);
}
analysis.addRowFilter(table, expression);
}
private void analyzeColumnMask(String currentIdentity, Table table, QualifiedObjectName tableName, ColumnMetadata columnMetadata, Scope scope, ViewExpression mask)
{
String column = columnMetadata.getName();
if (analysis.hasColumnMask(tableName, column, currentIdentity)) {
throw new PrestoException(INVALID_COLUMN_MASK, format("Column mask for '%s.%s' is recursive", tableName, column), null);
}
Expression expression;
try {
expression = sqlParser.createExpression(mask.getExpression(), createParsingOptions(session));
}
catch (ParsingException e) {
throw new PrestoException(INVALID_COLUMN_MASK, format("Invalid column mask for '%s.%s': %s", tableName, column, e.getErrorMessage()), e);
}
ExpressionAnalysis expressionAnalysis;
analysis.registerTableForColumnMasking(tableName, column, currentIdentity);
try {
expressionAnalysis = ExpressionAnalyzer.analyzeExpression(
createViewSession(mask.getCatalog(), mask.getSchema(), new Identity(mask.getIdentity(), Optional.empty())), // TODO: path should be included in row filter
metadata,
accessControl,
sqlParser,View on GitHub (pinned to 55bb57d202)
Solutions
- Rewrite the mask so its expression does not read the same masked column/table transitively
- Break the cycle by having the mask use literals, session functions (e.g. current_user()), or an unmasked lookup table
- Restructure policies so view-level and table-level masks don't reference each other
Example fix
// before: mask on t.ssn contains SELECT ssn FROM t WHERE ... (SELECT ssn FROM t WHERE id = current_user()) // after CASE WHEN is_admin(current_user()) THEN ssn ELSE '***' END
Defensive patterns
Strategy: validation
Validate before calling
// Ensure the mask expression does not reference the masked column/table itself:
Set<QualifiedObjectName> refs = extractTableReferences(parse(maskExpr));
if (refs.contains(tableName)) {
throw new IllegalStateException("Column mask must not reference " + tableName);
} Try / catch
try { session.execute("SELECT masked_col FROM " + table); }
catch (PrestoException e) {
if ("INVALID_COLUMN_MASK".equals(e.getErrorCode().getName())) {
// locate the cycling mask and break the reference chain
} else { throw e; }
} Prevention
- Design masks to use session functions/literals, not subqueries over masked tables
- Map mask dependencies before stacking policies on views over base tables
- Review masking policies as a dependency graph in code review
When it happens
Trigger: A column mask expression on table A column c reads from a table (or same table/column) whose mask chain leads back to A.c under the same identity; thrown at the start of analyzeColumnMask when analysis.hasColumnMask(tableName, column, currentIdentity) is true.
Common situations: Masks defined with subqueries over masked tables; a mask on a column that the mask expression itself selects; stacked policies across views and base tables creating a cycle.
Related errors
- INVALID_ROW_FILTER
- PERMISSION_DENIED
- INVALID_COLUMN_MASK
- Full data access is restricted by row filters and column mas
- Cannot set catalog session property:
AI-assisted analysis of prestodb/presto@55bb57d202 (2026-09-04).
Data as JSON: /api/errors/23194f0670be223f.
Report an issue: GitHub.