tursodatabase/turso · error · InvalidOperationException
Function {function} was called with a NULL value at ordinal
Error message
Function {function} was called with a NULL value at ordinal {ordinal}. What it means
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.
Source
Thrown at bindings/dotnet/src/Turso.Data.Sqlite/SqliteConnection.Functions.cs:81
=> function(ConvertArgument<T1>(name, args[0], 0), ConvertArgument<T2>(name, args[1], 1));
private static object? InvokeTypedFunction<TState, TResult>(TState state, Func<TState, TResult> function, object?[] args)
=> function(state);
private static object? InvokeTypedFunction<TState, T1, TResult>(string name, TState state, Func<TState, T1, TResult> function, object?[] args)
=> function(state, ConvertArgument<T1>(name, args[0], 0));
private static object? InvokeTypedFunction<TState, T1, T2, TResult>(string name, TState state, Func<TState, T1, T2, TResult> function, object?[] args)
=> function(state, ConvertArgument<T1>(name, args[0], 0), ConvertArgument<T2>(name, args[1], 1));
private static T ConvertArgument<T>(string functionName, object? value, int ordinal)
{
if (value is null or DBNull)
{
if (!typeof(T).IsValueType || Nullable.GetUnderlyingType(typeof(T)) is not null)
return default!;
throw new InvalidOperationException(Properties.Resources.UDFCalledWithNull(functionName, ordinal));
}
var targetType = Nullable.GetUnderlyingType(typeof(T)) ?? typeof(T);
if (targetType == typeof(object))
return (T)value;
if (targetType == typeof(byte[]) && value is byte[] bytes)
return (T)(object)bytes;
if (targetType == typeof(string))
return (T)(object)Convert.ToString(value, CultureInfo.InvariantCulture)!;
if (targetType == typeof(bool))
return (T)(object)Convert.ToBoolean(value, CultureInfo.InvariantCulture);
return (T)Convert.ChangeType(value, targetType, CultureInfo.InvariantCulture);
}
private static TursoExtensionValue InvokeScalarFunction(IntPtr context, int argc, IntPtr argv, IntPtr contextDestructor, IntPtr valueDestructor)
{
tryView on GitHub (pinned to 6c72522679)
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.
Example fix
// before
conn.CreateFunction<long, long>("double", v => v * 2);
cmd.CommandText = "SELECT double(x) FROM t"; // x is NULL -> throws
// after
conn.CreateFunction<long?, long?>("double", v => v is null ? null : v * 2);
cmd.CommandText = "SELECT double(x) FROM t"; Defensive patterns
Strategy: type-guard
Validate before calling
static void RegisterSafeFunction(SqliteConnection conn, string name, Func<object?, long?> f)
{
conn.CreateFunction<object?, long?>(name, f); // object? accepts NULL instead of throwing
}
// or filter/replace NULLs in SQL before they reach the UDF:
// SELECT myfn(COALESCE(nullable_col, 0)) FROM t;
// SELECT myfn(col) FROM t WHERE col IS NOT NULL; Type guard
static bool IsSqlNull(object? value) => value is null or DBNull; // inside an object?-typed UDF: // if (IsSqlNull(args[0])) return null;
Try / catch
null
Prevention
- 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.
When it happens
Trigger: '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.
Common situations: 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.
Related errors
- Cannot access a disposed object. Object name: 'AggregateInv
- ScalarFunctionRegistration
- Encryption is not supported by {libraryName}.
- Missing parameter values for {parameters}.
- Parameter name {parameterName} is ambiguous.
AI-assisted analysis of tursodatabase/turso@6c72522679 (2026-08-20).
Data as JSON: /api/errors/32c8efabefbc04fa.
Report an issue: GitHub.