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

  1. Use only a top-level column name as the AFTER target.
  2. 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.
  3. 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

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


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