EvalExpr_Rhai DuckDB Extension

The EvalExpr_Rhai extension, developed by Query.Farm, enables DuckDB to evaluate expressions written in the Rhai scripting language directly within SQL queries. Rhai is a lightweight, embedded scripting language designed for safety, speed, and ease of useβ€”making it ideal for custom logic, transformations, and dynamic expression evaluation.

DuckDB extensions are plugins that expand the core DuckDB engine with new capabilities.

Key Features

  • Inline expression evaluation: Use Rhai scripts within SQL queries to compute values dynamically.
  • Custom logic and transformations: Perform conditional logic, mathematical operations, string manipulation, and more using familiar scripting syntax.
  • Secure and sandboxed: Execute scripts in a safe, isolated environment with no filesystem or network access by default.
  • High performance: Rhai’s fast runtime ensures minimal overhead even when evaluating complex expressions.

Use Cases

  • Creating derived columns using logic too complex for standard SQL expressions.
  • Implementing dynamic filters, rules, or scoring systems within queries.
  • Embedding custom business logic without needing to write a DuckDB extension in a lower-level language.

Getting Started

EvalExpr_Rhai is a DuckDB community extension maintained and supported by Query.Farm.

Install EvalExpr_Rhai in DuckDB by running:

INSTALL evalexpr_rhai FROM community;

Then load it with:

LOAD evalexpr_rhai;

API

The extension provides a single function:

evalexpr_rhai(VARCHAR, JSON) -> UNION['ok': JSON, 'error': VARCHAR]

The arguments in order are:

  1. The Rhai expression to evaluate.
  2. Any context values that will be available to the Rhai expression by accessing a variable called context.

The return value is a union type. The union type is very similar to the Result type from Rust.

If the Rhai expression was successfully evaluated the JSON result of the expression will be returned in the ok element of the union. If there was an error evaluating the expression it will be returned in the error element of the expression.

Love ❀️ this DuckDB extension? You’ll Love This.

Get the best from Query.Farm β€” smart tips, powerful tools, and project updates sent directly to your inbox, but only when we’ve got something great to share.

Examples

-- Calculate the value of an expression
SELECT evalexpr_rhai('40+2');

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚     evalexpr_rhai('40+2')     β”‚
β”‚ union(ok json, error varchar) β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ 42                            β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

-- Expression's return type is a union of
--
-- ok JSON - the result of the expression as a JSON value
-- error VARCHAR - the error if any from evaluating the expression

-- Demonstrate returning a JSON object from Rhai
SELECT evalexpr_rhai('#{"apple": 5, "price": 3.52}').ok;

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ (evalexpr_rhai('#{"apple": 5, "price": 3.52}')).ok β”‚
β”‚                        json                        β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ {"apple":5,"price":3.52}                           β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

-- Demonstrate what happens when the expression
-- cannot be parsed, an error is returned.
SELECT evalexpr_rhai('#{"apple: 5}').error;

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚       (evalexpr_rhai('#{"apple: 5}')).error        β”‚
β”‚                      varchar                       β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Open string is not terminated (line 1, position 3) β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

-- Either .ok or .error will be populated but never both.

-- When evaluating and expression you can also pass in
-- variables via context.
CREATE TABLE employees (name text, state text, zip integer);
INSERT INTO employees values
  ('Jane', 'FL', 33139),
  ('John', 'NJ', 08520);

-- Pass the row from the employees table in as "context.row"
SELECT evalexpr_rhai(
  '
  context.row.name + " is in " + context.row.state
  ',
  {
    row: employees
  }) AS result from employees;

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚            result             β”‚
β”‚ union(ok json, error varchar) β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ "Jane is in FL"               β”‚
β”‚ "John is in NJ"               β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

-- To demonstrate how Rhai can be used to implement
-- a function in DuckDB, the next example creates
-- a macro function that calls a Rhai function
-- to calculate the Collatz sequence length.

CREATE MACRO collatz_series_length(n) AS
  evalexpr_rhai('
    fn collatz_series(n) {
        let count = 0;
        while n > 1 {
          count += 1;
          if n % 2 == 0 {
              n /= 2;
          } else {
              n = n * 3 + 1;
          }
        }
        return count
    }
    collatz_series(context.n)
  ', {'n': n});

-- Use the previously defined macro.
SELECT
  collatz_series_length(range).ok::bigint AS sequence_length,
  range AS starting_value
FROM
  range(10000, 20000)
ORDER BY 1 DESC limit 10;

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ sequence_length β”‚ starting_value β”‚
β”‚      int64      β”‚     int64      β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚             278 β”‚          17647 β”‚
β”‚             278 β”‚          17673 β”‚
β”‚             275 β”‚          13255 β”‚
β”‚             273 β”‚          19593 β”‚
β”‚             273 β”‚          19883 β”‚
β”‚             270 β”‚          14695 β”‚
β”‚             270 β”‚          15039 β”‚
β”‚             267 β”‚          10971 β”‚
β”‚             265 β”‚          16457 β”‚
β”‚             265 β”‚          16777 β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ 10 rows                2 columns β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜