apache/cassandra · error · InvalidRequestException

Masking function doesn't belong to the same keyspace as the…

Error message

Masking function %s doesn't belong to the same keyspace as the table %s.%s

What it means

ColumnMask.findMaskingFunction requires that a non-native (user-defined) masking function lives in the same keyspace as the masked table. When the resolved UDF's keyspace differs from the table's keyspace, the DDL is rejected to keep masking policies keyspace-local.

Solutions

  1. Create the masking UDF in the same keyspace as the table (CREATE FUNCTION ks1.my_mask ...).
  2. Or use the built-in native masking functions, which have no keyspace restriction.
  3. Or create the table in the keyspace that already holds the masking function.

Example fix

// before
CREATE FUNCTION shared_ks.mask_email(text) ...;
ALTER TABLE app_ks.users ALTER email MASKED WITH shared_ks.mask_email(text);
// after
CREATE FUNCTION app_ks.mask_email(text) ...;
ALTER TABLE app_ks.users ALTER email MASKED WITH app_ks.mask_email(text);
Defensive patterns

Strategy: validation

Validate before calling

if (fnKeyspace != null && !fnKeyspace.equals(tableKeyspace) && !isNative(fnName))
  throw new IllegalArgumentException("masking UDF must live in keyspace " + tableKeyspace);

Prevention

When it happens

Trigger: ALTER TABLE ks1.t ALTER col MASKED WITH ks2.my_mask(...) where ks2 != ks1 and my_mask is a UDF.

Common situations: Teams share masking UDFs from a central keyspace and reference them fully-qualified from tables in other keyspaces; native masking functions are exempt but UDFs are not.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


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

Appendix: source

Thrown at src/java/org/apache/cassandra/cql3/functions/masking/ColumnMask.java:256

            List<AssignmentTestable> args = new ArrayList<>(rawPartialArguments.size() + 1);
            args.add(type);
            args.addAll(rawPartialArguments);

            Function function = FunctionResolver.get(keyspace, name, args, keyspace, table, type, functions);

            if (function == null)
                throw invalidRequest("Unable to find masking function for %s, " +
                                     "no declared function matches the signature %s",
                                     column, this);

            if (function.isAggregate())
                throw invalidRequest("Aggregate function %s cannot be used for masking table columns", this);

            if (function.isNative() && !(function instanceof MaskingFunction))
                throw invalidRequest("Not-masking function %s cannot be used for masking table columns", this);

            if (!function.isNative() && !function.name().keyspace.equals(keyspace))
                throw invalidRequest("Masking function %s doesn't belong to the same keyspace as the table %s.%s",
                                     this, keyspace, table);

            CQL3Type returnType = function.returnType().asCQL3Type();
            CQL3Type expectedType = type.asCQL3Type();
            if (!returnType.equals(expectedType))
                throw invalidRequest("Masking function %s return type is %s. " +
                                     "This is different to the type of the masked column %s of type %s. " +
                                     "Masking functions can only be attached to table columns " +
                                     "if they return the same data type as the masked column.",
                                     this, returnType, column, expectedType);

            return (ScalarFunction) function;
        }

        private ByteBuffer[] preparePartialArguments(String keyspace, ScalarFunction function)
        {
            // Note that there could be null arguments
            ByteBuffer[] arguments = new ByteBuffer[rawPartialArguments.size()];

View on GitHub (pinned to 88fd0f6a0e)