prestodb/presto · error · SemanticException

DUPLICATE_COLUMN_NAME

DUPLICATE_COLUMN_NAME

Error message

Duplicate field name '%s' in ROW

What it means

visitRow rejects a ROW type literal whose field names repeat. Duplicates are disallowed because a row type with repeated field names cannot be round-tripped through its TypeSignature, which the native worker relies on. The check uses the delimited or lowercased identifier value when comparing names.

Source

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

            Set<String> declaredNames = new HashSet<>();
            for (Row.Field field : node.getFields()) {
                Type fieldType = process(field.getExpression(), context);
                Optional<Identifier> name = field.getName();
                if (!name.isPresent()) {
                    fields.add(RowType.field(fieldType));
                    continue;
                }
                // Fold the name the same way CAST(... AS ROW(...)) does. Cast.transformCase
                // lowercases everything outside double quotes, so an undelimited name is folded to
                // lower case and a delimited one is kept verbatim. Doing the same here keeps
                // ROW(1 AS Abc) and CAST(ROW(1) AS ROW(Abc integer)) producing the same type.
                // Note this is deliberately not getCanonicalValue(), which upper cases instead.
                Identifier identifier = name.get();
                String fieldName = identifier.isDelimited() ? identifier.getValue() : identifier.getValueLowerCase();
                // Duplicates are rejected because a row type with repeated field names cannot be
                // round-tripped through its TypeSignature, which the native worker relies on.
                if (!declaredNames.add(fieldName)) {
                    throw new SemanticException(DUPLICATE_COLUMN_NAME, node, "Duplicate field name '%s' in ROW", fieldName);
                }
                fields.add(RowType.field(fieldName, fieldType, identifier.isDelimited()));
            }

            return setExpressionType(node, RowType.from(fields.build()));
        }

        @Override
        protected Type visitCurrentTime(CurrentTime node, StackableAstVisitorContext<Context> context)
        {
            if (node.getPrecision() != null) {
                throw new SemanticException(NOT_SUPPORTED, node, "non-default precision not yet supported");
            }

            Type type;
            switch (node.getFunction()) {
                case DATE:
                    type = DATE;

View on GitHub (pinned to 55bb57d202)

Solutions

  1. Rename the duplicated field in the ROW type definition
  2. Unquote deliberately-cased names so they differ, or change casing so the lowercased names differ
  3. Regenerate the SQL/schema so column aliases are unique

Example fix

// before
CAST(ROW(1, 'x') AS ROW(a INTEGER, a VARCHAR))
// after
CAST(ROW(1, 'x') AS ROW(a INTEGER, b VARCHAR))
Defensive patterns

Strategy: validation

Validate before calling

Set<String> names = rowFields.stream()
    .map(f -> f.getName().toLowerCase())
    .collect(Collectors.toSet());
if (names.size() != rowFields.size()) throw new IllegalArgumentException("Duplicate ROW field names");

Try / catch

try {
    return session.execute(sql);
} catch (SemanticException e) {
    if (e.getCode() == DUPLICATE_COLUMN_NAME) {
        // auto-rename or report to user with offending field
    } else throw e;
}

Prevention

When it happens

Trigger: Writing a ROW literal or CAST to ROW with two fields sharing the same name, e.g. CAST(ROW(1,2) AS ROW(a INTEGER, a VARCHAR)) or ROW('x' AS k, 'y' AS k).

Common situations: Hand-written SQL with copy-pasted field lists, generated SQL from ORMs producing duplicate aliases, schema evolution tools emitting a ROW type with duplicated column names.

Related errors


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