{"record":{"id":"881ea05d8bbac08e","repo":"apache/cassandra","slug":"function-s-must-have-s-directive","errorCode":null,"errorMessage":"Function '%s' must have %s directive","messagePattern":"Function '(.+?)' must have (.+?) directive","errorType":"validation","errorClass":"InvalidRequestException","httpStatus":null,"severity":"error","filePath":"src/java/org/apache/cassandra/cql3/statements/schema/CreateFunctionStatement.java","lineNumber":150,"sourceCode":"                              calledOnNullInput,\n                              language,\n                              body);\n\n        UserFunction existingFunction = keyspace.userFunctions.find(function.name(), argumentTypes).orElse(null);\n        if (null != existingFunction)\n        {\n            if (existingFunction.isAggregate())\n                throw ire(\"Function '%s' cannot replace an aggregate\", functionName);\n\n            if (ifNotExists)\n                return schema;\n\n            if (!orReplace)\n                throw ire(\"Function '%s' already exists\", functionName);\n\n            if (calledOnNullInput != ((UDFunction) existingFunction).isCalledOnNullInput())\n            {\n                throw ire(\"Function '%s' must have %s directive\",\n                          functionName,\n                          calledOnNullInput ? \"CALLED ON NULL INPUT\" : \"RETURNS NULL ON NULL INPUT\");\n            }\n\n            if (!returnType.isCompatibleWith(existingFunction.returnType()))\n            {\n                throw ire(\"Cannot replace function '%s', the new return type %s is not compatible with the return type %s of existing function\",\n                          functionName,\n                          returnType.asCQL3Type(),\n                          existingFunction.returnType().asCQL3Type());\n            }\n\n            // TODO: update dependent aggregates\n        }\n\n        return schema.withAddedOrUpdated(keyspace.withSwapped(keyspace.userFunctions.withAddedOrUpdated(function)));\n    }\n","sourceCodeStart":132,"sourceCodeEnd":168,"githubUrl":"https://github.com/apache/cassandra/blob/88fd0f6a0eaed8943f05ac9e8f947882b8ddc8f1/src/java/org/apache/cassandra/cql3/statements/schema/CreateFunctionStatement.java#L132-L168","documentation":"When OR REPLACE replaces an existing UDF, Cassandra requires the null-input behavior directive to match: a replacement declared CALLED ON NULL INPUT cannot replace one declared RETURNS NULL ON NULL INPUT, and vice versa. The statement compares calledOnNullInput against the existing UDFunction's flag and throws when they differ.","triggerScenarios":"CREATE OR REPLACE FUNCTION where the new declaration's CALLED ON NULL INPUT / RETURNS NULL ON NULL INPUT directive differs from the existing function's directive.","commonSituations":"Changing a function's null-handling semantics during an update; copying a function body but altering the directive; schema generated by tools that default the directive differently.","solutions":["Match the existing directive in the OR REPLACE statement","Drop and recreate the function if the null-input semantics must genuinely change","Query system_schema.functions to check the existing function's called_on_null_input flag first"],"exampleFix":"// before\nCREATE OR REPLACE FUNCTION ks.f(int) CALLED ON NULL INPUT RETURNS int ...  -- existing is RETURNS NULL ON NULL INPUT\n// after\nCREATE OR REPLACE FUNCTION ks.f(int) RETURNS NULL ON NULL INPUT RETURNS int ...","handlingStrategy":"validation","validationCode":"ResultSet rs = session.execute(\"SELECT called_on_null_input FROM system_schema.functions WHERE keyspace_name = ? AND function_name = ?\", ks, fn);\nboolean existing = rs.all().get(0).getBool(0);\nif (existing != newCalledOnNullInput) throw new IllegalStateException(\"Directive mismatch for \" + fn);","typeGuard":null,"tryCatchPattern":"try { session.execute(stmt); } catch (InvalidRequestException e) { if (e.getMessage().contains(\"must have\")) { /* align CALLED/RETURNS ON NULL INPUT directive */ } else throw e; }","preventionTips":["Look up called_on_null_input before OR REPLACE","Keep the null-input directive stable across function versions","To change semantics, drop and recreate explicitly"],"tags":["cql","udf","or-replace","null-input"],"backgroundTag":"schema-validation-failed","analyzedSha":"88fd0f6a0eaed8943f05ac9e8f947882b8ddc8f1","analyzedAt":"2026-09-10T07:29:22.284Z","contentChangedAt":"2026-09-10T07:29:22.284Z","schemaVersion":2},"datasetVersion":"2026-09-17T15:17:12.973Z"}