apache/cassandra · error · InvalidRequestException

Function ' ' is still referenced by column masks in tables

Error message

Function '%s' is still referenced by column masks in tables %s

What it means

DROP FUNCTION was rejected because the function is used as a column mask (dynamic data masking) on one or more tables. Cassandra refuses to drop functions still referenced by column masking definitions.

Solutions

  1. Remove the mask from the dependent columns first (ALTER TABLE ... ALTER col ... DROP MASKED or similar).
  2. Drop the listed tables if they are no longer needed.
  3. Then re-run DROP FUNCTION.

Example fix

// before
DROP FUNCTION ks.mask_ssn (text);
// after
ALTER TABLE ks.users ALTER ssn DROP MASKED;
DROP FUNCTION ks.mask_ssn (text);
Defensive patterns

Strategy: try-catch

Validate before calling

boolean masked = keyspace.tables.stream().anyMatch(t -> t.columns().stream()
    .anyMatch(c -> c.mask() != null && c.mask().function().equals(fn)));
if (masked) unmaskColumnsFirst();

Try / catch

try { dropFunction(...); } catch (InvalidRequestException e) { if (e.getMessage().contains("referenced by column masks")) { /* unmask listed tables, then retry */ } else throw e; }

Prevention

When it happens

Trigger: DROP FUNCTION on a function referenced by a MASKED WITH column mask in any table of the keyspace; detected via keyspace.tablesUsingFunction(function).

Common situations: Forgetting to unmask column definitions before removing a masking function; security cleanup of masking functions while masking policies are still applied.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


AI-assisted analysis of apache/cassandra@88fd0f6a0e (2026-09-10). Data as JSON: /api/errors/c45b451ed8f96875. Report an issue: GitHub.

Appendix: source

Thrown at src/java/org/apache/cassandra/cql3/statements/schema/DropFunctionStatement.java:141

            throw ire("Function '%s' doesn't exist", name);
        }

        String dependentAggregates =
            keyspace.userFunctions
                    .aggregatesUsingFunction(function)
                    .map(a -> a.name().toString())
                    .collect(joining(", "));

        if (!dependentAggregates.isEmpty())
            throw ire("Function '%s' is still referenced by aggregates %s", name, dependentAggregates);

        String dependentTables = keyspace.tablesUsingFunction(function)
                                         .map(table -> table.name)
                                         .collect(joining(", "));

        if (!dependentTables.isEmpty())
            throw ire("Function '%s' is still referenced by column masks in tables %s", name, dependentTables);

        return schema.withAddedOrUpdated(keyspace.withSwapped(keyspace.userFunctions.without(function)));
    }

    SchemaChange schemaChangeEvent(KeyspacesDiff diff)
    {
        UserFunctions dropped = diff.altered.get(0).udfs.dropped;
        assert dropped.size() == 1;
        return SchemaChange.forFunction(Change.DROPPED, (UDFunction) dropped.iterator().next());
    }

    public void authorize(ClientState client)
    {
        KeyspaceMetadata keyspace = Schema.instance.getKeyspaceMetadata(keyspaceName);
        if (null == keyspace)
            return;

        Stream<UserFunction> functions = keyspace.userFunctions.get(new FunctionName(keyspaceName, functionName)).stream();

View on GitHub (pinned to 88fd0f6a0e)