Class: DuckDB::ScalarFunction

Inherits:
Object
  • Object
show all
Includes:
FunctionTypeValidation
Defined in:
lib/duckdb/scalar_function.rb,
lib/duckdb/scalar_function/bind_info.rb,
ext/duckdb/scalar_function.c

Overview

Note:

DuckDB::ScalarFunction is experimental.

DuckDB::ScalarFunction encapsulates DuckDB’s scalar function

Defined Under Namespace

Classes: BindInfo

Constant Summary

Constants included from FunctionTypeValidation

FunctionTypeValidation::SUPPORTED_TYPES

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeObject



118
119
120
121
122
123
124
125
# File 'ext/duckdb/scalar_function.c', line 118

static VALUE duckdb_scalar_function_initialize(VALUE self) {
    rubyDuckDBScalarFunction *p;
    TypedData_Get_Struct(self, rubyDuckDBScalarFunction, &scalar_function_data_type, p);
    p->scalar_function = duckdb_create_scalar_function();
    p->function_proc = Qnil;
    p->bind_proc = Qnil;
    return self;
}

Class Method Details

.create(return_type:, name: nil, parameter_type: nil, parameter_types: nil, varargs_type: nil, null_handling: false) {|fixed_args..., *varargs| ... } ⇒ DuckDB::ScalarFunction

Create and configure a scalar function in one call

Examples:

Single parameter function

sf = DuckDB::ScalarFunction.create(
  name: :triple,
  return_type: :integer,
  parameter_type: :integer
) { |v| v * 3 }

Multiple parameters

sf = DuckDB::ScalarFunction.create(
  name: :add,
  return_type: :integer,
  parameter_types: [:integer, :integer]
) { |a, b| a + b }

Varargs function (variable number of arguments)

sf = DuckDB::ScalarFunction.create(
  name: :sum_all,
  return_type: :integer,
  varargs_type: :integer
) { |*args| args.sum }

Fixed parameter(s) followed by varargs

sf = DuckDB::ScalarFunction.create(
  name: :join_with_sep,
  return_type: :varchar,
  parameter_type: :varchar,  # separator
  varargs_type: :varchar     # values to join
) { |sep, *parts| parts.join(sep) }

No parameters (constant function)

sf = DuckDB::ScalarFunction.create(
  name: :random_num,
  return_type: :integer
) { rand(100) }

NULL-aware function (block receives nil for NULL inputs)

sf = DuckDB::ScalarFunction.create(
  name: :null_as_zero,
  return_type: :integer,
  parameter_type: :integer,
  null_handling: true
) { |v| v.nil? ? 0 : v }

Parameters:

  • name (String, Symbol, nil) (defaults to: nil)

    the function name; use nil when creating overloads intended for use in a DuckDB::ScalarFunctionSet (the set provides the name)

  • return_type (DuckDB::LogicalType|:logical_type_symbol)

    the return type

  • parameter_type (DuckDB::LogicalType|:logical_type_symbol, nil) (defaults to: nil)

    single fixed parameter type

  • parameter_types (Array<DuckDB::LogicalType|:logical_type_symbol>, nil) (defaults to: nil)

    multiple fixed parameter types

  • varargs_type (DuckDB::LogicalType|:logical_type_symbol, nil) (defaults to: nil)

    trailing varargs element type; can be combined with parameter_type/parameter_types to add fixed leading parameters

  • null_handling (Boolean) (defaults to: false)

    when true, calls set_special_handling so the block receives nil for NULL inputs instead of DuckDB short-circuiting and returning NULL. Default is false (standard SQL NULL propagation).

Yields:

  • (fixed_args..., *varargs)

    the function implementation

Returns:

Raises:

  • (ArgumentError)

    if block is not provided, or both parameter_type and parameter_types are specified



66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
# File 'lib/duckdb/scalar_function.rb', line 66

def self.create( # rubocop:disable Metrics/MethodLength,Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity,Metrics/ParameterLists
  return_type:, name: nil, parameter_type: nil, parameter_types: nil, varargs_type: nil, null_handling: false, &
)
  raise ArgumentError, 'Block required' unless block_given?
  raise ArgumentError, 'Cannot specify both parameter_type and parameter_types' if parameter_type && parameter_types

  params = if parameter_type
             [parameter_type]
           elsif parameter_types
             parameter_types
           else
             []
           end

  sf = new
  sf.name = name.to_s if name
  sf.return_type = return_type
  params.each { |type| sf.add_parameter(type) }
  sf.varargs_type = varargs_type if varargs_type
  sf.set_special_handling if null_handling
  sf.set_function(&)
  sf
end

Instance Method Details

#add_parameter(logical_type) ⇒ DuckDB::ScalarFunction

Adds a parameter to the scalar function. Currently supports BIGINT, BLOB, BOOLEAN, DATE, DECIMAL, DOUBLE, FLOAT, HUGEINT, INTEGER, INTERVAL, SMALLINT, TIME, TIMESTAMP, TIMESTAMP_S, TIMESTAMP_MS, TIMESTAMP_NS, TIME_TZ, TIMESTAMP_TZ, TINYINT, UBIGINT, UHUGEINT, UINTEGER, USMALLINT, UTINYINT, UUID, and VARCHAR types. For DECIMAL, pass a DuckDB::LogicalType instance created with DuckDB::LogicalType.create_decimal(width, scale).

Parameters:

Returns:

Raises:



101
102
103
104
105
# File 'lib/duckdb/scalar_function.rb', line 101

def add_parameter(logical_type)
  logical_type = check_supported_type!(logical_type)

  _add_parameter(logical_type)
end

#name=(name) ⇒ Object



127
128
129
130
131
132
133
134
135
# File 'ext/duckdb/scalar_function.c', line 127

static VALUE rbduckdb_scalar_function_set_name(VALUE self, VALUE name) {
    rubyDuckDBScalarFunction *p;
    TypedData_Get_Struct(self, rubyDuckDBScalarFunction, &scalar_function_data_type, p);

    const char *str = StringValuePtr(name);
    duckdb_scalar_function_set_name(p->scalar_function, str);

    return self;
}

#return_type=(logical_type) ⇒ DuckDB::ScalarFunction

Sets the return type for the scalar function. Currently supports BIGINT, BLOB, BOOLEAN, DATE, DECIMAL, DOUBLE, FLOAT, HUGEINT, INTEGER, INTERVAL, SMALLINT, TIME, TIMESTAMP, TIMESTAMP_S, TIMESTAMP_MS, TIMESTAMP_NS, TIME_TZ, TIMESTAMP_TZ, TINYINT, UBIGINT, UHUGEINT, UINTEGER, USMALLINT, UTINYINT, UUID, and VARCHAR types. For DECIMAL, pass a DuckDB::LogicalType instance created with DuckDB::LogicalType.create_decimal(width, scale).

Parameters:

Returns:

Raises:



116
117
118
119
120
# File 'lib/duckdb/scalar_function.rb', line 116

def return_type=(logical_type)
  logical_type = check_supported_type!(logical_type)

  _set_return_type(logical_type)
end

#set_bind {|bind_info| ... } ⇒ DuckDB::ScalarFunction

Registers a bind callback for the scalar function. The block is called once at query planning time, before execution. It receives a DuckDB::ScalarFunction::BindInfo object.

Examples:

Validate argument count at planning time

sf.set_bind do |bind_info|
  bind_info.set_error('expected 1 argument') if bind_info.argument_count != 1
end

Yields:

  • (bind_info)

    called at planning time with a BindInfo object

Yield Parameters:

Returns:

Raises:

  • (ArgumentError)

    if no block is given



168
169
170
171
172
# File 'lib/duckdb/scalar_function.rb', line 168

def set_bind(&block)
  raise ArgumentError, 'block is required' unless block

  _set_bind(&block)
end

#set_functionObject

:nodoc:



304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
# File 'ext/duckdb/scalar_function.c', line 304

static VALUE rbduckdb_scalar_function_set_function(VALUE self) {
    rubyDuckDBScalarFunction *p;

    if (!rb_block_given_p()) {
        rb_raise(rb_eArgError, "block is required");
    }

    TypedData_Get_Struct(self, rubyDuckDBScalarFunction, &scalar_function_data_type, p);

    p->function_proc = rb_block_proc();

    duckdb_scalar_function_set_extra_info(p->scalar_function, p, NULL);
    duckdb_scalar_function_set_function(p->scalar_function, scalar_function_callback);

    /*
     * Mark as volatile to prevent constant folding during query optimization.
     * This prevents DuckDB from evaluating the function at planning time.
     */
    duckdb_scalar_function_set_volatile(p->scalar_function);

    /* Ensure the global executor thread is running for multi-thread dispatch */
    rbduckdb_function_executor_ensure_started();

    return self;
}

#set_name(name) ⇒ Object



127
128
129
130
131
132
133
134
135
# File 'ext/duckdb/scalar_function.c', line 127

static VALUE rbduckdb_scalar_function_set_name(VALUE self, VALUE name) {
    rubyDuckDBScalarFunction *p;
    TypedData_Get_Struct(self, rubyDuckDBScalarFunction, &scalar_function_data_type, p);

    const char *str = StringValuePtr(name);
    duckdb_scalar_function_set_name(p->scalar_function, str);

    return self;
}

#set_special_handlingDuckDB::ScalarFunction

Sets special NULL handling for the scalar function. By default DuckDB skips the callback and returns NULL when any input row contains a NULL argument. Calling this method disables that behaviour so the block is invoked even when inputs are NULL, receiving nil for each NULL argument. This lets the function implement its own NULL semantics (e.g. treating NULL as 0, or counting NULLs).

Wraps duckdb_scalar_function_set_special_handling.

Returns:



132
133
134
# File 'lib/duckdb/scalar_function.rb', line 132

def set_special_handling
  _set_special_handling
end

#varargs_type=(logical_type) ⇒ DuckDB::ScalarFunction

Sets the varargs type for the scalar function. Marks the function to accept a variable number of trailing arguments all of the given type. Can be combined with add_parameter to add fixed leading parameters (e.g. a separator followed by a variable list of values). The block receives fixed parameters positionally, then varargs as a splat (|fixed, *rest|). Currently supports BIGINT, BLOB, BOOLEAN, DATE, DECIMAL, DOUBLE, FLOAT, HUGEINT, INTEGER, INTERVAL, SMALLINT, TIME, TIMESTAMP, TIMESTAMP_S, TIMESTAMP_MS, TIMESTAMP_NS, TIME_TZ, TIMESTAMP_TZ, TINYINT, UBIGINT, UHUGEINT, UINTEGER, USMALLINT, UTINYINT, UUID, and VARCHAR types. For DECIMAL, pass a DuckDB::LogicalType instance created with DuckDB::LogicalType.create_decimal(width, scale).

Parameters:

Returns:

Raises:



149
150
151
152
153
# File 'lib/duckdb/scalar_function.rb', line 149

def varargs_type=(logical_type)
  logical_type = check_supported_type!(logical_type)

  _set_varargs(logical_type)
end