surrealdb-core 3.2.3

A scalable, distributed, collaborative, document-graph database, for the realtime web
Documentation
//! Helpers for analyzer `FUNCTION` clause references (`fn::` and `mod::`).

use surrealdb_types::{SqlFormat, ToSql, write_sql};

use crate::fmt::EscapeKwFreeIdent;

/// Returns the fully qualified function name for display in errors.
pub(crate) fn qualified_name(name: &str) -> String {
	if name.starts_with("mod::") || name.starts_with("fn::") {
		name.to_owned()
	} else {
		format!("fn::{name}")
	}
}

/// Converts a stored analyzer function reference into an executable function.
pub(crate) fn function_from_storage(name: &str) -> crate::expr::Function {
	if let Some(rest) = name.strip_prefix("mod::") {
		let mut parts = rest.split("::");
		let module = parts.next().unwrap_or_default().to_owned();
		let sub = parts.collect::<Vec<_>>().join("::");
		let sub = if sub.is_empty() {
			None
		} else {
			Some(sub)
		};
		crate::expr::Function::Module(module, sub)
	} else {
		let name = name.strip_prefix("fn::").unwrap_or(name);
		crate::expr::Function::Custom(name.to_owned())
	}
}

/// Writes the `FUNCTION fn::...` or `FUNCTION mod::...` clause for an analyzer.
pub(crate) fn fmt_analyzer_function(f: &mut String, sql_fmt: SqlFormat, name: &str) {
	let (kind, path) = if let Some(rest) = name.strip_prefix("mod::") {
		("mod", rest)
	} else if let Some(rest) = name.strip_prefix("fn::") {
		("fn", rest)
	} else {
		("fn", name)
	};

	write_sql!(f, sql_fmt, " FUNCTION {kind}");
	for segment in path.split("::") {
		f.push_str("::");
		EscapeKwFreeIdent(segment).fmt_sql(f, sql_fmt);
	}
}

#[cfg(test)]
mod tests {
	use super::*;

	#[test]
	fn qualified_name_legacy_fn_suffix() {
		assert_eq!(qualified_name("foo::bar"), "fn::foo::bar");
	}

	#[test]
	fn qualified_name_mod_prefix() {
		assert_eq!(qualified_name("mod::demo::alter"), "mod::demo::alter");
	}

	#[test]
	fn function_from_storage_fn_legacy() {
		assert!(matches!(
			function_from_storage("foo::bar"),
			crate::expr::Function::Custom(s) if s == "foo::bar"
		));
	}

	#[test]
	fn function_from_storage_fn_prefix() {
		assert!(matches!(
			function_from_storage("fn::foo::bar"),
			crate::expr::Function::Custom(s) if s == "foo::bar"
		));
	}

	#[test]
	fn function_from_storage_mod() {
		assert!(matches!(
			function_from_storage("mod::demo::math::add"),
			crate::expr::Function::Module(m, Some(s)) if m == "demo" && s == "math::add"
		));
	}

	#[test]
	fn fmt_analyzer_function_fn() {
		let mut sql = String::new();
		fmt_analyzer_function(&mut sql, SqlFormat::SingleLine, "foo::bar");
		assert_eq!(sql, " FUNCTION fn::foo::bar");
	}

	#[test]
	fn fmt_analyzer_function_mod() {
		let mut sql = String::new();
		fmt_analyzer_function(&mut sql, SqlFormat::SingleLine, "mod::demo::alter");
		assert_eq!(sql, " FUNCTION mod::demo::alter");
	}
}