//! Curated set of SQL function names the playground recognises //! (ADR-0022 Amendment 6, issues #15 / #16). //! //! This is the single source of truth for "what SQL function names //! does this playground know about", shared by two consumers: //! //! - **Tab completion** (issue #15): at a `sql_expr_ident` slot the //! completion engine offers these as `CandidateKind::Function` //! candidates, so a learner can discover `sum` / `upper` / … without //! already knowing them. //! - **The typing-time column-typo hint** (issue #16): at the same //! slot, `invalid_ident_at_cursor` flags a partial as "no such //! column" only when it matches neither a schema column *nor* a //! known function name — so a genuine typo still warns early while //! a real function name like `sum` does not get mis-flagged. //! //! Scope (ADR-0031 §1): the SQL expression grammar admits any //! function-call *shape* without knowing which names are real — the //! walker is a structural matcher, not an evaluator. This list is a //! deliberately *curated pedagogical set*, not "every SQLite //! built-in". It covers the aggregates a learner meets first plus the //! common scalar functions, and is the allow-list / discovery source //! layered on top of that shape-only grammar. //! //! Deliberately excluded: `cast`. SQLite's `CAST` uses the //! `CAST(expr AS type)` syntax, which the expression grammar does //! **not** parse as a function call (function args are expressions, //! and `expr AS type` is not one). Offering `cast` as a completion //! candidate would surface a name that does not parse in the call //! shape, so it stays out of the set until the grammar grows a //! dedicated `CAST` form. /// The curated SQL function names, lowercase and sorted. /// /// Sorted + lowercase is an invariant (pinned by a unit test): the /// completion engine relies on a stable order, and the prefix match /// is case-insensitive against these canonical lowercase spellings. /// /// Grouping (all plain `name(args)` call shapes): /// - **Aggregates:** `avg`, `count`, `max`, `min`, `sum`. /// - **Common scalars:** `abs`, `coalesce`, `length`, `lower`, /// `round`, `substr`, `trim`, `upper`. /// - **Broader scalars:** `date`, `datetime`, `hex`, `ifnull`, /// `instr`, `nullif`, `random`, `replace`, `strftime`, `typeof`. pub const KNOWN_SQL_FUNCTIONS: &[&str] = &[ "abs", "avg", "coalesce", "count", "date", "datetime", "hex", "ifnull", "instr", "length", "lower", "max", "min", "nullif", "random", "replace", "round", "strftime", "substr", "sum", "trim", "typeof", "upper", ]; /// Whether `partial` is a case-insensitive prefix of at least one /// known function name. /// /// An empty `partial` matches every function (it is a prefix of /// all), mirroring the empty-prefix behaviour of the keyword / /// identifier completion sources. Used by `invalid_ident_at_cursor` /// to decide whether a partial at a `sql_expr_ident` slot might still /// resolve to a function name (and so must not be flagged as an /// unknown column). #[must_use] pub fn is_known_function_prefix(partial: &str) -> bool { let lowered = partial.to_lowercase(); KNOWN_SQL_FUNCTIONS.iter().any(|f| f.starts_with(&lowered)) } #[cfg(test)] mod tests { use super::*; #[test] fn list_is_sorted_and_lowercase() { // The completion engine and prefix matcher both rely on the // canonical sorted-lowercase invariant. for f in KNOWN_SQL_FUNCTIONS { assert_eq!( *f, f.to_lowercase(), "function name `{f}` must be lowercase", ); } let mut sorted = KNOWN_SQL_FUNCTIONS.to_vec(); sorted.sort_unstable(); assert_eq!( sorted.as_slice(), KNOWN_SQL_FUNCTIONS, "KNOWN_SQL_FUNCTIONS must be declared in sorted order", ); } #[test] fn list_has_no_duplicates() { let mut seen = std::collections::HashSet::new(); for f in KNOWN_SQL_FUNCTIONS { assert!(seen.insert(*f), "duplicate function name `{f}`"); } } #[test] fn cast_is_excluded() { // `CAST(expr AS type)` is not a plain call shape the grammar // parses — it must not be offered as a candidate (faithfulness // to ADR-0031's call-shape grammar). assert!( !KNOWN_SQL_FUNCTIONS.contains(&"cast"), "`cast` is not a plain-call function and must stay out of the set", ); } #[test] fn prefix_match_is_case_insensitive() { assert!(is_known_function_prefix("su")); // sum, substr assert!(is_known_function_prefix("SU")); // case-folded assert!(is_known_function_prefix("sum")); // exact assert!(is_known_function_prefix("UPP")); // upper } #[test] fn empty_prefix_matches_all() { assert!(is_known_function_prefix("")); } #[test] fn unknown_prefix_does_not_match() { assert!(!is_known_function_prefix("zzz")); assert!(!is_known_function_prefix("xqz")); // A genuine column-typo shape that prefixes no function. assert!(!is_known_function_prefix("Agx")); } }