prestodb/presto · error · PrestoException

INVALID_FUNCTION_ARGUMENT

INVALID_FUNCTION_ARGUMENT

Error message

COLUMNS descriptor is null

What it means

The exclude_columns table function requires its COLUMNS descriptor argument to be a real descriptor, not the sentinel NULL_DESCRIPTOR. During analyze(), if the descriptor argument equals NULL_DESCRIPTOR (the user passed NULL for the COLUMNS argument), Presto throws INVALID_FUNCTION_ARGUMENT because there are no columns to exclude.

Source

Thrown at presto-main-base/src/main/java/com/facebook/presto/operator/table/ExcludeColumns.java:92

                    "builtin",
                    NAME,
                    ImmutableList.of(
                            TableArgumentSpecification.builder()
                                    .name(TABLE_ARGUMENT_NAME)
                                    .rowSemantics()
                                    .build(),
                            DescriptorArgumentSpecification.builder()
                                    .name(DESCRIPTOR_ARGUMENT_NAME)
                                    .build()),
                    GENERIC_TABLE);
        }

        @Override
        public TableFunctionAnalysis analyze(ConnectorSession session, ConnectorTransactionHandle transaction, Map<String, Argument> arguments)
        {
            DescriptorArgument excludedColumns = (DescriptorArgument) arguments.get(DESCRIPTOR_ARGUMENT_NAME);
            if (excludedColumns.equals(NULL_DESCRIPTOR)) {
                throw new PrestoException(INVALID_FUNCTION_ARGUMENT, "COLUMNS descriptor is null");
            }
            Descriptor excludedColumnsDescriptor = excludedColumns.getDescriptor().orElseThrow(() -> new PrestoException(INVALID_ARGUMENTS, "Missing exclude columns descriptor"));
            if (excludedColumnsDescriptor.getFields().stream().anyMatch(field -> field.getType().isPresent())) {
                throw new PrestoException(INVALID_FUNCTION_ARGUMENT, "COLUMNS descriptor contains types");
            }

            // column names in DescriptorArgument are canonical wrt SQL identifier semantics.
            // column names in TableArgument are not canonical wrt SQL identifier semantics, as they are taken from the corresponding RelationType.
            // because of that, we match the excluded columns names case-insensitive
            // TODO: apply proper identifier semantics
            Set<String> excludedNames = excludedColumnsDescriptor.getFields().stream()
                    .map(Descriptor.Field::getName)
                    .map(name -> name.orElseThrow(() -> new PrestoException(INVALID_ARGUMENTS, "Missing Descriptor field name")).toLowerCase(ENGLISH))
                    .collect(toImmutableSet());

            List<RowType.Field> inputSchema = ((TableArgument) arguments.get(TABLE_ARGUMENT_NAME)).getRowType().getFields();
            Set<String> inputNames = inputSchema.stream()
                    .map(RowType.Field::getName)

View on GitHub (pinned to 55bb57d202)

Solutions

  1. Pass a non-null COLUMNS descriptor with at least one column name
  2. Omit the exclude_columns table function entirely if there is nothing to exclude
  3. In generated SQL, substitute an empty descriptor instead of NULL, or skip the function call when the exclusion list is null
  4. Validate the exclusion list is non-null before building the query

Example fix

// before
SELECT * FROM TABLE(exclude_columns(TABLE => t, COLUMNS => NULL));
// after
SELECT * FROM TABLE(exclude_columns(TABLE => t, COLUMNS => 'col1 col2')); -- or drop the function if nothing to exclude
Defensive patterns

Strategy: validation

Validate before calling

if (columnsArg == null) { throw new IllegalArgumentException("COLUMNS descriptor must not be null for exclude_columns"); }

Type guard

boolean hasValidDescriptor = arguments.get("COLUMNS") instanceof DescriptorArgument && !((DescriptorArgument) arguments.get("COLUMNS")).equals(NULL_DESCRIPTOR);

Try / catch

try { return TABLE(exclude_columns(TABLE => t, COLUMNS => cols)); } catch (PrestoException e) { if (INVALID_FUNCTION_ARGUMENT.toErrorCode().equals(e.getErrorCode())) { /* drop the exclude_columns wrapper and select the table directly */ } throw e; }

Prevention

When it happens

Trigger: Calling TABLE(exclude_columns(TABLE => t, COLUMNS => NULL)) — passing NULL as the COLUMNS descriptor argument to the exclude_columns table function.

Common situations: Dynamically built SQL where the column list parameter was null/empty and serialized as NULL; generated queries from ORMs or scripts that substitute null for an empty descriptor list.

Related errors


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