fv-value
A small expression dialect for computing values over rows.
status == 'ACTIVE' && roundTo(speed * 1.852, 1) > 100
coalesce(nickname, split(email, '@')[0])
geoWithinVolume(zone, position, altitudeM, 5000, 12000) ? 'inside' : 'outside'
Arithmetic, strings, dates, lists, logic and geospatial functions (2D predicates and 3D geodesy), with strict booleans and null propagation. Compile an expression once, evaluate it per row against any scope — a map, or a row slice with no per-row allocation. Add your own functions as closures, remove ones you do not want, or expose only the set you choose.
Install
Quick start
use ;
let expr = compile.unwrap;
assert_eq!; // the columns it reads
let row: Scope = .into;
assert_eq!;
Every Rust example in this README is compiled and run as a doctest by CI.
Guide
Compile once, evaluate per row
compile parses and checks an expression: syntax, function names and arities are all errors at
compile time, before any row is seen. The Expr it returns is Send + Sync, cheap to clone, and
evaluates against anything that implements Lookup:
use ;
let expr = compile.unwrap;
// A row slice: no map built per row — the shape for hot loops.
let rows: = vec!;
let totals: = rows.iter.map.collect;
assert_eq!;
Scope (a BTreeMap), HashMap<String, Value>, Vec<(String, Value)> and Empty all implement
Lookup; implement it yourself to read straight from your own row type.
Values
One numeric type (f64), strings, booleans, null, lists and objects. Value converts from the
obvious Rust types and prints as JSON:
use ;
assert_eq!;
assert_eq!;
assert_eq!;
// Objects and lists are navigable.
assert_eq!;
assert_eq!;
The semantics that matter, all pinned by the conformance vectors:
nullpropagates through arithmetic and most functions:missing + 1isnull.&&,||,!and?:require booleans and short-circuit;!5is an error, notfalse.==never equates a boolean with a number:true == 1isfalse;null == nullistrue.<and friends order two numbers or two strings; anything involvingnullisfalse.%keeps the dividend's sign;/and%by zero are errors.
Errors are typed
use ;
assert!;
assert!;
assert!;
assert_eq!;
assert!;
// Compile-time errors mean a bad expression (report it to the author); run-time errors mean a
// bad row (skip it, null it, count it).
assert!;
assert!;
Your own functions
A Dialect is a Functions registry plus the grammar. Start from the standard set, an empty one,
or anything in between; functions are closures over &[Value]:
use ;
let functions = standard
.with
.with
.without;
let dialect = new;
let row: Scope = .into;
assert_eq!;
assert_eq!;
assert!;
Arity is checked at compile time from the bounds you declare, so a function body can index its
arguments safely. Report a failure with ExprError::call.
The function catalogue
catalogue() describes the standard functions as JSON — names and arity bounds, nothing else —
for tooling that validates expressions without evaluating them (an editor, a publish-time check
in another language). cargo run --example emit_catalogue prints it.
let cat = catalogue;
assert_eq!;
assert_eq!; // variadic
assert!;
Any custom dialect has the same: dialect.catalogue().
Inspecting an expression
Expr::ast() exposes the syntax tree with typed operators and calls already bound to their
functions, for lineage, rewriting or translation to another engine:
use ;
let e = compile.unwrap;
let Binary = e.ast else ;
assert!;
assert!;
The standard functions
103 functions in six groups; catalogue() is the authoritative list.
- Numeric —
abs,floor,ceil,trunc,round,roundTo,sqrt,cbrt,exp,ln,log,log10,log2,pow,hypot,sign,min,max,clamp, trigonometry and hyperbolics,degToRad,radToDeg,pi,factorial,gcd,lcm,toNumber. - String —
upper,lower,trim,trimStart,trimEnd,capitalize,reverse,concat,concatWs,contains,startsWith,endsWith,replace,indexOf,repeat,split,splitPart,substr,substring,left,right,padStart,padEnd,chr,ascii,toHex,toString,regexMatch,regexExtract,regexReplace. - Date —
epochMs,epochToIso,yearOf,monthOf,dayOf,hourOf,minuteOf,dayOfWeekOf,dayOfYear(all UTC, over epoch milliseconds). - Array —
arrayContains,first,last,arraySum,arrayMin,arrayMax,arrayReverse,arrayJoin,distinct. - Logic —
coalesce,isNull,nullIf,isNumber,isString,isBoolean,length,typeOf. - Geo — 2D:
haversineKm,bearingDeg,geoContains,geoWithin,geoIntersects(GeoJSON shapes, DE-9IM); 3D:distance3dM,altitudeBetween,geoWithinVolume,geodeticToEcef,ecefToGeodetic,groundStrike(WGS84, viageo3d).
Using it inside DataFusion
The sibling crate fv-value-datafusion translates a compiled Expr into a
DataFusion expression wherever the semantics are provably identical — so a dialect filter pushes
down to the source and runs vectorized — and falls back to a per-row UDF for the rest. The same
conformance vectors are replayed through DataFusion to prove the two paths agree.
Conformance
The dialect's behaviour is defined by a published set of conformance vectors (in the
fv-contract crate): 221 expressions with their expected
values or errors. The test suite replays every one, and checks the catalogue against the contract's
snapshot, so an implementation or validator in any language can be held to the same definition.
Develop
&& &&
CI runs all of the above plus a build on the declared minimum Rust version (1.85).
Versioning and license
Plain semver; tags vX.Y.Z at the published commit. A change that alters what an existing
expression evaluates to is a major bump. See CHANGELOG.md. Apache-2.0, see
LICENSE.