{"record":{"id":"32c8efabefbc04fa","repo":"tursodatabase/turso","slug":"function-function-was-called-with-a-null-value-a","errorCode":null,"errorMessage":"Function {function} was called with a NULL value at ordinal {ordinal}.","messagePattern":"Function (.+?) was called with a NULL value at ordinal (.+?)\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"bindings/dotnet/src/Turso.Data.Sqlite/SqliteConnection.Functions.cs","lineNumber":81,"sourceCode":"        => function(ConvertArgument<T1>(name, args[0], 0), ConvertArgument<T2>(name, args[1], 1));\n\n    private static object? InvokeTypedFunction<TState, TResult>(TState state, Func<TState, TResult> function, object?[] args)\n        => function(state);\n\n    private static object? InvokeTypedFunction<TState, T1, TResult>(string name, TState state, Func<TState, T1, TResult> function, object?[] args)\n        => function(state, ConvertArgument<T1>(name, args[0], 0));\n\n    private static object? InvokeTypedFunction<TState, T1, T2, TResult>(string name, TState state, Func<TState, T1, T2, TResult> function, object?[] args)\n        => function(state, ConvertArgument<T1>(name, args[0], 0), ConvertArgument<T2>(name, args[1], 1));\n\n    private static T ConvertArgument<T>(string functionName, object? value, int ordinal)\n    {\n        if (value is null or DBNull)\n        {\n            if (!typeof(T).IsValueType || Nullable.GetUnderlyingType(typeof(T)) is not null)\n                return default!;\n\n            throw new InvalidOperationException(Properties.Resources.UDFCalledWithNull(functionName, ordinal));\n        }\n\n        var targetType = Nullable.GetUnderlyingType(typeof(T)) ?? typeof(T);\n        if (targetType == typeof(object))\n            return (T)value;\n        if (targetType == typeof(byte[]) && value is byte[] bytes)\n            return (T)(object)bytes;\n        if (targetType == typeof(string))\n            return (T)(object)Convert.ToString(value, CultureInfo.InvariantCulture)!;\n        if (targetType == typeof(bool))\n            return (T)(object)Convert.ToBoolean(value, CultureInfo.InvariantCulture);\n\n        return (T)Convert.ChangeType(value, targetType, CultureInfo.InvariantCulture);\n    }\n\n    private static TursoExtensionValue InvokeScalarFunction(IntPtr context, int argc, IntPtr argv, IntPtr contextDestructor, IntPtr valueDestructor)\n    {\n        try","sourceCodeStart":63,"sourceCodeEnd":99,"githubUrl":"https://github.com/tursodatabase/turso/blob/6c7252267988c76e632af00a671e4b9788dfae13/bindings/dotnet/src/Turso.Data.Sqlite/SqliteConnection.Functions.cs#L63-L99","documentation":"A user-defined function registered with CreateFunction using a typed delegate (e.g. Func<int, int, int>) received SQL NULL at an argument position whose .NET type is a non-nullable value type (int, long, double, etc., not int? or object). ConvertArgument cannot map NULL/DBNull onto a non-nullable value type, so it throws InvalidOperationException naming the function and the 0-based ordinal. Nullable value types (int?) and reference types (string, byte[], object) accept NULL and return null/default instead.","triggerScenarios":"'SELECT myfn(NULL)' where myfn was registered as Func<long, long>; passing a column that contains NULLs (e.g. 'SELECT myfn(nullable_col) FROM t') to a function whose argument type is a plain value type; JOIN/WHERE shapes that yield NULL for a missing side; COALESCE omitted because the developer assumed the column was NOT NULL.","commonSituations":"Schema drift where a column becomes nullable (migration adds rows with NULL), testing only with clean seed data, porting UDFs from providers or databases where NULL arguments were silently coerced to 0.","solutions":["Change the UDF's argument type to its nullable form (Func<int?, ...>) or use object?/byte[]? and handle null inside the function.","Filter NULLs at the call site: 'SELECT myfn(col) FROM t WHERE col IS NOT NULL' or wrap the argument in COALESCE(col, 0).","Fix the schema: add a NOT NULL constraint or backfill the column if NULLs are not intended."],"exampleFix":"// before\nconn.CreateFunction<long, long>(\"double\", v => v * 2);\ncmd.CommandText = \"SELECT double(x) FROM t\"; // x is NULL -> throws\n\n// after\nconn.CreateFunction<long?, long?>(\"double\", v => v is null ? null : v * 2);\ncmd.CommandText = \"SELECT double(x) FROM t\";","handlingStrategy":"type-guard","validationCode":"static void RegisterSafeFunction(SqliteConnection conn, string name, Func<object?, long?> f)\n{\n    conn.CreateFunction<object?, long?>(name, f); // object? accepts NULL instead of throwing\n}\n\n// or filter/replace NULLs in SQL before they reach the UDF:\n// SELECT myfn(COALESCE(nullable_col, 0)) FROM t;\n// SELECT myfn(col) FROM t WHERE col IS NOT NULL;","typeGuard":"static bool IsSqlNull(object? value) => value is null or DBNull;\n\n// inside an object?-typed UDF:\n// if (IsSqlNull(args[0])) return null;","tryCatchPattern":"null","preventionTips":["Prefer nullable value types (int?, long?) or object? for UDF arguments unless NULL is truly impossible.","Add NOT NULL constraints or COALESCE at call sites for columns fed to typed UDFs.","Write a test that calls each UDF with NULL once; it fails fast at registration design time rather than in production."],"tags":["ado-net","sqlite","turso","user-defined-functions","null"],"backgroundTag":"udf-null-argument","analyzedSha":"6c7252267988c76e632af00a671e4b9788dfae13","analyzedAt":"2026-08-20T07:02:18.389Z","contentChangedAt":"2026-08-20T07:02:18.389Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}