use arrow::array::{ArrayRef, BinaryArray, Float64Array, Int64Array, StringArray};
use arrow::datatypes::{DataType, Field, Schema as ArrowSchema};
use arrow::record_batch::RecordBatch;
use chrono::{
DateTime, Datelike, Duration, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Timelike, Utc,
};
use datafusion::catalog::{TableFunctionArgs, TableFunctionImpl, TableProvider};
use datafusion::common::{DataFusionError, Result as DFResult, ScalarValue};
use datafusion::datasource::MemTable;
use datafusion::logical_expr::{
ColumnarValue, Expr as DfExpr, ScalarFunctionArgs, ScalarUDF, ScalarUDFImpl, Signature,
TypeSignature, Volatility,
};
use datafusion::prelude::SessionContext;
use serde_json::{Map as JsonMap, Number as JsonNumber, Value as JsonValue};
use std::hash::{Hash, Hasher};
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc;
static RANDOM_STATE: AtomicU64 = AtomicU64::new(0x9E37_79B9_7F4A_7C15);
#[derive(Debug, Default)]
pub struct ExtendedSqlState {
last_changes: AtomicU64,
total_changes: AtomicU64,
last_insert_rowid: AtomicU64,
}
impl ExtendedSqlState {
pub fn record_changes(&self, changes: u64, last_insert_rowid: Option<u64>) {
self.last_changes.store(changes, Ordering::Relaxed);
self.total_changes.fetch_add(changes, Ordering::Relaxed);
if let Some(rowid) = last_insert_rowid {
self.last_insert_rowid.store(rowid, Ordering::Relaxed);
}
}
fn last_changes(&self) -> u64 {
self.last_changes.load(Ordering::Relaxed)
}
fn total_changes(&self) -> u64 {
self.total_changes.load(Ordering::Relaxed)
}
fn last_insert_rowid(&self) -> u64 {
self.last_insert_rowid.load(Ordering::Relaxed)
}
}
pub fn register_extended_sql_functions(ctx: &SessionContext) {
register_extended_sql_functions_with_state(ctx, Arc::new(ExtendedSqlState::default()));
}
pub fn register_extended_sql_functions_with_state(
ctx: &SessionContext,
state: Arc<ExtendedSqlState>,
) {
for func in extended_functions() {
ctx.register_udf(ScalarUDF::from(func.with_state(Arc::clone(&state))));
}
ctx.register_udtf("json_each", Arc::new(JsonEachFunc));
ctx.register_udtf("json_tree", Arc::new(JsonTreeFunc));
ctx.register_udtf("jsonb_each", Arc::new(JsonbEachFunc));
ctx.register_udtf("jsonb_tree", Arc::new(JsonbTreeFunc));
ctx.register_udtf("series", Arc::new(SeriesFunc));
}
pub fn extended_sql_function_names() -> Vec<&'static str> {
vec![
"date",
"time",
"datetime",
"julianday",
"unixepoch",
"strftime",
"timediff",
"json",
"jsonb",
"json_valid",
"json_extract",
"jsonb_extract",
"json_type",
"json_array_length",
"json_quote",
"json_array",
"jsonb_array",
"json_object",
"jsonb_object",
"json_patch",
"jsonb_patch",
"json_pretty",
"json_error_position",
"json_array_insert",
"jsonb_array_insert",
"json_set",
"jsonb_set",
"json_insert",
"jsonb_insert",
"json_replace",
"jsonb_replace",
"json_remove",
"jsonb_remove",
"json_each",
"json_tree",
"jsonb_each",
"jsonb_tree",
"series",
"abs",
"changes",
"instr",
"last_insert_rowid",
"quote",
"hex",
"unhex",
"printf",
"format",
"char",
"coalesce",
"concat",
"concat_ws",
"like",
"mod",
"pow",
"power",
"pi",
"random",
"randomblob",
"zeroblob",
"glob",
"regexp",
"length",
"octet_length",
"replace",
"round",
"sign",
"substr",
"substring",
"typeof",
"unicode",
"unistr",
"unistr_quote",
"lower",
"upper",
"ltrim",
"rtrim",
"trim",
"likely",
"unlikely",
"likelihood",
"ifnull",
"iif",
"if",
"nullif",
"soundex",
"mongreldb_compileoption_get",
"mongreldb_compileoption_used",
"mongreldb_offset",
"mongreldb_source_id",
"mongreldb_version",
"total_changes",
"load_extension",
"acos",
"acosh",
"asin",
"asinh",
"atan",
"atan2",
"atanh",
"ceil",
"ceiling",
"cos",
"cosh",
"degrees",
"exp",
"floor",
"ln",
"log",
"log10",
"log2",
"radians",
"sin",
"sinh",
"sqrt",
"tan",
"tanh",
]
}
pub fn contains_volatile_extended_function(sql: &str) -> bool {
let lower = sql.to_ascii_lowercase();
[
"date",
"time",
"datetime",
"julianday",
"unixepoch",
"strftime",
"timediff",
"random",
"randomblob",
]
.iter()
.any(|name| contains_function_call(&lower, name))
}
fn contains_function_call(sql: &str, name: &str) -> bool {
let mut offset = 0;
while let Some(pos) = sql[offset..].find(name) {
let start = offset + pos;
let end = start + name.len();
let before_boundary = start == 0 || !is_ident_byte(sql.as_bytes()[start - 1]);
let after_boundary = sql
.as_bytes()
.get(end)
.map(|b| !is_ident_byte(*b))
.unwrap_or(true);
if before_boundary && after_boundary {
let rest = &sql[end..];
if rest.trim_start().starts_with('(') {
return true;
}
}
offset = end;
}
false
}
fn is_ident_byte(b: u8) -> bool {
b.is_ascii_alphanumeric() || b == b'_'
}
#[derive(Debug, Clone)]
struct ExtendedSqlFunc {
name: &'static str,
aliases: Vec<String>,
kind: ExtendedFuncKind,
signature: Signature,
state: Arc<ExtendedSqlState>,
}
impl PartialEq for ExtendedSqlFunc {
fn eq(&self, other: &Self) -> bool {
self.name == other.name
&& self.aliases == other.aliases
&& self.kind == other.kind
&& self.signature == other.signature
}
}
impl Eq for ExtendedSqlFunc {}
impl Hash for ExtendedSqlFunc {
fn hash<H: Hasher>(&self, state: &mut H) {
self.name.hash(state);
self.aliases.hash(state);
self.kind.hash(state);
self.signature.hash(state);
}
}
impl ExtendedSqlFunc {
fn new(name: &'static str, kind: ExtendedFuncKind, volatility: Volatility) -> Self {
Self {
name,
aliases: Vec::new(),
kind,
signature: Signature::variadic_any(volatility),
state: Arc::new(ExtendedSqlState::default()),
}
}
fn zero_or_more(name: &'static str, kind: ExtendedFuncKind, volatility: Volatility) -> Self {
Self {
name,
aliases: Vec::new(),
kind,
signature: Signature::new(
TypeSignature::OneOf(vec![TypeSignature::Nullary, TypeSignature::VariadicAny]),
volatility,
),
state: Arc::new(ExtendedSqlState::default()),
}
}
fn nullary(name: &'static str, kind: ExtendedFuncKind, volatility: Volatility) -> Self {
Self {
name,
aliases: Vec::new(),
kind,
signature: Signature::new(TypeSignature::Nullary, volatility),
state: Arc::new(ExtendedSqlState::default()),
}
}
fn with_aliases(mut self, aliases: &[&str]) -> Self {
self.aliases = aliases.iter().map(|s| s.to_string()).collect();
self
}
fn with_state(mut self, state: Arc<ExtendedSqlState>) -> Self {
self.state = state;
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
enum ExtendedFuncKind {
Date,
Time,
DateTime,
JulianDay,
UnixEpoch,
Strftime,
TimeDiff,
Json,
JsonValid,
JsonExtract,
JsonType,
JsonArrayLength,
JsonQuote,
JsonArray,
JsonObject,
JsonPatch,
JsonPretty,
JsonErrorPosition,
JsonArrayInsert,
JsonSet,
JsonInsert,
JsonReplace,
JsonRemove,
Abs,
Changes,
TotalChanges,
LastInsertRowid,
Instr,
Quote,
Hex,
Unhex,
Printf,
Char,
Coalesce,
Concat,
ConcatWs,
Like,
Mod,
Pow,
Pi,
Random,
RandomBlob,
ZeroBlob,
Glob,
Regexp,
Length,
OctetLength,
Replace,
Round,
Sign,
Substr,
TypeOf,
Unicode,
Unistr,
UnistrQuote,
Lower,
Upper,
Ltrim,
Rtrim,
Trim,
Likely,
Unlikely,
Likelihood,
IfNull,
Iif,
NullIf,
ScalarMax,
ScalarMin,
Soundex,
CompileOptionGet,
CompileOptionUsed,
Offset,
SourceId,
Version,
LoadExtension,
Acos,
Acosh,
Asin,
Asinh,
Atan,
Atan2,
Atanh,
Ceil,
Cos,
Cosh,
Degrees,
Exp,
Floor,
Ln,
Log,
Log10,
Log2,
Radians,
Sin,
Sinh,
Sqrt,
Tan,
Tanh,
}
impl ScalarUDFImpl for ExtendedSqlFunc {
fn name(&self) -> &str {
self.name
}
fn aliases(&self) -> &[String] {
&self.aliases
}
fn signature(&self) -> &Signature {
&self.signature
}
fn return_type(&self, _args: &[DataType]) -> DFResult<DataType> {
Ok(match self.kind {
ExtendedFuncKind::JsonValid
| ExtendedFuncKind::JsonArrayLength
| ExtendedFuncKind::Instr
| ExtendedFuncKind::UnixEpoch
| ExtendedFuncKind::Random
| ExtendedFuncKind::Changes
| ExtendedFuncKind::TotalChanges
| ExtendedFuncKind::LastInsertRowid
| ExtendedFuncKind::Glob
| ExtendedFuncKind::Regexp
| ExtendedFuncKind::Like
| ExtendedFuncKind::Length
| ExtendedFuncKind::OctetLength
| ExtendedFuncKind::Sign
| ExtendedFuncKind::Unicode
| ExtendedFuncKind::CompileOptionUsed
| ExtendedFuncKind::Offset
| ExtendedFuncKind::JsonErrorPosition => DataType::Int64,
ExtendedFuncKind::JulianDay
| ExtendedFuncKind::TimeDiff
| ExtendedFuncKind::Mod
| ExtendedFuncKind::Pow
| ExtendedFuncKind::Pi
| ExtendedFuncKind::Round
| ExtendedFuncKind::Acos
| ExtendedFuncKind::Acosh
| ExtendedFuncKind::Asin
| ExtendedFuncKind::Asinh
| ExtendedFuncKind::Atan
| ExtendedFuncKind::Atan2
| ExtendedFuncKind::Atanh
| ExtendedFuncKind::Ceil
| ExtendedFuncKind::Cos
| ExtendedFuncKind::Cosh
| ExtendedFuncKind::Degrees
| ExtendedFuncKind::Exp
| ExtendedFuncKind::Floor
| ExtendedFuncKind::Ln
| ExtendedFuncKind::Log
| ExtendedFuncKind::Log10
| ExtendedFuncKind::Log2
| ExtendedFuncKind::Radians
| ExtendedFuncKind::Sin
| ExtendedFuncKind::Sinh
| ExtendedFuncKind::Sqrt
| ExtendedFuncKind::Tan
| ExtendedFuncKind::Tanh => DataType::Float64,
ExtendedFuncKind::RandomBlob | ExtendedFuncKind::ZeroBlob => DataType::Binary,
ExtendedFuncKind::Abs => _args.first().cloned().unwrap_or(DataType::Float64),
ExtendedFuncKind::Likely
| ExtendedFuncKind::Unlikely
| ExtendedFuncKind::Likelihood => _args.first().cloned().unwrap_or(DataType::Utf8),
ExtendedFuncKind::Coalesce => _args
.iter()
.find(|ty| **ty != DataType::Null)
.cloned()
.unwrap_or(DataType::Utf8),
ExtendedFuncKind::ScalarMax | ExtendedFuncKind::ScalarMin => _args
.iter()
.find(|ty| **ty != DataType::Null)
.cloned()
.unwrap_or(DataType::Utf8),
ExtendedFuncKind::IfNull => _args
.first()
.filter(|ty| **ty != DataType::Null)
.or_else(|| _args.get(1))
.cloned()
.unwrap_or(DataType::Utf8),
ExtendedFuncKind::Iif => _args.get(1).cloned().unwrap_or(DataType::Utf8),
ExtendedFuncKind::NullIf => _args.first().cloned().unwrap_or(DataType::Utf8),
_ => DataType::Utf8,
})
}
fn invoke_with_args(&self, args: ScalarFunctionArgs) -> DFResult<ColumnarValue> {
match self.kind {
ExtendedFuncKind::Date => eval_string(args, |row| {
parse_datetime_arg(row, 0).map(|dt| dt.format("%Y-%m-%d").to_string())
}),
ExtendedFuncKind::Time => eval_string(args, |row| {
parse_datetime_arg(row, 0).map(|dt| dt.format("%H:%M:%S").to_string())
}),
ExtendedFuncKind::DateTime => eval_string(args, |row| {
parse_datetime_arg(row, 0).map(|dt| dt.format("%Y-%m-%d %H:%M:%S").to_string())
}),
ExtendedFuncKind::JulianDay => eval_float(args, |row| {
parse_datetime_arg(row, 0).map(|dt| unix_to_julian(dt.timestamp() as f64))
}),
ExtendedFuncKind::UnixEpoch => eval_int(args, |row| {
parse_datetime_arg(row, 0).map(|dt| dt.timestamp())
}),
ExtendedFuncKind::Strftime => eval_string(args, strftime_row),
ExtendedFuncKind::TimeDiff => eval_float(args, |row| {
let lhs = parse_datetime_at(row, 0)?;
let rhs = parse_datetime_at(row, 1)?;
Some((lhs.timestamp_millis() - rhs.timestamp_millis()) as f64 / 1000.0)
}),
ExtendedFuncKind::Json => eval_string(args, |row| {
let parsed = parse_json_arg(row, 0)?;
Some(parsed.to_string())
}),
ExtendedFuncKind::JsonValid => eval_int(args, |row| {
Some(
if row
.text(0)
.as_deref()
.and_then(|s| serde_json::from_str::<JsonValue>(s).ok())
.is_some()
{
1
} else {
0
},
)
}),
ExtendedFuncKind::JsonExtract => eval_string(args, json_extract_row),
ExtendedFuncKind::JsonType => eval_string(args, json_type_row),
ExtendedFuncKind::JsonArrayLength => eval_int(args, json_array_length_row),
ExtendedFuncKind::JsonQuote => eval_string(args, |row| {
row.scalar_any(0)
.map(scalar_to_json_arg)
.map(|v| v.to_string())
}),
ExtendedFuncKind::JsonArray => eval_string(args, json_array_row),
ExtendedFuncKind::JsonObject => eval_string(args, json_object_row),
ExtendedFuncKind::JsonPatch => eval_string(args, json_patch_row),
ExtendedFuncKind::JsonPretty => eval_string(args, |row| {
let parsed = parse_json_arg(row, 0)?;
serde_json::to_string_pretty(&parsed).ok()
}),
ExtendedFuncKind::JsonErrorPosition => eval_int(args, |row| {
Some(json_error_position(row.text(0).as_deref().unwrap_or_default()) as i64)
}),
ExtendedFuncKind::JsonArrayInsert => eval_string(args, json_array_insert_row),
ExtendedFuncKind::JsonSet => {
eval_string(args, |row| json_mutate_row(row, JsonMutation::Set))
}
ExtendedFuncKind::JsonInsert => {
eval_string(args, |row| json_mutate_row(row, JsonMutation::Insert))
}
ExtendedFuncKind::JsonReplace => {
eval_string(args, |row| json_mutate_row(row, JsonMutation::Replace))
}
ExtendedFuncKind::JsonRemove => eval_string(args, json_remove_row),
ExtendedFuncKind::Abs => eval_scalar(args, abs_scalar),
ExtendedFuncKind::Changes => {
let state = Arc::clone(&self.state);
eval_int(args, move |_| Some(state.last_changes() as i64))
}
ExtendedFuncKind::TotalChanges => {
let state = Arc::clone(&self.state);
eval_int(args, move |_| Some(state.total_changes() as i64))
}
ExtendedFuncKind::LastInsertRowid => {
let state = Arc::clone(&self.state);
eval_int(args, move |_| Some(state.last_insert_rowid() as i64))
}
ExtendedFuncKind::Instr => eval_int(args, |row| {
let haystack = row.text(0)?;
let needle = row.text(1)?;
Some(instr(&haystack, &needle))
}),
ExtendedFuncKind::Quote => eval_string(args, |row| row.scalar_any(0).map(sql_quote)),
ExtendedFuncKind::Hex => eval_string(args, |row| {
row.scalar_any(0).map(|v| hex_encode(&scalar_bytes(&v)))
}),
ExtendedFuncKind::Unhex => eval_string(args, |row| {
let ignored = row.text(1).unwrap_or_default();
let bytes = hex_decode_ignoring(&row.text(0)?, &ignored)?;
String::from_utf8(bytes).ok()
}),
ExtendedFuncKind::Printf => eval_string(args, printf_row),
ExtendedFuncKind::Char => eval_string(args, char_row),
ExtendedFuncKind::Coalesce => eval_scalar(args, coalesce_scalar),
ExtendedFuncKind::Concat => eval_string(args, concat_row),
ExtendedFuncKind::ConcatWs => eval_string(args, concat_ws_row),
ExtendedFuncKind::Like => eval_int(args, |row| {
let pattern = row.text(0)?;
let value = row.text(1)?;
let escape = row.text(2).and_then(|s| s.chars().next());
Some(i64::from(like_match_sql(&value, &pattern, escape)))
}),
ExtendedFuncKind::Mod => eval_float(args, |row| {
let lhs = row.f64(0)?;
let rhs = row.f64(1)?;
if rhs == 0.0 {
None
} else {
Some(lhs % rhs)
}
}),
ExtendedFuncKind::Pow => eval_float(args, |row| Some(row.f64(0)?.powf(row.f64(1)?))),
ExtendedFuncKind::Pi => eval_float(args, |_| Some(std::f64::consts::PI)),
ExtendedFuncKind::Random => eval_int(args, |_| Some(next_random())),
ExtendedFuncKind::RandomBlob => eval_binary(args, |row| {
let len = row.i64(0).unwrap_or(1).max(1) as usize;
Some(random_bytes(len))
}),
ExtendedFuncKind::ZeroBlob => eval_binary(args, |row| {
let len = row.i64(0).unwrap_or(1).max(1) as usize;
Some(vec![0; len])
}),
ExtendedFuncKind::Glob => eval_int(args, |row| {
let pattern = row.text(0)?;
let value = row.text(1)?;
Some(if glob_match(&pattern, &value) { 1 } else { 0 })
}),
ExtendedFuncKind::Regexp => eval_int(args, |row| {
let pattern = row.text(0)?;
let value = row.text(1)?;
match regex::Regex::new(&pattern) {
Ok(re) => Some(if re.is_match(&value) { 1 } else { 0 }),
Err(_) => Some(0),
}
}),
ExtendedFuncKind::Length => eval_int(args, |row| {
let value = row.scalar(0)?;
Some(match value {
ScalarValue::Binary(Some(v))
| ScalarValue::LargeBinary(Some(v))
| ScalarValue::BinaryView(Some(v)) => v.len() as i64,
_ => scalar_text(&value).chars().count() as i64,
})
}),
ExtendedFuncKind::OctetLength => eval_int(args, |row| {
let value = row.scalar(0)?;
Some(scalar_bytes(&value).len() as i64)
}),
ExtendedFuncKind::Replace => eval_string(args, |row| {
let input = row.text(0)?;
let from = row.text(1)?;
let to = row.text(2)?;
if from.is_empty() {
Some(input)
} else {
Some(input.replace(&from, &to))
}
}),
ExtendedFuncKind::Round => eval_float(args, |row| {
let value = row.f64(0)?;
let places = row.i64(1).unwrap_or(0).max(0) as i32;
let scale = 10_f64.powi(places);
Some((value * scale).round() / scale)
}),
ExtendedFuncKind::Sign => eval_int(args, |row| {
let value = row.f64(0)?;
Some(if value > 0.0 {
1
} else if value < 0.0 {
-1
} else {
0
})
}),
ExtendedFuncKind::Substr => eval_string(args, substr_row),
ExtendedFuncKind::TypeOf => eval_string(args, |row| {
row.scalar_any(0).map(|v| scalar_type_name(&v).to_string())
}),
ExtendedFuncKind::Unicode => {
eval_int(args, |row| row.text(0)?.chars().next().map(|ch| ch as i64))
}
ExtendedFuncKind::Unistr => eval_string(args, |row| unistr(&row.text(0)?)),
ExtendedFuncKind::UnistrQuote => eval_string(args, |row| {
row.text(0)
.map(|value| sql_quote(ScalarValue::Utf8(Some(unistr_quote_text(&value)))))
}),
ExtendedFuncKind::Lower => eval_string(args, |row| Some(row.text(0)?.to_lowercase())),
ExtendedFuncKind::Upper => eval_string(args, |row| Some(row.text(0)?.to_uppercase())),
ExtendedFuncKind::Ltrim => eval_string(args, |row| {
let chars = row.text(1).unwrap_or_else(|| " ".to_string());
Some(
row.text(0)?
.trim_start_matches(|c| chars.contains(c))
.to_string(),
)
}),
ExtendedFuncKind::Rtrim => eval_string(args, |row| {
let chars = row.text(1).unwrap_or_else(|| " ".to_string());
Some(
row.text(0)?
.trim_end_matches(|c| chars.contains(c))
.to_string(),
)
}),
ExtendedFuncKind::Trim => eval_string(args, |row| {
let chars = row.text(1).unwrap_or_else(|| " ".to_string());
Some(row.text(0)?.trim_matches(|c| chars.contains(c)).to_string())
}),
ExtendedFuncKind::Likely | ExtendedFuncKind::Unlikely => {
eval_scalar(args, |row| row.scalar_any(0).unwrap_or(ScalarValue::Null))
}
ExtendedFuncKind::Likelihood => {
eval_scalar(args, |row| row.scalar_any(0).unwrap_or(ScalarValue::Null))
}
ExtendedFuncKind::IfNull => eval_scalar(args, |row| {
let first = row.scalar_any(0).unwrap_or(ScalarValue::Null);
if first.is_null() {
row.scalar_any(1).unwrap_or(ScalarValue::Null)
} else {
first
}
}),
ExtendedFuncKind::Iif => eval_scalar(args, |row| {
if row.truthy(0) {
row.scalar_any(1).unwrap_or(ScalarValue::Null)
} else {
row.scalar_any(2).unwrap_or(ScalarValue::Null)
}
}),
ExtendedFuncKind::NullIf => eval_scalar(args, |row| {
let first = row.scalar_any(0).unwrap_or(ScalarValue::Null);
let first_type = first.data_type();
let second = row.scalar_any(1).unwrap_or(ScalarValue::Null);
if !first.is_null()
&& !second.is_null()
&& scalar_text(&first) == scalar_text(&second)
{
ScalarValue::try_new_null(&first_type).unwrap_or(ScalarValue::Null)
} else {
first
}
}),
ExtendedFuncKind::ScalarMax => eval_scalar(args, |row| scalar_extreme(row, true)),
ExtendedFuncKind::ScalarMin => eval_scalar(args, |row| scalar_extreme(row, false)),
ExtendedFuncKind::Soundex => eval_string(args, |row| Some(soundex(&row.text(0)?))),
ExtendedFuncKind::CompileOptionGet => eval_string(args, |row| {
row.i64(0)
.and_then(|idx| compile_options().get(idx as usize).map(|s| s.to_string()))
}),
ExtendedFuncKind::CompileOptionUsed => eval_int(args, |row| {
let needle = row.text(0)?.to_ascii_uppercase();
Some(i64::from(
compile_options()
.iter()
.any(|opt| opt.eq_ignore_ascii_case(&needle)),
))
}),
ExtendedFuncKind::Offset => eval_int(args, |_| None),
ExtendedFuncKind::SourceId => eval_string(args, |_| {
Some(format!(
"mongreldb-query {} extended-sql-profile",
env!("CARGO_PKG_VERSION")
))
}),
ExtendedFuncKind::Version => {
eval_string(args, |_| Some(env!("CARGO_PKG_VERSION").to_string()))
}
ExtendedFuncKind::LoadExtension => Err(DataFusionError::Execution(
"load_extension is disabled in MongrelDB SQL sessions".into(),
)),
ExtendedFuncKind::Acos => eval_float(args, |row| math_unary(row, f64::acos)),
ExtendedFuncKind::Acosh => eval_float(args, |row| math_unary(row, f64::acosh)),
ExtendedFuncKind::Asin => eval_float(args, |row| math_unary(row, f64::asin)),
ExtendedFuncKind::Asinh => eval_float(args, |row| math_unary(row, f64::asinh)),
ExtendedFuncKind::Atan => eval_float(args, |row| math_unary(row, f64::atan)),
ExtendedFuncKind::Atan2 => eval_float(args, |row| Some(row.f64(0)?.atan2(row.f64(1)?))),
ExtendedFuncKind::Atanh => eval_float(args, |row| math_unary(row, f64::atanh)),
ExtendedFuncKind::Ceil => eval_float(args, |row| math_unary(row, f64::ceil)),
ExtendedFuncKind::Cos => eval_float(args, |row| math_unary(row, f64::cos)),
ExtendedFuncKind::Cosh => eval_float(args, |row| math_unary(row, f64::cosh)),
ExtendedFuncKind::Degrees => eval_float(args, |row| math_unary(row, f64::to_degrees)),
ExtendedFuncKind::Exp => eval_float(args, |row| math_unary(row, f64::exp)),
ExtendedFuncKind::Floor => eval_float(args, |row| math_unary(row, f64::floor)),
ExtendedFuncKind::Ln => eval_float(args, |row| math_unary(row, f64::ln)),
ExtendedFuncKind::Log => eval_float(args, log_row),
ExtendedFuncKind::Log10 => eval_float(args, |row| math_unary(row, f64::log10)),
ExtendedFuncKind::Log2 => eval_float(args, |row| math_unary(row, f64::log2)),
ExtendedFuncKind::Radians => eval_float(args, |row| math_unary(row, f64::to_radians)),
ExtendedFuncKind::Sin => eval_float(args, |row| math_unary(row, f64::sin)),
ExtendedFuncKind::Sinh => eval_float(args, |row| math_unary(row, f64::sinh)),
ExtendedFuncKind::Sqrt => eval_float(args, |row| math_unary(row, f64::sqrt)),
ExtendedFuncKind::Tan => eval_float(args, |row| math_unary(row, f64::tan)),
ExtendedFuncKind::Tanh => eval_float(args, |row| math_unary(row, f64::tanh)),
}
}
}
fn extended_functions() -> Vec<ExtendedSqlFunc> {
use ExtendedFuncKind::*;
vec![
ExtendedSqlFunc::zero_or_more("date", Date, Volatility::Volatile),
ExtendedSqlFunc::zero_or_more("time", Time, Volatility::Volatile),
ExtendedSqlFunc::zero_or_more("datetime", DateTime, Volatility::Volatile),
ExtendedSqlFunc::zero_or_more("julianday", JulianDay, Volatility::Volatile),
ExtendedSqlFunc::zero_or_more("unixepoch", UnixEpoch, Volatility::Volatile),
ExtendedSqlFunc::new("strftime", Strftime, Volatility::Volatile),
ExtendedSqlFunc::new("timediff", TimeDiff, Volatility::Volatile),
ExtendedSqlFunc::new("json", Json, Volatility::Immutable).with_aliases(&["jsonb"]),
ExtendedSqlFunc::new("json_valid", JsonValid, Volatility::Immutable),
ExtendedSqlFunc::new("json_extract", JsonExtract, Volatility::Immutable)
.with_aliases(&["jsonb_extract"]),
ExtendedSqlFunc::new("json_type", JsonType, Volatility::Immutable),
ExtendedSqlFunc::new("json_array_length", JsonArrayLength, Volatility::Immutable),
ExtendedSqlFunc::new("json_quote", JsonQuote, Volatility::Immutable),
ExtendedSqlFunc::new("json_array", JsonArray, Volatility::Immutable)
.with_aliases(&["jsonb_array"]),
ExtendedSqlFunc::new("json_object", JsonObject, Volatility::Immutable)
.with_aliases(&["jsonb_object"]),
ExtendedSqlFunc::new("json_patch", JsonPatch, Volatility::Immutable)
.with_aliases(&["jsonb_patch"]),
ExtendedSqlFunc::new("json_pretty", JsonPretty, Volatility::Immutable),
ExtendedSqlFunc::new(
"json_error_position",
JsonErrorPosition,
Volatility::Immutable,
),
ExtendedSqlFunc::new("json_array_insert", JsonArrayInsert, Volatility::Immutable)
.with_aliases(&["jsonb_array_insert"]),
ExtendedSqlFunc::new("json_set", JsonSet, Volatility::Immutable)
.with_aliases(&["jsonb_set"]),
ExtendedSqlFunc::new("json_insert", JsonInsert, Volatility::Immutable)
.with_aliases(&["jsonb_insert"]),
ExtendedSqlFunc::new("json_replace", JsonReplace, Volatility::Immutable)
.with_aliases(&["jsonb_replace"]),
ExtendedSqlFunc::new("json_remove", JsonRemove, Volatility::Immutable)
.with_aliases(&["jsonb_remove"]),
ExtendedSqlFunc::new("abs", Abs, Volatility::Immutable),
ExtendedSqlFunc::nullary("changes", Changes, Volatility::Volatile),
ExtendedSqlFunc::nullary("total_changes", TotalChanges, Volatility::Volatile),
ExtendedSqlFunc::nullary("last_insert_rowid", LastInsertRowid, Volatility::Volatile),
ExtendedSqlFunc::new("instr", Instr, Volatility::Immutable),
ExtendedSqlFunc::new("quote", Quote, Volatility::Immutable),
ExtendedSqlFunc::new("hex", Hex, Volatility::Immutable),
ExtendedSqlFunc::new("unhex", Unhex, Volatility::Immutable),
ExtendedSqlFunc::new("printf", Printf, Volatility::Immutable).with_aliases(&["format"]),
ExtendedSqlFunc::new("char", Char, Volatility::Immutable),
ExtendedSqlFunc::new("coalesce", Coalesce, Volatility::Immutable),
ExtendedSqlFunc::new("concat", Concat, Volatility::Immutable),
ExtendedSqlFunc::new("concat_ws", ConcatWs, Volatility::Immutable),
ExtendedSqlFunc::new("like", Like, Volatility::Immutable),
ExtendedSqlFunc::new("mod", Mod, Volatility::Immutable),
ExtendedSqlFunc::new("pow", Pow, Volatility::Immutable).with_aliases(&["power"]),
ExtendedSqlFunc::nullary("pi", Pi, Volatility::Immutable),
ExtendedSqlFunc::nullary("random", Random, Volatility::Volatile),
ExtendedSqlFunc::new("randomblob", RandomBlob, Volatility::Volatile),
ExtendedSqlFunc::new("zeroblob", ZeroBlob, Volatility::Immutable),
ExtendedSqlFunc::new("glob", Glob, Volatility::Immutable),
ExtendedSqlFunc::new("regexp", Regexp, Volatility::Immutable),
ExtendedSqlFunc::new("length", Length, Volatility::Immutable),
ExtendedSqlFunc::new("octet_length", OctetLength, Volatility::Immutable),
ExtendedSqlFunc::new("replace", Replace, Volatility::Immutable),
ExtendedSqlFunc::new("round", Round, Volatility::Immutable),
ExtendedSqlFunc::new("sign", Sign, Volatility::Immutable),
ExtendedSqlFunc::new("substr", Substr, Volatility::Immutable).with_aliases(&["substring"]),
ExtendedSqlFunc::new("typeof", TypeOf, Volatility::Immutable),
ExtendedSqlFunc::new("unicode", Unicode, Volatility::Immutable),
ExtendedSqlFunc::new("unistr", Unistr, Volatility::Immutable),
ExtendedSqlFunc::new("unistr_quote", UnistrQuote, Volatility::Immutable),
ExtendedSqlFunc::new("lower", Lower, Volatility::Immutable),
ExtendedSqlFunc::new("upper", Upper, Volatility::Immutable),
ExtendedSqlFunc::new("ltrim", Ltrim, Volatility::Immutable),
ExtendedSqlFunc::new("rtrim", Rtrim, Volatility::Immutable),
ExtendedSqlFunc::new("trim", Trim, Volatility::Immutable),
ExtendedSqlFunc::new("likely", Likely, Volatility::Immutable),
ExtendedSqlFunc::new("unlikely", Unlikely, Volatility::Immutable),
ExtendedSqlFunc::new("likelihood", Likelihood, Volatility::Immutable),
ExtendedSqlFunc::new("ifnull", IfNull, Volatility::Immutable),
ExtendedSqlFunc::new("iif", Iif, Volatility::Immutable).with_aliases(&["if"]),
ExtendedSqlFunc::new("nullif", NullIf, Volatility::Immutable),
ExtendedSqlFunc::new("__mongreldb_scalar_max", ScalarMax, Volatility::Immutable),
ExtendedSqlFunc::new("__mongreldb_scalar_min", ScalarMin, Volatility::Immutable),
ExtendedSqlFunc::new("soundex", Soundex, Volatility::Immutable),
ExtendedSqlFunc::new(
"mongreldb_compileoption_get",
CompileOptionGet,
Volatility::Immutable,
),
ExtendedSqlFunc::new(
"mongreldb_compileoption_used",
CompileOptionUsed,
Volatility::Immutable,
),
ExtendedSqlFunc::new("mongreldb_offset", Offset, Volatility::Immutable),
ExtendedSqlFunc::nullary("mongreldb_source_id", SourceId, Volatility::Immutable),
ExtendedSqlFunc::nullary("mongreldb_version", Version, Volatility::Immutable),
ExtendedSqlFunc::new("load_extension", LoadExtension, Volatility::Volatile),
ExtendedSqlFunc::new("acos", Acos, Volatility::Immutable),
ExtendedSqlFunc::new("acosh", Acosh, Volatility::Immutable),
ExtendedSqlFunc::new("asin", Asin, Volatility::Immutable),
ExtendedSqlFunc::new("asinh", Asinh, Volatility::Immutable),
ExtendedSqlFunc::new("atan", Atan, Volatility::Immutable),
ExtendedSqlFunc::new("atan2", Atan2, Volatility::Immutable),
ExtendedSqlFunc::new("atanh", Atanh, Volatility::Immutable),
ExtendedSqlFunc::new("ceil", Ceil, Volatility::Immutable).with_aliases(&["ceiling"]),
ExtendedSqlFunc::new("cos", Cos, Volatility::Immutable),
ExtendedSqlFunc::new("cosh", Cosh, Volatility::Immutable),
ExtendedSqlFunc::new("degrees", Degrees, Volatility::Immutable),
ExtendedSqlFunc::new("exp", Exp, Volatility::Immutable),
ExtendedSqlFunc::new("floor", Floor, Volatility::Immutable),
ExtendedSqlFunc::new("ln", Ln, Volatility::Immutable),
ExtendedSqlFunc::new("log", Log, Volatility::Immutable),
ExtendedSqlFunc::new("log10", Log10, Volatility::Immutable),
ExtendedSqlFunc::new("log2", Log2, Volatility::Immutable),
ExtendedSqlFunc::new("radians", Radians, Volatility::Immutable),
ExtendedSqlFunc::new("sin", Sin, Volatility::Immutable),
ExtendedSqlFunc::new("sinh", Sinh, Volatility::Immutable),
ExtendedSqlFunc::new("sqrt", Sqrt, Volatility::Immutable),
ExtendedSqlFunc::new("tan", Tan, Volatility::Immutable),
ExtendedSqlFunc::new("tanh", Tanh, Volatility::Immutable),
]
}
struct RowArgs {
arrays: Vec<ArrayRef>,
row: usize,
}
impl RowArgs {
fn scalar(&self, idx: usize) -> Option<ScalarValue> {
self.scalar_any(idx).filter(|v| !v.is_null())
}
fn scalar_any(&self, idx: usize) -> Option<ScalarValue> {
self.arrays
.get(idx)
.and_then(|arr| ScalarValue::try_from_array(arr, self.row).ok())
}
fn text(&self, idx: usize) -> Option<String> {
self.scalar(idx).map(|v| scalar_text(&v))
}
fn i64(&self, idx: usize) -> Option<i64> {
scalar_i64(&self.scalar(idx)?)
}
fn f64(&self, idx: usize) -> Option<f64> {
scalar_f64(&self.scalar(idx)?)
}
fn len(&self) -> usize {
self.arrays.len()
}
fn truthy(&self, idx: usize) -> bool {
match self.scalar(idx) {
Some(ScalarValue::Boolean(Some(v))) => v,
Some(v) => scalar_f64(&v).is_some_and(|n| n != 0.0),
None => false,
}
}
}
fn eval_string(
args: ScalarFunctionArgs,
mut f: impl FnMut(&RowArgs) -> Option<String>,
) -> DFResult<ColumnarValue> {
let (arrays, rows, scalar) = expand_args(args.args, args.number_rows)?;
let values: Vec<Option<String>> = (0..rows)
.map(|row| {
f(&RowArgs {
arrays: arrays.clone(),
row,
})
})
.collect();
finish(scalar, Arc::new(StringArray::from(values)))
}
fn eval_int(
args: ScalarFunctionArgs,
mut f: impl FnMut(&RowArgs) -> Option<i64>,
) -> DFResult<ColumnarValue> {
let (arrays, rows, scalar) = expand_args(args.args, args.number_rows)?;
let values: Vec<Option<i64>> = (0..rows)
.map(|row| {
f(&RowArgs {
arrays: arrays.clone(),
row,
})
})
.collect();
finish(scalar, Arc::new(Int64Array::from(values)))
}
fn eval_float(
args: ScalarFunctionArgs,
mut f: impl FnMut(&RowArgs) -> Option<f64>,
) -> DFResult<ColumnarValue> {
let (arrays, rows, scalar) = expand_args(args.args, args.number_rows)?;
let values: Vec<Option<f64>> = (0..rows)
.map(|row| {
f(&RowArgs {
arrays: arrays.clone(),
row,
})
})
.collect();
finish(scalar, Arc::new(Float64Array::from(values)))
}
fn eval_binary(
args: ScalarFunctionArgs,
mut f: impl FnMut(&RowArgs) -> Option<Vec<u8>>,
) -> DFResult<ColumnarValue> {
let (arrays, rows, scalar) = expand_args(args.args, args.number_rows)?;
let values: Vec<Option<Vec<u8>>> = (0..rows)
.map(|row| {
f(&RowArgs {
arrays: arrays.clone(),
row,
})
})
.collect();
let borrowed = values
.iter()
.map(|value| value.as_deref())
.collect::<Vec<_>>();
finish(scalar, Arc::new(BinaryArray::from(borrowed)))
}
fn eval_scalar(
args: ScalarFunctionArgs,
mut f: impl FnMut(&RowArgs) -> ScalarValue,
) -> DFResult<ColumnarValue> {
let (arrays, rows, scalar) = expand_args(args.args, args.number_rows)?;
let values: Vec<ScalarValue> = (0..rows)
.map(|row| {
f(&RowArgs {
arrays: arrays.clone(),
row,
})
})
.collect();
if scalar {
Ok(ColumnarValue::Scalar(
values.into_iter().next().unwrap_or(ScalarValue::Null),
))
} else {
Ok(ColumnarValue::Array(ScalarValue::iter_to_array(values)?))
}
}
fn expand_args(
args: Vec<ColumnarValue>,
number_rows: usize,
) -> DFResult<(Vec<ArrayRef>, usize, bool)> {
let scalar = args
.iter()
.all(|arg| matches!(arg, ColumnarValue::Scalar(_)));
let rows = if scalar {
number_rows.max(1)
} else {
number_rows
};
let arrays = args
.into_iter()
.map(|arg| arg.into_array(rows))
.collect::<DFResult<Vec<_>>>()?;
Ok((arrays, rows, scalar))
}
fn finish(scalar: bool, arr: ArrayRef) -> DFResult<ColumnarValue> {
if scalar {
Ok(ColumnarValue::Scalar(ScalarValue::try_from_array(&arr, 0)?))
} else {
Ok(ColumnarValue::Array(arr))
}
}
fn scalar_text(v: &ScalarValue) -> String {
match v {
ScalarValue::Utf8(Some(s))
| ScalarValue::LargeUtf8(Some(s))
| ScalarValue::Utf8View(Some(s)) => s.clone(),
ScalarValue::Boolean(Some(v)) => {
if *v {
"1".into()
} else {
"0".into()
}
}
ScalarValue::Int8(Some(v)) => v.to_string(),
ScalarValue::Int16(Some(v)) => v.to_string(),
ScalarValue::Int32(Some(v)) => v.to_string(),
ScalarValue::Int64(Some(v)) => v.to_string(),
ScalarValue::UInt8(Some(v)) => v.to_string(),
ScalarValue::UInt16(Some(v)) => v.to_string(),
ScalarValue::UInt32(Some(v)) => v.to_string(),
ScalarValue::UInt64(Some(v)) => v.to_string(),
ScalarValue::Float32(Some(v)) => v.to_string(),
ScalarValue::Float64(Some(v)) => v.to_string(),
ScalarValue::Binary(Some(v))
| ScalarValue::LargeBinary(Some(v))
| ScalarValue::BinaryView(Some(v)) => String::from_utf8_lossy(v).into_owned(),
ScalarValue::Date32(Some(days)) => {
let date = NaiveDate::from_num_days_from_ce_opt(*days + 719_163);
date.map(|d| d.format("%Y-%m-%d").to_string())
.unwrap_or_default()
}
ScalarValue::TimestampSecond(Some(v), _) => format_unix(*v, 1),
ScalarValue::TimestampMillisecond(Some(v), _) => format_unix(*v, 1_000),
ScalarValue::TimestampMicrosecond(Some(v), _) => format_unix(*v, 1_000_000),
ScalarValue::TimestampNanosecond(Some(v), _) => format_unix(*v, 1_000_000_000),
_ => String::new(),
}
}
fn scalar_i64(v: &ScalarValue) -> Option<i64> {
match v {
ScalarValue::Int8(Some(v)) => Some(*v as i64),
ScalarValue::Int16(Some(v)) => Some(*v as i64),
ScalarValue::Int32(Some(v)) => Some(*v as i64),
ScalarValue::Int64(Some(v)) => Some(*v),
ScalarValue::UInt8(Some(v)) => Some(*v as i64),
ScalarValue::UInt16(Some(v)) => Some(*v as i64),
ScalarValue::UInt32(Some(v)) => Some(*v as i64),
ScalarValue::UInt64(Some(v)) => i64::try_from(*v).ok(),
ScalarValue::Float32(Some(v)) => Some(*v as i64),
ScalarValue::Float64(Some(v)) => Some(*v as i64),
ScalarValue::Utf8(Some(s))
| ScalarValue::LargeUtf8(Some(s))
| ScalarValue::Utf8View(Some(s)) => s.trim().parse().ok(),
_ => None,
}
}
fn scalar_f64(v: &ScalarValue) -> Option<f64> {
match v {
ScalarValue::Float32(Some(v)) => Some(*v as f64),
ScalarValue::Float64(Some(v)) => Some(*v),
ScalarValue::Int8(Some(v)) => Some(*v as f64),
ScalarValue::Int16(Some(v)) => Some(*v as f64),
ScalarValue::Int32(Some(v)) => Some(*v as f64),
ScalarValue::Int64(Some(v)) => Some(*v as f64),
ScalarValue::UInt8(Some(v)) => Some(*v as f64),
ScalarValue::UInt16(Some(v)) => Some(*v as f64),
ScalarValue::UInt32(Some(v)) => Some(*v as f64),
ScalarValue::UInt64(Some(v)) => Some(*v as f64),
ScalarValue::Utf8(Some(s))
| ScalarValue::LargeUtf8(Some(s))
| ScalarValue::Utf8View(Some(s)) => s.trim().parse().ok(),
_ => None,
}
}
fn scalar_bytes(v: &ScalarValue) -> Vec<u8> {
match v {
ScalarValue::Binary(Some(v))
| ScalarValue::LargeBinary(Some(v))
| ScalarValue::BinaryView(Some(v)) => v.clone(),
_ => scalar_text(v).into_bytes(),
}
}
fn scalar_to_json_arg(v: ScalarValue) -> JsonValue {
match v {
ScalarValue::Boolean(Some(v)) => JsonValue::Bool(v),
ScalarValue::Int8(Some(v)) => JsonValue::Number(JsonNumber::from(v)),
ScalarValue::Int16(Some(v)) => JsonValue::Number(JsonNumber::from(v)),
ScalarValue::Int32(Some(v)) => JsonValue::Number(JsonNumber::from(v)),
ScalarValue::Int64(Some(v)) => JsonValue::Number(JsonNumber::from(v)),
ScalarValue::UInt8(Some(v)) => JsonValue::Number(JsonNumber::from(v)),
ScalarValue::UInt16(Some(v)) => JsonValue::Number(JsonNumber::from(v)),
ScalarValue::UInt32(Some(v)) => JsonValue::Number(JsonNumber::from(v)),
ScalarValue::UInt64(Some(v)) => JsonValue::Number(JsonNumber::from(v)),
ScalarValue::Float32(Some(v)) => JsonNumber::from_f64(v as f64)
.map(JsonValue::Number)
.unwrap_or(JsonValue::Null),
ScalarValue::Float64(Some(v)) => JsonNumber::from_f64(v)
.map(JsonValue::Number)
.unwrap_or(JsonValue::Null),
ScalarValue::Utf8(Some(s))
| ScalarValue::LargeUtf8(Some(s))
| ScalarValue::Utf8View(Some(s)) => JsonValue::String(s),
ScalarValue::Null => JsonValue::Null,
_ if v.is_null() => JsonValue::Null,
_ => JsonValue::String(scalar_text(&v)),
}
}
fn scalar_type_name(v: &ScalarValue) -> &'static str {
if v.is_null() {
return "null";
}
match v {
ScalarValue::Boolean(_) => "integer",
ScalarValue::Int8(_)
| ScalarValue::Int16(_)
| ScalarValue::Int32(_)
| ScalarValue::Int64(_)
| ScalarValue::UInt8(_)
| ScalarValue::UInt16(_)
| ScalarValue::UInt32(_)
| ScalarValue::UInt64(_) => "integer",
ScalarValue::Float16(_) | ScalarValue::Float32(_) | ScalarValue::Float64(_) => "real",
ScalarValue::Binary(_) | ScalarValue::LargeBinary(_) | ScalarValue::BinaryView(_) => "blob",
_ => "text",
}
}
fn abs_scalar(row: &RowArgs) -> ScalarValue {
let Some(value) = row.scalar_any(0) else {
return ScalarValue::Null;
};
match value {
ScalarValue::Int8(Some(v)) => ScalarValue::Int8(v.checked_abs()),
ScalarValue::Int16(Some(v)) => ScalarValue::Int16(v.checked_abs()),
ScalarValue::Int32(Some(v)) => ScalarValue::Int32(v.checked_abs()),
ScalarValue::Int64(Some(v)) => ScalarValue::Int64(v.checked_abs()),
ScalarValue::UInt8(_)
| ScalarValue::UInt16(_)
| ScalarValue::UInt32(_)
| ScalarValue::UInt64(_) => value,
ScalarValue::Float32(Some(v)) => ScalarValue::Float32(Some(v.abs())),
ScalarValue::Float64(Some(v)) => ScalarValue::Float64(Some(v.abs())),
v if v.is_null() => v,
v => scalar_f64(&v)
.map(|n| ScalarValue::Float64(Some(n.abs())))
.unwrap_or(ScalarValue::Float64(Some(0.0))),
}
}
fn coalesce_scalar(row: &RowArgs) -> ScalarValue {
for idx in 0..row.len() {
let value = row.scalar_any(idx).unwrap_or(ScalarValue::Null);
if !value.is_null() {
return value;
}
}
ScalarValue::Null
}
fn concat_row(row: &RowArgs) -> Option<String> {
let mut out = String::new();
for idx in 0..row.len() {
if let Some(value) = row.scalar(idx) {
out.push_str(&scalar_text(&value));
}
}
Some(out)
}
fn concat_ws_row(row: &RowArgs) -> Option<String> {
let separator = row.text(0)?;
let parts = (1..row.len())
.filter_map(|idx| row.scalar(idx).map(|value| scalar_text(&value)))
.collect::<Vec<_>>();
Some(parts.join(&separator))
}
fn scalar_extreme(row: &RowArgs, max: bool) -> ScalarValue {
let mut best: Option<ScalarValue> = None;
for idx in 0..row.len() {
let value = row.scalar_any(idx).unwrap_or(ScalarValue::Null);
if value.is_null() {
return ScalarValue::Null;
}
let replace = best
.as_ref()
.map(|current| {
let ordering = compare_scalar_values(&value, current);
if max {
ordering == std::cmp::Ordering::Greater
} else {
ordering == std::cmp::Ordering::Less
}
})
.unwrap_or(true);
if replace {
best = Some(value);
}
}
best.unwrap_or(ScalarValue::Null)
}
fn compare_scalar_values(left: &ScalarValue, right: &ScalarValue) -> std::cmp::Ordering {
match (scalar_f64(left), scalar_f64(right)) {
(Some(a), Some(b)) => a.partial_cmp(&b).unwrap_or(std::cmp::Ordering::Equal),
_ => scalar_text(left).cmp(&scalar_text(right)),
}
}
fn like_match_sql(value: &str, pattern: &str, escape: Option<char>) -> bool {
fn rec(value: &[char], pattern: &[char], escape: Option<char>) -> bool {
let Some((&head, rest)) = pattern.split_first() else {
return value.is_empty();
};
if Some(head) == escape {
let Some((&literal, rest)) = rest.split_first() else {
return value.is_empty();
};
return value.first() == Some(&literal) && rec(&value[1..], rest, escape);
}
match head {
'%' => {
rec(value, rest, escape) || (!value.is_empty() && rec(&value[1..], pattern, escape))
}
'_' => !value.is_empty() && rec(&value[1..], rest, escape),
ch => {
value
.first()
.is_some_and(|value_ch| value_ch.eq_ignore_ascii_case(&ch))
&& rec(&value[1..], rest, escape)
}
}
}
rec(
&value.chars().collect::<Vec<_>>(),
&pattern.chars().collect::<Vec<_>>(),
escape,
)
}
fn compile_options() -> &'static [&'static str] {
&[
"DATAFUSION_54",
"EXTENDED_SQL_FUNCTIONS",
"JSON_TABLE_FUNCTIONS",
"LOG_STRUCTURED_STORAGE",
"MVCC",
"WAL",
]
}
fn math_unary(row: &RowArgs, f: impl FnOnce(f64) -> f64) -> Option<f64> {
let out = f(row.f64(0)?);
if out.is_nan() {
None
} else {
Some(out)
}
}
fn log_row(row: &RowArgs) -> Option<f64> {
let out = if row.len() == 1 {
row.f64(0)?.log10()
} else {
row.f64(1)?.log(row.f64(0)?)
};
if out.is_nan() {
None
} else {
Some(out)
}
}
fn soundex(input: &str) -> String {
let mut letters = input.chars().filter(|ch| ch.is_ascii_alphabetic());
let Some(first) = letters.next() else {
return "?000".to_string();
};
let first = first.to_ascii_uppercase();
let mut out = String::with_capacity(4);
out.push(first);
let mut last = soundex_code(first);
for ch in letters {
let code = soundex_code(ch.to_ascii_uppercase());
if code != '0' && code != last {
out.push(code);
if out.len() == 4 {
break;
}
}
last = code;
}
while out.len() < 4 {
out.push('0');
}
out
}
fn soundex_code(ch: char) -> char {
match ch {
'B' | 'F' | 'P' | 'V' => '1',
'C' | 'G' | 'J' | 'K' | 'Q' | 'S' | 'X' | 'Z' => '2',
'D' | 'T' => '3',
'L' => '4',
'M' | 'N' => '5',
'R' => '6',
_ => '0',
}
}
fn unistr(input: &str) -> Option<String> {
let chars = input.chars().collect::<Vec<_>>();
let mut out = String::new();
let mut idx = 0;
while idx < chars.len() {
let ch = chars[idx];
idx += 1;
if ch != '\\' {
out.push(ch);
continue;
}
let Some(marker) = chars.get(idx).copied() else {
out.push('\\');
break;
};
match marker {
'\\' => {
out.push('\\');
idx += 1;
}
'u' => {
idx += 1;
out.push(read_hex_char(&chars, &mut idx, 4)?);
}
'U' => {
idx += 1;
out.push(read_hex_char(&chars, &mut idx, 8)?);
}
'+' => {
idx += 1;
out.push(read_hex_char(&chars, &mut idx, 6)?);
}
'0'..='9' | 'A'..='F' | 'a'..='f' => {
out.push(read_hex_char(&chars, &mut idx, 4)?);
}
other => {
out.push(other);
idx += 1;
}
}
}
Some(out)
}
fn read_hex_char(chars: &[char], idx: &mut usize, width: usize) -> Option<char> {
if *idx + width > chars.len() {
return None;
}
let mut value = 0_u32;
for _ in 0..width {
value = value.checked_mul(16)?;
value = value.checked_add(chars[*idx].to_digit(16)?)?;
*idx += 1;
}
char::from_u32(value)
}
fn unistr_quote_text(input: &str) -> String {
let mut out = String::new();
for ch in input.chars() {
match ch {
'\u{0001}'..='\u{001f}' | '\\' => out.push_str(&format!("\\u{:04X}", ch as u32)),
_ => out.push(ch),
}
}
out
}
fn format_unix(value: i64, scale: i64) -> String {
let secs = value / scale;
Utc.timestamp_opt(secs, 0)
.single()
.map(|dt| dt.format("%Y-%m-%d %H:%M:%S").to_string())
.unwrap_or_default()
}
fn parse_datetime_arg(row: &RowArgs, idx: usize) -> Option<DateTime<Utc>> {
let mut dt = parse_datetime_at(row, idx)?;
for i in (idx + 1)..row.len() {
if let Some(modifier) = row.text(i) {
dt = apply_datetime_modifier(dt, modifier.trim())?;
}
}
Some(dt)
}
fn parse_datetime_at(row: &RowArgs, idx: usize) -> Option<DateTime<Utc>> {
match row.scalar(idx) {
None => Some(Utc::now()),
Some(v) => parse_datetime_scalar(&v),
}
}
fn parse_datetime_scalar(v: &ScalarValue) -> Option<DateTime<Utc>> {
if let Some(seconds) = scalar_f64(v) {
return unix_seconds_to_datetime(seconds);
}
let s = scalar_text(v);
parse_datetime_text(s.trim())
}
fn parse_datetime_text(s: &str) -> Option<DateTime<Utc>> {
if s.is_empty() || s.eq_ignore_ascii_case("now") {
return Some(Utc::now());
}
if let Ok(dt) = DateTime::parse_from_rfc3339(s) {
return Some(dt.with_timezone(&Utc));
}
for fmt in ["%Y-%m-%d %H:%M:%S", "%Y-%m-%dT%H:%M:%S"] {
if let Ok(dt) = NaiveDateTime::parse_from_str(s, fmt) {
return Some(Utc.from_utc_datetime(&dt));
}
}
if let Ok(date) = NaiveDate::parse_from_str(s, "%Y-%m-%d") {
return date
.and_hms_opt(0, 0, 0)
.map(|dt| Utc.from_utc_datetime(&dt));
}
if let Ok(time) = NaiveTime::parse_from_str(s, "%H:%M:%S") {
let date = NaiveDate::from_ymd_opt(1970, 1, 1)?;
return Some(Utc.from_utc_datetime(&NaiveDateTime::new(date, time)));
}
s.parse::<f64>().ok().and_then(unix_seconds_to_datetime)
}
fn unix_seconds_to_datetime(seconds: f64) -> Option<DateTime<Utc>> {
if !seconds.is_finite() {
return None;
}
let secs = seconds.trunc() as i64;
let nanos = ((seconds.fract().abs()) * 1_000_000_000.0) as u32;
Utc.timestamp_opt(secs, nanos).single()
}
fn apply_datetime_modifier(dt: DateTime<Utc>, modifier: &str) -> Option<DateTime<Utc>> {
if modifier.is_empty() || modifier.eq_ignore_ascii_case("unixepoch") {
return Some(dt);
}
if modifier.eq_ignore_ascii_case("start of day") {
return dt
.date_naive()
.and_hms_opt(0, 0, 0)
.map(|v| Utc.from_utc_datetime(&v));
}
let mut parts = modifier.split_whitespace();
let amount = parts.next()?.parse::<f64>().ok()?;
let unit = parts.next()?.trim_end_matches('s').to_ascii_lowercase();
let millis = match unit.as_str() {
"day" => amount * 86_400_000.0,
"hour" => amount * 3_600_000.0,
"minute" => amount * 60_000.0,
"second" => amount * 1_000.0,
_ => return None,
};
Some(dt + Duration::milliseconds(millis as i64))
}
fn unix_to_julian(seconds: f64) -> f64 {
seconds / 86_400.0 + 2_440_587.5
}
fn strftime_row(row: &RowArgs) -> Option<String> {
let fmt = row.text(0)?;
let dt = parse_datetime_arg(row, 1)?;
let mut out = String::new();
let mut chars = fmt.chars();
while let Some(ch) = chars.next() {
if ch != '%' {
out.push(ch);
continue;
}
match chars.next()? {
'%' => out.push('%'),
'Y' => out.push_str(&format!("{:04}", dt.year())),
'm' => out.push_str(&format!("{:02}", dt.month())),
'd' => out.push_str(&format!("{:02}", dt.day())),
'H' => out.push_str(&format!("{:02}", dt.hour())),
'M' => out.push_str(&format!("{:02}", dt.minute())),
'S' => out.push_str(&format!("{:02}", dt.second())),
'f' => out.push_str(&format!(
"{:02}.{:03}",
dt.second(),
dt.timestamp_subsec_millis()
)),
's' => out.push_str(&dt.timestamp().to_string()),
'J' => out.push_str(&unix_to_julian(dt.timestamp() as f64).to_string()),
other => {
out.push('%');
out.push(other);
}
}
}
Some(out)
}
fn parse_json_arg(row: &RowArgs, idx: usize) -> Option<JsonValue> {
serde_json::from_str(&row.text(idx)?).ok()
}
fn json_extract_row(row: &RowArgs) -> Option<String> {
let root = parse_json_arg(row, 0)?;
if row.len() <= 1 {
return json_scalar_result(&root);
}
let mut values = Vec::new();
for idx in 1..row.len() {
let path = row.text(idx)?;
let found = json_path_get(&root, &parse_json_path(&path)?)
.cloned()
.unwrap_or(JsonValue::Null);
if row.len() == 2 {
return json_scalar_result(&found);
}
values.push(found);
}
Some(JsonValue::Array(values).to_string())
}
fn json_type_row(row: &RowArgs) -> Option<String> {
let root = parse_json_arg(row, 0)?;
let value = if row.len() > 1 {
let path = row.text(1)?;
json_path_get(&root, &parse_json_path(&path)?)?
} else {
&root
};
Some(json_type(value).to_string())
}
fn json_array_length_row(row: &RowArgs) -> Option<i64> {
let root = parse_json_arg(row, 0)?;
let value = if row.len() > 1 {
let path = row.text(1)?;
json_path_get(&root, &parse_json_path(&path)?)?
} else {
&root
};
Some(value.as_array().map(|v| v.len() as i64).unwrap_or(0))
}
#[derive(Clone, Copy)]
enum JsonMutation {
Set,
Insert,
Replace,
}
fn json_mutate_row(row: &RowArgs, mode: JsonMutation) -> Option<String> {
if row.len() < 3 {
return None;
}
let mut root = parse_json_arg(row, 0)?;
let mut idx = 1;
while idx + 1 < row.len() {
let path = parse_json_path(&row.text(idx)?)?;
let value = row.scalar(idx + 1).map(scalar_to_json_arg)?;
match mode {
JsonMutation::Set => json_path_set(&mut root, &path, value, true),
JsonMutation::Insert => json_path_set(&mut root, &path, value, false),
JsonMutation::Replace => {
if json_path_get(&root, &path).is_some() {
json_path_set(&mut root, &path, value, true);
}
}
}
idx += 2;
}
Some(root.to_string())
}
fn json_array_insert_row(row: &RowArgs) -> Option<String> {
if row.len() < 3 {
return None;
}
let mut root = parse_json_arg(row, 0)?;
let mut idx = 1;
while idx + 1 < row.len() {
let path = parse_json_path(&row.text(idx)?)?;
let value = row.scalar(idx + 1).map(scalar_to_json_arg)?;
json_path_array_insert(&mut root, &path, value);
idx += 2;
}
Some(root.to_string())
}
fn json_path_array_insert(value: &mut JsonValue, path: &[JsonPathToken], new_value: JsonValue) {
let Some((last, parent_path)) = path.split_last() else {
return;
};
let mut cur = value;
for token in parent_path {
cur = match token {
JsonPathToken::Key(k) => match cur.as_object_mut().and_then(|obj| obj.get_mut(k)) {
Some(value) => value,
None => return,
},
JsonPathToken::Index(i) => match cur.as_array_mut().and_then(|arr| arr.get_mut(*i)) {
Some(value) => value,
None => return,
},
};
}
let JsonPathToken::Index(index) = last else {
return;
};
if let Some(arr) = cur.as_array_mut() {
if *index <= arr.len() {
arr.insert(*index, new_value);
}
}
}
fn json_array_row(row: &RowArgs) -> Option<String> {
let values = (0..row.len())
.map(|idx| scalar_to_json_arg(row.scalar_any(idx).unwrap_or(ScalarValue::Null)))
.collect::<Vec<_>>();
Some(JsonValue::Array(values).to_string())
}
fn json_object_row(row: &RowArgs) -> Option<String> {
if row.len() % 2 != 0 {
return None;
}
let mut obj = JsonMap::new();
let mut idx = 0;
while idx + 1 < row.len() {
let key = row.text(idx)?;
let value = scalar_to_json_arg(row.scalar_any(idx + 1).unwrap_or(ScalarValue::Null));
obj.insert(key, value);
idx += 2;
}
Some(JsonValue::Object(obj).to_string())
}
fn json_patch_row(row: &RowArgs) -> Option<String> {
let target = parse_json_arg(row, 0)?;
let patch = parse_json_arg(row, 1)?;
Some(json_merge_patch(target, patch).to_string())
}
fn json_merge_patch(target: JsonValue, patch: JsonValue) -> JsonValue {
let JsonValue::Object(patch_obj) = patch else {
return patch;
};
let mut target_obj = match target {
JsonValue::Object(obj) => obj,
_ => JsonMap::new(),
};
for (key, value) in patch_obj {
if value.is_null() {
target_obj.remove(&key);
} else {
let current = target_obj.remove(&key).unwrap_or(JsonValue::Null);
target_obj.insert(key, json_merge_patch(current, value));
}
}
JsonValue::Object(target_obj)
}
fn json_error_position(input: &str) -> usize {
match serde_json::from_str::<JsonValue>(input) {
Ok(_) => 0,
Err(err) => {
let line = err.line().max(1);
let column = err.column();
let mut current_line = 1;
let mut line_start = 0;
for (idx, ch) in input.char_indices() {
if current_line == line {
break;
}
if ch == '\n' {
current_line += 1;
line_start = idx + ch.len_utf8();
}
}
line_start + column.max(1)
}
}
}
fn json_remove_row(row: &RowArgs) -> Option<String> {
let mut root = parse_json_arg(row, 0)?;
for idx in 1..row.len() {
let path = parse_json_path(&row.text(idx)?)?;
json_path_remove(&mut root, &path);
}
Some(root.to_string())
}
fn json_scalar_result(value: &JsonValue) -> Option<String> {
match value {
JsonValue::Null => None,
JsonValue::Bool(true) => Some("1".into()),
JsonValue::Bool(false) => Some("0".into()),
JsonValue::Number(n) => Some(n.to_string()),
JsonValue::String(s) => Some(s.clone()),
JsonValue::Array(_) | JsonValue::Object(_) => Some(value.to_string()),
}
}
fn json_type(value: &JsonValue) -> &'static str {
match value {
JsonValue::Null => "null",
JsonValue::Bool(true) => "true",
JsonValue::Bool(false) => "false",
JsonValue::Number(n) if n.is_i64() || n.is_u64() => "integer",
JsonValue::Number(_) => "real",
JsonValue::String(_) => "text",
JsonValue::Array(_) => "array",
JsonValue::Object(_) => "object",
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
enum JsonPathToken {
Key(String),
Index(usize),
}
fn parse_json_path(path: &str) -> Option<Vec<JsonPathToken>> {
let chars = path.chars().collect::<Vec<_>>();
if chars.first() != Some(&'$') {
return None;
}
let mut idx = 1;
let mut tokens = Vec::new();
while idx < chars.len() {
match chars[idx] {
'.' => {
idx += 1;
let mut key = String::new();
while idx < chars.len() {
let c = chars[idx];
if c == '.' || c == '[' {
break;
}
key.push(c);
idx += 1;
}
if key.is_empty() {
return None;
}
tokens.push(JsonPathToken::Key(key));
}
'[' => {
idx += 1;
if idx >= chars.len() {
return None;
}
if matches!(chars[idx], '"' | '\'') {
let quote = chars[idx];
idx += 1;
let mut key = String::new();
while idx < chars.len() {
let c = chars[idx];
idx += 1;
if c == quote {
break;
}
if c == '\\' && idx < chars.len() {
key.push(chars[idx]);
idx += 1;
} else {
key.push(c);
}
}
if idx >= chars.len() || chars[idx] != ']' {
return None;
}
idx += 1;
tokens.push(JsonPathToken::Key(key));
} else {
let mut n = String::new();
while idx < chars.len() && chars[idx] != ']' {
n.push(chars[idx]);
idx += 1;
}
if idx >= chars.len() || chars[idx] != ']' {
return None;
}
idx += 1;
tokens.push(JsonPathToken::Index(n.parse().ok()?));
}
}
_ => return None,
}
}
Some(tokens)
}
fn json_path_get<'a>(value: &'a JsonValue, path: &[JsonPathToken]) -> Option<&'a JsonValue> {
let mut cur = value;
for token in path {
cur = match token {
JsonPathToken::Key(k) => cur.as_object()?.get(k)?,
JsonPathToken::Index(i) => cur.as_array()?.get(*i)?,
};
}
Some(cur)
}
fn json_path_set(
value: &mut JsonValue,
path: &[JsonPathToken],
new_value: JsonValue,
overwrite: bool,
) {
if path.is_empty() {
if overwrite {
*value = new_value;
}
return;
}
let mut cur = value;
for token in &path[..path.len() - 1] {
match token {
JsonPathToken::Key(k) => {
if !cur.is_object() {
*cur = JsonValue::Object(JsonMap::new());
}
cur = cur
.as_object_mut()
.unwrap()
.entry(k.clone())
.or_insert(JsonValue::Object(JsonMap::new()));
}
JsonPathToken::Index(i) => {
if let Some(arr) = cur.as_array_mut() {
if *i >= arr.len() {
return;
}
cur = &mut arr[*i];
} else {
return;
}
}
}
}
match path.last().unwrap() {
JsonPathToken::Key(k) => {
if !cur.is_object() {
*cur = JsonValue::Object(JsonMap::new());
}
let obj = cur.as_object_mut().unwrap();
if overwrite || !obj.contains_key(k) {
obj.insert(k.clone(), new_value);
}
}
JsonPathToken::Index(i) => {
if let Some(arr) = cur.as_array_mut() {
if *i < arr.len() {
if overwrite {
arr[*i] = new_value;
}
} else if *i == arr.len() {
arr.push(new_value);
}
}
}
}
}
fn json_path_remove(value: &mut JsonValue, path: &[JsonPathToken]) {
if path.is_empty() {
return;
}
let mut cur = value;
for token in &path[..path.len() - 1] {
cur = match token {
JsonPathToken::Key(k) => match cur.as_object_mut().and_then(|o| o.get_mut(k)) {
Some(v) => v,
None => return,
},
JsonPathToken::Index(i) => match cur.as_array_mut().and_then(|a| a.get_mut(*i)) {
Some(v) => v,
None => return,
},
};
}
match path.last().unwrap() {
JsonPathToken::Key(k) => {
if let Some(obj) = cur.as_object_mut() {
obj.remove(k);
}
}
JsonPathToken::Index(i) => {
if let Some(arr) = cur.as_array_mut() {
if *i < arr.len() {
arr.remove(*i);
}
}
}
}
}
fn instr(haystack: &str, needle: &str) -> i64 {
if needle.is_empty() {
return 1;
}
haystack
.find(needle)
.map(|byte| haystack[..byte].chars().count() as i64 + 1)
.unwrap_or(0)
}
fn sql_quote(v: ScalarValue) -> String {
if v.is_null() {
return "NULL".into();
}
match v {
ScalarValue::Utf8(Some(s))
| ScalarValue::LargeUtf8(Some(s))
| ScalarValue::Utf8View(Some(s)) => format!("'{}'", s.replace('\'', "''")),
ScalarValue::Binary(Some(b))
| ScalarValue::LargeBinary(Some(b))
| ScalarValue::BinaryView(Some(b)) => format!("X'{}'", hex_encode(&b)),
_ => scalar_text(&v),
}
}
fn hex_encode(bytes: &[u8]) -> String {
const HEX: &[u8; 16] = b"0123456789ABCDEF";
let mut out = String::with_capacity(bytes.len() * 2);
for b in bytes {
out.push(HEX[(b >> 4) as usize] as char);
out.push(HEX[(b & 0x0f) as usize] as char);
}
out
}
fn hex_decode_ignoring(value: &str, ignored: &str) -> Option<Vec<u8>> {
let value = value.trim();
let filtered = value
.bytes()
.filter(|b| !ignored.as_bytes().contains(b))
.collect::<Vec<_>>();
if filtered.len() % 2 != 0 {
return None;
}
let mut out = Vec::with_capacity(filtered.len() / 2);
for i in (0..filtered.len()).step_by(2) {
let hi = hex_value(filtered[i])?;
let lo = hex_value(filtered[i + 1])?;
out.push((hi << 4) | lo);
}
Some(out)
}
fn hex_value(b: u8) -> Option<u8> {
match b {
b'0'..=b'9' => Some(b - b'0'),
b'a'..=b'f' => Some(b - b'a' + 10),
b'A'..=b'F' => Some(b - b'A' + 10),
_ => None,
}
}
fn printf_row(row: &RowArgs) -> Option<String> {
let format = row.text(0)?;
let mut out = String::new();
let mut arg_idx = 1;
let chars = format.chars().collect::<Vec<_>>();
let mut idx = 0;
while idx < chars.len() {
let ch = chars[idx];
idx += 1;
if ch != '%' {
out.push(ch);
continue;
}
if idx >= chars.len() {
out.push('%');
break;
}
if chars[idx] == '%' {
out.push('%');
idx += 1;
continue;
}
let spec_start = idx;
let flags = PrintfFlags::parse(&chars, &mut idx);
let width = parse_decimal(&chars, &mut idx);
let precision = if idx < chars.len() && chars[idx] == '.' {
idx += 1;
Some(parse_decimal(&chars, &mut idx).unwrap_or(0))
} else {
None
};
while idx < chars.len() && matches!(chars[idx], 'l' | 'z' | 't' | 'j') {
idx += 1;
}
if idx >= chars.len() {
out.push('%');
for ch in &chars[spec_start..] {
out.push(*ch);
}
break;
}
let spec = chars[idx];
idx += 1;
let formatted = match spec {
's' | 'z' | 'q' | 'Q' => {
let value = row.scalar(arg_idx).unwrap_or(ScalarValue::Null);
arg_idx += 1;
Some(format_printf_text(value, spec, precision))
}
'd' | 'i' | 'u' => {
let value = row.i64(arg_idx).unwrap_or(0);
arg_idx += 1;
Some(format_printf_int(value, 10, false, &flags, precision))
}
'x' | 'X' => {
let value = row.i64(arg_idx).unwrap_or(0);
arg_idx += 1;
Some(format_printf_int(value, 16, spec == 'X', &flags, precision))
}
'o' => {
let value = row.i64(arg_idx).unwrap_or(0);
arg_idx += 1;
Some(format_printf_int(value, 8, false, &flags, precision))
}
'c' => {
let value = row.scalar(arg_idx).unwrap_or(ScalarValue::Null);
arg_idx += 1;
Some(format_printf_char(value, precision.unwrap_or(1)))
}
'f' | 'F' | 'e' | 'E' | 'g' | 'G' => {
let value = row.f64(arg_idx).unwrap_or(0.0);
arg_idx += 1;
Some(format_printf_float(value, spec, &flags, precision))
}
'n' => {
arg_idx += 1;
Some(String::new())
}
other => Some(format!("%{other}")),
}
.unwrap_or_default();
out.push_str(&apply_printf_width(
formatted,
width,
flags.left,
flags.zero && !flags.left,
));
}
Some(out)
}
#[derive(Default)]
struct PrintfFlags {
left: bool,
plus: bool,
space: bool,
alternate: bool,
zero: bool,
}
impl PrintfFlags {
fn parse(chars: &[char], idx: &mut usize) -> Self {
let mut flags = Self::default();
while *idx < chars.len() {
match chars[*idx] {
'-' => flags.left = true,
'+' => flags.plus = true,
' ' => flags.space = true,
'#' => flags.alternate = true,
'0' => flags.zero = true,
',' | '!' => {}
_ => break,
}
*idx += 1;
}
flags
}
}
fn parse_decimal(chars: &[char], idx: &mut usize) -> Option<usize> {
let start = *idx;
let mut value = 0_usize;
while *idx < chars.len() {
let Some(digit) = chars[*idx].to_digit(10) else {
break;
};
value = value.saturating_mul(10).saturating_add(digit as usize);
*idx += 1;
}
if *idx == start {
None
} else {
Some(value)
}
}
fn format_printf_text(value: ScalarValue, spec: char, precision: Option<usize>) -> String {
if spec == 'Q' && value.is_null() {
return "NULL".to_string();
}
let mut text = scalar_text(&value);
if matches!(spec, 'q' | 'Q') {
text = text.replace('\'', "''");
}
if let Some(limit) = precision {
text = text.chars().take(limit).collect();
}
if spec == 'Q' {
format!("'{text}'")
} else {
text
}
}
fn format_printf_int(
value: i64,
radix: u32,
uppercase: bool,
flags: &PrintfFlags,
precision: Option<usize>,
) -> String {
let negative = value < 0 && radix == 10;
let magnitude = if negative {
value.unsigned_abs()
} else {
value as u64
};
let mut digits = match radix {
8 => format!("{magnitude:o}"),
16 if uppercase => format!("{magnitude:X}"),
16 => format!("{magnitude:x}"),
_ => magnitude.to_string(),
};
if let Some(precision) = precision {
if precision == 0 && magnitude == 0 {
digits.clear();
} else if digits.len() < precision {
digits = format!("{}{}", "0".repeat(precision - digits.len()), digits);
}
}
let mut out = String::new();
if negative {
out.push('-');
} else if flags.plus && radix == 10 {
out.push('+');
} else if flags.space && radix == 10 {
out.push(' ');
}
if flags.alternate {
match radix {
8 if !digits.starts_with('0') => out.push('0'),
16 if uppercase && magnitude != 0 => out.push_str("0X"),
16 if magnitude != 0 => out.push_str("0x"),
_ => {}
}
}
out.push_str(&digits);
out
}
fn format_printf_float(
value: f64,
spec: char,
flags: &PrintfFlags,
precision: Option<usize>,
) -> String {
let precision = precision.unwrap_or(6);
let mut out = match spec {
'e' => format!("{value:.precision$e}"),
'E' => format!("{value:.precision$E}"),
'g' | 'G' => {
let abs = value.abs();
if abs != 0.0 && !(0.0001..1_000_000.0).contains(&abs) {
if spec == 'G' {
format!("{value:.precision$E}")
} else {
format!("{value:.precision$e}")
}
} else {
let text = format!("{value:.precision$}");
text.trim_end_matches('0').trim_end_matches('.').to_string()
}
}
_ => format!("{value:.precision$}"),
};
if value >= 0.0 {
if flags.plus {
out.insert(0, '+');
} else if flags.space {
out.insert(0, ' ');
}
}
if flags.alternate && !out.contains('.') && !out.contains('e') && !out.contains('E') {
out.push('.');
}
out
}
fn format_printf_char(value: ScalarValue, repeat: usize) -> String {
let ch = match value {
ScalarValue::Utf8(Some(s))
| ScalarValue::LargeUtf8(Some(s))
| ScalarValue::Utf8View(Some(s)) => s.chars().next().unwrap_or('\0'),
other => scalar_i64(&other)
.and_then(|v| char::from_u32(v as u32))
.unwrap_or('\0'),
};
ch.to_string().repeat(repeat.max(1))
}
fn apply_printf_width(mut text: String, width: Option<usize>, left: bool, zero: bool) -> String {
let Some(width) = width else {
return text;
};
let len = text.chars().count();
if len >= width {
return text;
}
let pad = width - len;
let ch = if zero { '0' } else { ' ' };
if left {
text.extend(std::iter::repeat(ch).take(pad));
text
} else if zero && matches!(text.as_bytes().first(), Some(b'+' | b'-' | b' ')) {
let sign = text.remove(0);
format!("{sign}{}{}", ch.to_string().repeat(pad), text)
} else if zero && (text.starts_with("0x") || text.starts_with("0X")) {
let prefix = text[..2].to_string();
let rest = text[2..].to_string();
format!("{prefix}{}{}", ch.to_string().repeat(pad), rest)
} else {
format!("{}{}", ch.to_string().repeat(pad), text)
}
}
fn char_row(row: &RowArgs) -> Option<String> {
let mut out = String::new();
for idx in 0..row.len() {
if let Some(code) = row.i64(idx) {
if let Some(ch) = char::from_u32(code as u32) {
out.push(ch);
}
}
}
Some(out)
}
fn substr_row(row: &RowArgs) -> Option<String> {
let value = row.text(0)?;
let chars = value.chars().collect::<Vec<_>>();
let start = row.i64(1)?;
let len = row.i64(2);
let start_idx = if start > 0 {
(start - 1) as usize
} else if start < 0 {
chars.len().saturating_sub(start.unsigned_abs() as usize)
} else {
0
};
if start_idx >= chars.len() {
return Some(String::new());
}
let end = len
.map(|n| start_idx.saturating_add(n.max(0) as usize))
.unwrap_or(chars.len())
.min(chars.len());
Some(chars[start_idx..end].iter().collect())
}
fn glob_match(pattern: &str, value: &str) -> bool {
fn rec(pattern: &[char], value: &[char]) -> bool {
match pattern.split_first() {
None => value.is_empty(),
Some(('*', rest)) => {
rec(rest, value) || (!value.is_empty() && rec(pattern, &value[1..]))
}
Some(('?', rest)) => !value.is_empty() && rec(rest, &value[1..]),
Some(('[', _)) => {
let Some((matched, rest)) = glob_class_match(pattern, value.first().copied())
else {
return value.first() == Some(&'[') && rec(&pattern[1..], &value[1..]);
};
matched && !value.is_empty() && rec(rest, &value[1..])
}
Some((ch, rest)) => value.first() == Some(ch) && rec(rest, &value[1..]),
}
}
rec(
&pattern.chars().collect::<Vec<_>>(),
&value.chars().collect::<Vec<_>>(),
)
}
fn glob_class_match(pattern: &[char], value: Option<char>) -> Option<(bool, &[char])> {
if pattern.first() != Some(&'[') {
return None;
}
let value = value?;
let mut idx = 1;
let mut negated = false;
if matches!(pattern.get(idx), Some('^' | '!')) {
negated = true;
idx += 1;
}
let mut matched = false;
let mut saw_entry = false;
while idx < pattern.len() {
if pattern[idx] == ']' && saw_entry {
return Some((
if negated { !matched } else { matched },
&pattern[idx + 1..],
));
}
let start = pattern[idx];
saw_entry = true;
if idx + 2 < pattern.len() && pattern[idx + 1] == '-' && pattern[idx + 2] != ']' {
let end = pattern[idx + 2];
if start <= value && value <= end {
matched = true;
}
idx += 3;
} else {
if value == start {
matched = true;
}
idx += 1;
}
}
None
}
fn random_bytes(len: usize) -> Vec<u8> {
let mut out = Vec::with_capacity(len);
while out.len() < len {
out.extend_from_slice(&next_random().to_ne_bytes());
}
out.truncate(len);
out
}
fn next_random() -> i64 {
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_nanos() as u64)
.unwrap_or(0);
let old = RANDOM_STATE.fetch_add(0x9E37_79B9_7F4A_7C15, Ordering::Relaxed);
let mut x = old ^ now;
x ^= x >> 12;
x ^= x << 25;
x ^= x >> 27;
let value = (x.wrapping_mul(0x2545_F491_4F6C_DD1D)) as i64;
if value == i64::MIN {
0
} else {
value
}
}
#[derive(Debug)]
struct JsonEachFunc;
impl TableFunctionImpl for JsonEachFunc {
fn call_with_args(&self, args: TableFunctionArgs) -> DFResult<Arc<dyn TableProvider>> {
json_table_function(args, "json_each", JsonTableMode::Each)
}
}
#[derive(Debug)]
struct JsonTreeFunc;
impl TableFunctionImpl for JsonTreeFunc {
fn call_with_args(&self, args: TableFunctionArgs) -> DFResult<Arc<dyn TableProvider>> {
json_table_function(args, "json_tree", JsonTableMode::Tree)
}
}
#[derive(Debug)]
struct JsonbEachFunc;
impl TableFunctionImpl for JsonbEachFunc {
fn call_with_args(&self, args: TableFunctionArgs) -> DFResult<Arc<dyn TableProvider>> {
json_table_function(args, "jsonb_each", JsonTableMode::Each)
}
}
#[derive(Debug)]
struct JsonbTreeFunc;
impl TableFunctionImpl for JsonbTreeFunc {
fn call_with_args(&self, args: TableFunctionArgs) -> DFResult<Arc<dyn TableProvider>> {
json_table_function(args, "jsonb_tree", JsonTableMode::Tree)
}
}
#[derive(Debug)]
struct SeriesFunc;
impl TableFunctionImpl for SeriesFunc {
fn call_with_args(&self, args: TableFunctionArgs) -> DFResult<Arc<dyn TableProvider>> {
let exprs = args.exprs();
let mut values = Vec::with_capacity(exprs.len());
for expr in exprs {
values.push(literal_i64(expr).ok_or_else(|| {
DataFusionError::Plan("series arguments must be integer literals".into())
})?);
}
let (start, stop, step) = match values.as_slice() {
[stop] => (0, *stop, 1),
[start, stop] => (*start, *stop, 1),
[start, stop, step] => (*start, *stop, *step),
_ => {
return Err(DataFusionError::Plan(
"series requires stop, start/stop, or start/stop/step".into(),
));
}
};
series_provider(start, stop, step)
}
}
fn literal_string(expr: &DfExpr) -> Option<String> {
match expr {
DfExpr::Literal(
ScalarValue::Utf8(Some(s))
| ScalarValue::LargeUtf8(Some(s))
| ScalarValue::Utf8View(Some(s)),
_,
) => Some(s.clone()),
_ => None,
}
}
fn literal_i64(expr: &DfExpr) -> Option<i64> {
match expr {
DfExpr::Literal(ScalarValue::Int64(Some(v)), _) => Some(*v),
DfExpr::Literal(ScalarValue::Int32(Some(v)), _) => Some(*v as i64),
DfExpr::Literal(ScalarValue::UInt64(Some(v)), _) => i64::try_from(*v).ok(),
DfExpr::Literal(ScalarValue::UInt32(Some(v)), _) => Some(*v as i64),
_ => None,
}
}
pub(crate) fn series_provider(
start: i64,
stop: i64,
step: i64,
) -> DFResult<Arc<dyn TableProvider>> {
if step == 0 {
return Err(DataFusionError::Plan("series step must not be 0".into()));
}
let mut values = Vec::new();
let mut current = start;
while if step > 0 {
current <= stop
} else {
current >= stop
} {
if values.len() >= 1_000_000 {
return Err(DataFusionError::Plan(
"series output is capped at 1,000,000 rows".into(),
));
}
values.push(current);
current = current.saturating_add(step);
if (step > 0 && current == i64::MAX) || (step < 0 && current == i64::MIN) {
break;
}
}
let schema = Arc::new(ArrowSchema::new(vec![Field::new(
"value",
DataType::Int64,
false,
)]));
let batch = arrow::record_batch::RecordBatch::try_new(
schema.clone(),
vec![Arc::new(Int64Array::from(values)) as ArrayRef],
)?;
Ok(Arc::new(MemTable::try_new(schema, vec![vec![batch]])?))
}
#[derive(Clone, Copy)]
pub(crate) enum JsonTableMode {
Each,
Tree,
}
fn json_table_function(
args: TableFunctionArgs,
name: &str,
mode: JsonTableMode,
) -> DFResult<Arc<dyn TableProvider>> {
let exprs = args.exprs();
if exprs.is_empty() || exprs.len() > 2 {
return Err(DataFusionError::Plan(format!(
"{name} requires one JSON argument and an optional root path"
)));
}
let json = literal_string(&exprs[0]).ok_or_else(|| {
DataFusionError::Plan(format!("{name} arguments must be string literals"))
})?;
let root_path = if exprs.len() == 2 {
literal_string(&exprs[1]).ok_or_else(|| {
DataFusionError::Plan(format!("{name} root path must be a string literal"))
})?
} else {
"$".to_string()
};
json_table_provider_from_text(name, &json, Some(&root_path), mode)
}
#[derive(Default)]
struct JsonTableRows {
key: Vec<Option<String>>,
value: Vec<Option<String>>,
ty: Vec<Option<String>>,
atom: Vec<Option<String>>,
id: Vec<i64>,
parent: Vec<Option<i64>>,
fullkey: Vec<Option<String>>,
path: Vec<Option<String>>,
next_id: i64,
}
impl JsonTableRows {
fn push(
&mut self,
row_key: Option<String>,
row_value: &JsonValue,
fullkey: String,
path: String,
parent: Option<i64>,
) -> i64 {
let row_id = self.next_id;
self.next_id += 1;
self.key.push(row_key);
self.value
.push(json_scalar_result(row_value).or_else(|| Some(row_value.to_string())));
self.ty.push(Some(json_type(row_value).to_string()));
self.atom.push(match row_value {
JsonValue::Array(_) | JsonValue::Object(_) => None,
_ => json_scalar_result(row_value),
});
self.id.push(row_id);
self.parent.push(parent);
self.fullkey.push(Some(fullkey));
self.path.push(Some(path));
row_id
}
}
pub(crate) fn json_table_provider_from_text(
name: &str,
json: &str,
root_path: Option<&str>,
mode: JsonTableMode,
) -> DFResult<Arc<dyn TableProvider>> {
let (schema, batches) = json_table_batches_from_text(name, json, root_path, mode)?;
Ok(Arc::new(MemTable::try_new(schema, vec![batches])?))
}
pub(crate) fn json_table_batches_from_text(
name: &str,
json: &str,
root_path: Option<&str>,
mode: JsonTableMode,
) -> DFResult<(Arc<ArrowSchema>, Vec<RecordBatch>)> {
let root_path = root_path.unwrap_or("$");
let root = serde_json::from_str::<JsonValue>(json)
.map_err(|e| DataFusionError::Plan(format!("invalid JSON for {name}: {e}")))?;
let path = parse_json_path(root_path)
.ok_or_else(|| DataFusionError::Plan(format!("invalid {name} root path")))?;
let value = json_path_get(&root, &path)
.cloned()
.unwrap_or(JsonValue::Null);
json_table_batches(&value, root_path, mode)
}
fn json_table_batches(
value: &JsonValue,
root_path: &str,
mode: JsonTableMode,
) -> DFResult<(Arc<ArrowSchema>, Vec<RecordBatch>)> {
let mut rows = JsonTableRows::default();
match mode {
JsonTableMode::Each => json_each_rows(&mut rows, value, root_path),
JsonTableMode::Tree => {
json_tree_rows(
&mut rows,
None,
value,
root_path.to_string(),
root_path.to_string(),
None,
);
}
}
let schema = Arc::new(ArrowSchema::new(vec![
Field::new("key", DataType::Utf8, true),
Field::new("value", DataType::Utf8, true),
Field::new("type", DataType::Utf8, true),
Field::new("atom", DataType::Utf8, true),
Field::new("id", DataType::Int64, false),
Field::new("parent", DataType::Int64, true),
Field::new("fullkey", DataType::Utf8, true),
Field::new("path", DataType::Utf8, true),
]));
let batch = arrow::record_batch::RecordBatch::try_new(
schema.clone(),
vec![
Arc::new(StringArray::from(rows.key)) as ArrayRef,
Arc::new(StringArray::from(rows.value)),
Arc::new(StringArray::from(rows.ty)),
Arc::new(StringArray::from(rows.atom)),
Arc::new(Int64Array::from(rows.id)),
Arc::new(Int64Array::from(rows.parent)),
Arc::new(StringArray::from(rows.fullkey)),
Arc::new(StringArray::from(rows.path)),
],
)?;
Ok((schema, vec![batch]))
}
fn json_each_rows(rows: &mut JsonTableRows, value: &JsonValue, root_path: &str) {
match value {
JsonValue::Array(values) => {
for (idx, item) in values.iter().enumerate() {
rows.push(
Some(idx.to_string()),
item,
format!("{root_path}[{idx}]"),
root_path.to_string(),
None,
);
}
}
JsonValue::Object(values) => {
for (key, item) in values {
rows.push(
Some(key.clone()),
item,
json_child_path(root_path, &JsonPathToken::Key(key.clone())),
root_path.to_string(),
None,
);
}
}
JsonValue::Null => {}
scalar => {
rows.push(
None,
scalar,
root_path.to_string(),
root_path.to_string(),
None,
);
}
}
}
fn json_tree_rows(
rows: &mut JsonTableRows,
row_key: Option<String>,
value: &JsonValue,
fullkey: String,
path: String,
parent: Option<i64>,
) {
let row_id = rows.push(row_key, value, fullkey.clone(), path, parent);
match value {
JsonValue::Array(values) => {
for (idx, item) in values.iter().enumerate() {
let child_fullkey = format!("{fullkey}[{idx}]");
json_tree_rows(
rows,
Some(idx.to_string()),
item,
child_fullkey,
fullkey.clone(),
Some(row_id),
);
}
}
JsonValue::Object(values) => {
for (key, item) in values {
let token = JsonPathToken::Key(key.clone());
let child_fullkey = json_child_path(&fullkey, &token);
json_tree_rows(
rows,
Some(key.clone()),
item,
child_fullkey,
fullkey.clone(),
Some(row_id),
);
}
}
_ => {}
}
}
fn json_child_path(parent: &str, token: &JsonPathToken) -> String {
match token {
JsonPathToken::Index(idx) => format!("{parent}[{idx}]"),
JsonPathToken::Key(key) if is_simple_json_key(key) => format!("{parent}.{key}"),
JsonPathToken::Key(key) => format!("{parent}[{}]", JsonValue::String(key.clone())),
}
}
fn is_simple_json_key(key: &str) -> bool {
let mut chars = key.chars();
let Some(first) = chars.next() else {
return false;
};
(first == '_' || first.is_ascii_alphabetic())
&& chars.all(|ch| ch == '_' || ch.is_ascii_alphanumeric())
}