use std::collections::HashMap;
use std::sync::LazyLock;
use tree_sitter::Language;
use crate::grammar_generated::{GENERATED_FUNCTIONS, RENAMED_FUNCTIONS};
use crate::semantic::type_expr::TypeExpr;
unsafe extern "C" {
fn tree_sitter_surrealql() -> *const tree_sitter::ffi::TSLanguage;
}
include!(concat!(env!("OUT_DIR"), "/keywords.rs"));
#[derive(Debug, Clone, Copy)]
pub struct BuiltinNamespace {
pub name: &'static str,
pub summary: &'static str,
pub documentation_url: &'static str,
}
#[derive(Debug, Clone, Copy)]
pub struct BuiltinFunction {
pub name: &'static str,
pub signature: &'static str,
pub summary: &'static str,
pub documentation_url: &'static str,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ParamForm {
Required,
Optional,
Variadic,
}
#[derive(Debug, Clone, Copy)]
pub struct GeneratedParam {
pub name: &'static str,
pub ty: &'static str,
pub form: ParamForm,
}
#[derive(Debug, Clone, Copy)]
pub struct GeneratedFunction {
pub name: &'static str,
pub params: &'static [GeneratedParam],
pub is_async: bool,
pub not_callable: bool,
pub signature_known: bool,
}
pub const BUILTIN_NAMESPACES: &[&str] = &[
"array::",
"crypto::",
"duration::",
"encoding::",
"geo::",
"http::",
"math::",
"meta::",
"not::",
"object::",
"parse::",
"rand::",
"record::",
"search::",
"session::",
"sleep::",
"string::",
"time::",
"type::",
"vector::",
];
pub const BUILTIN_NAMESPACE_DOCS: &[BuiltinNamespace] = &[
BuiltinNamespace {
name: "string::",
summary: "Builtin functions for string validation and text manipulation.",
documentation_url: "https://surrealdb.com/docs/surrealql/functions/database/string",
},
BuiltinNamespace {
name: "type::",
summary: "Builtin functions for type coercion, inspection, and record construction.",
documentation_url: "https://surrealdb.com/docs/surrealql/functions/database/type",
},
];
pub const BUILTIN_FUNCTIONS: &[BuiltinFunction] = &[
BuiltinFunction {
name: "string::capitalize",
signature: "string::capitalize(string) -> string",
summary: "Capitalizes each word in a string.",
documentation_url: "https://surrealdb.com/docs/surrealql/functions/database/string",
},
BuiltinFunction {
name: "string::concat",
signature: "string::concat(value...) -> string",
summary: "Concatenates values into a single string.",
documentation_url: "https://surrealdb.com/docs/surrealql/functions/database/string",
},
BuiltinFunction {
name: "string::contains",
signature: "string::contains(string, predicate: string) -> bool",
summary: "Checks whether a string contains another string.",
documentation_url: "https://surrealdb.com/docs/surrealql/functions/database/string",
},
BuiltinFunction {
name: "string::ends_with",
signature: "string::ends_with(string, other: string) -> bool",
summary: "Checks whether a string ends with another string.",
documentation_url: "https://surrealdb.com/docs/surrealql/functions/database/string",
},
BuiltinFunction {
name: "string::join",
signature: "string::join(delimiter: value, value...) -> string",
summary: "Joins values together with a delimiter.",
documentation_url: "https://surrealdb.com/docs/surrealql/functions/database/string",
},
BuiltinFunction {
name: "string::len",
signature: "string::len(string) -> number",
summary: "Returns the length of a string in characters.",
documentation_url: "https://surrealdb.com/docs/surrealql/functions/database/string",
},
BuiltinFunction {
name: "string::lowercase",
signature: "string::lowercase(string) -> string",
summary: "Converts a string to lowercase.",
documentation_url: "https://surrealdb.com/docs/surrealql/functions/database/string",
},
BuiltinFunction {
name: "string::matches",
signature: "string::matches(string, pattern: string|regex) -> bool",
summary: "Performs a regex match on a string.",
documentation_url: "https://surrealdb.com/docs/surrealql/functions/database/string",
},
BuiltinFunction {
name: "string::repeat",
signature: "string::repeat(string, count: number) -> string",
summary: "Repeats a string a number of times.",
documentation_url: "https://surrealdb.com/docs/surrealql/functions/database/string",
},
BuiltinFunction {
name: "string::replace",
signature: "string::replace(string, pattern: string, replacement: string) -> string",
summary: "Replaces part of a string with another string.",
documentation_url: "https://surrealdb.com/docs/surrealql/functions/database/string",
},
BuiltinFunction {
name: "string::slice",
signature: "string::slice(string, from: number, to?: number) -> string",
summary: "Returns a substring from a string.",
documentation_url: "https://surrealdb.com/docs/surrealql/functions/database/string",
},
BuiltinFunction {
name: "string::slug",
signature: "string::slug(string) -> string",
summary: "Converts a string into a slug-safe form.",
documentation_url: "https://surrealdb.com/docs/surrealql/functions/database/string",
},
BuiltinFunction {
name: "string::split",
signature: "string::split(string, delimiter: string|regex) -> array<string>",
summary: "Splits a string by a delimiter or regex.",
documentation_url: "https://surrealdb.com/docs/surrealql/functions/database/string",
},
BuiltinFunction {
name: "string::starts_with",
signature: "string::starts_with(string, other: string) -> bool",
summary: "Checks whether a string starts with another string.",
documentation_url: "https://surrealdb.com/docs/surrealql/functions/database/string",
},
BuiltinFunction {
name: "string::trim",
signature: "string::trim(string) -> string",
summary: "Trims leading and trailing whitespace.",
documentation_url: "https://surrealdb.com/docs/surrealql/functions/database/string",
},
BuiltinFunction {
name: "string::uppercase",
signature: "string::uppercase(string) -> string",
summary: "Converts a string to uppercase.",
documentation_url: "https://surrealdb.com/docs/surrealql/functions/database/string",
},
BuiltinFunction {
name: "string::words",
signature: "string::words(string) -> array<string>",
summary: "Splits a string into words.",
documentation_url: "https://surrealdb.com/docs/surrealql/functions/database/string",
},
BuiltinFunction {
name: "string::html::sanitize",
signature: "string::html::sanitize(string) -> string",
summary: "Sanitizes HTML while keeping safe markup intact.",
documentation_url: "https://surrealdb.com/docs/surrealql/functions/database/string",
},
BuiltinFunction {
name: "string::is_alphanum",
signature: "string::is_alphanum(any) -> bool",
summary: "Checks whether a value is alphanumeric text.",
documentation_url: "https://surrealdb.com/docs/surrealql/functions/database/string",
},
BuiltinFunction {
name: "string::is_alpha",
signature: "string::is_alpha(any) -> bool",
summary: "Checks whether a value only contains letters.",
documentation_url: "https://surrealdb.com/docs/surrealql/functions/database/string",
},
BuiltinFunction {
name: "string::is_ascii",
signature: "string::is_ascii(any) -> bool",
summary: "Checks whether a value only contains ASCII characters.",
documentation_url: "https://surrealdb.com/docs/surrealql/functions/database/string",
},
BuiltinFunction {
name: "string::is_datetime",
signature: "string::is_datetime(any) -> bool",
summary: "Checks whether a value is a valid datetime string.",
documentation_url: "https://surrealdb.com/docs/surrealql/functions/database/string",
},
BuiltinFunction {
name: "string::is_domain",
signature: "string::is_domain(any) -> bool",
summary: "Checks whether a value is a valid domain name.",
documentation_url: "https://surrealdb.com/docs/surrealql/functions/database/string",
},
BuiltinFunction {
name: "string::is_email",
signature: "string::is_email(any) -> bool",
summary: "Checks whether a value is a valid email address.",
documentation_url: "https://surrealdb.com/docs/surrealql/functions/database/string",
},
BuiltinFunction {
name: "string::is_hexadecimal",
signature: "string::is_hexadecimal(any) -> bool",
summary: "Checks whether a value is valid hexadecimal text.",
documentation_url: "https://surrealdb.com/docs/surrealql/functions/database/string",
},
BuiltinFunction {
name: "string::is_ip",
signature: "string::is_ip(any) -> bool",
summary: "Checks whether a value is a valid IP address.",
documentation_url: "https://surrealdb.com/docs/surrealql/functions/database/string",
},
BuiltinFunction {
name: "string::is_ipv4",
signature: "string::is_ipv4(any) -> bool",
summary: "Checks whether a value is a valid IPv4 address.",
documentation_url: "https://surrealdb.com/docs/surrealql/functions/database/string",
},
BuiltinFunction {
name: "string::is_ipv6",
signature: "string::is_ipv6(any) -> bool",
summary: "Checks whether a value is a valid IPv6 address.",
documentation_url: "https://surrealdb.com/docs/surrealql/functions/database/string",
},
BuiltinFunction {
name: "string::is_latitude",
signature: "string::is_latitude(any) -> bool",
summary: "Checks whether a value is a valid latitude.",
documentation_url: "https://surrealdb.com/docs/surrealql/functions/database/string",
},
BuiltinFunction {
name: "string::is_longitude",
signature: "string::is_longitude(any) -> bool",
summary: "Checks whether a value is a valid longitude.",
documentation_url: "https://surrealdb.com/docs/surrealql/functions/database/string",
},
BuiltinFunction {
name: "string::is_numeric",
signature: "string::is_numeric(any) -> bool",
summary: "Checks whether a value is numeric text.",
documentation_url: "https://surrealdb.com/docs/surrealql/functions/database/string",
},
BuiltinFunction {
name: "string::is_semver",
signature: "string::is_semver(any) -> bool",
summary: "Checks whether a value is a semantic-version string.",
documentation_url: "https://surrealdb.com/docs/surrealql/functions/database/string",
},
BuiltinFunction {
name: "string::is_ulid",
signature: "string::is_ulid(any) -> bool",
summary: "Checks whether a value is a valid ULID.",
documentation_url: "https://surrealdb.com/docs/surrealql/functions/database/string",
},
BuiltinFunction {
name: "string::is_url",
signature: "string::is_url(any) -> bool",
summary: "Checks whether a value is a valid URL.",
documentation_url: "https://surrealdb.com/docs/surrealql/functions/database/string",
},
BuiltinFunction {
name: "string::is_uuid",
signature: "string::is_uuid(any) -> bool",
summary: "Checks whether a value is a valid UUID string.",
documentation_url: "https://surrealdb.com/docs/surrealql/functions/database/string",
},
BuiltinFunction {
name: "type::array",
signature: "type::array(any) -> array",
summary: "Converts a value into an array.",
documentation_url: "https://surrealdb.com/docs/surrealql/functions/database/type",
},
BuiltinFunction {
name: "type::bool",
signature: "type::bool(any) -> bool",
summary: "Converts a value into a boolean.",
documentation_url: "https://surrealdb.com/docs/surrealql/functions/database/type",
},
BuiltinFunction {
name: "type::bytes",
signature: "type::bytes(any) -> bytes",
summary: "Converts a value into bytes.",
documentation_url: "https://surrealdb.com/docs/surrealql/functions/database/type",
},
BuiltinFunction {
name: "type::datetime",
signature: "type::datetime(any) -> datetime",
summary: "Converts a value into a datetime.",
documentation_url: "https://surrealdb.com/docs/surrealql/functions/database/type",
},
BuiltinFunction {
name: "type::decimal",
signature: "type::decimal(any) -> decimal",
summary: "Converts a value into a decimal.",
documentation_url: "https://surrealdb.com/docs/surrealql/functions/database/type",
},
BuiltinFunction {
name: "type::duration",
signature: "type::duration(any) -> duration",
summary: "Converts a value into a duration.",
documentation_url: "https://surrealdb.com/docs/surrealql/functions/database/type",
},
BuiltinFunction {
name: "type::field",
signature: "type::field(any) -> field",
summary: "Projects a single field in a SELECT statement.",
documentation_url: "https://surrealdb.com/docs/surrealql/functions/database/type",
},
BuiltinFunction {
name: "type::fields",
signature: "type::fields(any...) -> array<field>",
summary: "Projects multiple fields in a SELECT statement.",
documentation_url: "https://surrealdb.com/docs/surrealql/functions/database/type",
},
BuiltinFunction {
name: "type::file",
signature: "type::file(path: string, mime?: string) -> file",
summary: "Builds a file pointer from string input.",
documentation_url: "https://surrealdb.com/docs/surrealql/functions/database/type",
},
BuiltinFunction {
name: "type::float",
signature: "type::float(any) -> float",
summary: "Converts a value into a float.",
documentation_url: "https://surrealdb.com/docs/surrealql/functions/database/type",
},
BuiltinFunction {
name: "type::int",
signature: "type::int(any) -> int",
summary: "Converts a value into an integer.",
documentation_url: "https://surrealdb.com/docs/surrealql/functions/database/type",
},
BuiltinFunction {
name: "type::number",
signature: "type::number(any) -> number",
summary: "Converts a value into a number.",
documentation_url: "https://surrealdb.com/docs/surrealql/functions/database/type",
},
BuiltinFunction {
name: "type::of",
signature: "type::of(value: any) -> string",
summary: "Returns the SurrealQL type name of a value.",
documentation_url: "https://surrealdb.com/docs/surrealql/functions/database/type",
},
BuiltinFunction {
name: "type::point",
signature: "type::point(array|point) -> point",
summary: "Converts a value into a geometry point.",
documentation_url: "https://surrealdb.com/docs/surrealql/functions/database/type",
},
BuiltinFunction {
name: "type::range",
signature: "type::range(range|array) -> range<record>",
summary: "Converts a value into a range.",
documentation_url: "https://surrealdb.com/docs/surrealql/functions/database/type",
},
BuiltinFunction {
name: "type::record",
signature: "type::record(table: any, key: any) -> record",
summary: "Builds a record id from a table and key.",
documentation_url: "https://surrealdb.com/docs/surrealql/functions/database/type",
},
BuiltinFunction {
name: "type::string",
signature: "type::string(any) -> string",
summary: "Converts a value into a string.",
documentation_url: "https://surrealdb.com/docs/surrealql/functions/database/type",
},
BuiltinFunction {
name: "type::string_lossy",
signature: "type::string_lossy(any) -> string",
summary: "Converts a value into a string, replacing invalid byte sequences.",
documentation_url: "https://surrealdb.com/docs/surrealql/functions/database/type",
},
BuiltinFunction {
name: "type::table",
signature: "type::table(record|string) -> string",
summary: "Extracts or coerces a table name.",
documentation_url: "https://surrealdb.com/docs/surrealql/functions/database/type",
},
BuiltinFunction {
name: "type::uuid",
signature: "type::uuid(any) -> uuid",
summary: "Converts a value into a UUID.",
documentation_url: "https://surrealdb.com/docs/surrealql/functions/database/type",
},
BuiltinFunction {
name: "type::is_array",
signature: "type::is_array(any) -> bool",
summary: "Checks whether a value is an array.",
documentation_url: "https://surrealdb.com/docs/surrealql/functions/database/type",
},
BuiltinFunction {
name: "type::is_bool",
signature: "type::is_bool(any) -> bool",
summary: "Checks whether a value is a boolean.",
documentation_url: "https://surrealdb.com/docs/surrealql/functions/database/type",
},
BuiltinFunction {
name: "type::is_bytes",
signature: "type::is_bytes(any) -> bool",
summary: "Checks whether a value is bytes.",
documentation_url: "https://surrealdb.com/docs/surrealql/functions/database/type",
},
BuiltinFunction {
name: "type::is_collection",
signature: "type::is_collection(any) -> bool",
summary: "Checks whether a value is a geometry collection.",
documentation_url: "https://surrealdb.com/docs/surrealql/functions/database/type",
},
BuiltinFunction {
name: "type::is_datetime",
signature: "type::is_datetime(any) -> bool",
summary: "Checks whether a value is a datetime.",
documentation_url: "https://surrealdb.com/docs/surrealql/functions/database/type",
},
BuiltinFunction {
name: "type::is_decimal",
signature: "type::is_decimal(any) -> bool",
summary: "Checks whether a value is a decimal.",
documentation_url: "https://surrealdb.com/docs/surrealql/functions/database/type",
},
BuiltinFunction {
name: "type::is_duration",
signature: "type::is_duration(any) -> bool",
summary: "Checks whether a value is a duration.",
documentation_url: "https://surrealdb.com/docs/surrealql/functions/database/type",
},
BuiltinFunction {
name: "type::is_float",
signature: "type::is_float(any) -> bool",
summary: "Checks whether a value is a float.",
documentation_url: "https://surrealdb.com/docs/surrealql/functions/database/type",
},
BuiltinFunction {
name: "type::is_geometry",
signature: "type::is_geometry(any) -> bool",
summary: "Checks whether a value is a geometry value.",
documentation_url: "https://surrealdb.com/docs/surrealql/functions/database/type",
},
BuiltinFunction {
name: "type::is_int",
signature: "type::is_int(any) -> bool",
summary: "Checks whether a value is an integer.",
documentation_url: "https://surrealdb.com/docs/surrealql/functions/database/type",
},
BuiltinFunction {
name: "type::is_line",
signature: "type::is_line(any) -> bool",
summary: "Checks whether a value is a geometry line.",
documentation_url: "https://surrealdb.com/docs/surrealql/functions/database/type",
},
BuiltinFunction {
name: "type::is_multiline",
signature: "type::is_multiline(any) -> bool",
summary: "Checks whether a value is a geometry multiline.",
documentation_url: "https://surrealdb.com/docs/surrealql/functions/database/type",
},
BuiltinFunction {
name: "type::is_multipoint",
signature: "type::is_multipoint(any) -> bool",
summary: "Checks whether a value is a geometry multipoint.",
documentation_url: "https://surrealdb.com/docs/surrealql/functions/database/type",
},
BuiltinFunction {
name: "type::is_multipolygon",
signature: "type::is_multipolygon(any) -> bool",
summary: "Checks whether a value is a geometry multipolygon.",
documentation_url: "https://surrealdb.com/docs/surrealql/functions/database/type",
},
BuiltinFunction {
name: "type::is_none",
signature: "type::is_none(any) -> bool",
summary: "Checks whether a value is NONE.",
documentation_url: "https://surrealdb.com/docs/surrealql/functions/database/type",
},
BuiltinFunction {
name: "type::is_null",
signature: "type::is_null(any) -> bool",
summary: "Checks whether a value is NULL.",
documentation_url: "https://surrealdb.com/docs/surrealql/functions/database/type",
},
BuiltinFunction {
name: "type::is_number",
signature: "type::is_number(any) -> bool",
summary: "Checks whether a value is numeric.",
documentation_url: "https://surrealdb.com/docs/surrealql/functions/database/type",
},
BuiltinFunction {
name: "type::is_object",
signature: "type::is_object(any) -> bool",
summary: "Checks whether a value is an object.",
documentation_url: "https://surrealdb.com/docs/surrealql/functions/database/type",
},
BuiltinFunction {
name: "type::is_point",
signature: "type::is_point(any) -> bool",
summary: "Checks whether a value is a geometry point.",
documentation_url: "https://surrealdb.com/docs/surrealql/functions/database/type",
},
BuiltinFunction {
name: "type::is_polygon",
signature: "type::is_polygon(any) -> bool",
summary: "Checks whether a value is a geometry polygon.",
documentation_url: "https://surrealdb.com/docs/surrealql/functions/database/type",
},
BuiltinFunction {
name: "type::is_range",
signature: "type::is_range(any) -> bool",
summary: "Checks whether a value is a range.",
documentation_url: "https://surrealdb.com/docs/surrealql/functions/database/type",
},
BuiltinFunction {
name: "type::is_record",
signature: "type::is_record(any, table?: string) -> bool",
summary: "Checks whether a value is a record, optionally on a specific table.",
documentation_url: "https://surrealdb.com/docs/surrealql/functions/database/type",
},
BuiltinFunction {
name: "type::is_string",
signature: "type::is_string(any) -> bool",
summary: "Checks whether a value is a string.",
documentation_url: "https://surrealdb.com/docs/surrealql/functions/database/type",
},
BuiltinFunction {
name: "type::is_uuid",
signature: "type::is_uuid(any) -> bool",
summary: "Checks whether a value is a UUID.",
documentation_url: "https://surrealdb.com/docs/surrealql/functions/database/type",
},
];
pub const SPECIAL_VARIABLES: &[(&str, &str)] = &[
("$this", "Current record in scope."),
("$auth", "Authenticated user/session context."),
("$action", "Current action within hooks or permissions."),
("$file", "Current file input context."),
("$target", "Current target record."),
("$value", "Current value being evaluated."),
("$parent", "Parent object or record in scope."),
("$access", "Access definition context."),
("$event", "Current event payload."),
("$before", "State before a change."),
("$after", "State after a change."),
("$request", "Current request metadata."),
("$reference", "Reference clause context."),
("$token", "Current auth token payload."),
("$session", "Current session details."),
("$input", "Current input payload."),
];
pub fn builtin_namespace(name: &str) -> Option<&'static BuiltinNamespace> {
let normalized = name.trim().to_ascii_lowercase();
BUILTIN_NAMESPACE_DOCS
.iter()
.find(|namespace| namespace.name.eq_ignore_ascii_case(&normalized))
}
static BUILTIN_INDEX: LazyLock<HashMap<&'static str, (&'static BuiltinFunction, TypeExpr)>> =
LazyLock::new(|| {
BUILTIN_FUNCTIONS
.iter()
.map(|function| {
let returns = parse_return_type(function.signature);
(function.name, (function, returns))
})
.collect()
});
fn parse_return_type(signature: &str) -> TypeExpr {
match signature.rsplit_once("->") {
Some((_, returns)) => TypeExpr::parse(returns.trim()),
None => TypeExpr::Unknown,
}
}
pub fn builtin_function(name: &str) -> Option<&'static BuiltinFunction> {
let normalized = normalize_builtin_function_name(name);
BUILTIN_INDEX
.get(normalized.as_str())
.map(|(function, _)| *function)
}
pub fn builtin_return_type(name: &str) -> Option<&'static TypeExpr> {
let normalized = normalize_builtin_function_name(name);
BUILTIN_INDEX
.get(normalized.as_str())
.map(|(_, returns)| returns)
}
fn normalize_builtin_function_name(name: &str) -> String {
let mut normalized = name.trim().to_ascii_lowercase();
if normalized.contains("::is::") {
normalized = normalized.replace("::is::", "::is_");
}
if let Some((_, current)) = RENAMED_FUNCTIONS
.iter()
.find(|(previous, _)| previous.eq_ignore_ascii_case(&normalized))
{
normalized = (*current).to_string();
}
normalized
}
#[derive(Debug)]
pub struct BuiltinSignature {
pub generated: &'static GeneratedFunction,
pub param_types: Vec<TypeExpr>,
}
impl BuiltinSignature {
pub fn required_arity(&self) -> usize {
self.generated
.params
.iter()
.rposition(|param| param.form == ParamForm::Required)
.map_or(0, |index| index + 1)
}
pub fn maximum_arity(&self) -> Option<usize> {
if self
.generated
.params
.iter()
.any(|param| param.form == ParamForm::Variadic)
{
return None;
}
Some(self.generated.params.len())
}
pub fn param_labels(&self) -> Vec<String> {
self.generated
.params
.iter()
.map(|param| match param.form {
ParamForm::Required => format!("{}: {}", param.name, param.ty),
ParamForm::Optional => format!("{}?: {}", param.name, param.ty),
ParamForm::Variadic => format!("{}: {}...", param.name, param.ty),
})
.collect()
}
pub fn display_signature(&self) -> Option<String> {
if !self.generated.signature_known {
return None;
}
Some(format!(
"{}({})",
self.generated.name,
self.param_labels().join(", ")
))
}
pub fn param_type_at(&self, index: usize) -> Option<&TypeExpr> {
if let Some(found) = self.param_types.get(index) {
return Some(found);
}
match self.generated.params.last() {
Some(last) if last.form == ParamForm::Variadic => self.param_types.last(),
_ => None,
}
}
}
static GENERATED_INDEX: LazyLock<HashMap<&'static str, BuiltinSignature>> = LazyLock::new(|| {
GENERATED_FUNCTIONS
.iter()
.map(|generated| {
let param_types = generated
.params
.iter()
.map(|param| TypeExpr::parse(param.ty))
.collect();
(
generated.name,
BuiltinSignature {
generated,
param_types,
},
)
})
.collect()
});
pub fn builtin_signature(name: &str) -> Option<&'static BuiltinSignature> {
let normalized = normalize_builtin_function_name(name);
GENERATED_INDEX.get(normalized.as_str())
}
pub fn is_builtin_function(name: &str) -> bool {
let normalized = normalize_builtin_function_name(name);
GENERATED_INDEX.contains_key(normalized.as_str())
}
pub fn renamed_builtin(name: &str) -> Option<&'static str> {
let normalized = name.trim().to_ascii_lowercase();
RENAMED_FUNCTIONS
.iter()
.find(|(previous, _)| previous.eq_ignore_ascii_case(&normalized))
.map(|(_, current)| *current)
}
pub fn language() -> Language {
unsafe { Language::from_raw(tree_sitter_surrealql()) }
}
#[cfg(test)]
mod tests {
use super::*;
fn signature(name: &str) -> &'static BuiltinSignature {
builtin_signature(name).unwrap_or_else(|| panic!("`{name}` must be in the catalogue"))
}
#[test]
fn a_builtin_from_a_namespace_with_no_curated_prose_still_has_types() {
let clamp = signature("math::clamp");
assert!(clamp.generated.signature_known);
assert_eq!(clamp.param_types.len(), 3);
for parsed in &clamp.param_types {
assert_eq!(*parsed, TypeExpr::Scalar("number".to_string()));
}
}
#[test]
fn parameter_types_are_parsed_not_left_as_strings() {
let sum = signature("math::sum");
assert_eq!(
sum.param_types[0],
TypeExpr::Array(Box::new(TypeExpr::Scalar("number".to_string()))),
"array<number> must reach the checker as an Array, not an Other"
);
}
#[test]
fn a_set_parameter_parses_as_a_set() {
let contains = signature("set::contains");
assert_eq!(contains.param_types[0], TypeExpr::Scalar("set".to_string()));
}
#[test]
fn required_arity_counts_only_the_leading_required_run() {
let slice = signature("string::slice");
assert_eq!(slice.required_arity(), 1);
assert_eq!(slice.maximum_arity(), Some(3));
}
#[test]
fn a_variadic_makes_the_maximum_arity_unbounded() {
let concat = signature("string::concat");
assert_eq!(concat.maximum_arity(), None);
assert_eq!(
concat.required_arity(),
0,
"a self-validating variadic requires nothing"
);
}
#[test]
fn a_zero_argument_function_has_a_known_signature_and_no_parameters() {
let now = signature("time::now");
assert!(now.generated.signature_known);
assert!(now.generated.params.is_empty());
assert_eq!(now.required_arity(), 0);
assert_eq!(now.maximum_arity(), Some(0));
}
#[test]
fn a_variadic_types_every_argument_beyond_its_position() {
let concat = signature("array::concat");
let first = concat.param_type_at(0).cloned();
assert_eq!(
concat.param_type_at(7).cloned(),
first,
"index 7 is covered"
);
}
#[test]
fn a_non_variadic_function_types_nothing_past_its_last_parameter() {
assert!(signature("array::at").param_type_at(5).is_none());
}
#[test]
fn an_old_function_name_resolves_to_its_current_spelling() {
assert!(builtin_signature("type::thing").is_some());
assert_eq!(renamed_builtin("type::thing"), Some("type::record"));
assert_eq!(renamed_builtin("type::record"), None);
}
#[test]
fn the_catalogue_knows_names_the_curated_table_never_had() {
assert!(is_builtin_function("vector::distance::knn"));
assert!(is_builtin_function("crypto::argon2::compare"));
assert!(!is_builtin_function("string::definitely_not_a_function"));
}
#[test]
fn a_cast_parameter_is_permissive() {
let matches = signature("string::matches");
assert_eq!(
matches.param_types[1],
TypeExpr::Scalar("any".to_string()),
"a Cast parameter must stay permissive"
);
}
}