prestodb/presto · error · SemanticException

TYPE_MISMATCH

TYPE_MISMATCH

Error message

Expression %s is not of type ROW

What it means

A dereference expression (base.field) requires the base expression to be of ROW type (after unwrapping TypeWithName and DistinctType). If the resolved base type is anything else, the analyzer throws TYPE_MISMATCH pointing at the base expression.

Source

Thrown at presto-main-base/src/main/java/com/facebook/presto/sql/analyzer/ExpressionAnalyzer.java:589

                    }
                    if (outerScopeSymbolTypes.containsKey(NodeRef.of(node))) {
                        return setExpressionType(node, outerScopeSymbolTypes.get(NodeRef.of(node)));
                    }
                    throw missingAttributeException(node, qualifiedName);
                }
            }

            Type baseType = process(node.getBase(), context);
            addColumnSubfieldReferences(node, context);

            if (((baseType instanceof TypeWithName) && ((TypeWithName) baseType).getType() instanceof RowType)) {
                baseType = ((TypeWithName) baseType).getType();
            }
            if (baseType instanceof DistinctType) {
                baseType = ((DistinctType) baseType).getBaseType();
            }
            if (!(baseType instanceof RowType)) {
                throw new SemanticException(TYPE_MISMATCH, node.getBase(), "Expression %s is not of type ROW", node.getBase());
            }

            RowType rowType = (RowType) baseType;
            String fieldName = node.getField().getValue();

            Type rowFieldType = null;
            for (RowType.Field rowField : rowType.getFields()) {
                if (fieldName.equalsIgnoreCase(rowField.getName().orElse(null))) {
                    rowFieldType = rowField.getType();
                    break;
                }
            }

            if (sqlFunctionProperties.isLegacyRowFieldOrdinalAccessEnabled() && rowFieldType == null) {
                OptionalInt rowIndex = parseAnonymousRowFieldOrdinalAccess(fieldName, rowType.getFields());
                if (rowIndex.isPresent()) {
                    rowFieldType = rowType.getFields().get(rowIndex.getAsInt()).getType();
                }

View on GitHub (pinned to 55bb57d202)

Solutions

  1. Verify the base expression's type is ROW; cast it if needed
  2. Use subscript syntax for maps/arrays instead of dot access
  3. Fix upstream schema/type so the column is actually a ROW

Example fix

// before
SELECT my_map.key FROM t
// after
SELECT my_map['key'] FROM t
Defensive patterns

Strategy: type-guard

Validate before calling

Type baseType = expressionAnalyzer.getType(baseExpr);
if (!(unwrap(baseType) instanceof RowType)) {
    throw new IllegalArgumentException("Base of field access must be ROW, got " + baseType);
}

Type guard

boolean isRowAccess(Expression base, FunctionAndTypeManager types) {
    Type t = types.getType(base);
    while (t instanceof TypeWithName) t = ((TypeWithName) t).getType();
    return t instanceof RowType;
}

Try / catch

try {
    return execute(sql);
} catch (SemanticException e) {
    if (e.getCode() == TYPE_MISMATCH && e.getMessage().contains("is not of type ROW")) {
        // suggest map subscript or cast, or report type of offending base
    } else throw e;
}

Prevention

When it happens

Trigger: Using dot-notation field access on a non-ROW value, e.g. my_map.key where my_map is MAP, or col.field where col is VARCHAR/ARRAY.

Common situations: Confusing map access with row field access (SQL maps use [] not dot), dereferencing a column whose type changed after a schema change, accessing fields of a NULL-typed or unknown expression.

Related errors


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