Skip to main content

reifydb_routine/function/
mod.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2025 ReifyDB
3
4pub mod error;
5pub mod registry;
6
7pub mod blob;
8pub mod clock;
9pub mod date;
10pub mod datetime;
11pub mod duration;
12pub mod flow;
13pub mod identity;
14pub mod is;
15pub mod json;
16pub mod math;
17pub mod meta;
18pub mod rql;
19pub mod series;
20pub mod subscription;
21pub mod testing;
22pub mod text;
23pub mod time;
24pub mod uuid;
25
26use error::{AggregateFunctionResult, GeneratorFunctionResult, ScalarFunctionResult};
27use reifydb_catalog::catalog::Catalog;
28use reifydb_core::{
29	util::ioc::IocContainer,
30	value::column::{
31		Column,
32		columns::Columns,
33		data::ColumnData,
34		view::group_by::{GroupByView, GroupKey},
35	},
36};
37use reifydb_runtime::context::RuntimeContext;
38use reifydb_transaction::transaction::Transaction;
39use reifydb_type::{
40	fragment::Fragment,
41	util::bitvec::BitVec,
42	value::{
43		identity::IdentityId,
44		r#type::{Type, input_types::InputTypes},
45	},
46};
47
48use self::uuid::{v4::UuidV4, v7::UuidV7};
49
50pub struct GeneratorContext<'a> {
51	pub fragment: Fragment,
52	pub params: Columns,
53	pub txn: &'a mut Transaction<'a>,
54	pub catalog: &'a Catalog,
55	pub identity: IdentityId,
56	pub ioc: &'a IocContainer,
57}
58
59pub trait GeneratorFunction: Send + Sync {
60	fn generate<'a>(&self, ctx: GeneratorContext<'a>) -> GeneratorFunctionResult<Columns>;
61}
62
63pub struct ScalarFunctionContext<'a> {
64	pub fragment: Fragment,
65	pub columns: &'a Columns,
66	pub row_count: usize,
67	pub runtime_context: &'a RuntimeContext,
68	pub identity: IdentityId,
69}
70
71pub trait ScalarFunction: Send + Sync {
72	fn scalar<'a>(&'a self, ctx: ScalarFunctionContext<'a>) -> ScalarFunctionResult<ColumnData>;
73	fn return_type(&self, input_types: &[Type]) -> Type;
74}
75
76pub struct AggregateFunctionContext<'a> {
77	pub fragment: Fragment,
78	pub column: &'a Column,
79	pub groups: &'a GroupByView,
80}
81
82pub trait AggregateFunction: Send + Sync {
83	fn aggregate<'a>(&'a mut self, ctx: AggregateFunctionContext<'a>) -> AggregateFunctionResult<()>;
84	fn finalize(&mut self) -> AggregateFunctionResult<(Vec<GroupKey>, ColumnData)>;
85	fn return_type(&self, input_type: &Type) -> Type;
86	fn accepted_types(&self) -> InputTypes;
87}
88
89/// Helper for scalar functions to opt into Option propagation.
90///
91/// If any argument column is `ColumnData::Option`,
92/// this unwraps the Option wrappers, calls `func.scalar()` recursively on the
93/// inner data, and re-wraps the result with the combined bitvec.
94///
95/// Returns `None` when no Option columns are present (the caller should
96/// proceed with its normal typed logic).
97///
98/// Functions that need raw access to Options (e.g. `is::some`, `is::none`)
99/// simply don't call this helper.
100pub fn propagate_options(
101	func: &dyn ScalarFunction,
102	ctx: &ScalarFunctionContext,
103) -> Option<ScalarFunctionResult<ColumnData>> {
104	let has_option = ctx.columns.iter().any(|c| matches!(c.data(), ColumnData::Option { .. }));
105	if !has_option {
106		return None;
107	}
108
109	let mut combined_bv: Option<BitVec> = None;
110	let mut unwrapped = Vec::with_capacity(ctx.columns.len());
111	for col in ctx.columns.iter() {
112		let (inner, bv) = col.data().unwrap_option();
113		if let Some(bv) = bv {
114			combined_bv = Some(match combined_bv {
115				Some(existing) => existing.and(bv),
116				None => bv.clone(),
117			});
118		}
119		unwrapped.push(Column::new(col.name().clone(), inner.clone()));
120	}
121
122	// Short-circuit: when all combined values are None, skip the inner function
123	// call entirely to avoid type-validation errors on placeholder inner types
124	// (e.g. none typed as Option<Any> would fail numeric type checks).
125	if let Some(ref bv) = combined_bv {
126		if bv.count_ones() == 0 {
127			let input_types: Vec<Type> = unwrapped.iter().map(|c| c.data().get_type()).collect();
128			let result_type = func.return_type(&input_types);
129			return Some(Ok(ColumnData::none_typed(result_type, ctx.row_count)));
130		}
131	}
132
133	let unwrapped_columns = Columns::new(unwrapped);
134	let result = func.scalar(ScalarFunctionContext {
135		fragment: ctx.fragment.clone(),
136		columns: &unwrapped_columns,
137		row_count: ctx.row_count,
138		runtime_context: ctx.runtime_context,
139		identity: ctx.identity,
140	});
141
142	Some(result.map(|data| match combined_bv {
143		Some(bv) => ColumnData::Option {
144			inner: Box::new(data),
145			bitvec: bv,
146		},
147		None => data,
148	}))
149}
150
151pub fn default_functions() -> registry::FunctionsBuilder {
152	let builder = registry::Functions::builder()
153		.register_aggregate("math::sum", math::aggregate::sum::Sum::new)
154		.register_aggregate("math::min", math::aggregate::min::Min::new)
155		.register_aggregate("math::max", math::aggregate::max::Max::new)
156		.register_aggregate("math::avg", math::aggregate::avg::Avg::new)
157		.register_aggregate("math::count", math::aggregate::count::Count::new)
158		.register_scalar("flow_node::to_json", flow::to_json::FlowNodeToJson::new)
159		.register_scalar("clock::now", clock::now::Now::new)
160		.register_scalar("blob::b58", blob::b58::BlobB58::new)
161		.register_scalar("blob::b64", blob::b64::BlobB64::new)
162		.register_scalar("blob::b64url", blob::b64url::BlobB64url::new)
163		.register_scalar("blob::hex", blob::hex::BlobHex::new)
164		.register_scalar("blob::utf8", blob::utf8::BlobUtf8::new)
165		.register_scalar("math::abs", math::scalar::abs::Abs::new)
166		.register_scalar("math::acos", math::scalar::acos::Acos::new)
167		.register_scalar("math::asin", math::scalar::asin::Asin::new)
168		.register_scalar("math::atan", math::scalar::atan::Atan::new)
169		.register_scalar("math::atan2", math::scalar::atan2::Atan2::new)
170		.register_scalar("math::avg", math::scalar::avg::Avg::new)
171		.register_scalar("math::ceil", math::scalar::ceil::Ceil::new)
172		.register_scalar("math::clamp", math::scalar::clamp::Clamp::new)
173		.register_scalar("math::cos", math::scalar::cos::Cos::new)
174		.register_scalar("math::e", math::scalar::euler::Euler::new)
175		.register_scalar("math::exp", math::scalar::exp::Exp::new)
176		.register_scalar("math::floor", math::scalar::floor::Floor::new)
177		.register_scalar("math::gcd", math::scalar::gcd::Gcd::new)
178		.register_scalar("math::lcm", math::scalar::lcm::Lcm::new)
179		.register_scalar("math::log", math::scalar::log::Log::new)
180		.register_scalar("math::log10", math::scalar::log10::Log10::new)
181		.register_scalar("math::log2", math::scalar::log2::Log2::new)
182		.register_scalar("math::max", math::scalar::max::Max::new)
183		.register_scalar("math::min", math::scalar::min::Min::new)
184		.register_scalar("math::mod", math::scalar::modulo::Modulo::new)
185		.register_scalar("math::pi", math::scalar::pi::Pi::new)
186		.register_scalar("math::power", math::scalar::power::Power::new)
187		.register_scalar("math::round", math::scalar::round::Round::new)
188		.register_scalar("math::sign", math::scalar::sign::Sign::new)
189		.register_scalar("math::sin", math::scalar::sin::Sin::new)
190		.register_scalar("math::sqrt", math::scalar::sqrt::Sqrt::new)
191		.register_scalar("math::tan", math::scalar::tan::Tan::new)
192		.register_scalar("math::truncate", math::scalar::truncate::Truncate::new)
193		.register_scalar("date::year", date::year::DateYear::new)
194		.register_scalar("date::month", date::month::DateMonth::new)
195		.register_scalar("date::day", date::day::DateDay::new)
196		.register_scalar("date::day_of_year", date::day_of_year::DateDayOfYear::new)
197		.register_scalar("date::day_of_week", date::day_of_week::DateDayOfWeek::new)
198		.register_scalar("date::quarter", date::quarter::DateQuarter::new)
199		.register_scalar("date::week", date::week::DateWeek::new)
200		.register_scalar("date::is_leap_year", date::is_leap_year::DateIsLeapYear::new)
201		.register_scalar("date::days_in_month", date::days_in_month::DateDaysInMonth::new)
202		.register_scalar("date::end_of_month", date::end_of_month::DateEndOfMonth::new)
203		.register_scalar("date::start_of_month", date::start_of_month::DateStartOfMonth::new)
204		.register_scalar("date::start_of_year", date::start_of_year::DateStartOfYear::new)
205		.register_scalar("date::new", date::new::DateNew::new)
206		.register_scalar("date::now", date::now::DateNow::new)
207		.register_scalar("date::add", date::add::DateAdd::new)
208		.register_scalar("date::subtract", date::subtract::DateSubtract::new)
209		.register_scalar("date::diff", date::diff::DateDiff::new)
210		.register_scalar("date::trunc", date::trunc::DateTrunc::new)
211		.register_scalar("date::age", date::age::DateAge::new)
212		.register_scalar("date::format", date::format::DateFormat::new)
213		.register_scalar("time::hour", time::hour::TimeHour::new)
214		.register_scalar("time::minute", time::minute::TimeMinute::new)
215		.register_scalar("time::second", time::second::TimeSecond::new)
216		.register_scalar("time::nanosecond", time::nanosecond::TimeNanosecond::new)
217		.register_scalar("time::new", time::new::TimeNew::new)
218		.register_scalar("time::now", time::now::TimeNow::new)
219		.register_scalar("time::add", time::add::TimeAdd::new)
220		.register_scalar("time::subtract", time::subtract::TimeSubtract::new)
221		.register_scalar("time::diff", time::diff::TimeDiff::new)
222		.register_scalar("time::trunc", time::trunc::TimeTrunc::new)
223		.register_scalar("time::age", time::age::TimeAge::new)
224		.register_scalar("time::format", time::format::TimeFormat::new)
225		.register_scalar("datetime::year", datetime::year::DateTimeYear::new)
226		.register_scalar("datetime::month", datetime::month::DateTimeMonth::new)
227		.register_scalar("datetime::day", datetime::day::DateTimeDay::new)
228		.register_scalar("datetime::hour", datetime::hour::DateTimeHour::new)
229		.register_scalar("datetime::minute", datetime::minute::DateTimeMinute::new)
230		.register_scalar("datetime::second", datetime::second::DateTimeSecond::new)
231		.register_scalar("datetime::nanosecond", datetime::nanosecond::DateTimeNanosecond::new)
232		.register_scalar("datetime::day_of_year", datetime::day_of_year::DateTimeDayOfYear::new)
233		.register_scalar("datetime::day_of_week", datetime::day_of_week::DateTimeDayOfWeek::new)
234		.register_scalar("datetime::quarter", datetime::quarter::DateTimeQuarter::new)
235		.register_scalar("datetime::week", datetime::week::DateTimeWeek::new)
236		.register_scalar("datetime::date", datetime::date::DateTimeDate::new)
237		.register_scalar("datetime::time", datetime::time::DateTimeTime::new)
238		.register_scalar("datetime::epoch", datetime::epoch::DateTimeEpoch::new)
239		.register_scalar("datetime::epoch_millis", datetime::epoch_millis::DateTimeEpochMillis::new)
240		.register_scalar("datetime::new", datetime::new::DateTimeNew::new)
241		.register_scalar("datetime::now", datetime::now::DateTimeNow::new)
242		.register_scalar("datetime::from_epoch", datetime::from_epoch::DateTimeFromEpoch::new)
243		.register_scalar(
244			"datetime::from_epoch_millis",
245			datetime::from_epoch_millis::DateTimeFromEpochMillis::new,
246		)
247		.register_scalar("datetime::add", datetime::add::DateTimeAdd::new)
248		.register_scalar("datetime::subtract", datetime::subtract::DateTimeSubtract::new)
249		.register_scalar("datetime::diff", datetime::diff::DateTimeDiff::new)
250		.register_scalar("datetime::trunc", datetime::trunc::DateTimeTrunc::new)
251		.register_scalar("datetime::age", datetime::age::DateTimeAge::new)
252		.register_scalar("datetime::format", datetime::format::DateTimeFormat::new)
253		.register_scalar("duration::years", duration::years::DurationYears::new)
254		.register_scalar("duration::months", duration::months::DurationMonths::new)
255		.register_scalar("duration::weeks", duration::weeks::DurationWeeks::new)
256		.register_scalar("duration::days", duration::days::DurationDays::new)
257		.register_scalar("duration::hours", duration::hours::DurationHours::new)
258		.register_scalar("duration::minutes", duration::minutes::DurationMinutes::new)
259		.register_scalar("duration::seconds", duration::seconds::DurationSeconds::new)
260		.register_scalar("duration::millis", duration::millis::DurationMillis::new)
261		.register_scalar("duration::year", duration::years::DurationYears::new)
262		.register_scalar("duration::month", duration::months::DurationMonths::new)
263		.register_scalar("duration::week", duration::weeks::DurationWeeks::new)
264		.register_scalar("duration::day", duration::days::DurationDays::new)
265		.register_scalar("duration::hour", duration::hours::DurationHours::new)
266		.register_scalar("duration::minute", duration::minutes::DurationMinutes::new)
267		.register_scalar("duration::second", duration::seconds::DurationSeconds::new)
268		.register_scalar("duration::get_months", duration::get_months::DurationGetMonths::new)
269		.register_scalar("duration::get_days", duration::get_days::DurationGetDays::new)
270		.register_scalar("duration::get_nanos", duration::get_nanos::DurationGetNanos::new)
271		.register_scalar("duration::add", duration::add::DurationAdd::new)
272		.register_scalar("duration::subtract", duration::subtract::DurationSubtract::new)
273		.register_scalar("duration::negate", duration::negate::DurationNegate::new)
274		.register_scalar("duration::scale", duration::scale::DurationScale::new)
275		.register_scalar("duration::trunc", duration::trunc::DurationTrunc::new)
276		.register_scalar("duration::format", duration::format::DurationFormat::new)
277		.register_scalar("text::ascii", text::ascii::TextAscii::new)
278		.register_scalar("text::char", text::char::TextChar::new)
279		.register_scalar("text::concat", text::concat::TextConcat::new)
280		.register_scalar("text::contains", text::contains::TextContains::new)
281		.register_scalar("text::count", text::count::TextCount::new)
282		.register_scalar("text::ends_with", text::ends_with::TextEndsWith::new)
283		.register_scalar("text::index_of", text::index_of::TextIndexOf::new)
284		.register_scalar("text::pad_left", text::pad_left::TextPadLeft::new)
285		.register_scalar("text::pad_right", text::pad_right::TextPadRight::new)
286		.register_scalar("text::repeat", text::repeat::TextRepeat::new)
287		.register_scalar("text::replace", text::replace::TextReplace::new)
288		.register_scalar("text::reverse", text::reverse::TextReverse::new)
289		.register_scalar("text::starts_with", text::starts_with::TextStartsWith::new)
290		.register_scalar("text::length", text::length::TextLength::new)
291		.register_scalar("text::trim", text::trim::TextTrim::new)
292		.register_scalar("text::trim_end", text::trim_end::TextTrimEnd::new)
293		.register_scalar("text::trim_start", text::trim_start::TextTrimStart::new)
294		.register_scalar("text::upper", text::upper::TextUpper::new)
295		.register_scalar("text::lower", text::lower::TextLower::new)
296		.register_scalar("text::substring", text::substring::TextSubstring::new)
297		.register_scalar("text::format_bytes", text::format_bytes::FormatBytes::new)
298		.register_scalar("text::format_bytes_si", text::format_bytes_si::FormatBytesSi::new)
299		.register_scalar("meta::type", meta::r#type::Type::new)
300		.register_scalar("identity::id", identity::id::Id::new)
301		.register_scalar("is::some", is::some::IsSome::new)
302		.register_scalar("is::none", is::none::IsNone::new)
303		.register_scalar("is::type", is::r#type::IsType::new)
304		.register_scalar("is::root", is::root::IsRoot::new)
305		.register_scalar("is::anonymous", is::anonymous::IsAnonymous::new)
306		.register_scalar("json::object", json::object::JsonObject::new)
307		.register_scalar("json::array", json::array::JsonArray::new)
308		.register_scalar("json::pretty", json::pretty::JsonPretty::new)
309		.register_scalar("json::serialize", json::serialize::JsonSerialize::new)
310		.register_scalar("uuid::v4", UuidV4::new)
311		.register_scalar("uuid::v7", UuidV7::new)
312		.register_scalar("gen::series", series::Series::new)
313		.register_generator("generate_series", series::GenerateSeries::new)
314		.register_generator("inspect_subscription", subscription::inspect::InspectSubscription::new)
315		.register_scalar("rql::fingerprint", rql::fingerprint::RqlFingerprint::new);
316	testing::register_testing_functions(builder)
317}