radixdb_executor/mutation/
view_binding.rs1use rustc_hash::FxHashSet;
12
13use radixdb_core::{Error, Result};
14use radixdb_sql::ast::{self, SelectStatement, Statement};
15
16pub fn bind_from_select(select: &SelectStatement) -> Vec<String> {
17 let mut dependencies = FxHashSet::default();
18 ast::walk_physical_table_sources(select, &mut |source| {
19 dependencies.insert(source.name.value_lower.to_string());
20 });
21 let mut dependencies: Vec<String> = dependencies.into_iter().collect();
22 dependencies.sort_unstable();
23 dependencies
24}
25
26pub fn bind_from_sql(query: &str) -> Result<Vec<String>> {
29 let statements = radixdb_sql::parse_sql(query)
30 .map_err(|error| Error::parse(format!("invalid persisted view query: {error}")))?;
31 let [Statement::Select(select)] = statements.as_slice() else {
32 return Err(Error::invalid_argument(
33 "view definition must contain exactly one SELECT statement",
34 ));
35 };
36 Ok(bind_from_select(select))
37}
38
39#[cfg(test)]
40mod tests {
41 use super::*;
42
43 #[test]
44 fn scoped_binding_excludes_cte_aliases_and_normalizes_sources() {
45 assert_eq!(
46 bind_from_sql(
47 "WITH local_rows AS (SELECT * FROM source_rows) \
48 SELECT * FROM local_rows JOIN other_rows \
49 ON local_rows.id = other_rows.id",
50 )
51 .unwrap(),
52 vec!["other_rows", "source_rows"],
53 );
54 }
55
56 #[test]
57 fn persisted_binding_rejects_non_select_payloads() {
58 let error = bind_from_sql("DELETE FROM source_rows").unwrap_err();
59 assert!(error.to_string().contains("exactly one SELECT statement"));
60 }
61}