use super::eval::{self, EvalCtx};
use crate::error::{Error, Result};
use crate::sql::ast::{Expr, Literal};
use crate::value::Value;
use alloc::string::String;
use alloc::vec::Vec;
const MAX_BLOB_LEN: usize = 1_000_000_000;
pub fn is_aggregate(name: &str) -> bool {
matches!(
name.to_ascii_lowercase().as_str(),
"count" | "sum" | "total" | "avg" | "min" | "max" | "group_concat" | "geopoly_group_bbox"
)
}
pub fn is_aggregate_call(name: &str, nargs: usize, star: bool) -> bool {
match name.to_ascii_lowercase().as_str() {
"count" | "sum" | "total" | "avg" | "group_concat" | "string_agg" => true,
"json_group_array" | "jsonb_group_array" | "json_group_object" | "jsonb_group_object" => {
true
}
"geopoly_group_bbox" => true,
"min" | "max" => star || nargs == 1,
_ => false,
}
}
pub type FunctionListEntry = (&'static str, char, i32);
const FUNCTION_LIST: &[FunctionListEntry] = &[
("abs", 's', 1),
("acos", 's', 1),
("acosh", 's', 1),
("asin", 's', 1),
("asinh", 's', 1),
("atan", 's', 1),
("atan2", 's', 2),
("atanh", 's', 1),
("ceil", 's', 1),
("ceiling", 's', 1),
("changes", 's', 0),
("char", 's', -1),
("coalesce", 's', -1),
("concat", 's', -1),
("concat_ws", 's', -1),
("cos", 's', 1),
("cosh", 's', 1),
("date", 's', -1),
("datetime", 's', -1),
("degrees", 's', 1),
("exp", 's', 1),
("floor", 's', 1),
("format", 's', -1),
("geopoly_area", 's', 1),
("geopoly_bbox", 's', 1),
("geopoly_blob", 's', 1),
("geopoly_ccw", 's', 1),
("geopoly_contains_point", 's', 3),
("geopoly_json", 's', 1),
("geopoly_overlap", 's', 2),
("geopoly_regular", 's', 4),
("geopoly_svg", 's', -1),
("geopoly_within", 's', 2),
("geopoly_xform", 's', 7),
("glob", 's', 2),
("hex", 's', 1),
("if", 's', 3),
("ifnull", 's', 2),
("iif", 's', 3),
("instr", 's', 2),
("json", 's', 1),
("json_array", 's', -1),
("json_array_length", 's', -1),
("json_error_position", 's', 1),
("json_extract", 's', -1),
("json_insert", 's', -1),
("json_object", 's', -1),
("json_patch", 's', 2),
("json_pretty", 's', -1),
("json_quote", 's', 1),
("json_remove", 's', -1),
("json_replace", 's', -1),
("json_set", 's', -1),
("json_type", 's', -1),
("json_valid", 's', -1),
("jsonb", 's', 1),
("jsonb_array", 's', -1),
("jsonb_extract", 's', -1),
("jsonb_insert", 's', -1),
("jsonb_object", 's', -1),
("jsonb_patch", 's', 2),
("jsonb_remove", 's', -1),
("jsonb_replace", 's', -1),
("jsonb_set", 's', -1),
("julianday", 's', -1),
("last_insert_rowid", 's', 0),
("length", 's', 1),
("like", 's', -1),
("likelihood", 's', 2),
("likely", 's', 1),
("ln", 's', 1),
("log", 's', -1),
("log10", 's', 1),
("log2", 's', 1),
("lower", 's', 1),
("ltrim", 's', -1),
("max", 's', -1),
("min", 's', -1),
("mod", 's', 2),
("nullif", 's', 2),
("octet_length", 's', 1),
("pi", 's', 0),
("pow", 's', 2),
("power", 's', 2),
("printf", 's', -1),
("quote", 's', 1),
("radians", 's', 1),
("random", 's', 0),
("randomblob", 's', 1),
("replace", 's', 3),
("round", 's', -1),
("rtrim", 's', -1),
("sign", 's', 1),
("sin", 's', 1),
("sinh", 's', 1),
("soundex", 's', 1),
("sqlite_compileoption_get", 's', 1),
("sqlite_compileoption_used", 's', 1),
("sqlite_source_id", 's', 0),
("sqlite_version", 's', 0),
("sqrt", 's', 1),
("strftime", 's', -1),
("substr", 's', -1),
("substring", 's', -1),
("subtype", 's', 1),
("tan", 's', 1),
("tanh", 's', 1),
("time", 's', -1),
("timediff", 's', 2),
("total_changes", 's', 0),
("trim", 's', -1),
("trunc", 's', 1),
("typeof", 's', 1),
("unhex", 's', -1),
("unicode", 's', 1),
("unistr", 's', 1),
("unistr_quote", 's', 1),
("unixepoch", 's', -1),
("unlikely", 's', 1),
("upper", 's', 1),
("zeroblob", 's', 1),
("avg", 'a', 1),
("count", 'a', -1),
("geopoly_group_bbox", 'a', 1),
("group_concat", 'a', -1),
("json_group_array", 'a', 1),
("json_group_object", 'a', 2),
("jsonb_group_array", 'a', 1),
("jsonb_group_object", 'a', 2),
("max", 'a', 1),
("min", 'a', 1),
("string_agg", 'a', 2),
("sum", 'a', 1),
("total", 'a', 1),
("cume_dist", 'w', 0),
("dense_rank", 'w', 0),
("first_value", 'w', 1),
("lag", 'w', -1),
("last_value", 'w', 1),
("lead", 'w', -1),
("nth_value", 'w', 2),
("ntile", 'w', 1),
("percent_rank", 'w', 0),
("rank", 'w', 0),
("row_number", 'w', 0),
];
#[cfg(feature = "fts5")]
const FTS5_FUNCTION_LIST: &[FunctionListEntry] = &[
("bm25", 's', -1),
("highlight", 's', 4),
("match", 's', 2),
("snippet", 's', 6),
];
pub fn function_list() -> Vec<FunctionListEntry> {
let mut out: Vec<FunctionListEntry> = FUNCTION_LIST.to_vec();
#[cfg(feature = "fts5")]
out.extend_from_slice(FTS5_FUNCTION_LIST);
out.sort_by(|a, b| a.0.cmp(b.0).then(a.1.cmp(&b.1)));
out
}
#[cfg(feature = "fts5")]
fn fts5_match_columns(
operand: &Expr,
ctx: &EvalCtx,
) -> Option<(Vec<(String, String)>, crate::vtab::Fts5Tok)> {
let (table, column) = match operand {
Expr::Column { table, column, .. } => (table.as_deref(), column.as_str()),
Expr::Paren(e) => return fts5_match_columns(e, ctx),
_ => return None,
};
let tok = |t: &str| {
ctx.subqueries
.map_or_else(crate::vtab::Fts5Tok::default, |s| s.fts5_tok(t))
};
if let Some(i) = ctx.columns.iter().position(|c| {
c.name.eq_ignore_ascii_case(column) && table.is_none_or(|t| c.table.eq_ignore_ascii_case(t))
}) {
let c = &ctx.columns[i];
let unindexed = ctx
.subqueries
.and_then(|s| s.fts5_indexed_columns(&c.table))
.is_some_and(|cols| !cols.iter().any(|n| n.eq_ignore_ascii_case(&c.name)));
if unindexed {
return Some((Vec::new(), crate::vtab::Fts5Tok::default()));
}
return Some((
alloc::vec![(c.name.clone(), eval::to_text(&ctx.row[i]))],
tok(&c.table),
));
}
if table.is_none() {
let indexed = ctx.subqueries.and_then(|s| s.fts5_indexed_columns(column));
let cols: Vec<(String, String)> = ctx
.columns
.iter()
.enumerate()
.filter(|(_, c)| c.table.eq_ignore_ascii_case(column))
.filter(|(_, c)| {
indexed
.as_ref()
.is_none_or(|cols| cols.iter().any(|n| n.eq_ignore_ascii_case(&c.name)))
})
.map(|(i, c)| (c.name.clone(), eval::to_text(&ctx.row[i])))
.collect();
if !cols.is_empty() {
return Some((cols, tok(column)));
}
}
None
}
#[cfg(feature = "fts5")]
fn fts5_match_operand_table(operand: &Expr, ctx: &EvalCtx) -> Option<alloc::string::String> {
let (table, column) = match operand {
Expr::Column { table, column, .. } => (table.as_deref(), column.as_str()),
Expr::Paren(e) => return fts5_match_operand_table(e, ctx),
_ => return None,
};
if let Some(t) = table {
return Some(alloc::string::String::from(t));
}
ctx.columns
.iter()
.find(|c| c.table.eq_ignore_ascii_case(column))
.map(|c| c.table.clone())
.or_else(|| Some(alloc::string::String::from(column)))
}
#[cfg(feature = "fts5")]
fn fts5_operand_is_contentless(operand: &Expr, ctx: &EvalCtx) -> bool {
fts5_match_operand_table(operand, ctx)
.zip(ctx.subqueries)
.is_some_and(|(t, s)| s.fts5_is_contentless_table(&t))
}
pub fn eval_scalar(name: &str, args: &[Expr], star: bool, ctx: &EvalCtx) -> Result<Value> {
let lname = name.to_ascii_lowercase();
if is_aggregate_call(&lname, args.len(), star) {
return Err(Error::Error(alloc::format!(
"misuse of aggregate function {name}()"
)));
}
if star {
return Err(Error::Error(alloc::format!(
"{name}(*) is not a scalar call"
)));
}
match lname.as_str() {
"last_insert_rowid" | "changes" | "total_changes" => {
arity(&lname, args, 0)?;
let n = ctx.subqueries.map_or(0, |s| match lname.as_str() {
"last_insert_rowid" => s.last_insert_rowid(),
"changes" => s.changes(),
_ => s.total_changes(),
});
return Ok(Value::Integer(n));
}
"random" => {
arity(&lname, args, 0)?;
return Ok(Value::Integer(
ctx.subqueries.map_or(0, |s| s.next_random()),
));
}
"randomblob" => {
arity(&lname, args, 1)?;
let n = eval::to_int_value(&eval::eval(&args[0], ctx)?);
let len = if n < 1 { 1 } else { n as usize };
if len > MAX_BLOB_LEN {
return Err(Error::Error("string or blob too big".into()));
}
let mut bytes = Vec::new();
if let Some(s) = ctx.subqueries {
while bytes.len() < len {
bytes.extend_from_slice(&s.next_random().to_le_bytes());
}
bytes.truncate(len);
} else {
bytes.resize(len, 0);
}
return Ok(Value::Blob(bytes));
}
#[cfg(feature = "fts5")]
"match" if args.len() == 2 => {
if let Some((cols, tok)) = fts5_match_columns(&args[1], ctx) {
let pattern = eval::eval(&args[0], ctx)?;
return Ok(match pattern {
Value::Null => Value::Null,
p => {
let q = eval::to_text(&p);
if let (Some(table), Some(rowid)) =
(fts5_match_operand_table(&args[1], ctx), ctx.rowid)
&& let Some(m) = ctx
.subqueries
.and_then(|s| s.fts5_contentless_match(&table, &q, rowid))
{
return Ok(Value::Integer(m as i64));
}
Value::Integer(crate::vtab::fts5_query_matches(&q, &cols, tok) as i64)
}
});
}
}
#[cfg(feature = "fts5")]
"bm25" if !args.is_empty() && ctx.rowid.is_some() => {
let weights: Vec<f64> = args[1..]
.iter()
.map(|a| Ok(eval::to_f64(&eval::eval(a, ctx)?)))
.collect::<Result<_>>()?;
if let Some(score) = ctx
.rowid
.and_then(|r| ctx.subqueries?.fts5_bm25(r, &weights))
{
return Ok(Value::Real(score));
}
}
#[cfg(feature = "fts5")]
"highlight" if args.len() == 4 => {
if fts5_operand_is_contentless(&args[0], ctx) {
return Ok(Value::Null);
}
let col = eval::to_int_value(&eval::eval(&args[1], ctx)?);
let open = eval::to_text(&eval::eval(&args[2], ctx)?);
let close = eval::to_text(&eval::eval(&args[3], ctx)?);
if let Ok(col) = usize::try_from(col) {
let text = ctx.row.get(col).map(eval::to_text).unwrap_or_default();
if let Some(s) = ctx
.subqueries
.and_then(|s| s.fts5_highlight(col, &text, &open, &close))
{
return Ok(Value::Text(s.into()));
}
}
}
#[cfg(feature = "fts5")]
"snippet" if args.len() == 6 => {
if fts5_operand_is_contentless(&args[0], ctx) {
return Ok(Value::Null);
}
let col = eval::to_int_value(&eval::eval(&args[1], ctx)?);
let open = eval::to_text(&eval::eval(&args[2], ctx)?);
let close = eval::to_text(&eval::eval(&args[3], ctx)?);
let ellipsis = eval::to_text(&eval::eval(&args[4], ctx)?);
let ntokens = eval::to_int_value(&eval::eval(&args[5], ctx)?);
if let Ok(ntokens) = usize::try_from(ntokens) {
let cols: Vec<alloc::string::String> = ctx.row.iter().map(eval::to_text).collect();
if let Some(s) = ctx
.subqueries
.and_then(|s| s.fts5_snippet(col, &cols, &open, &close, &ellipsis, ntokens))
{
return Ok(Value::Text(s.into()));
}
}
}
_ => {}
}
match lname.as_str() {
"coalesce" => {
if args.len() < 2 {
return Err(wrong_arg_count("coalesce"));
}
for a in args {
let v = eval::eval(a, ctx)?;
if !matches!(v, Value::Null) {
return Ok(v);
}
}
return Ok(Value::Null);
}
"ifnull" => {
arity(&lname, args, 2)?;
let a = eval::eval(&args[0], ctx)?;
return if matches!(a, Value::Null) {
eval::eval(&args[1], ctx)
} else {
Ok(a)
};
}
"json_quote" if args.len() == 1 && carries_json_subtype(&args[0], ctx) => {
let val = eval::eval(&args[0], ctx)?;
if let Value::Blob(b) = &val {
let j = super::json::Json::from_jsonb(b)
.ok_or_else(|| Error::Error("JSON cannot hold BLOB values".into()))?;
return Ok(Value::Text(j.quote().into()));
}
return Ok(val);
}
_ => {}
}
let v: Vec<Value> = args
.iter()
.map(|a| eval::eval(a, ctx))
.collect::<Result<_>>()?;
Ok(match lname.as_str() {
"abs" => {
arity(&lname, args, 1)?;
match &v[0] {
Value::Null => Value::Null,
Value::Integer(i) => match i.checked_abs() {
Some(a) => Value::Integer(a),
None => return Err(Error::Error("integer overflow".into())),
},
Value::Real(r) => Value::Real(crate::util::float::abs(*r) + 0.0),
other => Value::Real(crate::util::float::abs(eval::to_f64(other)) + 0.0),
}
}
"length" => {
arity(&lname, args, 1)?;
match &v[0] {
Value::Null => Value::Null,
Value::Blob(b) => Value::Integer(b.len() as i64),
Value::Text(s) => {
let mut n = 0i64;
for &b in s.as_bytes() {
if b == 0 {
break;
}
if (b & 0xc0) != 0x80 {
n += 1;
}
}
Value::Integer(n)
}
other => Value::Integer(eval::to_text(other).chars().count() as i64),
}
}
"octet_length" => {
arity(&lname, args, 1)?;
match &v[0] {
Value::Null => Value::Null,
Value::Blob(b) => Value::Integer(b.len() as i64),
Value::Text(s) => Value::Integer(s.byte_len() as i64),
other => Value::Integer(eval::to_text(other).len() as i64),
}
}
"glob" => {
arity(&lname, args, 2)?;
if v.iter().take(2).any(|x| matches!(x, Value::Null)) {
Value::Null
} else {
let m = eval::glob_match_bytes(&eval::text_bytes(&v[0]), &eval::text_bytes(&v[1]));
Value::Integer(m as i64)
}
}
"lower" => {
arity(&lname, args, 1)?;
if let Value::Text(s) = &v[0]
&& core::str::from_utf8(s.as_bytes()).is_err()
{
return Ok(byte_map_text(&v[0], u8::to_ascii_lowercase));
}
#[cfg(feature = "unicode")]
{
str_map(&v[0], |s| s.to_lowercase())
}
#[cfg(not(feature = "unicode"))]
{
str_map(&v[0], |s| s.to_ascii_lowercase())
}
}
"upper" => {
arity(&lname, args, 1)?;
if let Value::Text(s) = &v[0]
&& core::str::from_utf8(s.as_bytes()).is_err()
{
return Ok(byte_map_text(&v[0], u8::to_ascii_uppercase));
}
#[cfg(feature = "unicode")]
{
str_map(&v[0], |s| s.to_uppercase())
}
#[cfg(not(feature = "unicode"))]
{
str_map(&v[0], |s| s.to_ascii_uppercase())
}
}
"trim" | "ltrim" | "rtrim" => {
if args.is_empty() || args.len() > 2 {
return Err(wrong_arg_count(&lname));
}
let (left, right) = match lname.as_str() {
"ltrim" => (true, false),
"rtrim" => (false, true),
_ => (true, true),
};
trim_fn(&v, left, right)
}
"soundex" => {
arity(&lname, args, 1)?;
Value::Text(soundex(&c_text(&v[0])).into())
}
"typeof" => {
arity(&lname, args, 1)?;
Value::Text(String::from(type_name(&v[0])).into())
}
"nullif" => {
arity(&lname, args, 2)?;
let coll = eval::resolve_collation(&args[0], &args[1], ctx);
if crate::value::cmp_values_coll(&v[0], &v[1], coll) == core::cmp::Ordering::Equal {
Value::Null
} else {
v[0].clone()
}
}
"n/a" => unreachable!(),
"substr" | "substring" => substr(&v)?,
"instr" => instr(&v)?,
"replace" => replace(&v)?,
"round" => round(&v)?,
"min" => scalar_min_max(&v, true)?,
"max" => scalar_min_max(&v, false)?,
"hex" => {
arity(&lname, args, 1)?;
Value::Text(hex_encode(&v[0]).into())
}
"char" => char_fn(&v),
"unicode" => {
arity(&lname, args, 1)?;
match &v[0] {
Value::Null => Value::Null,
Value::Text(s) => {
let bytes = s.as_bytes();
match bytes.first() {
None | Some(0) => Value::Null,
Some(_) => Value::Integer(i64::from(utf8_read_first(bytes))),
}
}
other => {
let text = eval::to_text(other);
text.split('\0')
.next()
.unwrap_or("")
.chars()
.next()
.map(|c| Value::Integer(c as i64))
.unwrap_or(Value::Null)
}
}
}
"iif" | "if" => {
if args.len() < 2 {
return Err(wrong_arg_count(&lname));
}
let n = v.len();
let mut out = if n % 2 == 1 {
v[n - 1].clone()
} else {
Value::Null
};
let mut i = 0;
while i + 1 < n {
if eval::truth(&v[i]) == Some(true) {
out = v[i + 1].clone();
break;
}
i += 2;
}
out
}
"sqlite_version" => {
arity(&lname, args, 0)?;
Value::Text(crate::TARGET_SQLITE_VERSION.into())
}
"sqlite_source_id" => {
arity(&lname, args, 0)?;
Value::Text(crate::TARGET_SQLITE_SOURCE_ID.into())
}
"sqlite_compileoption_used" => {
arity(&lname, args, 1)?;
match &v[0] {
Value::Null => Value::Null,
other => {
let name = eval::to_text(other);
Value::Integer(compileoption_used(&name) as i64)
}
}
}
"sqlite_compileoption_get" => {
arity(&lname, args, 1)?;
let n = eval::to_int_value(&v[0]);
let opts = super::compile_option_names();
if n >= 0 && (n as usize) < opts.len() {
Value::Text(opts[n as usize].into())
} else {
Value::Null
}
}
"zeroblob" => {
arity(&lname, args, 1)?;
let n = eval::to_int_value(&v[0]).max(0) as usize;
if n > MAX_BLOB_LEN {
return Err(Error::Error("string or blob too big".into()));
}
Value::Blob(alloc::vec![0u8; n])
}
"quote" => {
arity(&lname, args, 1)?;
match &v[0] {
Value::Text(s) => {
let bytes = s.as_bytes();
let end = bytes.iter().position(|&b| b == 0).unwrap_or(bytes.len());
let mut out = alloc::vec::Vec::with_capacity(end + 2);
out.push(b'\'');
for &b in &bytes[..end] {
if b == b'\'' {
out.push(b'\'');
}
out.push(b);
}
out.push(b'\'');
Value::Text(crate::value::Text::from_bytes(out))
}
other => Value::Text(quote_value(other).into()),
}
}
"unistr" => {
arity(&lname, args, 1)?;
match &v[0] {
Value::Null => Value::Null,
other => Value::Text(unistr_decode(&eval::to_text(other))?.into()),
}
}
"unistr_quote" => {
arity(&lname, args, 1)?;
match &v[0] {
Value::Text(s) if s.chars().any(|c| (c as u32) < 0x20) => {
Value::Text(unistr_quote_text(s).into())
}
other => Value::Text(quote_value(other).into()),
}
}
"subtype" => {
arity(&lname, args, 1)?;
let is_json = args.first().is_some_and(|e| carries_json_subtype(e, ctx));
Value::Integer(if is_json { 74 } else { 0 })
}
"sign" => {
arity(&lname, args, 1)?;
let num = match &v[0] {
Value::Integer(i) => Some(*i as f64),
Value::Real(r) => Some(*r),
Value::Text(s) => eval::parse_decimal_f64(s.trim()),
_ => None,
};
match num {
Some(r) if r > 0.0 => Value::Integer(1),
Some(r) if r < 0.0 => Value::Integer(-1),
Some(_) => Value::Integer(0),
None => Value::Null,
}
}
"concat" => {
if v.is_empty() {
return Err(wrong_arg_count("concat"));
}
let mut out: Vec<u8> = Vec::new();
for x in &v {
if !matches!(x, Value::Null) {
out.extend_from_slice(&eval::text_bytes(x));
}
}
Value::Text(crate::value::Text::from_bytes(out))
}
"concat_ws" => {
if v.len() < 2 {
return Err(wrong_arg_count("concat_ws"));
}
if matches!(v[0], Value::Null) {
Value::Null
} else {
let sep = eval::text_bytes(&v[0]);
let mut out: Vec<u8> = Vec::new();
let mut first = true;
for x in &v[1..] {
if matches!(x, Value::Null) {
continue;
}
if !first {
out.extend_from_slice(&sep);
}
out.extend_from_slice(&eval::text_bytes(x));
first = false;
}
Value::Text(crate::value::Text::from_bytes(out))
}
}
"like" => {
if v.len() < 2 || v.len() > 3 {
return Err(wrong_arg_count("like"));
}
if v.iter().any(|x| matches!(x, Value::Null)) {
Value::Null
} else {
let escape = match v.get(2) {
Some(e) => {
let s = eval::to_text(e);
let mut it = s.chars();
match (it.next(), it.next()) {
(Some(c), None) => Some(c),
_ => {
return Err(Error::Error(
"ESCAPE expression must be a single character".into(),
));
}
}
}
None => None,
};
let m = eval::like_match_escape_bytes(
&eval::text_bytes(&v[0]),
&eval::text_bytes(&v[1]),
escape,
ctx.subqueries.is_some_and(|s| s.case_sensitive_like()),
);
Value::Integer(m as i64)
}
}
"likely" | "unlikely" => {
arity(&lname, args, 1)?;
v[0].clone()
}
"likelihood" => {
arity(&lname, args, 2)?;
if !likelihood_prob_is_valid(&args[1]) {
return Err(Error::Error(
"second argument to likelihood() must be a constant between 0.0 and 1.0".into(),
));
}
v[0].clone()
}
"unhex" => {
if v.is_empty() || v.len() > 2 {
return Err(wrong_arg_count("unhex"));
}
let ignore = match v.get(1) {
Some(Value::Null) => return Ok(Value::Null),
Some(set) => Some(c_text(set)),
None => None,
};
match &v[0] {
Value::Null => Value::Null,
other => match unhex(&c_text(other), ignore.as_deref()) {
Some(b) => Value::Blob(b),
None => Value::Null,
},
}
}
"pi" => {
arity(&lname, args, 0)?;
Value::Real(crate::util::float::PI)
}
"ceil" | "ceiling" => math_round_to_int(&lname, &v, crate::util::float::ceil)?,
"floor" => math_round_to_int(&lname, &v, crate::util::float::floor)?,
"trunc" => math_round_to_int(&lname, &v, crate::util::float::trunc)?,
"sqrt" => math1(&lname, &v, crate::util::float::sqrt)?,
"exp" => math1(&lname, &v, crate::util::float::exp)?,
"ln" => math1(&lname, &v, crate::util::float::ln)?,
"log2" => math1(&lname, &v, crate::util::float::log2)?,
"sin" => math1(&lname, &v, crate::util::float::sin)?,
"cos" => math1(&lname, &v, crate::util::float::cos)?,
"tan" => math1(&lname, &v, crate::util::float::tan)?,
"asin" => math1(&lname, &v, crate::util::float::asin)?,
"acos" => math1(&lname, &v, crate::util::float::acos)?,
"atan" => math1(&lname, &v, crate::util::float::atan)?,
"sinh" => math1(&lname, &v, crate::util::float::sinh)?,
"cosh" => math1(&lname, &v, crate::util::float::cosh)?,
"tanh" => math1(&lname, &v, crate::util::float::tanh)?,
"asinh" => math1(&lname, &v, crate::util::float::asinh)?,
"acosh" => math1(&lname, &v, crate::util::float::acosh)?,
"atanh" => math1(&lname, &v, crate::util::float::atanh)?,
"degrees" => math1(&lname, &v, crate::util::float::degrees)?,
"radians" => math1(&lname, &v, crate::util::float::radians)?,
"log10" => math1(&lname, &v, crate::util::float::log10)?,
"log" => {
if v.len() == 1 {
math_finite(real_arg(&v[0]).map(crate::util::float::log10))
} else {
arity(&lname, args, 2)?;
match (real_arg(&v[0]), real_arg(&v[1])) {
(Some(1.0), Some(_)) => Value::Null,
(Some(b), Some(x)) => {
math_finite(Some(crate::util::float::ln(x) / crate::util::float::ln(b)))
}
_ => Value::Null,
}
}
}
"pow" | "power" => {
arity(&lname, args, 2)?;
match (real_arg(&v[0]), real_arg(&v[1])) {
(Some(b), Some(e)) => math_finite(Some(crate::util::float::pow(b, e))),
_ => Value::Null,
}
}
"atan2" => {
arity(&lname, args, 2)?;
match (real_arg(&v[0]), real_arg(&v[1])) {
(Some(y), Some(x)) => math_finite(Some(crate::util::float::atan2(y, x))),
_ => Value::Null,
}
}
"mod" => {
arity(&lname, args, 2)?;
match (real_arg(&v[0]), real_arg(&v[1])) {
(Some(x), Some(y)) => math_finite(Some(crate::util::float::fmod(x, y))),
_ => Value::Null,
}
}
"json" => {
arity(&lname, args, 1)?;
match json_root(&v[0])? {
None => Value::Null,
Some(j) => Value::Text(j.serialize().into()),
}
}
"jsonb" => {
arity(&lname, args, 1)?;
match json_root(&v[0])? {
None => Value::Null,
Some(j) => Value::Blob(j.to_jsonb()),
}
}
"json_valid" => {
if v.is_empty() || v.len() > 2 {
return Err(Error::Error(
"wrong number of arguments to function json_valid()".into(),
));
}
let flags = match v.get(1) {
None => 1,
Some(f) => {
let n = eval::to_int_value(f);
if !(1..=15).contains(&n) {
return Err(Error::Error(
"FLAGS parameter to json_valid() must be between 1 and 15".into(),
));
}
n
}
};
match &v[0] {
Value::Null => Value::Null,
Value::Blob(b) => {
let ok = flags & 0x0c != 0 && super::json::Json::from_jsonb(b).is_some();
Value::Integer(ok as i64)
}
other => {
let text = eval::to_text(other);
let ok = (flags & 0x01 != 0 && super::json::is_strict_json(&text))
|| (flags & 0x02 != 0 && super::json::parse(&text).is_some());
Value::Integer(ok as i64)
}
}
}
"json_error_position" => {
arity(&lname, args, 1)?;
match &v[0] {
Value::Null => Value::Null,
other => {
let pos = match super::json::parse_with_error_position(&eval::to_text(other)) {
Ok(_) => 0,
Err(off) => off as i64 + 1,
};
Value::Integer(pos)
}
}
}
"json_pretty" => {
if v.is_empty() || v.len() > 2 {
return Err(wrong_arg_count("json_pretty"));
}
match &v[0] {
Value::Null => Value::Null,
other => {
let indent = match v.get(1) {
Some(Value::Null) | None => alloc::string::String::from(" "),
Some(iv) => eval::to_text(iv),
};
let _ = other;
match json_root(&v[0])? {
None => Value::Null,
Some(j) => Value::Text(j.pretty(&indent).into()),
}
}
}
}
"json_quote" => {
arity(&lname, args, 1)?;
let j = match &v[0] {
Value::Blob(b) => super::json::Json::from_jsonb(b)
.ok_or_else(|| Error::Error("JSON cannot hold BLOB values".into()))?,
other => super::json::value_to_json(other),
};
Value::Text(j.quote().into())
}
"json_type" => {
if v.is_empty() || v.len() > 2 {
return Err(wrong_arg_count("json_type"));
}
match json_root(&v[0])? {
None => Value::Null,
Some(root) => {
let target = if v.len() == 2 {
check_path(&v[1])?;
super::json::navigate(&root, &eval::to_text(&v[1]))
} else {
Some(&root)
};
match target {
Some(j) => Value::Text(String::from(j.type_name()).into()),
None => Value::Null,
}
}
}
}
"json_array_length" => {
if v.is_empty() || v.len() > 2 {
return Err(wrong_arg_count("json_array_length"));
}
match json_root(&v[0])? {
None => Value::Null,
Some(root) => {
let target = if v.len() == 2 {
check_path(&v[1])?;
super::json::navigate(&root, &eval::to_text(&v[1]))
} else {
Some(&root)
};
match target {
Some(super::json::Json::Array(items)) => Value::Integer(items.len() as i64),
Some(_) => Value::Integer(0),
None => Value::Null,
}
}
}
}
"json_extract" | "jsonb_extract" => {
if v.len() < 2 {
return Ok(Value::Null);
}
match json_root(&v[0])? {
None => Value::Null,
Some(root) => json_extract(&root, &v[1..], lname.starts_with("jsonb"))?,
}
}
"json_array" | "jsonb_array" => {
let mut items = Vec::with_capacity(v.len());
for (i, val) in v.iter().enumerate() {
items.push(json_value_arg(val, args.get(i), ctx)?);
}
json_doc_result(&lname, &super::json::Json::Array(items))
}
"json_object" | "jsonb_object" => {
if !v.len().is_multiple_of(2) {
return Err(Error::Error(
"json_object() requires an even number of arguments".into(),
));
}
let mut members = Vec::with_capacity(v.len() / 2);
for pair in v.chunks(2).enumerate() {
let (i, kv) = pair;
let Value::Text(key) = &kv[0] else {
return Err(Error::Error("json_object() labels must be TEXT".into()));
};
let val = json_value_arg(&kv[1], args.get(2 * i + 1), ctx)?;
members.push((String::from(key.as_str()), None, val));
}
json_doc_result(&lname, &super::json::Json::Object(members))
}
"json_set" | "json_insert" | "json_replace" | "jsonb_set" | "jsonb_insert"
| "jsonb_replace" => {
if v.is_empty() {
return Ok(Value::Null);
}
if v.len().is_multiple_of(2) {
let report = lname
.strip_prefix("jsonb_")
.map_or_else(|| lname.clone(), |rest| alloc::format!("json_{rest}"));
return Err(Error::Error(alloc::format!(
"{report}() needs an odd number of arguments"
)));
}
let mode = if lname.ends_with("set") {
super::json::SetMode::Set
} else if lname.ends_with("insert") {
super::json::SetMode::Insert
} else {
super::json::SetMode::Replace
};
match json_root(&v[0])? {
None => Value::Null,
Some(mut root) => {
let mut i = 1;
while i + 1 < v.len() {
check_path(&v[i])?;
let path = eval::to_text(&v[i]);
let val = json_edit_value_arg(&v[i + 1], args.get(i + 1), ctx)?;
super::json::set_path(&mut root, &path, val, mode);
i += 2;
}
json_doc_result(&lname, &root)
}
}
}
"json_remove" | "jsonb_remove" => {
if v.is_empty() {
return Err(Error::Error("json_remove() requires a document".into()));
}
match json_root(&v[0])? {
None => Value::Null,
Some(mut root) => {
let mut removed = Value::Text(String::new().into());
for p in &v[1..] {
if matches!(p, Value::Null) {
removed = Value::Null;
break;
}
check_path(p)?;
if matches!(p, Value::Text(s) if s == "$") {
removed = Value::Null;
continue;
}
super::json::remove_path(&mut root, &eval::to_text(p));
}
if matches!(removed, Value::Null) {
Value::Null
} else {
json_doc_result(&lname, &root)
}
}
}
}
"json_patch" | "jsonb_patch" => {
arity(&lname, args, 2)?;
match (json_root(&v[0])?, json_root(&v[1])?) {
(Some(mut root), Some(patch)) => {
super::json::merge_patch(&mut root, &patch);
json_doc_result(&lname, &root)
}
_ => Value::Null,
}
}
"date" => super::datetime::date(&v),
"time" => super::datetime::time(&v),
"datetime" => super::datetime::datetime(&v),
"julianday" => super::datetime::julianday(&v),
"unixepoch" => super::datetime::unixepoch(&v),
"strftime" => super::datetime::strftime(&v),
"timediff" => {
arity(&lname, args, 2)?;
super::datetime::timediff(&v[0], &v[1])
}
"geopoly_json" => {
arity(&lname, args, 1)?;
match crate::geopoly::parse_value(&v[0]) {
Some(p) => Value::Text(p.to_json().into()),
None => Value::Null,
}
}
"geopoly_blob" => {
arity(&lname, args, 1)?;
match crate::geopoly::parse_value(&v[0]) {
Some(p) => Value::Blob(p.to_blob()),
None => Value::Null,
}
}
"geopoly_area" => {
arity(&lname, args, 1)?;
match crate::geopoly::parse_value(&v[0]) {
Some(p) => Value::Real(p.area()),
None => Value::Null,
}
}
"geopoly_bbox" => {
arity(&lname, args, 1)?;
match crate::geopoly::parse_value(&v[0]) {
Some(p) => Value::Blob(p.bbox().to_blob()),
None => Value::Null,
}
}
"geopoly_ccw" => {
arity(&lname, args, 1)?;
match crate::geopoly::parse_value(&v[0]) {
Some(p) => Value::Blob(p.ccw().to_blob()),
None => Value::Null,
}
}
"geopoly_regular" => {
arity(&lname, args, 4)?;
if v.iter().any(|x| matches!(x, Value::Null)) {
Value::Null
} else {
let cx = eval::to_f64(&v[0]);
let cy = eval::to_f64(&v[1]);
let r = eval::to_f64(&v[2]);
let n = eval::to_int_value(&v[3]);
match crate::geopoly::regular(cx, cy, r, n) {
Some(p) => Value::Blob(p.to_blob()),
None => Value::Null,
}
}
}
"geopoly_contains_point" => {
arity(&lname, args, 3)?;
match crate::geopoly::parse_value(&v[0]) {
Some(p) => {
Value::Integer(p.contains_point(eval::to_f64(&v[1]), eval::to_f64(&v[2])))
}
None => Value::Null,
}
}
"geopoly_overlap" => {
arity(&lname, args, 2)?;
match (
crate::geopoly::parse_value(&v[0]),
crate::geopoly::parse_value(&v[1]),
) {
(Some(p1), Some(p2)) => Value::Integer(crate::geopoly::overlap(&p1, &p2)),
_ => Value::Null,
}
}
"geopoly_within" => {
arity(&lname, args, 2)?;
match (
crate::geopoly::parse_value(&v[0]),
crate::geopoly::parse_value(&v[1]),
) {
(Some(p1), Some(p2)) => Value::Integer(crate::geopoly::within(&p1, &p2)),
_ => Value::Null,
}
}
"geopoly_svg" => {
if args.is_empty() {
return Ok(Value::Null);
}
match crate::geopoly::parse_value(&v[0]) {
Some(p) => {
let extra: Vec<Option<String>> = v[1..]
.iter()
.map(|x| match x {
Value::Null => None,
other => Some(eval::to_text(other)),
})
.collect();
Value::Text(p.to_svg(&extra).into())
}
None => Value::Null,
}
}
"geopoly_xform" => {
arity(&lname, args, 7)?;
match crate::geopoly::parse_value(&v[0]) {
Some(p) => Value::Blob(
p.xform(
eval::to_f64(&v[1]),
eval::to_f64(&v[2]),
eval::to_f64(&v[3]),
eval::to_f64(&v[4]),
eval::to_f64(&v[5]),
eval::to_f64(&v[6]),
)
.to_blob(),
),
None => Value::Null,
}
}
"printf" | "format" => super::datetime::printf(&v),
_ => {
if let Some(result) = ctx.subqueries.and_then(|s| s.call_udf(&lname, &v)) {
return result;
}
if is_window_function_name(&lname) {
return Err(Error::Error(alloc::format!(
"misuse of window function {lname}()"
)));
}
if lname == "raise" {
return Err(Error::Error(alloc::string::String::from(
"RAISE() may only be used within a trigger-program",
)));
}
return Err(Error::Error(alloc::format!("no such function: {name}")));
}
})
}
fn quote_value(v: &Value) -> String {
match v {
Value::Null => String::from("NULL"),
Value::Integer(i) => alloc::format!("{i}"),
Value::Real(r) if !r.is_finite() => {
String::from(if *r < 0.0 { "-9.0e+999" } else { "9.0e+999" })
}
Value::Real(r) => crate::util::fpdecode::quote_real(*r),
Value::Text(s) => {
let s = s.split('\0').next().unwrap_or("");
alloc::format!("'{}'", s.replace('\'', "''"))
}
Value::Blob(b) => {
let mut s = String::from("X'");
for byte in b {
s.push_str(&alloc::format!("{byte:02X}"));
}
s.push('\'');
s
}
}
}
fn unistr_decode(s: &str) -> Result<String> {
let cs: alloc::vec::Vec<char> = s.chars().collect();
let mut out = String::with_capacity(s.len());
let invalid = || Error::Error(String::from("invalid Unicode escape"));
let hex_char = |cs: &[char], at: usize, n: usize| -> Option<u32> {
let slice = cs.get(at..at + n)?;
if !slice.iter().all(|c| c.is_ascii_hexdigit()) {
return None;
}
let s: String = slice.iter().collect();
u32::from_str_radix(&s, 16).ok()
};
let mut i = 0;
while i < cs.len() {
if cs[i] != '\\' {
out.push(cs[i]);
i += 1;
continue;
}
match cs.get(i + 1) {
Some('\\') => {
out.push('\\');
i += 2;
}
Some('u') => {
let cp = hex_char(&cs, i + 2, 4).ok_or_else(invalid)?;
out.push(char::from_u32(cp).unwrap_or('\u{FFFD}'));
i += 6;
}
Some('U') => {
let cp = hex_char(&cs, i + 2, 8).ok_or_else(invalid)?;
out.push(char::from_u32(cp).unwrap_or('\u{FFFD}'));
i += 10;
}
_ => return Err(invalid()),
}
}
Ok(out)
}
fn unistr_quote_text(s: &str) -> String {
let mut out = String::from("unistr('");
for c in s.chars() {
let cp = c as u32;
if cp < 0x20 {
out.push_str(&alloc::format!("\\u{cp:04x}"));
} else if c == '\\' {
out.push_str("\\\\");
} else if c == '\'' {
out.push_str("''");
} else {
out.push(c);
}
}
out.push_str("')");
out
}
fn unhex(s: &str, ignore: Option<&str>) -> Option<alloc::vec::Vec<u8>> {
let hexval = |c: char| -> Option<u8> {
match c {
'0'..='9' => Some(c as u8 - b'0'),
'a'..='f' => Some(c as u8 - b'a' + 10),
'A'..='F' => Some(c as u8 - b'A' + 10),
_ => None,
}
};
let ignored = |c: char| -> bool { ignore.is_some_and(|set| set.contains(c)) };
let mut out = alloc::vec::Vec::new();
let mut it = s.chars();
loop {
let hi = loop {
match it.next() {
None => return Some(out),
Some(c) if hexval(c).is_some() => break c,
Some(c) if ignored(c) => continue,
Some(_) => return None,
}
};
let lo = it.next()?;
out.push((hexval(hi)? << 4) | hexval(lo)?);
}
}
fn is_id_char(b: u8) -> bool {
b.is_ascii_alphanumeric() || b == b'_' || b == b'$'
}
fn compileoption_used(name: &str) -> bool {
let stripped = name
.get(..7)
.filter(|p| p.eq_ignore_ascii_case("SQLITE_"))
.map(|_| &name[7..])
.unwrap_or(name);
let n = stripped.len();
let needle = stripped.as_bytes();
super::compile_option_names().iter().any(|opt| {
let ob = opt.as_bytes();
ob.len() >= n
&& ob[..n].eq_ignore_ascii_case(needle)
&& ob.get(n).map(|&b| !is_id_char(b)).unwrap_or(true)
})
}
pub(crate) fn likelihood_prob_is_valid(e: &Expr) -> bool {
match e {
Expr::Paren(inner) => likelihood_prob_is_valid(inner),
Expr::Literal(Literal::Real(r)) => (0.0..=1.0).contains(r),
_ => false,
}
}
fn arity(name: &str, args: &[Expr], n: usize) -> Result<()> {
if args.len() == n {
Ok(())
} else {
Err(wrong_arg_count(name))
}
}
fn wrong_arg_count(name: &str) -> Error {
Error::Error(alloc::format!(
"wrong number of arguments to function {name}()"
))
}
fn is_window_function_name(name: &str) -> bool {
matches!(
name,
"row_number"
| "rank"
| "dense_rank"
| "percent_rank"
| "cume_dist"
| "ntile"
| "first_value"
| "last_value"
| "nth_value"
| "lag"
| "lead"
)
}
fn soundex(s: &str) -> String {
const CODE: [u8; 26] = [
0, 1, 2, 3, 0, 1, 2, 0, 0, 2, 2, 4, 5, 5, 0, 1, 2, 6, 2, 3, 0, 1, 0, 2, 0, 2,
];
let code_of = |c: u8| -> u8 {
if c.is_ascii_alphabetic() {
CODE[(c.to_ascii_lowercase() - b'a') as usize]
} else {
0
}
};
let b = s.as_bytes();
let mut i = 0;
while i < b.len() && !b[i].is_ascii_alphabetic() {
i += 1;
}
if i >= b.len() {
return String::from("?000");
}
let mut out = String::with_capacity(4);
out.push(b[i].to_ascii_uppercase() as char);
let mut prev = code_of(b[i]);
let mut j = 1;
while j < 4 && i < b.len() {
let code = code_of(b[i]);
if code > 0 {
if code != prev {
prev = code;
out.push((b'0' + code) as char);
j += 1;
}
} else {
prev = 0;
}
i += 1;
}
while j < 4 {
out.push('0');
j += 1;
}
out
}
fn c_text(v: &Value) -> String {
match v {
Value::Blob(b) => {
let end = b.iter().position(|&x| x == 0).unwrap_or(b.len());
String::from_utf8_lossy(&b[..end]).into_owned()
}
other => eval::to_text(other),
}
}
fn str_map(v: &Value, f: impl Fn(&str) -> String) -> Value {
match v {
Value::Null => Value::Null,
other => Value::Text(f(&c_text(other)).into()),
}
}
fn byte_map_text(v: &Value, f: impl Fn(&u8) -> u8) -> Value {
match v {
Value::Text(s) => Value::Text(crate::value::Text::from_bytes(
s.as_bytes().iter().map(f).collect(),
)),
_ => v.clone(),
}
}
fn real_arg(v: &Value) -> Option<f64> {
eval::to_number_strict(v).map(|n| eval::to_f64(&n))
}
fn math_finite(r: Option<f64>) -> Value {
match r {
Some(x) if x.is_nan() => Value::Null,
Some(x) => Value::Real(x),
None => Value::Null,
}
}
fn math1(name: &str, v: &[Value], f: impl Fn(f64) -> f64) -> Result<Value> {
if v.len() != 1 {
return Err(Error::Error(alloc::format!(
"wrong number of arguments to function {name}()"
)));
}
Ok(math_finite(real_arg(&v[0]).map(f)))
}
fn math_round_to_int(name: &str, v: &[Value], f: impl Fn(f64) -> f64) -> Result<Value> {
if v.len() != 1 {
return Err(Error::Error(alloc::format!(
"wrong number of arguments to function {name}()"
)));
}
Ok(match eval::to_number_strict(&v[0]) {
Some(Value::Integer(i)) => Value::Integer(i),
Some(Value::Real(r)) => math_finite(Some(f(r))),
_ => Value::Null,
})
}
fn json_doc_result(lname: &str, j: &super::json::Json) -> Value {
if lname.starts_with("jsonb") {
Value::Blob(j.to_jsonb())
} else {
Value::Text(j.serialize().into())
}
}
fn json_root(v: &Value) -> Result<Option<super::json::Json>> {
match v {
Value::Null => Ok(None),
Value::Blob(b) => match super::json::Json::from_jsonb(b) {
Some(j) => Ok(Some(j)),
None => Err(Error::Error("malformed JSON".into())),
},
other => match super::json::parse(&eval::to_text(other)) {
Some(j) => Ok(Some(j)),
None => Err(Error::Error("malformed JSON".into())),
},
}
}
fn check_path(p: &Value) -> Result<()> {
if matches!(p, Value::Null) {
return Ok(());
}
let s = eval::to_text(p);
if !super::json::path_is_valid(&s) {
return Err(Error::Error(alloc::format!("bad JSON path: '{s}'")));
}
Ok(())
}
fn json_value_arg(val: &Value, expr: Option<&Expr>, ctx: &EvalCtx) -> Result<super::json::Json> {
if let Value::Blob(b) = val {
return super::json::Json::from_jsonb(b)
.ok_or_else(|| Error::Error("JSON cannot hold BLOB values".into()));
}
let subtype = expr.is_some_and(|e| carries_json_subtype(e, ctx));
Ok(arg_to_json_with_subtype(val, subtype))
}
fn json_edit_value_arg(
val: &Value,
expr: Option<&Expr>,
ctx: &EvalCtx,
) -> Result<super::json::Json> {
if let Value::Blob(b) = val {
return super::json::Json::from_jsonb(b)
.ok_or_else(|| Error::Error("JSON cannot hold BLOB values".into()));
}
let subtype = expr.is_some_and(|e| carries_json_subtype(e, ctx));
if let Value::Text(s) = val
&& !subtype
{
return Ok(super::json::Json::text_raw(s.as_str()));
}
Ok(arg_to_json_with_subtype(val, subtype))
}
fn json_extract(root: &super::json::Json, paths: &[Value], jsonb: bool) -> Result<Value> {
for p in paths {
if matches!(p, Value::Null) {
return Ok(Value::Null);
}
check_path(p)?;
}
let scalar_or_doc = |j: &super::json::Json| -> Value {
match j {
super::json::Json::Array(_) | super::json::Json::Object(_) if jsonb => {
Value::Blob(j.to_jsonb())
}
_ => j.to_sql(),
}
};
if paths.len() == 1 {
return Ok(
match super::json::navigate(root, &eval::to_text(&paths[0])) {
Some(j) => scalar_or_doc(j),
None => Value::Null,
},
);
}
let items = paths
.iter()
.map(|p| match super::json::navigate(root, &eval::to_text(p)) {
Some(j) => j.clone(),
None => super::json::Json::Null,
})
.collect();
let arr = super::json::Json::Array(items);
Ok(if jsonb {
Value::Blob(arr.to_jsonb())
} else {
Value::Text(arr.serialize().into())
})
}
pub(crate) fn arg_to_json_with_subtype(val: &Value, subtype: bool) -> super::json::Json {
if let Value::Text(s) = val
&& subtype
&& let Some(j) = super::json::parse(s)
{
return j;
}
super::json::value_to_json(val)
}
pub(crate) fn carries_json_subtype(expr: &Expr, ctx: &EvalCtx) -> bool {
if let Expr::Paren(inner) = expr {
return carries_json_subtype(inner, ctx);
}
if produces_json(expr) {
return true;
}
if let Expr::Function { name, args, .. } = expr
&& name.eq_ignore_ascii_case("json_extract")
&& args.len() == 2
&& let Ok(doc) = eval::eval(&args[0], ctx)
&& let Ok(Some(root)) = json_root(&doc)
&& let Ok(path) = eval::eval(&args[1], ctx)
&& !matches!(path, Value::Null)
{
return matches!(
super::json::navigate(&root, &eval::to_text(&path)),
Some(super::json::Json::Array(_) | super::json::Json::Object(_))
);
}
false
}
pub(crate) fn might_carry_json_subtype(expr: &Expr) -> bool {
if let Expr::Paren(inner) = expr {
return might_carry_json_subtype(inner);
}
if produces_json(expr) {
return true;
}
matches!(expr,
Expr::Function { name, args, .. }
if name.eq_ignore_ascii_case("json_extract") && args.len() == 2)
}
pub(crate) fn produces_json(e: &Expr) -> bool {
match e {
Expr::Function { name, args, .. } => {
let lname = name.to_ascii_lowercase();
match lname.as_str() {
"json" | "json_quote" | "json_array" | "json_object" | "json_insert"
| "json_replace" | "json_set" | "json_patch" | "json_remove"
| "json_group_array" | "json_group_object" => true,
"json_extract" => args.len() >= 3,
_ => false,
}
}
Expr::Binary {
op: crate::sql::ast::BinaryOp::JsonExtract,
..
} => true,
Expr::Paren(inner) => produces_json(inner),
_ => false,
}
}
fn type_name(v: &Value) -> &'static str {
match v {
Value::Null => "null",
Value::Integer(_) => "integer",
Value::Real(_) => "real",
Value::Text(_) => "text",
Value::Blob(_) => "blob",
}
}
fn trim_fn(v: &[Value], left: bool, right: bool) -> Value {
if v.is_empty() || matches!(v[0], Value::Null) {
return Value::Null;
}
if v.len() >= 2 && matches!(v[1], Value::Null) {
return Value::Null;
}
let s = eval::text_bytes(&v[0]);
let set = if v.len() >= 2 {
eval::text_bytes(&v[1])
} else {
alloc::vec![b' ']
};
let s_bounds = utf8_unit_boundaries(&s);
let set_bounds = utf8_unit_boundaries(&set);
let set_units: Vec<&[u8]> = (0..set_bounds.len() - 1)
.map(|k| &set[set_bounds[k]..set_bounds[k + 1]])
.collect();
let is_trim = |unit: &[u8]| set_units.contains(&unit);
let n = s_bounds.len() - 1;
let mut start = 0;
let mut end = n;
if left {
while start < end && is_trim(&s[s_bounds[start]..s_bounds[start + 1]]) {
start += 1;
}
}
if right {
while end > start && is_trim(&s[s_bounds[end - 1]..s_bounds[end]]) {
end -= 1;
}
}
Value::Text(crate::value::Text::from_bytes(
s[s_bounds[start]..s_bounds[end]].to_vec(),
))
}
fn utf8_read_first(bytes: &[u8]) -> u32 {
const TRANS1: [u8; 64] = [
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d,
0x1e, 0x1f, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c,
0x0d, 0x0e, 0x0f, 0x00, 0x01, 0x02, 0x03, 0x00, 0x01, 0x02, 0x03, 0x00, 0x01, 0x02, 0x03,
0x00, 0x01, 0x02, 0x03,
];
let first = bytes[0];
if first < 0xc0 {
return u32::from(first);
}
let mut c = u32::from(TRANS1[(first - 0xc0) as usize]);
let mut i = 1;
while i < bytes.len() && (bytes[i] & 0xc0) == 0x80 {
c = (c << 6) + u32::from(bytes[i] & 0x3f);
i += 1;
}
if c < 0x80 || (c & 0xffff_f800) == 0xd800 || (c & 0xffff_fffe) == 0xfffe {
0xfffd
} else {
c
}
}
fn utf8_unit_boundaries(bytes: &[u8]) -> alloc::vec::Vec<usize> {
let mut bounds = alloc::vec::Vec::new();
let mut i = 0;
while i < bytes.len() {
bounds.push(i);
let lead = bytes[i];
i += 1;
if lead >= 0xc0 {
while i < bytes.len() && (bytes[i] & 0xc0) == 0x80 {
i += 1;
}
}
}
bounds.push(bytes.len());
bounds
}
fn substr(v: &[Value]) -> Result<Value> {
if v.len() < 2 || v.len() > 3 {
return Err(wrong_arg_count("substr"));
}
if matches!(v[0], Value::Null) {
return Ok(Value::Null);
}
let blob = matches!(v[0], Value::Blob(_));
let data: alloc::vec::Vec<u8> = match &v[0] {
Value::Blob(b) => b.clone(),
Value::Text(s) => s.as_bytes().to_vec(),
other => eval::to_text(other).into_bytes(),
};
let bounds: alloc::vec::Vec<usize> = if blob {
(0..=data.len()).collect()
} else {
utf8_unit_boundaries(&data)
};
if matches!(v[1], Value::Null) {
return Ok(Value::Null);
}
let len = (bounds.len() - 1) as i64;
let mut p1 = eval::to_int_value(&v[1]);
let mut p2 = if v.len() == 3 {
if matches!(v[2], Value::Null) {
return Ok(Value::Null);
}
eval::to_int_value(&v[2])
} else {
1_000_000_000
};
if p1 < 0 {
p1 = p1.saturating_add(len);
if p1 < 0 {
if p2 < 0 {
p2 = 0;
} else {
p2 = p2.saturating_add(p1);
}
p1 = 0;
}
} else if p1 > 0 {
p1 -= 1;
} else if p2 > 0 {
p2 -= 1;
}
if p2 < 0 {
if p2 < -p1 {
p2 = p1;
} else {
p2 = -p2;
}
p1 = p1.saturating_sub(p2);
}
let unit_count = bounds.len() - 1;
let start = (p1.max(0) as usize).min(unit_count);
let take = (p2.max(0) as usize).min(unit_count - start);
let out = data[bounds[start]..bounds[start + take]].to_vec();
if blob {
Ok(Value::Blob(out))
} else {
Ok(Value::Text(crate::value::Text::from_bytes(out)))
}
}
fn instr(v: &[Value]) -> Result<Value> {
if v.len() != 2 {
return Err(wrong_arg_count("instr"));
}
if matches!(v[0], Value::Null) || matches!(v[1], Value::Null) {
return Ok(Value::Null);
}
let both_blob = matches!(v[0], Value::Blob(_)) && matches!(v[1], Value::Blob(_));
let hay = eval::text_bytes(&v[0]);
let needle = eval::text_bytes(&v[1]);
let mut n: i64 = 0;
let mut off = 0usize;
let found = loop {
if off + needle.len() <= hay.len() && hay[off..off + needle.len()] == needle[..] {
break true;
}
if off >= hay.len() {
break false;
}
off += 1;
if !both_blob {
while off < hay.len() && (hay[off] & 0xc0) == 0x80 {
off += 1;
}
}
n += 1;
};
Ok(Value::Integer(if found { n + 1 } else { 0 }))
}
fn replace(v: &[Value]) -> Result<Value> {
if v.len() != 3 {
return Err(wrong_arg_count("replace"));
}
if matches!(v[0], Value::Null) || matches!(v[1], Value::Null) {
return Ok(Value::Null);
}
let s = eval::text_bytes(&v[0]);
let from = eval::text_bytes(&v[1]);
if from.is_empty() {
return Ok(Value::Text(crate::value::Text::from_bytes(s)));
}
if matches!(v[2], Value::Null) {
return Ok(Value::Null);
}
let to = eval::text_bytes(&v[2]);
let mut out: Vec<u8> = Vec::with_capacity(s.len());
let mut i = 0;
while i < s.len() {
if s[i..].starts_with(&from[..]) {
out.extend_from_slice(&to);
i += from.len();
} else {
out.push(s[i]);
i += 1;
}
}
Ok(Value::Text(crate::value::Text::from_bytes(out)))
}
fn round(v: &[Value]) -> Result<Value> {
if v.is_empty() || v.len() > 2 {
return Err(wrong_arg_count("round"));
}
if matches!(v[0], Value::Null) || matches!(v.get(1), Some(Value::Null)) {
return Ok(Value::Null);
}
let x = eval::to_f64(&v[0]);
let digits = if v.len() == 2 {
eval::to_int_value(&v[1]).clamp(0, 30) as u32
} else {
0
};
let r = round_half_away(x, digits);
Ok(Value::Real(if r == 0.0 { 0.0 } else { r }))
}
pub(crate) fn round_half_away(x: f64, n: u32) -> f64 {
if !x.is_finite() || x == 0.0 {
return x;
}
if crate::util::float::abs(x) >= 4_503_599_627_370_496.0 {
return x;
}
let neg = x < 0.0;
let ax = crate::util::float::abs(x);
let prec = n as usize + 25;
let s = alloc::format!("{ax:.prec$}");
let dot = s.find('.').unwrap_or(s.len());
let frac = if dot < s.len() { &s[dot + 1..] } else { "" };
let round_up = frac.as_bytes().get(n as usize).is_some_and(|&d| d >= b'5');
let mut digits: alloc::vec::Vec<u8> = alloc::vec::Vec::new();
digits.extend_from_slice(&s.as_bytes()[..dot]);
if n > 0 {
let take = (n as usize).min(frac.len());
digits.extend_from_slice(&frac.as_bytes()[..take]);
digits.resize(dot + n as usize, b'0');
}
if round_up {
let mut i = digits.len();
loop {
if i == 0 {
digits.insert(0, b'1');
break;
}
i -= 1;
if digits[i] == b'9' {
digits[i] = b'0';
} else {
digits[i] += 1;
break;
}
}
}
let nn = n as usize;
let s2 = if nn == 0 {
alloc::string::String::from_utf8(digits).unwrap_or_default()
} else {
let point = digits.len() - nn;
let mut out = alloc::string::String::new();
out.push_str(core::str::from_utf8(&digits[..point]).unwrap_or("0"));
out.push('.');
out.push_str(core::str::from_utf8(&digits[point..]).unwrap_or("0"));
out
};
let mag: f64 = s2.parse().unwrap_or(ax);
if neg { -mag } else { mag }
}
fn scalar_min_max(v: &[Value], want_min: bool) -> Result<Value> {
if v.is_empty() {
let name = if want_min { "min" } else { "max" };
return Err(Error::Error(alloc::format!(
"wrong number of arguments to function {name}()"
)));
}
if v.iter().any(|x| matches!(x, Value::Null)) {
return Ok(Value::Null);
}
let mut best = v[0].clone();
for x in &v[1..] {
let ord = eval::compare(&best, x);
let take = if want_min {
ord != core::cmp::Ordering::Less
} else {
ord == core::cmp::Ordering::Less
};
if take {
best = x.clone();
}
}
Ok(best)
}
fn hex_encode(v: &Value) -> String {
let bytes = match v {
Value::Blob(b) => b.clone(),
Value::Text(s) => s.as_bytes().to_vec(),
other => eval::to_text(other).into_bytes(),
};
let mut s = String::with_capacity(bytes.len() * 2);
for b in bytes {
s.push(nibble(b >> 4));
s.push(nibble(b & 0xf));
}
s
}
fn nibble(n: u8) -> char {
match n {
0..=9 => (b'0' + n) as char,
_ => (b'A' + n - 10) as char,
}
}
fn char_fn(v: &[Value]) -> Value {
let mut out: alloc::vec::Vec<u8> = alloc::vec::Vec::new();
for x in v {
let cp = eval::to_int_value(x);
let cp = if (0..=0x10_ffff).contains(&cp) {
cp
} else {
0xfffd
};
let c = (cp as u32) & 0x1f_ffff;
if c < 0x80 {
out.push(c as u8);
} else if c < 0x800 {
out.push(0xc0 + ((c >> 6) & 0x1f) as u8);
out.push(0x80 + (c & 0x3f) as u8);
} else if c < 0x1_0000 {
out.push(0xe0 + ((c >> 12) & 0x0f) as u8);
out.push(0x80 + ((c >> 6) & 0x3f) as u8);
out.push(0x80 + (c & 0x3f) as u8);
} else {
out.push(0xf0 + ((c >> 18) & 0x07) as u8);
out.push(0x80 + ((c >> 12) & 0x3f) as u8);
out.push(0x80 + ((c >> 6) & 0x3f) as u8);
out.push(0x80 + (c & 0x3f) as u8);
}
}
Value::Text(crate::value::Text::from_bytes(out))
}