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
- Rename the duplicated field in the ROW type definition
- Unquote deliberately-cased names so they differ, or change casing so the lowercased names differ
- 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
- Always give ROW fields explicit unique names
- Deduplicate aliases when generating ROW types programmatically
- Add a schema lint step that checks ROW type definitions for duplicate names
- Remember comparisons are on delimited value or lowercased name — casing cannot hide duplicates
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
- INVALID_FUNCTION_ARGUMENT
- TYPE_MISMATCH
- INVALID_PARAMETER_USAGE
- INVALID_TABLE_PROPERTY
- MISSING_ATTRIBUTE
AI-assisted analysis of prestodb/presto@55bb57d202 (2026-09-04).
Data as JSON: /api/errors/dd4cf0bf1b8e3c72.
Report an issue: GitHub.