@@ -24,3 +24,76 @@ pub fn convert_hex_literals_postgres(sql: &str) -> String {
2424 . replace_all ( sql, r"'\x$1'" )
2525 . into_owned ( )
2626}
27+
28+ /// Merge generated CTE definitions into a user query.
29+ ///
30+ /// If query already starts with a WITH clause, generated CTEs are prepended
31+ /// to the existing CTE list. Otherwise, a new WITH clause is added.
32+ pub fn merge_ctes_into_query ( sql : & str , generated_ctes : & [ String ] ) -> String {
33+ if generated_ctes. is_empty ( ) {
34+ return sql. to_string ( ) ;
35+ }
36+
37+ let ctes = generated_ctes. join ( ", " ) ;
38+ let trimmed = sql. trim_start ( ) ;
39+ let leading_ws = & sql[ ..sql. len ( ) - trimmed. len ( ) ] ;
40+
41+ if starts_with_keyword ( trimmed, "WITH RECURSIVE" ) {
42+ let rest = trimmed[ "WITH RECURSIVE" . len ( ) ..] . trim_start ( ) ;
43+ return format ! ( "{leading_ws}WITH RECURSIVE {ctes}, {rest}" ) ;
44+ }
45+
46+ if starts_with_keyword ( trimmed, "WITH" ) {
47+ let rest = trimmed[ "WITH" . len ( ) ..] . trim_start ( ) ;
48+ return format ! ( "{leading_ws}WITH {ctes}, {rest}" ) ;
49+ }
50+
51+ format ! ( "{leading_ws}WITH {ctes} {trimmed}" )
52+ }
53+
54+ fn starts_with_keyword ( input : & str , keyword : & str ) -> bool {
55+ input. len ( ) >= keyword. len ( )
56+ && input[ ..keyword. len ( ) ] . eq_ignore_ascii_case ( keyword)
57+ && input[ keyword. len ( ) ..]
58+ . chars ( )
59+ . next ( )
60+ . is_none_or ( |c| c. is_ascii_whitespace ( ) )
61+ }
62+
63+ #[ cfg( test) ]
64+ mod tests {
65+ use super :: merge_ctes_into_query;
66+
67+ #[ test]
68+ fn test_merge_ctes_into_plain_select ( ) {
69+ let merged = merge_ctes_into_query (
70+ "SELECT n FROM numbers" ,
71+ & [ "Transfer AS (SELECT 1)" . to_string ( ) ] ,
72+ ) ;
73+ assert_eq ! ( merged, "WITH Transfer AS (SELECT 1) SELECT n FROM numbers" ) ;
74+ }
75+
76+ #[ test]
77+ fn test_merge_ctes_into_existing_with ( ) {
78+ let merged = merge_ctes_into_query (
79+ "WITH numbers AS (SELECT 1 AS n) SELECT n FROM numbers" ,
80+ & [ "Transfer AS (SELECT 1)" . to_string ( ) ] ,
81+ ) ;
82+ assert_eq ! (
83+ merged,
84+ "WITH Transfer AS (SELECT 1), numbers AS (SELECT 1 AS n) SELECT n FROM numbers"
85+ ) ;
86+ }
87+
88+ #[ test]
89+ fn test_merge_ctes_into_existing_with_recursive ( ) {
90+ let merged = merge_ctes_into_query (
91+ "WITH RECURSIVE r AS (SELECT 1) SELECT * FROM r" ,
92+ & [ "Transfer AS (SELECT 1)" . to_string ( ) ] ,
93+ ) ;
94+ assert_eq ! (
95+ merged,
96+ "WITH RECURSIVE Transfer AS (SELECT 1), r AS (SELECT 1) SELECT * FROM r"
97+ ) ;
98+ }
99+ }
0 commit comments