prestodb/presto · error · PrestoException
COLUMN_NOT_FOUND
COLUMN_NOT_FOUND
Error message
Column '%s' does not exist as a top-level column. The AFTER clause requires a top-level column name, not a nested field path.
What it means
The AFTER clause of addColumn positions a new column among its siblings at the same nesting level, so the AFTER target must be a top-level column name. A dotted path like 'struct_col.nested' is a valid Iceberg nested-field path but not a valid AFTER target, so the connector raises COLUMN_NOT_FOUND with an explanatory message guarding direct SPI callers.
Source
Thrown at presto-iceberg/src/main/java/com/facebook/presto/iceberg/IcebergAbstractMetadata.java:1338
}
if (position instanceof ColumnPosition.After) {
String afterColumnName = ((ColumnPosition.After) position).getColumnName();
// The engine lowercases the target name, while an Iceberg field keeps whatever case the table was
// created with, so the lookup has to be case-insensitive to match the name SHOW COLUMNS reports.
// Only the top-level columns are scanned, rather than using Schema.caseInsensitiveFindField: that
// builds a lower-case index over the whole schema, which resolves dotted paths to nested fields
// whose leaf name would be wrong here, and throws outright if any two fields anywhere in the table
// differ only by case.
//
// A name that does not resolve is rejected here rather than passed to moveAfter, which would raise
// an IllegalArgumentException for a name it cannot find. The engine already rejects a target that
// is not a real column, so this is the guard for a caller that goes through the SPI directly.
//
// A dotted name (e.g. "struct_col.nested") is a valid nested-field path in Iceberg but not a valid
// AFTER target: the AFTER clause positions a column among its siblings at the same nesting level,
// so only a top-level column name is accepted here.
if (afterColumnName.contains(".")) {
throw new PrestoException(COLUMN_NOT_FOUND, format(
"Column '%s' does not exist as a top-level column. The AFTER clause requires a top-level column name, not a nested field path.",
afterColumnName));
}
NestedField afterColumn = findTopLevelColumn(schema, afterColumnName)
.orElseThrow(() -> new PrestoException(COLUMN_NOT_FOUND, format("Column '%s' does not exist", afterColumnName)));
updateSchema.moveAfter(columnName, afterColumn.name());
return;
}
throw new PrestoException(NOT_SUPPORTED, "Unsupported column position: " + position);
}
/**
* Finds a top-level column of {@code schema} by name, preferring an exact match over a case-insensitive
* one, so that a table whose top-level columns differ only by case resolves deterministically rather than
* by whatever order the fields happen to be in.
*/
private static Optional<NestedField> findTopLevelColumn(Schema schema, String columnName)
{View on GitHub (pinned to 55bb57d202)
Solutions
- Use only a top-level column name as the AFTER target.
- To add a nested field inside a struct, add it to the struct's row type (modify the parent column's type) instead of using AFTER with a dotted path.
- Validate that afterColumnName contains no '.' before calling addColumn.
Example fix
// before metadata.addColumn(session, handle, col, ColumnPosition.first(false, "location.city")); // after metadata.addColumn(session, handle, col, ColumnPosition.first(false, "location"));
Defensive patterns
Strategy: validation
Validate before calling
if (afterColumnName != null && afterColumnName.contains(".")) {
throw new IllegalArgumentException("AFTER target must be a top-level column, got: " + afterColumnName);
}
metadata.addColumn(session, handle, column, ColumnPosition.first(false, afterColumnName)); Type guard
boolean isTopLevelAfterTarget(ColumnPosition pos) {
return pos == null || !(pos instanceof ColumnPosition.First) || !((ColumnPosition.First) pos).getColumn().contains(".");
} Try / catch
try {
metadata.addColumn(session, handle, column, position);
} catch (PrestoException e) {
if (e.getErrorCode().code() == COLUMN_NOT_FOUND && afterColumnName.contains(".")) {
throw new IllegalArgumentException("Use a top-level AFTER target, not nested path: " + afterColumnName, e);
}
throw e;
} Prevention
- Only pass simple top-level names to AFTER positions.
- Add nested fields by modifying the parent struct type, not via AFTER.
- Validate position targets against SHOW COLUMNS before schema evolution calls.
When it happens
Trigger: Calling addColumn with a ColumnPosition.First(false, "struct_col.nested")-style after-column containing a '.'; the SQL parser normally rejects this, but direct SPI/procedure callers pass the dotted name through.
Common situations: Programmatic schema-evolution via the connector SPI; tools constructing ColumnPosition objects with nested field paths; users attempting to insert a nested field relative to a sibling inside a struct.
Related errors
- COLUMN_NOT_FOUND
- NOT_FOUND
- Not a Hive table:
- Not an Iceberg table: ${getSchemaTableName()}
- Table not found: ${new SchemaTableName(database, tableName)}
AI-assisted analysis of prestodb/presto@55bb57d202 (2026-09-04).
Data as JSON: /api/errors/5b801c660cd8f7fd.
Report an issue: GitHub.