graphitesql/exec/func.rs
1//! Built-in scalar functions.
2//!
3//! Aggregate functions (`count`, `sum`, …) are handled by the executor, which
4//! folds over rows; this module covers the per-row scalar functions. The set is
5//! a useful core and grows toward SQLite's full library (`func.c`, `date.c`).
6
7use super::eval::{self, EvalCtx};
8use crate::error::{Error, Result};
9use crate::sql::ast::{Expr, Literal};
10use crate::value::Value;
11use alloc::string::String;
12use alloc::vec::Vec;
13
14/// SQLite's default `SQLITE_MAX_LENGTH`: the largest string/blob a single value
15/// may hold. `zeroblob`/`randomblob` error with "string or blob too big" past it
16/// rather than attempting a multi-gigabyte allocation.
17const MAX_BLOB_LEN: usize = 1_000_000_000;
18
19/// Names that *can* be aggregates (used for catalog/name checks).
20pub fn is_aggregate(name: &str) -> bool {
21 matches!(
22 name.to_ascii_lowercase().as_str(),
23 "count" | "sum" | "total" | "avg" | "min" | "max" | "group_concat" | "geopoly_group_bbox"
24 )
25}
26
27/// Whether a *specific call* is an aggregate. `min`/`max` are scalar with 2+
28/// arguments and aggregate with exactly one (or `*`), matching SQLite.
29pub fn is_aggregate_call(name: &str, nargs: usize, star: bool) -> bool {
30 match name.to_ascii_lowercase().as_str() {
31 "count" | "sum" | "total" | "avg" | "group_concat" | "string_agg" => true,
32 // The JSON group aggregates have no scalar counterpart, so they are
33 // aggregates at any argument count — a wrong count must reach the
34 // aggregate arity guard ("wrong number of arguments"), not fall through
35 // to scalar dispatch ("no such function").
36 "json_group_array" | "jsonb_group_array" | "json_group_object" | "jsonb_group_object" => {
37 true
38 }
39 // geopoly_group_bbox has no scalar counterpart, so it is an aggregate at
40 // any argument count (the arity guard reports a wrong count).
41 "geopoly_group_bbox" => true,
42 "min" | "max" => star || nargs == 1,
43 _ => false,
44 }
45}
46
47/// The searchable `(column name, text)` pairs an FTS5 `MATCH` operand refers to,
48/// or `None` if the operand is not a column or table-name reference (so `MATCH`
49/// is not a full-text query in this context). A reference to an indexed column
50/// yields just that column; an unqualified reference to the table's own name
51/// yields every column (SQLite's table-wide `MATCH`, where `col:token` filters
52/// pick out individual columns).
53#[cfg(feature = "fts5")]
54fn fts5_match_columns(
55 operand: &Expr,
56 ctx: &EvalCtx,
57) -> Option<(Vec<(String, String)>, crate::vtab::Fts5Tok)> {
58 let (table, column) = match operand {
59 Expr::Column { table, column, .. } => (table.as_deref(), column.as_str()),
60 Expr::Paren(e) => return fts5_match_columns(e, ctx),
61 _ => return None,
62 };
63 // The table's tokenizer config (Porter stemming + `remove_diacritics` level),
64 // so the query folds exactly like the indexed documents.
65 let tok = |t: &str| {
66 ctx.subqueries
67 .map_or_else(crate::vtab::Fts5Tok::default, |s| s.fts5_tok(t))
68 };
69 // A reference to a specific indexed column searches only that column. An
70 // `UNINDEXED` column matches nothing (it carries no full-text index).
71 if let Some(i) = ctx.columns.iter().position(|c| {
72 c.name.eq_ignore_ascii_case(column) && table.is_none_or(|t| c.table.eq_ignore_ascii_case(t))
73 }) {
74 let c = &ctx.columns[i];
75 let unindexed = ctx
76 .subqueries
77 .and_then(|s| s.fts5_indexed_columns(&c.table))
78 .is_some_and(|cols| !cols.iter().any(|n| n.eq_ignore_ascii_case(&c.name)));
79 if unindexed {
80 return Some((Vec::new(), crate::vtab::Fts5Tok::default()));
81 }
82 return Some((
83 alloc::vec![(c.name.clone(), eval::to_text(&ctx.row[i]))],
84 tok(&c.table),
85 ));
86 }
87 // An unqualified reference to the table itself searches across every indexed
88 // column (`UNINDEXED` columns are stored but excluded from the full-text index).
89 if table.is_none() {
90 let indexed = ctx.subqueries.and_then(|s| s.fts5_indexed_columns(column));
91 let cols: Vec<(String, String)> = ctx
92 .columns
93 .iter()
94 .enumerate()
95 .filter(|(_, c)| c.table.eq_ignore_ascii_case(column))
96 .filter(|(_, c)| {
97 indexed
98 .as_ref()
99 .is_none_or(|cols| cols.iter().any(|n| n.eq_ignore_ascii_case(&c.name)))
100 })
101 .map(|(i, c)| (c.name.clone(), eval::to_text(&ctx.row[i])))
102 .collect();
103 if !cols.is_empty() {
104 return Some((cols, tok(column)));
105 }
106 }
107 None
108}
109
110/// The fts5 table a `MATCH` operand refers to: the operand `t` in `t MATCH q` (a
111/// bare table reference) or the owning table of `t.col MATCH q`. Resolves the name
112/// through the current row's column set (so an alias maps to the real table name),
113/// falling back to the operand's own identifier. `None` when the operand is not a
114/// column/table reference.
115#[cfg(feature = "fts5")]
116fn fts5_match_operand_table(operand: &Expr, ctx: &EvalCtx) -> Option<alloc::string::String> {
117 let (table, column) = match operand {
118 Expr::Column { table, column, .. } => (table.as_deref(), column.as_str()),
119 Expr::Paren(e) => return fts5_match_operand_table(e, ctx),
120 _ => return None,
121 };
122 // `t.col` → the table owning that column; bare `t` → the table named `t` in the
123 // row's column set (its `table` field holds the resolved base-table name).
124 if let Some(t) = table {
125 return Some(alloc::string::String::from(t));
126 }
127 ctx.columns
128 .iter()
129 .find(|c| c.table.eq_ignore_ascii_case(column))
130 .map(|c| c.table.clone())
131 .or_else(|| Some(alloc::string::String::from(column)))
132}
133
134/// Whether a `highlight`/`snippet` first argument names a contentless fts5 table.
135#[cfg(feature = "fts5")]
136fn fts5_operand_is_contentless(operand: &Expr, ctx: &EvalCtx) -> bool {
137 fts5_match_operand_table(operand, ctx)
138 .zip(ctx.subqueries)
139 .is_some_and(|(t, s)| s.fts5_is_contentless_table(&t))
140}
141
142/// Evaluate a scalar function call.
143pub fn eval_scalar(name: &str, args: &[Expr], star: bool, ctx: &EvalCtx) -> Result<Value> {
144 let lname = name.to_ascii_lowercase();
145 if is_aggregate_call(&lname, args.len(), star) {
146 // Reaching the scalar evaluator means this aggregate sits in a position
147 // that forbids one — most commonly nested inside another aggregate's
148 // argument or FILTER (`sum(count(a))`), where the inner call is evaluated
149 // per row as a scalar. SQLite reports this as `misuse of aggregate
150 // function NAME()`, naming the inner call. (Row-filtering positions —
151 // WHERE, join ON, ORDER BY of a non-aggregate query, UPDATE/DELETE
152 // expressions — are intercepted earlier at prepare time so they error even
153 // over an empty table; see `reject_misused_aggregate`.)
154 return Err(Error::Error(alloc::format!(
155 "misuse of aggregate function {name}()"
156 )));
157 }
158 if star {
159 return Err(Error::Error(alloc::format!(
160 "{name}(*) is not a scalar call"
161 )));
162 }
163
164 // Connection-state functions: read counters off the subquery handler.
165 match lname.as_str() {
166 "last_insert_rowid" | "changes" | "total_changes" => {
167 arity(&lname, args, 0)?;
168 let n = ctx.subqueries.map_or(0, |s| match lname.as_str() {
169 "last_insert_rowid" => s.last_insert_rowid(),
170 "changes" => s.changes(),
171 _ => s.total_changes(),
172 });
173 return Ok(Value::Integer(n));
174 }
175 "random" => {
176 arity(&lname, args, 0)?;
177 return Ok(Value::Integer(
178 ctx.subqueries.map_or(0, |s| s.next_random()),
179 ));
180 }
181 "randomblob" => {
182 arity(&lname, args, 1)?;
183 // SQLite coerces the argument to an integer (NULL/non-numeric text
184 // become 0, reals truncate) and clamps N < 1 to a single byte — so
185 // randomblob(NULL) is a 1-byte blob, not NULL.
186 let n = eval::to_int_value(&eval::eval(&args[0], ctx)?);
187 let len = if n < 1 { 1 } else { n as usize };
188 if len > MAX_BLOB_LEN {
189 return Err(Error::Error("string or blob too big".into()));
190 }
191 let mut bytes = Vec::new();
192 if let Some(s) = ctx.subqueries {
193 while bytes.len() < len {
194 bytes.extend_from_slice(&s.next_random().to_le_bytes());
195 }
196 bytes.truncate(len);
197 } else {
198 bytes.resize(len, 0);
199 }
200 return Ok(Value::Blob(bytes));
201 }
202 // FTS5 `MATCH`: `x MATCH 'query'` parsed to `match('query', x)`. When the
203 // operand `x` references an indexed column (or the table itself), run the
204 // full-text query against that document. When it is not a column/table
205 // reference (e.g. a literal), fall through so a user-registered `match`
206 // function — or the "no such function" error — applies, matching SQLite,
207 // where a bare `MATCH` outside a virtual-table context is an error.
208 #[cfg(feature = "fts5")]
209 "match" if args.len() == 2 => {
210 if let Some((cols, tok)) = fts5_match_columns(&args[1], ctx) {
211 let pattern = eval::eval(&args[0], ctx)?;
212 return Ok(match pattern {
213 Value::Null => Value::Null,
214 p => {
215 let q = eval::to_text(&p);
216 // A contentless table keeps no column text, so `MATCH` can't
217 // be re-checked from the (NULL) row — consult the index by
218 // rowid instead. `fts5_match_operand_table` names the table
219 // the operand refers to; the connection resolves whether it
220 // is contentless and, if so, evaluates against the index.
221 if let (Some(table), Some(rowid)) =
222 (fts5_match_operand_table(&args[1], ctx), ctx.rowid)
223 {
224 if let Some(m) = ctx
225 .subqueries
226 .and_then(|s| s.fts5_contentless_match(&table, &q, rowid))
227 {
228 return Ok(Value::Integer(m as i64));
229 }
230 }
231 Value::Integer(crate::vtab::fts5_query_matches(&q, &cols, tok) as i64)
232 }
233 });
234 }
235 }
236 // FTS5 `bm25(<table>[, w1, w2, …])`: the relevance score of the current row
237 // (optionally with per-column weights), computed by `run_core` for a `MATCH`
238 // query over an `fts5` table. Falls through when no such score is in scope
239 // (so `bm25()` elsewhere is the usual unknown name).
240 #[cfg(feature = "fts5")]
241 "bm25" if !args.is_empty() && ctx.rowid.is_some() => {
242 let weights: Vec<f64> = args[1..]
243 .iter()
244 .map(|a| Ok(eval::to_f64(&eval::eval(a, ctx)?)))
245 .collect::<Result<_>>()?;
246 if let Some(score) = ctx
247 .rowid
248 .and_then(|r| ctx.subqueries?.fts5_bm25(r, &weights))
249 {
250 return Ok(Value::Real(score));
251 }
252 }
253 // FTS5 `highlight(<table>, col, open, close)`: column `col`'s text with the
254 // matched tokens wrapped, in scope for a `MATCH` query over an `fts5` table.
255 #[cfg(feature = "fts5")]
256 "highlight" if args.len() == 4 => {
257 // A contentless table stores no text, so there is nothing to render:
258 // `highlight`/`snippet` return NULL (matching SQLite).
259 if fts5_operand_is_contentless(&args[0], ctx) {
260 return Ok(Value::Null);
261 }
262 let col = eval::to_int_value(&eval::eval(&args[1], ctx)?);
263 let open = eval::to_text(&eval::eval(&args[2], ctx)?);
264 let close = eval::to_text(&eval::eval(&args[3], ctx)?);
265 // The column text is the current row's value for that column.
266 if let Ok(col) = usize::try_from(col) {
267 let text = ctx.row.get(col).map(eval::to_text).unwrap_or_default();
268 if let Some(s) = ctx
269 .subqueries
270 .and_then(|s| s.fts5_highlight(col, &text, &open, &close))
271 {
272 return Ok(Value::Text(s));
273 }
274 }
275 }
276 // FTS5 `snippet(<table>, col, open, close, ellipsis, n)`: a window of up
277 // to `n` tokens from column `col`, matched tokens wrapped, ellipsis at any
278 // trimmed end. In scope for a `MATCH` query over an `fts5` table.
279 #[cfg(feature = "fts5")]
280 "snippet" if args.len() == 6 => {
281 if fts5_operand_is_contentless(&args[0], ctx) {
282 return Ok(Value::Null);
283 }
284 let col = eval::to_int_value(&eval::eval(&args[1], ctx)?);
285 let open = eval::to_text(&eval::eval(&args[2], ctx)?);
286 let close = eval::to_text(&eval::eval(&args[3], ctx)?);
287 let ellipsis = eval::to_text(&eval::eval(&args[4], ctx)?);
288 let ntokens = eval::to_int_value(&eval::eval(&args[5], ctx)?);
289 // A negative `col` auto-selects the best column, so pass every column's
290 // text; the module bounds the slice by the table's declared columns.
291 if let Ok(ntokens) = usize::try_from(ntokens) {
292 let cols: Vec<alloc::string::String> = ctx.row.iter().map(eval::to_text).collect();
293 if let Some(s) = ctx
294 .subqueries
295 .and_then(|s| s.fts5_snippet(col, &cols, &open, &close, &ellipsis, ntokens))
296 {
297 return Ok(Value::Text(s));
298 }
299 }
300 }
301 _ => {}
302 }
303
304 // Functions whose NULL-handling is special are done before arg evaluation.
305 match lname.as_str() {
306 "coalesce" => {
307 // SQLite requires at least two arguments.
308 if args.len() < 2 {
309 return Err(wrong_arg_count("coalesce"));
310 }
311 for a in args {
312 let v = eval::eval(a, ctx)?;
313 if !matches!(v, Value::Null) {
314 return Ok(v);
315 }
316 }
317 return Ok(Value::Null);
318 }
319 "ifnull" => {
320 arity(&lname, args, 2)?;
321 let a = eval::eval(&args[0], ctx)?;
322 return if matches!(a, Value::Null) {
323 eval::eval(&args[1], ctx)
324 } else {
325 Ok(a)
326 };
327 }
328 // `json_quote(X)` returns a JSON-subtyped argument as-is (it is already
329 // JSON text), and only quotes a value that is *not* JSON. graphite has no
330 // value subtypes, so it approximates the subtype syntactically: an arg
331 // that is itself a JSON-producing call (`json`, `json_array`, …) carries
332 // the subtype. Anything else falls through to the quoting arm below.
333 "json_quote" if args.len() == 1 && produces_json(&args[0]) => {
334 let val = eval::eval(&args[0], ctx)?;
335 // A JSONB blob (e.g. `json_quote(jsonb('[1,2]'))`) renders as its JSON
336 // text; any other blob is rejected.
337 if let Value::Blob(b) = &val {
338 let j = super::json::Json::from_jsonb(b)
339 .ok_or_else(|| Error::Error("JSON cannot hold BLOB values".into()))?;
340 return Ok(Value::Text(j.quote()));
341 }
342 return Ok(val);
343 }
344 _ => {}
345 }
346
347 let v: Vec<Value> = args
348 .iter()
349 .map(|a| eval::eval(a, ctx))
350 .collect::<Result<_>>()?;
351
352 Ok(match lname.as_str() {
353 "abs" => {
354 arity(&lname, args, 1)?;
355 match &v[0] {
356 Value::Null => Value::Null,
357 // `abs(-9223372036854775808)` has no i64 result — SQLite errors.
358 Value::Integer(i) => match i.checked_abs() {
359 Some(a) => Value::Integer(a),
360 None => return Err(Error::Error("integer overflow".into())),
361 },
362 // `+ 0.0` normalises a negative-zero result to `0.0`, matching
363 // SQLite (`abs(-0.0)` is `0.0`, not `-0.0`).
364 Value::Real(r) => Value::Real(crate::util::float::abs(*r) + 0.0),
365 // A text/blob argument is coerced to a real (SQLite gives
366 // `abs('5')` = 5.0, not 5).
367 other => Value::Real(crate::util::float::abs(eval::to_f64(other)) + 0.0),
368 }
369 }
370 "length" => {
371 arity(&lname, args, 1)?;
372 match &v[0] {
373 Value::Null => Value::Null,
374 Value::Blob(b) => Value::Integer(b.len() as i64),
375 // For a string, SQLite counts characters up to (not including)
376 // the first NUL — `length('A'||char(0)||'B')` is 1, not 3.
377 // Numbers stringify without NULs, so they are unaffected.
378 other => {
379 let t = eval::to_text(other);
380 let n = match t.find('\0') {
381 Some(i) => t[..i].chars().count(),
382 None => t.chars().count(),
383 };
384 Value::Integer(n as i64)
385 }
386 }
387 }
388 "octet_length" => {
389 arity(&lname, args, 1)?;
390 // Number of bytes in the value's encoding: blobs as-is, everything
391 // else as the UTF-8 length of its text representation.
392 match &v[0] {
393 Value::Null => Value::Null,
394 Value::Blob(b) => Value::Integer(b.len() as i64),
395 other => Value::Integer(eval::to_text(other).len() as i64),
396 }
397 }
398 "glob" => {
399 // glob(pattern, text) is the function form of `text GLOB pattern`.
400 arity(&lname, args, 2)?;
401 if v.iter().take(2).any(|x| matches!(x, Value::Null)) {
402 Value::Null
403 } else {
404 let m = eval::glob_match(&eval::to_text(&v[0]), &eval::to_text(&v[1]));
405 Value::Integer(m as i64)
406 }
407 }
408 "lower" => {
409 arity(&lname, args, 1)?;
410 // Stock sqlite3's lower()/upper() fold ASCII A–Z/a–z only; the optional
411 // `unicode` feature switches to full Unicode case-folding
412 // (`CAFÉ` → `café`), like a sqlite3 built with the ICU extension.
413 // `str::to_lowercase` provides it in core+alloc, so no dependency is
414 // needed. Default (feature off) stays byte-for-byte stock-sqlite3.
415 #[cfg(feature = "unicode")]
416 {
417 str_map(&v[0], |s| s.to_lowercase())
418 }
419 #[cfg(not(feature = "unicode"))]
420 {
421 str_map(&v[0], |s| s.to_ascii_lowercase())
422 }
423 }
424 "upper" => {
425 arity(&lname, args, 1)?;
426 #[cfg(feature = "unicode")]
427 {
428 str_map(&v[0], |s| s.to_uppercase())
429 }
430 #[cfg(not(feature = "unicode"))]
431 {
432 str_map(&v[0], |s| s.to_ascii_uppercase())
433 }
434 }
435 "trim" | "ltrim" | "rtrim" => {
436 // SQLite's trim family takes 1 (string) or 2 (string, chars) arguments.
437 if args.is_empty() || args.len() > 2 {
438 return Err(wrong_arg_count(&lname));
439 }
440 let (left, right) = match lname.as_str() {
441 "ltrim" => (true, false),
442 "rtrim" => (false, true),
443 _ => (true, true),
444 };
445 trim_fn(&v, left, right)
446 }
447 "soundex" => {
448 arity(&lname, args, 1)?;
449 // NULL/non-alpha input yields "?000" (SQLite does not propagate NULL).
450 Value::Text(soundex(&c_text(&v[0])))
451 }
452 "typeof" => {
453 arity(&lname, args, 1)?;
454 Value::Text(String::from(type_name(&v[0])))
455 }
456 "nullif" => {
457 arity(&lname, args, 2)?;
458 // The comparison follows the standard binary-comparison collation
459 // rule: an explicit `COLLATE` on either operand wins (left-preferred),
460 // else a column's declared collation, else BINARY — so
461 // `NULLIF('a','A' COLLATE NOCASE)` is NULL.
462 let coll = eval::resolve_collation(&args[0], &args[1], ctx);
463 if crate::value::cmp_values_coll(&v[0], &v[1], coll) == core::cmp::Ordering::Equal {
464 Value::Null
465 } else {
466 v[0].clone()
467 }
468 }
469 "n/a" => unreachable!(),
470 "substr" | "substring" => substr(&v)?,
471 "instr" => instr(&v)?,
472 "replace" => replace(&v)?,
473 "round" => round(&v)?,
474 "min" => scalar_min_max(&v, true)?,
475 "max" => scalar_min_max(&v, false)?,
476 "hex" => {
477 arity(&lname, args, 1)?;
478 Value::Text(hex_encode(&v[0]))
479 }
480 "char" => char_fn(&v),
481 "unicode" => {
482 arity(&lname, args, 1)?;
483 match &v[0] {
484 Value::Null => Value::Null,
485 other => eval::to_text(other)
486 .chars()
487 .next()
488 .map(|c| Value::Integer(c as i64))
489 .unwrap_or(Value::Null),
490 }
491 }
492 // `if` is SQLite's alias for `iif`. The parser desugars the scalar form
493 // (>= 2 args) into a CASE expression so it short-circuits, so this arm is
494 // normally reached only for the <2-arg arity error; it still evaluates the
495 // CASE-form semantics (`(when, then)` pairs with an optional trailing
496 // ELSE) for any non-desugared call, for robustness.
497 "iif" | "if" => {
498 if args.len() < 2 {
499 return Err(wrong_arg_count(&lname));
500 }
501 let n = v.len();
502 let mut out = if n % 2 == 1 {
503 v[n - 1].clone()
504 } else {
505 Value::Null
506 };
507 let mut i = 0;
508 while i + 1 < n {
509 if eval::truth(&v[i]) == Some(true) {
510 out = v[i + 1].clone();
511 break;
512 }
513 i += 2;
514 }
515 out
516 }
517 // The SQLite release graphitesql tracks and writes into new file headers
518 // (`SQLITE_VERSION_NUMBER` 3_053_002 = 3.53.2).
519 "sqlite_version" => {
520 arity(&lname, args, 0)?;
521 Value::Text(crate::TARGET_SQLITE_VERSION.into())
522 }
523 // Independent reimplementation: this is graphitesql's own identifier in
524 // SQLite's `YYYY-MM-DD HH:MM:SS <hash>` shape, not a C build's id.
525 "sqlite_source_id" => {
526 arity(&lname, args, 0)?;
527 Value::Text(crate::TARGET_SQLITE_SOURCE_ID.into())
528 }
529 "zeroblob" => {
530 arity(&lname, args, 1)?;
531 // SQLite reads the length via `sqlite3_value_int64`, which maps NULL to
532 // 0 — so `zeroblob(NULL)` is an empty blob, not NULL.
533 let n = eval::to_int_value(&v[0]).max(0) as usize;
534 if n > MAX_BLOB_LEN {
535 return Err(Error::Error("string or blob too big".into()));
536 }
537 Value::Blob(alloc::vec![0u8; n])
538 }
539 "quote" => {
540 arity(&lname, args, 1)?;
541 Value::Text(quote_value(&v[0]))
542 }
543 "unistr" => {
544 // Decode `\uXXXX` / `\UXXXXXXXX` / `\\` escapes in the argument's
545 // text. NULL passes through; any other escape errors, as in SQLite.
546 arity(&lname, args, 1)?;
547 match &v[0] {
548 Value::Null => Value::Null,
549 other => Value::Text(unistr_decode(&eval::to_text(other))?),
550 }
551 }
552 "unistr_quote" => {
553 // Like quote(), except a text value containing a control character
554 // (< U+0020) is rendered as `unistr('…')` with those characters
555 // escaped `\uXXXX`. Non-text values match quote() exactly.
556 arity(&lname, args, 1)?;
557 match &v[0] {
558 Value::Text(s) if s.chars().any(|c| (c as u32) < 0x20) => {
559 Value::Text(unistr_quote_text(s))
560 }
561 other => Value::Text(quote_value(other)),
562 }
563 }
564 "subtype" => {
565 // The value's subtype. graphite tracks no subtypes, so this is
566 // always 0 (SQLite also returns 0 for ordinary, non-JSON values).
567 arity(&lname, args, 1)?;
568 Value::Integer(0)
569 }
570 "sign" => {
571 arity(&lname, args, 1)?;
572 // Numeric (or losslessly-numeric text) only; otherwise NULL.
573 let num = match &v[0] {
574 Value::Integer(i) => Some(*i as f64),
575 Value::Real(r) => Some(*r),
576 Value::Text(s) => eval::parse_decimal_f64(s.trim()),
577 _ => None,
578 };
579 match num {
580 Some(r) if r > 0.0 => Value::Integer(1),
581 Some(r) if r < 0.0 => Value::Integer(-1),
582 Some(_) => Value::Integer(0),
583 None => Value::Null,
584 }
585 }
586 "concat" => {
587 // SQLite 3.44+: concatenate all args, treating NULL as empty.
588 // At least one argument is required.
589 if v.is_empty() {
590 return Err(wrong_arg_count("concat"));
591 }
592 let mut s = String::new();
593 for x in &v {
594 if !matches!(x, Value::Null) {
595 s.push_str(&eval::to_text(x));
596 }
597 }
598 Value::Text(s)
599 }
600 "concat_ws" => {
601 // A separator plus at least one value argument are required.
602 if v.len() < 2 {
603 return Err(wrong_arg_count("concat_ws"));
604 }
605 if matches!(v[0], Value::Null) {
606 Value::Null
607 } else {
608 let sep = eval::to_text(&v[0]);
609 let parts: alloc::vec::Vec<String> = v[1..]
610 .iter()
611 .filter(|x| !matches!(x, Value::Null))
612 .map(eval::to_text)
613 .collect();
614 Value::Text(parts.join(&sep))
615 }
616 }
617 "like" => {
618 // like(pattern, text[, escape]) — the function form of `text LIKE
619 // pattern`. NULL operand → NULL.
620 if v.len() < 2 || v.len() > 3 {
621 return Err(wrong_arg_count("like"));
622 }
623 // A NULL escape, like a NULL pattern/text, yields NULL.
624 if v.iter().any(|x| matches!(x, Value::Null)) {
625 Value::Null
626 } else {
627 // An explicit ESCAPE must be exactly one character (SQLite
628 // raises "ESCAPE expression must be a single character" for
629 // empty or multi-character escapes).
630 let escape = match v.get(2) {
631 Some(e) => {
632 let s = eval::to_text(e);
633 let mut it = s.chars();
634 match (it.next(), it.next()) {
635 (Some(c), None) => Some(c),
636 _ => {
637 return Err(Error::Error(
638 "ESCAPE expression must be a single character".into(),
639 ));
640 }
641 }
642 }
643 None => None,
644 };
645 let m = eval::like_match_escape(
646 &eval::to_text(&v[0]),
647 &eval::to_text(&v[1]),
648 escape,
649 ctx.subqueries.is_some_and(|s| s.case_sensitive_like()),
650 );
651 Value::Integer(m as i64)
652 }
653 }
654 // Optimizer hints that are no-ops at the value level (return the operand).
655 "likely" | "unlikely" => {
656 arity(&lname, args, 1)?;
657 v[0].clone()
658 }
659 "likelihood" => {
660 arity(&lname, args, 2)?;
661 // SQLite requires the second argument to be a floating-point *literal*
662 // in the range 0.0..=1.0, checked against the parsed AST rather than
663 // the runtime value: an integer literal (`0`, `1`, `2`), a negative,
664 // a string, an expression (`0.5+0.1`), or a column reference are all
665 // rejected, while `0.5`, `.5`, `1e0` and a parenthesized `(0.5)` are
666 // accepted (`exprProbability` in SQLite's `expr.c`).
667 if !likelihood_prob_is_valid(&args[1]) {
668 return Err(Error::Error(
669 "second argument to likelihood() must be a constant between 0.0 and 1.0".into(),
670 ));
671 }
672 v[0].clone()
673 }
674 "unhex" => {
675 if v.is_empty() || v.len() > 2 {
676 return Err(wrong_arg_count("unhex"));
677 }
678 // A NULL hex string or NULL ignore-set both yield NULL.
679 let ignore = match v.get(1) {
680 Some(Value::Null) => return Ok(Value::Null),
681 // The ignore set is itself coerced as a C string (NUL-terminated).
682 Some(set) => Some(c_text(set)),
683 None => None,
684 };
685 match &v[0] {
686 Value::Null => Value::Null,
687 // The hex input is read as a NUL-terminated C string, matching
688 // SQLite's `unhexFunc` (`sqlite3_value_text`).
689 other => match unhex(&c_text(other), ignore.as_deref()) {
690 Some(b) => Value::Blob(b),
691 None => Value::Null,
692 },
693 }
694 }
695 // Math functions (SQLite's `-DSQLITE_ENABLE_MATH_FUNCTIONS` set; the CLI
696 // ships with these enabled). Each coerces its argument(s) to a real and
697 // returns NULL when an argument is NULL or the result is not finite,
698 // matching SQLite.
699 "pi" => {
700 arity(&lname, args, 0)?;
701 Value::Real(crate::util::float::PI)
702 }
703 "ceil" | "ceiling" => math_round_to_int(&lname, &v, crate::util::float::ceil)?,
704 "floor" => math_round_to_int(&lname, &v, crate::util::float::floor)?,
705 "trunc" => math_round_to_int(&lname, &v, crate::util::float::trunc)?,
706 "sqrt" => math1(&lname, &v, crate::util::float::sqrt)?,
707 "exp" => math1(&lname, &v, crate::util::float::exp)?,
708 "ln" => math1(&lname, &v, crate::util::float::ln)?,
709 "log2" => math1(&lname, &v, crate::util::float::log2)?,
710 "sin" => math1(&lname, &v, crate::util::float::sin)?,
711 "cos" => math1(&lname, &v, crate::util::float::cos)?,
712 "tan" => math1(&lname, &v, crate::util::float::tan)?,
713 "asin" => math1(&lname, &v, crate::util::float::asin)?,
714 "acos" => math1(&lname, &v, crate::util::float::acos)?,
715 "atan" => math1(&lname, &v, crate::util::float::atan)?,
716 "sinh" => math1(&lname, &v, crate::util::float::sinh)?,
717 "cosh" => math1(&lname, &v, crate::util::float::cosh)?,
718 "tanh" => math1(&lname, &v, crate::util::float::tanh)?,
719 "asinh" => math1(&lname, &v, crate::util::float::asinh)?,
720 "acosh" => math1(&lname, &v, crate::util::float::acosh)?,
721 "atanh" => math1(&lname, &v, crate::util::float::atanh)?,
722 "degrees" => math1(&lname, &v, crate::util::float::degrees)?,
723 "radians" => math1(&lname, &v, crate::util::float::radians)?,
724 // `log(X)` is base-10; `log(B, X)` is base B. `log10` is base-10.
725 "log10" => math1(&lname, &v, crate::util::float::log10)?,
726 "log" => {
727 if v.len() == 1 {
728 math_finite(real_arg(&v[0]).map(crate::util::float::log10))
729 } else {
730 arity(&lname, args, 2)?;
731 match (real_arg(&v[0]), real_arg(&v[1])) {
732 // `log(B, X)` is NULL unless `B > 0`, `B != 1`, and `X > 0`
733 // (SQLite). A base of 1 makes `ln(B) == 0`, which the bare
734 // division would turn into ±Inf instead of NULL, so guard it.
735 (Some(1.0), Some(_)) => Value::Null,
736 (Some(b), Some(x)) => {
737 math_finite(Some(crate::util::float::ln(x) / crate::util::float::ln(b)))
738 }
739 _ => Value::Null,
740 }
741 }
742 }
743 "pow" | "power" => {
744 arity(&lname, args, 2)?;
745 match (real_arg(&v[0]), real_arg(&v[1])) {
746 (Some(b), Some(e)) => math_finite(Some(crate::util::float::pow(b, e))),
747 _ => Value::Null,
748 }
749 }
750 "atan2" => {
751 arity(&lname, args, 2)?;
752 match (real_arg(&v[0]), real_arg(&v[1])) {
753 (Some(y), Some(x)) => math_finite(Some(crate::util::float::atan2(y, x))),
754 _ => Value::Null,
755 }
756 }
757 "mod" => {
758 arity(&lname, args, 2)?;
759 match (real_arg(&v[0]), real_arg(&v[1])) {
760 (Some(x), Some(y)) => math_finite(Some(crate::util::float::fmod(x, y))),
761 _ => Value::Null,
762 }
763 }
764 // JSON functions (see `super::json`).
765 "json" => {
766 arity(&lname, args, 1)?;
767 match json_root(&v[0])? {
768 None => Value::Null,
769 Some(j) => Value::Text(j.serialize()),
770 }
771 }
772 // `jsonb(X)` — the JSONB (binary) form of `json(X)`: parse the JSON/JSON5
773 // text (or pass a JSONB blob through) and return its JSONB encoding.
774 "jsonb" => {
775 arity(&lname, args, 1)?;
776 match json_root(&v[0])? {
777 None => Value::Null,
778 Some(j) => Value::Blob(j.to_jsonb()),
779 }
780 }
781 "json_valid" => {
782 if v.is_empty() || v.len() > 2 {
783 return Err(Error::Error(
784 "wrong number of arguments to function json_valid()".into(),
785 ));
786 }
787 // The optional flags select which well-formedness checks count, and
788 // must be 1..=15 (sqlite errors otherwise): 0x01 = strict RFC-8259
789 // JSON text, 0x02 = JSON5 text, 0x04 = a BLOB that looks like JSONB,
790 // 0x08 = a BLOB that is fully-valid JSONB. The 1-argument form is 0x01
791 // (strict JSON only) — JSON5 acceptance needs the explicit flag.
792 let flags = match v.get(1) {
793 None => 1,
794 Some(f) => {
795 let n = eval::to_int_value(f);
796 if !(1..=15).contains(&n) {
797 return Err(Error::Error(
798 "FLAGS parameter to json_valid() must be between 1 and 15".into(),
799 ));
800 }
801 n
802 }
803 };
804 match &v[0] {
805 Value::Null => Value::Null,
806 // A BLOB is only ever judged against the JSONB flag bits; text bits
807 // do not apply (and vice versa for a text value).
808 Value::Blob(b) => {
809 let ok = flags & 0x0c != 0 && super::json::Json::from_jsonb(b).is_some();
810 Value::Integer(ok as i64)
811 }
812 other => {
813 let text = eval::to_text(other);
814 let ok = (flags & 0x01 != 0 && super::json::is_strict_json(&text))
815 || (flags & 0x02 != 0 && super::json::parse(&text).is_some());
816 Value::Integer(ok as i64)
817 }
818 }
819 }
820 // `json_error_position(X)` — the 1-based byte position of the first JSON
821 // syntax error in X, or 0 when X is well-formed JSON. NULL yields NULL,
822 // matching sqlite3.
823 "json_error_position" => {
824 arity(&lname, args, 1)?;
825 match &v[0] {
826 Value::Null => Value::Null,
827 other => {
828 let pos = match super::json::parse_with_error_position(&eval::to_text(other)) {
829 Ok(_) => 0,
830 Err(off) => off as i64 + 1,
831 };
832 Value::Integer(pos)
833 }
834 }
835 }
836 // `json_pretty(X [, indent])` — reformat with indentation (default 4
837 // spaces). Empty arrays/objects and scalars stay compact, like SQLite.
838 "json_pretty" => {
839 if v.is_empty() || v.len() > 2 {
840 return Err(wrong_arg_count("json_pretty"));
841 }
842 match &v[0] {
843 Value::Null => Value::Null,
844 other => {
845 let indent = match v.get(1) {
846 Some(Value::Null) | None => alloc::string::String::from(" "),
847 Some(iv) => eval::to_text(iv),
848 };
849 let _ = other;
850 match json_root(&v[0])? {
851 None => Value::Null,
852 Some(j) => Value::Text(j.pretty(&indent)),
853 }
854 }
855 }
856 }
857 "json_quote" => {
858 arity(&lname, args, 1)?;
859 // A BLOB that decodes as JSONB renders as its JSON text (so
860 // `json_quote(x'01')` → `true`, `json_quote(jsonb('[1,2]'))` →
861 // `[1,2]`); any other BLOB is rejected. graphite has no value
862 // subtypes, so it falls back to "does it parse as JSONB", matching
863 // sqlite.
864 let j = match &v[0] {
865 Value::Blob(b) => super::json::Json::from_jsonb(b)
866 .ok_or_else(|| Error::Error("JSON cannot hold BLOB values".into()))?,
867 other => super::json::value_to_json(other),
868 };
869 Value::Text(j.quote())
870 }
871 "json_type" => {
872 if v.is_empty() || v.len() > 2 {
873 return Err(wrong_arg_count("json_type"));
874 }
875 match json_root(&v[0])? {
876 None => Value::Null,
877 Some(root) => {
878 let target = if v.len() == 2 {
879 check_path(&v[1])?;
880 super::json::navigate(&root, &eval::to_text(&v[1]))
881 } else {
882 Some(&root)
883 };
884 match target {
885 Some(j) => Value::Text(String::from(j.type_name())),
886 None => Value::Null,
887 }
888 }
889 }
890 }
891 "json_array_length" => {
892 if v.is_empty() || v.len() > 2 {
893 return Err(wrong_arg_count("json_array_length"));
894 }
895 match json_root(&v[0])? {
896 None => Value::Null,
897 Some(root) => {
898 let target = if v.len() == 2 {
899 check_path(&v[1])?;
900 super::json::navigate(&root, &eval::to_text(&v[1]))
901 } else {
902 Some(&root)
903 };
904 match target {
905 Some(super::json::Json::Array(items)) => Value::Integer(items.len() as i64),
906 Some(_) => Value::Integer(0),
907 None => Value::Null,
908 }
909 }
910 }
911 }
912 "json_extract" | "jsonb_extract" => {
913 // SQLite's json_extract is variadic: with fewer than two arguments
914 // it returns NULL without even parsing the document (so an invalid
915 // or absent first argument is NOT an error here).
916 if v.len() < 2 {
917 return Ok(Value::Null);
918 }
919 match json_root(&v[0])? {
920 None => Value::Null,
921 Some(root) => json_extract(&root, &v[1..], lname.starts_with("jsonb"))?,
922 }
923 }
924 "json_array" | "jsonb_array" => {
925 let mut items = Vec::with_capacity(v.len());
926 for (i, val) in v.iter().enumerate() {
927 items.push(json_value_arg(val, args.get(i))?);
928 }
929 json_doc_result(&lname, &super::json::Json::Array(items))
930 }
931 "json_object" | "jsonb_object" => {
932 if !v.len().is_multiple_of(2) {
933 return Err(Error::Error(
934 "json_object() requires an even number of arguments".into(),
935 ));
936 }
937 let mut members = Vec::with_capacity(v.len() / 2);
938 for pair in v.chunks(2).enumerate() {
939 let (i, kv) = pair;
940 // SQLite requires object labels to be TEXT (a NULL, numeric, or
941 // BLOB key is an error), and rejects BLOB values.
942 let Value::Text(key) = &kv[0] else {
943 return Err(Error::Error("json_object() labels must be TEXT".into()));
944 };
945 let val = json_value_arg(&kv[1], args.get(2 * i + 1))?;
946 // A key built from a SQL TEXT arg carries no escape provenance.
947 members.push((key.clone(), None, val));
948 }
949 json_doc_result(&lname, &super::json::Json::Object(members))
950 }
951 "json_set" | "json_insert" | "json_replace" | "jsonb_set" | "jsonb_insert"
952 | "jsonb_replace" => {
953 // sqlite: zero args → NULL; an even arg count is a hard error (the
954 // message always names the text-output `json_*` form, even for the
955 // `jsonb_*` blob variants); an odd count is the document followed by
956 // zero or more (path, value) pairs (a bare document is a no-op).
957 if v.is_empty() {
958 return Ok(Value::Null);
959 }
960 if v.len().is_multiple_of(2) {
961 let report = lname
962 .strip_prefix("jsonb_")
963 .map_or_else(|| lname.clone(), |rest| alloc::format!("json_{rest}"));
964 return Err(Error::Error(alloc::format!(
965 "{report}() needs an odd number of arguments"
966 )));
967 }
968 let mode = if lname.ends_with("set") {
969 super::json::SetMode::Set
970 } else if lname.ends_with("insert") {
971 super::json::SetMode::Insert
972 } else {
973 super::json::SetMode::Replace
974 };
975 match json_root(&v[0])? {
976 None => Value::Null,
977 Some(mut root) => {
978 let mut i = 1;
979 while i + 1 < v.len() {
980 check_path(&v[i])?;
981 let path = eval::to_text(&v[i]);
982 let val = json_value_arg(&v[i + 1], args.get(i + 1))?;
983 super::json::set_path(&mut root, &path, val, mode);
984 i += 2;
985 }
986 json_doc_result(&lname, &root)
987 }
988 }
989 }
990 "json_remove" | "jsonb_remove" => {
991 if v.is_empty() {
992 return Err(Error::Error("json_remove() requires a document".into()));
993 }
994 match json_root(&v[0])? {
995 None => Value::Null,
996 Some(mut root) => {
997 let mut removed = Value::Text(String::new());
998 for p in &v[1..] {
999 // A NULL path collapses the whole call to NULL (scanning
1000 // left to right, so a malformed path *before* it still
1001 // errors via check_path first), discarding any removals
1002 // already applied — matching sqlite's json_remove.
1003 if matches!(p, Value::Null) {
1004 removed = Value::Null;
1005 break;
1006 }
1007 check_path(p)?;
1008 // Removing the whole document (`$`) yields SQL NULL.
1009 if matches!(p, Value::Text(s) if s == "$") {
1010 removed = Value::Null;
1011 continue;
1012 }
1013 super::json::remove_path(&mut root, &eval::to_text(p));
1014 }
1015 if matches!(removed, Value::Null) {
1016 Value::Null
1017 } else {
1018 json_doc_result(&lname, &root)
1019 }
1020 }
1021 }
1022 }
1023 "json_patch" | "jsonb_patch" => {
1024 arity(&lname, args, 2)?;
1025 match (json_root(&v[0])?, json_root(&v[1])?) {
1026 (Some(mut root), Some(patch)) => {
1027 super::json::merge_patch(&mut root, &patch);
1028 json_doc_result(&lname, &root)
1029 }
1030 _ => Value::Null,
1031 }
1032 }
1033 // Date/time functions (see `super::datetime`).
1034 "date" => super::datetime::date(&v),
1035 "time" => super::datetime::time(&v),
1036 "datetime" => super::datetime::datetime(&v),
1037 "julianday" => super::datetime::julianday(&v),
1038 "unixepoch" => super::datetime::unixepoch(&v),
1039 "strftime" => super::datetime::strftime(&v),
1040 "timediff" => {
1041 arity(&lname, args, 2)?;
1042 super::datetime::timediff(&v[0], &v[1])
1043 }
1044 "geopoly_json" => {
1045 arity(&lname, args, 1)?;
1046 match crate::geopoly::parse_value(&v[0]) {
1047 Some(p) => Value::Text(p.to_json()),
1048 None => Value::Null,
1049 }
1050 }
1051 "geopoly_blob" => {
1052 arity(&lname, args, 1)?;
1053 match crate::geopoly::parse_value(&v[0]) {
1054 Some(p) => Value::Blob(p.to_blob()),
1055 None => Value::Null,
1056 }
1057 }
1058 "geopoly_area" => {
1059 arity(&lname, args, 1)?;
1060 match crate::geopoly::parse_value(&v[0]) {
1061 Some(p) => Value::Real(p.area()),
1062 None => Value::Null,
1063 }
1064 }
1065 "geopoly_bbox" => {
1066 arity(&lname, args, 1)?;
1067 match crate::geopoly::parse_value(&v[0]) {
1068 Some(p) => Value::Blob(p.bbox().to_blob()),
1069 None => Value::Null,
1070 }
1071 }
1072 "geopoly_ccw" => {
1073 arity(&lname, args, 1)?;
1074 match crate::geopoly::parse_value(&v[0]) {
1075 Some(p) => Value::Blob(p.ccw().to_blob()),
1076 None => Value::Null,
1077 }
1078 }
1079 "geopoly_regular" => {
1080 arity(&lname, args, 4)?;
1081 // A NULL argument yields NULL (SQLite's `sqlite3_value_double`/`int`
1082 // treat NULL as 0, but n<3 or r<=0 then returns NULL anyway; an
1083 // explicit NULL check keeps the common cases NULL-clean).
1084 if v.iter().any(|x| matches!(x, Value::Null)) {
1085 Value::Null
1086 } else {
1087 let cx = eval::to_f64(&v[0]);
1088 let cy = eval::to_f64(&v[1]);
1089 let r = eval::to_f64(&v[2]);
1090 let n = eval::to_int_value(&v[3]);
1091 match crate::geopoly::regular(cx, cy, r, n) {
1092 Some(p) => Value::Blob(p.to_blob()),
1093 None => Value::Null,
1094 }
1095 }
1096 }
1097 "geopoly_contains_point" => {
1098 arity(&lname, args, 3)?;
1099 match crate::geopoly::parse_value(&v[0]) {
1100 Some(p) => {
1101 Value::Integer(p.contains_point(eval::to_f64(&v[1]), eval::to_f64(&v[2])))
1102 }
1103 None => Value::Null,
1104 }
1105 }
1106 "geopoly_overlap" => {
1107 arity(&lname, args, 2)?;
1108 match (
1109 crate::geopoly::parse_value(&v[0]),
1110 crate::geopoly::parse_value(&v[1]),
1111 ) {
1112 (Some(p1), Some(p2)) => Value::Integer(crate::geopoly::overlap(&p1, &p2)),
1113 _ => Value::Null,
1114 }
1115 }
1116 "geopoly_within" => {
1117 arity(&lname, args, 2)?;
1118 match (
1119 crate::geopoly::parse_value(&v[0]),
1120 crate::geopoly::parse_value(&v[1]),
1121 ) {
1122 (Some(p1), Some(p2)) => Value::Integer(crate::geopoly::within(&p1, &p2)),
1123 _ => Value::Null,
1124 }
1125 }
1126 "geopoly_svg" => {
1127 // geopoly_svg(X, ...) is variadic. With no arguments SQLite's
1128 // implementation simply returns NULL (`if(argc<1) return;`) rather
1129 // than raising an arity error.
1130 if args.is_empty() {
1131 return Ok(Value::Null);
1132 }
1133 match crate::geopoly::parse_value(&v[0]) {
1134 Some(p) => {
1135 let extra: Vec<Option<String>> = v[1..]
1136 .iter()
1137 .map(|x| match x {
1138 Value::Null => None,
1139 other => Some(eval::to_text(other)),
1140 })
1141 .collect();
1142 Value::Text(p.to_svg(&extra))
1143 }
1144 None => Value::Null,
1145 }
1146 }
1147 "geopoly_xform" => {
1148 arity(&lname, args, 7)?;
1149 match crate::geopoly::parse_value(&v[0]) {
1150 Some(p) => Value::Blob(
1151 p.xform(
1152 eval::to_f64(&v[1]),
1153 eval::to_f64(&v[2]),
1154 eval::to_f64(&v[3]),
1155 eval::to_f64(&v[4]),
1156 eval::to_f64(&v[5]),
1157 eval::to_f64(&v[6]),
1158 )
1159 .to_blob(),
1160 ),
1161 None => Value::Null,
1162 }
1163 }
1164 "printf" | "format" => super::datetime::printf(&v),
1165 _ => {
1166 // A user-defined function registered via `register_function`. Builtins
1167 // above take precedence; this fires only for an otherwise-unknown name.
1168 if let Some(result) = ctx.subqueries.and_then(|s| s.call_udf(&lname, &v)) {
1169 return result;
1170 }
1171 // A built-in window/ranking function used outside an `OVER` clause is
1172 // "misuse of window function NAME()" in SQLite, not "no such function".
1173 if is_window_function_name(&lname) {
1174 return Err(Error::Error(alloc::format!(
1175 "misuse of window function {lname}()"
1176 )));
1177 }
1178 // `RAISE(...)` parses into a canonical `raise(...)` call. A real
1179 // trigger program intercepts it before evaluation (see `fire_raise`);
1180 // reaching scalar dispatch means it was used outside a trigger, which
1181 // SQLite rejects with this dedicated message rather than "no such
1182 // function".
1183 if lname == "raise" {
1184 return Err(Error::Error(alloc::string::String::from(
1185 "RAISE() may only be used within a trigger-program",
1186 )));
1187 }
1188 return Err(Error::Error(alloc::format!("no such function: {name}")));
1189 }
1190 })
1191}
1192
1193/// Render a value as a SQL literal, like SQLite's `quote()`.
1194fn quote_value(v: &Value) -> String {
1195 match v {
1196 Value::Null => String::from("NULL"),
1197 Value::Integer(i) => alloc::format!("{i}"),
1198 // `quote()` renders an infinity as `±9.0e+999` (unlike plain text output,
1199 // which prints `Inf`).
1200 Value::Real(r) if !r.is_finite() => {
1201 String::from(if *r < 0.0 { "-9.0e+999" } else { "9.0e+999" })
1202 }
1203 // `quote()` renders a finite real at round-trip precision (`%!0.15g`,
1204 // falling back to `%!0.20e`), unlike the 15-significant-digit column
1205 // rendering `format_real` produces — so a dumped real reparses exactly.
1206 Value::Real(r) => crate::util::fpdecode::quote_real(*r),
1207 Value::Text(s) => {
1208 // SQLite's `quote()` reads the text through `sqlite3_value_text`, a
1209 // NUL-terminated C string, so an embedded NUL truncates the rendered
1210 // literal (the stored value keeps its full bytes). Match that.
1211 let s = s.split('\0').next().unwrap_or("");
1212 alloc::format!("'{}'", s.replace('\'', "''"))
1213 }
1214 Value::Blob(b) => {
1215 // SQLite renders blob literals as `X'ABCD'` — uppercase `X` and
1216 // uppercase hex digits.
1217 let mut s = String::from("X'");
1218 for byte in b {
1219 s.push_str(&alloc::format!("{byte:02X}"));
1220 }
1221 s.push('\'');
1222 s
1223 }
1224 }
1225}
1226
1227/// Decode SQLite `unistr()` escapes: `\uXXXX` (4 hex), `\UXXXXXXXX` (8 hex), and
1228/// `\\` (a literal backslash). Any other backslash sequence — including a `\u`/
1229/// `\U` with too few hex digits or a trailing `\` — is an error, matching
1230/// SQLite's "invalid Unicode escape". A code point Rust cannot represent (a lone
1231/// surrogate or one past U+10FFFF) becomes the replacement char `U+FFFD`; SQLite
1232/// emits raw WTF-8 there, an extreme edge graphite cannot store in a `String`.
1233fn unistr_decode(s: &str) -> Result<String> {
1234 let cs: alloc::vec::Vec<char> = s.chars().collect();
1235 let mut out = String::with_capacity(s.len());
1236 let invalid = || Error::Error(String::from("invalid Unicode escape"));
1237 let hex_char = |cs: &[char], at: usize, n: usize| -> Option<u32> {
1238 let slice = cs.get(at..at + n)?;
1239 if !slice.iter().all(|c| c.is_ascii_hexdigit()) {
1240 return None;
1241 }
1242 let s: String = slice.iter().collect();
1243 u32::from_str_radix(&s, 16).ok()
1244 };
1245 let mut i = 0;
1246 while i < cs.len() {
1247 if cs[i] != '\\' {
1248 out.push(cs[i]);
1249 i += 1;
1250 continue;
1251 }
1252 match cs.get(i + 1) {
1253 Some('\\') => {
1254 out.push('\\');
1255 i += 2;
1256 }
1257 Some('u') => {
1258 let cp = hex_char(&cs, i + 2, 4).ok_or_else(invalid)?;
1259 out.push(char::from_u32(cp).unwrap_or('\u{FFFD}'));
1260 i += 6;
1261 }
1262 Some('U') => {
1263 let cp = hex_char(&cs, i + 2, 8).ok_or_else(invalid)?;
1264 out.push(char::from_u32(cp).unwrap_or('\u{FFFD}'));
1265 i += 10;
1266 }
1267 _ => return Err(invalid()),
1268 }
1269 }
1270 Ok(out)
1271}
1272
1273/// Render a control-character-bearing text value as SQLite's `unistr('…')`: each
1274/// character below U+0020 becomes `\uXXXX`, a backslash doubles, a single quote
1275/// doubles, everything else (including non-ASCII) is kept literal.
1276fn unistr_quote_text(s: &str) -> String {
1277 let mut out = String::from("unistr('");
1278 for c in s.chars() {
1279 let cp = c as u32;
1280 if cp < 0x20 {
1281 out.push_str(&alloc::format!("\\u{cp:04x}"));
1282 } else if c == '\\' {
1283 out.push_str("\\\\");
1284 } else if c == '\'' {
1285 out.push_str("''");
1286 } else {
1287 out.push(c);
1288 }
1289 }
1290 out.push_str("')");
1291 out
1292}
1293
1294/// Decode a hex string to bytes, returning `None` on malformed input. A faithful
1295/// port of SQLite's `unhexFunc`:
1296///
1297/// - with no ignore set (`ignore` is `None`), the input must be an even number of
1298/// hex digits and nothing else;
1299/// - with an ignore set (the 2-argument form), characters from `ignore` may
1300/// appear *before*, *between*, and *after* complete byte pairs, but never
1301/// *within* a pair — so `unhex('AB CD', ' ')` is `X'ABCD'` while
1302/// `unhex('A BCD', ' ')` is `NULL`. Any character that is neither a hex digit
1303/// nor in the ignore set fails the whole decode.
1304fn unhex(s: &str, ignore: Option<&str>) -> Option<alloc::vec::Vec<u8>> {
1305 let hexval = |c: char| -> Option<u8> {
1306 match c {
1307 '0'..='9' => Some(c as u8 - b'0'),
1308 'a'..='f' => Some(c as u8 - b'a' + 10),
1309 'A'..='F' => Some(c as u8 - b'A' + 10),
1310 _ => None,
1311 }
1312 };
1313 let ignored = |c: char| -> bool { ignore.is_some_and(|set| set.contains(c)) };
1314 let mut out = alloc::vec::Vec::new();
1315 let mut it = s.chars();
1316 loop {
1317 // Skip any leading/inter-pair ignore characters. A non-ignored,
1318 // non-hex-digit character (or running out of input) ends this phase.
1319 let hi = loop {
1320 match it.next() {
1321 None => return Some(out),
1322 Some(c) if hexval(c).is_some() => break c,
1323 Some(c) if ignored(c) => continue,
1324 Some(_) => return None,
1325 }
1326 };
1327 // The second nibble must immediately follow the first.
1328 let lo = it.next()?;
1329 out.push((hexval(hi)? << 4) | hexval(lo)?);
1330 }
1331}
1332
1333/// Whether `e` is an acceptable `likelihood()` probability: a floating-point
1334/// literal in `0.0..=1.0`, after peeling any redundant parentheses. SQLite's
1335/// `exprProbability` only accepts a bare `TK_FLOAT` token in range, so an
1336/// integer literal, a negated float, or any compound expression is rejected.
1337pub(crate) fn likelihood_prob_is_valid(e: &Expr) -> bool {
1338 match e {
1339 Expr::Paren(inner) => likelihood_prob_is_valid(inner),
1340 Expr::Literal(Literal::Real(r)) => (0.0..=1.0).contains(r),
1341 _ => false,
1342 }
1343}
1344
1345fn arity(name: &str, args: &[Expr], n: usize) -> Result<()> {
1346 if args.len() == n {
1347 Ok(())
1348 } else {
1349 Err(wrong_arg_count(name))
1350 }
1351}
1352
1353/// SQLite's universal arity-error message: `wrong number of arguments to
1354/// function NAME()` — no "(want N, got M)" suffix, for every built-in.
1355fn wrong_arg_count(name: &str) -> Error {
1356 Error::Error(alloc::format!(
1357 "wrong number of arguments to function {name}()"
1358 ))
1359}
1360
1361/// The built-in ranking/value window functions, which are only valid inside an
1362/// `OVER` clause. Called as a plain scalar, SQLite reports "misuse of window
1363/// function NAME()". `name` must already be lowercased.
1364fn is_window_function_name(name: &str) -> bool {
1365 matches!(
1366 name,
1367 "row_number"
1368 | "rank"
1369 | "dense_rank"
1370 | "percent_rank"
1371 | "cume_dist"
1372 | "ntile"
1373 | "first_value"
1374 | "last_value"
1375 | "nth_value"
1376 | "lag"
1377 | "lead"
1378 )
1379}
1380
1381/// SQLite's `soundex(X)`: the phonetic code of the first word in `X` — the first
1382/// letter followed by up to three digits, padded with `0`. Input with no letters
1383/// (including NULL, which `to_text` maps to "") yields `"?000"`. Faithful port of
1384/// `soundexFunc`: each letter maps to a digit; a digit is emitted only when it is
1385/// nonzero and differs from the previous letter's code; a zero-code character
1386/// (vowel, `H`/`W`/`Y`, or non-letter) resets the running code.
1387fn soundex(s: &str) -> String {
1388 // Code for a-z (index `c.to_ascii_lowercase() - b'a'`); 0 = not coded.
1389 const CODE: [u8; 26] = [
1390 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,
1391 ];
1392 let code_of = |c: u8| -> u8 {
1393 if c.is_ascii_alphabetic() {
1394 CODE[(c.to_ascii_lowercase() - b'a') as usize]
1395 } else {
1396 0
1397 }
1398 };
1399 let b = s.as_bytes();
1400 let mut i = 0;
1401 while i < b.len() && !b[i].is_ascii_alphabetic() {
1402 i += 1;
1403 }
1404 if i >= b.len() {
1405 return String::from("?000");
1406 }
1407 let mut out = String::with_capacity(4);
1408 out.push(b[i].to_ascii_uppercase() as char);
1409 let mut prev = code_of(b[i]);
1410 let mut j = 1;
1411 while j < 4 && i < b.len() {
1412 let code = code_of(b[i]);
1413 if code > 0 {
1414 if code != prev {
1415 prev = code;
1416 out.push((b'0' + code) as char);
1417 j += 1;
1418 }
1419 } else {
1420 prev = 0;
1421 }
1422 i += 1;
1423 }
1424 while j < 4 {
1425 out.push('0');
1426 j += 1;
1427 }
1428 out
1429}
1430
1431/// Coerce a value to text for the string functions (`trim`/`upper`/`lower`/
1432/// `replace`/`substr`/`soundex`).
1433///
1434/// When the value is a BLOB, SQLite reads it as a NUL-terminated C string
1435/// (`sqlite3_value_text`), so an embedded `NUL` byte truncates the coerced
1436/// text: `trim(X'00200041…')` is `''`, not `' A '`. We reproduce that for
1437/// the blob path. A genuine TEXT value keeps its embedded NULs: this engine's
1438/// TEXT model is NUL-preserving (e.g. the JSON5 `\0` escape stores a real NUL
1439/// character — see `tests/json5.rs`), and `length`/`unicode` count through it,
1440/// so truncating TEXT here would be inconsistent with the rest of the engine.
1441fn c_text(v: &Value) -> String {
1442 match v {
1443 Value::Blob(b) => {
1444 let end = b.iter().position(|&x| x == 0).unwrap_or(b.len());
1445 String::from_utf8_lossy(&b[..end]).into_owned()
1446 }
1447 other => eval::to_text(other),
1448 }
1449}
1450
1451fn str_map(v: &Value, f: impl Fn(&str) -> String) -> Value {
1452 match v {
1453 Value::Null => Value::Null,
1454 other => Value::Text(f(&c_text(other))),
1455 }
1456}
1457
1458/// A numeric argument to a math function: `None` for SQL `NULL`, else the value
1459/// coerced to `f64` (SQLite applies REAL affinity to math-function arguments).
1460/// Coerce a math-function argument to `f64` using SQLite's
1461/// `sqlite3_value_numeric_type` rule: an INTEGER / REAL — or a text that is wholly
1462/// numeric (`'4'`, `' 9 '`, `'2e2'`) — yields its numeric value, while a
1463/// non-numeric text, a blob, or NULL yields `None` (so the caller returns SQL
1464/// NULL). This is *not* the lax `to_f64`, which would turn `'abc'` or a blob into
1465/// `0.0` and silently feed it to `sqrt`/`log`/`pow`/…
1466fn real_arg(v: &Value) -> Option<f64> {
1467 eval::to_number_strict(v).map(|n| eval::to_f64(&n))
1468}
1469
1470/// Wrap a computed math result, matching SQLite's split between a *domain error*
1471/// and an *overflow*:
1472///
1473/// - a missing argument or a `NaN` result (`sqrt(-1)`, `ln(0)`, `acos(2)`, …)
1474/// becomes SQL `NULL`;
1475/// - a `±∞` result is kept as a `REAL` — both genuine overflow (`exp(710)`,
1476/// `pow(2,2000)`) and poles (`pow(0,-1)`, `atanh(1)`) render as `Inf`/`-Inf`,
1477/// exactly as SQLite reports them.
1478///
1479/// Each underlying `float` routine is responsible for returning `NaN` (not `±∞`)
1480/// on a domain error, so that the NULL-vs-Inf decision is made consistently here.
1481fn math_finite(r: Option<f64>) -> Value {
1482 match r {
1483 Some(x) if x.is_nan() => Value::Null,
1484 Some(x) => Value::Real(x),
1485 None => Value::Null,
1486 }
1487}
1488
1489/// A one-argument math function: arity-check, coerce, apply, finiteness-guard.
1490fn math1(name: &str, v: &[Value], f: impl Fn(f64) -> f64) -> Result<Value> {
1491 if v.len() != 1 {
1492 return Err(Error::Error(alloc::format!(
1493 "wrong number of arguments to function {name}()"
1494 )));
1495 }
1496 Ok(math_finite(real_arg(&v[0]).map(f)))
1497}
1498
1499/// `ceil`/`ceiling`/`floor`/`trunc`: SQLite dispatches on `sqlite3_value_numeric_type`
1500/// — an INTEGER argument is returned UNCHANGED (so its exact value, incl. i64
1501/// extremes, survives), a REAL is rounded to a REAL, and a non-numeric text / blob
1502/// / NULL argument yields NULL. This differs from the other `math1` functions,
1503/// which always coerce to a real (and read a blob's bytes as a number).
1504fn math_round_to_int(name: &str, v: &[Value], f: impl Fn(f64) -> f64) -> Result<Value> {
1505 if v.len() != 1 {
1506 return Err(Error::Error(alloc::format!(
1507 "wrong number of arguments to function {name}()"
1508 )));
1509 }
1510 Ok(match eval::to_number_strict(&v[0]) {
1511 Some(Value::Integer(i)) => Value::Integer(i),
1512 Some(Value::Real(r)) => math_finite(Some(f(r))),
1513 _ => Value::Null,
1514 })
1515}
1516
1517/// Parse the first argument of a JSON function as a document: `NULL` → `None`;
1518/// malformed JSON is an error (matching SQLite).
1519/// Render a JSON document result as text (`json_*`) or as a JSONB blob
1520/// (`jsonb_*`), chosen by the function name.
1521fn json_doc_result(lname: &str, j: &super::json::Json) -> Value {
1522 if lname.starts_with("jsonb") {
1523 Value::Blob(j.to_jsonb())
1524 } else {
1525 Value::Text(j.serialize())
1526 }
1527}
1528
1529fn json_root(v: &Value) -> Result<Option<super::json::Json>> {
1530 match v {
1531 Value::Null => Ok(None),
1532 // A BLOB document is JSONB (SQLite's binary JSON); text is JSON/JSON5.
1533 Value::Blob(b) => match super::json::Json::from_jsonb(b) {
1534 Some(j) => Ok(Some(j)),
1535 None => Err(Error::Error("malformed JSON".into())),
1536 },
1537 other => match super::json::parse(&eval::to_text(other)) {
1538 Some(j) => Ok(Some(j)),
1539 None => Err(Error::Error("malformed JSON".into())),
1540 },
1541 }
1542}
1543
1544/// Validate a JSON path argument, raising SQLite's `bad JSON path: '<path>'`
1545/// error if it is malformed. A `NULL` path is left for the caller to treat as a
1546/// missing lookup (SQLite returns NULL rather than erroring on a NULL path).
1547fn check_path(p: &Value) -> Result<()> {
1548 // A NULL path is not validated — callers treat it as a missing lookup
1549 // (SQLite returns NULL rather than erroring). Every other type is coerced to
1550 // text first (SQLite applies its usual text conversion before parsing the
1551 // path), so an integer/real/blob argument is validated as the path it spells
1552 // — e.g. `json_extract(j, 1)` raises `bad JSON path: '1'`, just like sqlite.
1553 if matches!(p, Value::Null) {
1554 return Ok(());
1555 }
1556 let s = eval::to_text(p);
1557 if !super::json::path_is_valid(&s) {
1558 return Err(Error::Error(alloc::format!("bad JSON path: '{s}'")));
1559 }
1560 Ok(())
1561}
1562
1563/// Convert a *value* argument of a JSON/JSONB constructor or mutator to JSON. A
1564/// BLOB is embedded as its JSON when it decodes as JSONB (so `jsonb_*` results
1565/// compose, e.g. `jsonb_object('a', jsonb_array(1,2))`) and otherwise rejected —
1566/// graphite has no value subtypes, so it falls back to "does it parse as JSONB".
1567fn json_value_arg(val: &Value, expr: Option<&Expr>) -> Result<super::json::Json> {
1568 if let Value::Blob(b) = val {
1569 return super::json::Json::from_jsonb(b)
1570 .ok_or_else(|| Error::Error("JSON cannot hold BLOB values".into()));
1571 }
1572 Ok(arg_to_json(val, expr))
1573}
1574
1575/// `json_extract`: one path returns the SQL value at that path (objects/arrays as
1576/// minified JSON text); multiple paths return a JSON array of the extracted
1577/// elements (missing paths become JSON `null`). `jsonb_extract` is the same but
1578/// returns object/array results (and the multi-path array) as JSONB blobs.
1579fn json_extract(root: &super::json::Json, paths: &[Value], jsonb: bool) -> Result<Value> {
1580 // SQLite scans the paths left to right: a NULL path collapses the whole
1581 // result to NULL (even when a *later* path is malformed), but a malformed
1582 // non-NULL path that comes *first* still errors before the NULL is reached.
1583 for p in paths {
1584 if matches!(p, Value::Null) {
1585 return Ok(Value::Null);
1586 }
1587 check_path(p)?;
1588 }
1589 // A non-scalar single-path result is JSONB under jsonb_extract; scalars are
1590 // returned as their SQL value either way.
1591 let scalar_or_doc = |j: &super::json::Json| -> Value {
1592 match j {
1593 super::json::Json::Array(_) | super::json::Json::Object(_) if jsonb => {
1594 Value::Blob(j.to_jsonb())
1595 }
1596 _ => j.to_sql(),
1597 }
1598 };
1599 if paths.len() == 1 {
1600 return Ok(
1601 match super::json::navigate(root, &eval::to_text(&paths[0])) {
1602 Some(j) => scalar_or_doc(j),
1603 None => Value::Null,
1604 },
1605 );
1606 }
1607 let items = paths
1608 .iter()
1609 .map(|p| match super::json::navigate(root, &eval::to_text(p)) {
1610 Some(j) => j.clone(),
1611 None => super::json::Json::Null,
1612 })
1613 .collect();
1614 let arr = super::json::Json::Array(items);
1615 Ok(if jsonb {
1616 Value::Blob(arr.to_jsonb())
1617 } else {
1618 Value::Text(arr.serialize())
1619 })
1620}
1621
1622/// Convert a constructor argument to JSON. If the source expression is itself a
1623/// JSON-producing call (`json`, `json_array`, `json_object`), its text value is
1624/// embedded as parsed JSON — mirroring SQLite's JSON subtype propagation — rather
1625/// than quoted as a string.
1626pub(crate) fn arg_to_json(val: &Value, expr: Option<&Expr>) -> super::json::Json {
1627 if let (Value::Text(s), Some(e)) = (val, expr) {
1628 if produces_json(e) {
1629 if let Some(j) = super::json::parse(s) {
1630 return j;
1631 }
1632 }
1633 }
1634 super::json::value_to_json(val)
1635}
1636
1637/// Whether an expression *statically* yields a value carrying SQLite's JSON
1638/// subtype. Functions that always emit a JSON structure (including the
1639/// `json_group_array`/`json_group_object` aggregates) qualify; `json_extract`
1640/// only with two or more paths (then its result is always a JSON array — a
1641/// single path's subtype is value-dependent and needs the runtime subtype, not
1642/// modelled here); the `->` operator always carries the subtype (`->>` does not).
1643fn produces_json(e: &Expr) -> bool {
1644 match e {
1645 Expr::Function { name, args, .. } => {
1646 let lname = name.to_ascii_lowercase();
1647 match lname.as_str() {
1648 "json" | "json_array" | "json_object" | "json_insert" | "json_replace"
1649 | "json_set" | "json_patch" | "json_remove" | "json_group_array"
1650 | "json_group_object" => true,
1651 // Multiple paths → a JSON array (subtype). One path's subtype is
1652 // value-dependent (structure vs scalar), so don't claim it here.
1653 "json_extract" => args.len() >= 3,
1654 _ => false,
1655 }
1656 }
1657 // `->` (JsonExtract) carries the JSON subtype; `->>` (JsonExtractText) does not.
1658 Expr::Binary {
1659 op: crate::sql::ast::BinaryOp::JsonExtract,
1660 ..
1661 } => true,
1662 Expr::Paren(inner) => produces_json(inner),
1663 _ => false,
1664 }
1665}
1666
1667fn type_name(v: &Value) -> &'static str {
1668 match v {
1669 Value::Null => "null",
1670 Value::Integer(_) => "integer",
1671 Value::Real(_) => "real",
1672 Value::Text(_) => "text",
1673 Value::Blob(_) => "blob",
1674 }
1675}
1676
1677fn trim_fn(v: &[Value], left: bool, right: bool) -> Value {
1678 if v.is_empty() || matches!(v[0], Value::Null) {
1679 return Value::Null;
1680 }
1681 // A NULL trim-set yields NULL, like any other NULL argument (sqlite:
1682 // `trim(X, NULL)` / `ltrim` / `rtrim` are all NULL).
1683 if v.len() >= 2 && matches!(v[1], Value::Null) {
1684 return Value::Null;
1685 }
1686 let s = c_text(&v[0]);
1687 let trim_chars: Vec<char> = if v.len() >= 2 {
1688 c_text(&v[1]).chars().collect()
1689 } else {
1690 alloc::vec![' ']
1691 };
1692 let is_trim = |c: char| trim_chars.contains(&c);
1693 let chars: Vec<char> = s.chars().collect();
1694 let mut start = 0;
1695 let mut end = chars.len();
1696 if left {
1697 while start < end && is_trim(chars[start]) {
1698 start += 1;
1699 }
1700 }
1701 if right {
1702 while end > start && is_trim(chars[end - 1]) {
1703 end -= 1;
1704 }
1705 }
1706 Value::Text(chars[start..end].iter().collect())
1707}
1708
1709fn substr(v: &[Value]) -> Result<Value> {
1710 if v.len() < 2 || v.len() > 3 {
1711 return Err(wrong_arg_count("substr"));
1712 }
1713 if matches!(v[0], Value::Null) {
1714 return Ok(Value::Null);
1715 }
1716 // `substr` of a blob slices bytes and returns a blob; otherwise it slices
1717 // characters of the text form and returns text.
1718 let blob = matches!(v[0], Value::Blob(_));
1719 let units: alloc::vec::Vec<char> = if blob {
1720 match &v[0] {
1721 Value::Blob(b) => b.iter().map(|&x| x as char).collect(),
1722 _ => unreachable!(),
1723 }
1724 } else {
1725 eval::to_text(&v[0]).chars().collect()
1726 };
1727 // `substr(x, NULL [, …])` returns NULL — a NULL start position propagates
1728 // like any other NULL argument (the length already does, below).
1729 if matches!(v[1], Value::Null) {
1730 return Ok(Value::Null);
1731 }
1732 let len = units.len() as i64;
1733 // Faithful port of SQLite's `substrFunc` (src/func.c): `p1` is a 1-based
1734 // start (negative counts from the end), `p2` a signed length (negative
1735 // means "the |p2| units ending at p1"). The default `p2` for the 2-arg form
1736 // is the LENGTH limit (1e9), which we clamp to `len` below. All arithmetic
1737 // saturates so pathological i64 inputs (e.g. i64::MIN) cannot overflow,
1738 // matching SQLite's behaviour where the window collapses to empty or the
1739 // whole string.
1740 let mut p1 = eval::to_int_value(&v[1]);
1741 let mut p2 = if v.len() == 3 {
1742 // `substr(x, p1, NULL)` returns NULL (the length argument is required
1743 // to be non-NULL to produce a value).
1744 if matches!(v[2], Value::Null) {
1745 return Ok(Value::Null);
1746 }
1747 eval::to_int_value(&v[2])
1748 } else {
1749 1_000_000_000
1750 };
1751 if p1 < 0 {
1752 p1 = p1.saturating_add(len);
1753 if p1 < 0 {
1754 if p2 < 0 {
1755 p2 = 0;
1756 } else {
1757 p2 = p2.saturating_add(p1);
1758 }
1759 p1 = 0;
1760 }
1761 } else if p1 > 0 {
1762 p1 -= 1;
1763 } else if p2 > 0 {
1764 p2 -= 1;
1765 }
1766 if p2 < 0 {
1767 if p2 < -p1 {
1768 p2 = p1;
1769 } else {
1770 p2 = -p2;
1771 }
1772 p1 = p1.saturating_sub(p2);
1773 }
1774 // `p1 >= 0 && p2 >= 0` now holds. Clamp the window to the available units.
1775 let start = (p1.max(0) as usize).min(units.len());
1776 let take = (p2.max(0) as usize).min(units.len() - start);
1777 let slice = &units[start..start + take];
1778 if blob {
1779 Ok(Value::Blob(slice.iter().map(|&c| c as u8).collect()))
1780 } else {
1781 Ok(Value::Text(slice.iter().collect()))
1782 }
1783}
1784
1785fn instr(v: &[Value]) -> Result<Value> {
1786 if v.len() != 2 {
1787 return Err(wrong_arg_count("instr"));
1788 }
1789 if matches!(v[0], Value::Null) || matches!(v[1], Value::Null) {
1790 return Ok(Value::Null);
1791 }
1792 let hay = eval::to_text(&v[0]);
1793 let needle = eval::to_text(&v[1]);
1794 // SQLite returns a 1-based character index, 0 if not found.
1795 match hay.find(&needle) {
1796 None => Ok(Value::Integer(0)),
1797 Some(byte_idx) => {
1798 let char_idx = hay[..byte_idx].chars().count();
1799 Ok(Value::Integer(char_idx as i64 + 1))
1800 }
1801 }
1802}
1803
1804fn replace(v: &[Value]) -> Result<Value> {
1805 if v.len() != 3 {
1806 return Err(wrong_arg_count("replace"));
1807 }
1808 // SQLite short-circuits in a specific order: a NULL subject or a NULL pattern
1809 // yields NULL, but an EMPTY pattern returns the subject (converted to text)
1810 // *before* the replacement argument is examined — so `replace('ab','',NULL)`
1811 // is `'ab'`, not NULL. Checking all three for NULL up front got this wrong.
1812 if matches!(v[0], Value::Null) || matches!(v[1], Value::Null) {
1813 return Ok(Value::Null);
1814 }
1815 let s = c_text(&v[0]);
1816 let from = c_text(&v[1]);
1817 if from.is_empty() {
1818 return Ok(Value::Text(s));
1819 }
1820 if matches!(v[2], Value::Null) {
1821 return Ok(Value::Null);
1822 }
1823 let to = c_text(&v[2]);
1824 Ok(Value::Text(s.replace(&from, &to)))
1825}
1826
1827fn round(v: &[Value]) -> Result<Value> {
1828 if v.is_empty() || v.len() > 2 {
1829 return Err(wrong_arg_count("round"));
1830 }
1831 // A NULL value or NULL precision both yield NULL.
1832 if matches!(v[0], Value::Null) || matches!(v.get(1), Some(Value::Null)) {
1833 return Ok(Value::Null);
1834 }
1835 let x = eval::to_f64(&v[0]);
1836 let digits = if v.len() == 2 {
1837 eval::to_int_value(&v[1]).clamp(0, 30) as u32
1838 } else {
1839 0
1840 };
1841 let r = round_half_away(x, digits);
1842 // SQLite normalises a negative-zero result to positive zero
1843 // (`round(-0.4)` is `0.0`, not `-0.0`).
1844 Ok(Value::Real(if r == 0.0 { 0.0 } else { r }))
1845}
1846
1847/// Round `x` to `n` decimal places, half away from zero, matching SQLite. Instead
1848/// of `round(x * 10^n) / 10^n` — which loses precision (e.g. `2.675 * 100` rounds
1849/// *up* to exactly `267.5` in f64, giving 2.68 where SQLite gives 2.67) — this
1850/// formats `x` to high fixed precision (exposing the true decimal digits) and
1851/// rounds the digit string, so it sees that `2.675` is really `2.67499…`.
1852pub(crate) fn round_half_away(x: f64, n: u32) -> f64 {
1853 if !x.is_finite() || x == 0.0 {
1854 return x;
1855 }
1856 // Values at or beyond 2^52 have no fractional part in f64; return unchanged
1857 // (this also bounds the formatted string length).
1858 if crate::util::float::abs(x) >= 4_503_599_627_370_496.0 {
1859 return x;
1860 }
1861 let neg = x < 0.0;
1862 let ax = crate::util::float::abs(x);
1863 let prec = n as usize + 25;
1864 let s = alloc::format!("{ax:.prec$}");
1865 let dot = s.find('.').unwrap_or(s.len());
1866 let frac = if dot < s.len() { &s[dot + 1..] } else { "" };
1867 // Round up when the first dropped digit (position `n`) is >= 5.
1868 let round_up = frac.as_bytes().get(n as usize).is_some_and(|&d| d >= b'5');
1869 // Kept digits: the integer part followed by the first `n` fractional digits.
1870 let mut digits: alloc::vec::Vec<u8> = alloc::vec::Vec::new();
1871 digits.extend_from_slice(&s.as_bytes()[..dot]);
1872 if n > 0 {
1873 let take = (n as usize).min(frac.len());
1874 digits.extend_from_slice(&frac.as_bytes()[..take]);
1875 // Pad with zeros if the formatting produced fewer than n fraction digits.
1876 digits.resize(dot + n as usize, b'0');
1877 }
1878 if round_up {
1879 let mut i = digits.len();
1880 loop {
1881 if i == 0 {
1882 digits.insert(0, b'1');
1883 break;
1884 }
1885 i -= 1;
1886 if digits[i] == b'9' {
1887 digits[i] = b'0';
1888 } else {
1889 digits[i] += 1;
1890 break;
1891 }
1892 }
1893 }
1894 // Reassemble with the decimal point `n` digits from the right and parse back.
1895 let nn = n as usize;
1896 let s2 = if nn == 0 {
1897 alloc::string::String::from_utf8(digits).unwrap_or_default()
1898 } else {
1899 let point = digits.len() - nn;
1900 let mut out = alloc::string::String::new();
1901 out.push_str(core::str::from_utf8(&digits[..point]).unwrap_or("0"));
1902 out.push('.');
1903 out.push_str(core::str::from_utf8(&digits[point..]).unwrap_or("0"));
1904 out
1905 };
1906 let mag: f64 = s2.parse().unwrap_or(ax);
1907 if neg {
1908 -mag
1909 } else {
1910 mag
1911 }
1912}
1913
1914fn scalar_min_max(v: &[Value], want_min: bool) -> Result<Value> {
1915 // Scalar min()/max() take 2+ args (the 1-arg/`*` forms are aggregates,
1916 // routed elsewhere); 0 args is an error in SQLite.
1917 if v.is_empty() {
1918 let name = if want_min { "min" } else { "max" };
1919 return Err(Error::Error(alloc::format!(
1920 "wrong number of arguments to function {name}()"
1921 )));
1922 }
1923 // NULL if any arg is NULL.
1924 if v.iter().any(|x| matches!(x, Value::Null)) {
1925 return Ok(Value::Null);
1926 }
1927 // Mirror SQLite's `minmaxFunc`: scan left-to-right keeping the best so far.
1928 // For `min`, replace whenever `best >= candidate` (so on a tie the *later*
1929 // argument wins — `min(1.0,1)` is the integer `1`); for `max`, replace only
1930 // when `best < candidate` (so on a tie the *earlier* argument wins —
1931 // `max(1.0,1)` is the real `1.0`). This preserves the storage class of the
1932 // exact argument SQLite would return.
1933 let mut best = v[0].clone();
1934 for x in &v[1..] {
1935 let ord = eval::compare(&best, x);
1936 let take = if want_min {
1937 ord != core::cmp::Ordering::Less
1938 } else {
1939 ord == core::cmp::Ordering::Less
1940 };
1941 if take {
1942 best = x.clone();
1943 }
1944 }
1945 Ok(best)
1946}
1947
1948fn hex_encode(v: &Value) -> String {
1949 let bytes = match v {
1950 Value::Blob(b) => b.clone(),
1951 other => eval::to_text(other).into_bytes(),
1952 };
1953 let mut s = String::with_capacity(bytes.len() * 2);
1954 for b in bytes {
1955 s.push(nibble(b >> 4));
1956 s.push(nibble(b & 0xf));
1957 }
1958 s
1959}
1960
1961fn nibble(n: u8) -> char {
1962 match n {
1963 0..=9 => (b'0' + n) as char,
1964 _ => (b'A' + n - 10) as char,
1965 }
1966}
1967
1968fn char_fn(v: &[Value]) -> Value {
1969 let mut s = String::new();
1970 for x in v {
1971 // SQLite's `charFunc` reads each argument as a code point and substitutes
1972 // U+FFFD for an out-of-range value (negative or > U+10FFFF) rather than
1973 // dropping the character. Surrogates (U+D800..=U+DFFF) cannot be held in
1974 // Rust's UTF-8 `String`, so they too fall back to U+FFFD (SQLite emits
1975 // their raw 3-byte encoding — a faithful match would need invalid UTF-8,
1976 // which this engine's TEXT representation forbids).
1977 let cp = eval::to_int_value(x);
1978 let c = u32::try_from(cp)
1979 .ok()
1980 .and_then(char::from_u32)
1981 .unwrap_or('\u{FFFD}');
1982 s.push(c);
1983 }
1984 Value::Text(s)
1985}