sea_query/query/with.rs
1use crate::{
2 ColumnRef, DynIden, Expr, IdenList, IntoIden, QueryBuilder, QueryStatementBuilder,
3 QueryStatementWriter, SelectExpr, SelectStatement, SqlWriter, SubQueryStatement, TableName,
4 TableRef, Values,
5};
6
7#[derive(Debug, Clone, PartialEq)]
8pub(crate) enum CteQuery {
9 SubQuery(Box<SubQueryStatement>),
10 Values(Vec<Values>),
11}
12
13impl Default for CteQuery {
14 fn default() -> Self {
15 Self::Values(vec![])
16 }
17}
18
19/// A table definition inside a WITH clause ([WithClause]).
20///
21/// A WITH clause can contain one or multiple common table expressions ([CommonTableExpression]).
22///
23/// These named queries can act as a "query local table" that are materialized during execution and
24/// then can be used by the query prefixed with the WITH clause.
25///
26/// A WITH clause can contain multiple of these [CommonTableExpression]. (Except in the case of
27/// recursive WITH query which can only contain one [CommonTableExpression]).
28///
29/// A [CommonTableExpression] is a name, column names and a query returning data for those columns.
30///
31/// Some databases (like sqlite) restrict the acceptable kinds of queries inside of the WITH clause
32/// common table expressions. These databases only allow [SelectStatement]s to form a common table
33/// expression.
34///
35/// Other databases like postgres allow modification queries (UPDATE, DELETE) inside of the WITH
36/// clause but they have to return a table. (They must have a RETURNING clause).
37///
38/// sea-query doesn't check this or restrict the kind of [CommonTableExpression] that you can create
39/// in rust. This means that you can put an UPDATE or DELETE queries into WITH clause and sea-query
40/// will succeed in generating that kind of sql query but the execution inside the database will
41/// fail because they are invalid.
42///
43/// It is your responsibility to ensure that the kind of WITH clause that you put together makes
44/// sense and valid for that database that you are using.
45///
46/// NOTE that for recursive WITH queries (in sql: "WITH RECURSIVE") you can only have a
47/// single [CommonTableExpression] inside of the WITH clause. That query must match certain
48/// requirements:
49/// * It is a query of UNION or UNION ALL of two queries.
50/// * The first part of the query (the left side of the UNION) must be executable first in itself.
51/// It must be non-recursive. (Cannot contain self reference)
52/// * The self reference must appear in the right hand side of the UNION.
53/// * The query can only have a single self-reference.
54/// * Recursive data-modifying statements are not supported, but you can use the results of a
55/// recursive SELECT query in a data-modifying statement. (like so: WITH RECURSIVE
56/// cte_name(a,b,c,d) AS (SELECT ... UNION SELECT ... FROM ... JOIN cte_name ON ... WHERE ...)
57/// DELETE FROM table WHERE table.a = cte_name.a)
58///
59/// It is mandatory to set the [Self::table_name] and the [Self::query].
60#[derive(Debug, Clone, Default, PartialEq)]
61pub struct CommonTableExpression {
62 pub(crate) table_name: Option<DynIden>,
63 pub(crate) cols: Vec<DynIden>,
64 pub(crate) query: CteQuery,
65 pub(crate) materialized: Option<bool>,
66}
67
68impl CommonTableExpression {
69 /// Construct a new [`CommonTableExpression`]
70 pub fn new() -> CommonTableExpression {
71 Self::default()
72 }
73
74 /// Sets the CTE table name of the query.
75 pub fn table_name<T>(&mut self, table_name: T) -> &mut Self
76 where
77 T: IntoIden,
78 {
79 self.table_name = Some(table_name.into_iden());
80 self
81 }
82
83 /// Sets the CTE VALUES clause.
84 ///
85 /// It overwrites the query if it is already set for the CTE.
86 pub fn values(&mut self, values: Vec<Values>) -> &mut Self {
87 self.query = CteQuery::Values(values);
88 self
89 }
90
91 /// Adds a named column to the CTE table definition.
92 pub fn column<C>(&mut self, col: C) -> &mut Self
93 where
94 C: IntoIden,
95 {
96 self.cols.push(col.into_iden());
97 self
98 }
99
100 /// Adds a named columns to the CTE table definition.
101 pub fn columns<T, I>(&mut self, cols: I) -> &mut Self
102 where
103 T: IntoIden,
104 I: IntoIterator<Item = T>,
105 {
106 self.cols
107 .extend(cols.into_iter().map(|col| col.into_iden()));
108 self
109 }
110
111 /// Some databases allow you to put "MATERIALIZED" or "NOT MATERIALIZED" in the CTE definition.
112 /// This will affect how during the execution of [WithQuery] the CTE in the [WithClause] will be
113 /// executed. If the database doesn't support this syntax this option specified here will be
114 /// ignored and not appear in the generated sql.
115 pub fn materialized(&mut self, materialized: bool) -> &mut Self {
116 self.materialized = Some(materialized);
117 self
118 }
119
120 /// Set the query generating the CTE content. The query's result must match the defined
121 /// columns.
122 ///
123 /// It overwrites the values if it is already set for the CTE.
124 pub fn query<Q>(&mut self, query: Q) -> &mut Self
125 where
126 Q: Into<SubQueryStatement>,
127 {
128 self.query = CteQuery::SubQuery(Box::new(query.into()));
129 self
130 }
131
132 /// Create a CTE from a [SelectStatement] if the selections are named columns then this will
133 /// return a [CommonTableExpression] that has the column names set. The [Self::table_name] is
134 /// set if the [SelectStatement] from clause contains at least one table.
135 pub fn from_select(select: SelectStatement) -> Self {
136 let mut cte = Self::default();
137 cte.try_set_cols_from_selects(&select.selects);
138 if let Some(from) = select.from.first() {
139 match from {
140 TableRef::Table(_, Some(alias)) => cte.set_table_name_from_select(alias),
141 TableRef::Table(TableName(_, tbl), None) => cte.set_table_name_from_select(tbl),
142 _ => {}
143 }
144 }
145 cte.query = CteQuery::SubQuery(Box::new(select.into()));
146 cte
147 }
148
149 fn set_table_name_from_select(&mut self, iden: &DynIden) {
150 self.table_name = Some(format!("cte_{iden}").into_iden())
151 }
152
153 /// Set up the columns of the CTE to match the given [SelectStatement] selected columns.
154 /// This will fail if the select contains non named columns like expressions of wildcards.
155 ///
156 /// Returns true if the column setup from the select query was successful. If the returned
157 /// value is false the columns are untouched.
158 pub fn try_set_cols_from_select(&mut self, select: &SelectStatement) -> bool {
159 self.try_set_cols_from_selects(&select.selects)
160 }
161
162 fn try_set_cols_from_selects(&mut self, selects: &[SelectExpr]) -> bool {
163 let vec: Option<Vec<DynIden>> = selects
164 .iter()
165 .map(|select| {
166 if let Some(ident) = &select.alias {
167 Some(ident.clone())
168 } else {
169 match &select.expr {
170 Expr::Column(ColumnRef::Column(column_name)) => {
171 // We could depend on `itertools` instead of joining manually.
172 let mut joined_column_name = String::new();
173 for part in column_name.clone().into_iter() {
174 joined_column_name.push_str(&part.0);
175 joined_column_name.push('_');
176 }
177 // Remove the trailing underscore after the column name.
178 joined_column_name.pop();
179 Some(joined_column_name.into_iden())
180 }
181 _ => None,
182 }
183 }
184 })
185 .collect();
186
187 if let Some(c) = vec {
188 self.cols = c;
189 return true;
190 }
191
192 false
193 }
194}
195
196/// For recursive [WithQuery] [WithClause]s the traversing order can be specified in some databases
197/// that support this functionality.
198#[derive(Debug, Clone, PartialEq)]
199#[non_exhaustive]
200pub enum SearchOrder {
201 /// Breadth first traversal during the execution of the recursive query.
202 BREADTH,
203 /// Depth first traversal during the execution of the recursive query.
204 DEPTH,
205}
206
207/// For recursive [WithQuery] [WithClause]s the traversing order can be specified in some databases
208/// that support this functionality.
209///
210/// The clause contains the type of traversal: [SearchOrder] and the expression that is used to
211/// construct the current path.
212///
213/// A query can have both SEARCH and CYCLE clauses.
214///
215/// Setting [Self::order] and [Self::expr] is mandatory. The [SelectExpr] used must specify an alias
216/// which will be the name that you can use to order the result of the [CommonTableExpression].
217#[derive(Debug, Clone, Default, PartialEq)]
218pub struct Search {
219 pub(crate) order: Option<SearchOrder>,
220 pub(crate) expr: Option<SelectExpr>,
221}
222
223impl Search {
224 /// Create a complete [Search] specification from the [SearchOrder] and a [SelectExpr]. The
225 /// given [SelectExpr] must have an alias specified.
226 pub fn new_from_order_and_expr<EXPR>(order: SearchOrder, expr: EXPR) -> Self
227 where
228 EXPR: Into<SelectExpr>,
229 {
230 let expr = expr.into();
231 expr.alias.as_ref().unwrap();
232 Self {
233 order: Some(order),
234 expr: Some(expr),
235 }
236 }
237
238 /// Constructs a new empty [Search].
239 pub fn new() -> Self {
240 Self::default()
241 }
242
243 /// The traversal order to be used.
244 pub fn order(&mut self, order: SearchOrder) -> &mut Self {
245 self.order = Some(order);
246 self
247 }
248
249 /// The given [SelectExpr] must have an alias specified.
250 ///
251 /// The actual expression will be the one used to track the path in the graph.
252 ///
253 /// The alias of the given [SelectExpr] will be the name of the order column generated by this
254 /// clause.
255 pub fn expr<EXPR>(&mut self, expr: EXPR) -> &mut Self
256 where
257 EXPR: Into<SelectExpr>,
258 {
259 let expr = expr.into();
260 expr.alias.as_ref().unwrap();
261 self.expr = Some(expr);
262 self
263 }
264}
265
266/// For recursive [WithQuery] [WithClauses](WithClause) the CYCLE sql clause can be specified to avoid creating
267/// an infinite traversals that loops on graph cycles indefinitely.
268///
269/// You specify an expression that identifies a node in the graph, which is used during the query execution iteration, to determine newly appended values are distinct new nodes or are already visited, and therefore they should be added into the result again.
270///
271/// A query can have both SEARCH and CYCLE clauses.
272///
273/// Setting [Self::set], [Self::expr] and [Self::using] is mandatory.
274#[derive(Debug, Clone, Default, PartialEq)]
275pub struct Cycle {
276 pub(crate) expr: Option<Expr>,
277 pub(crate) set_as: Option<DynIden>,
278 pub(crate) using: Option<DynIden>,
279}
280
281impl Cycle {
282 /// Create a complete [Search] specification from the [SearchOrder] and a [SelectExpr]. The
283 /// given [SelectExpr] must have an alias specified.
284 pub fn new_from_expr_set_using<EXPR, ID1, ID2>(expr: EXPR, set: ID1, using: ID2) -> Self
285 where
286 EXPR: Into<Expr>,
287 ID1: IntoIden,
288 ID2: IntoIden,
289 {
290 Self {
291 expr: Some(expr.into()),
292 set_as: Some(set.into_iden()),
293 using: Some(using.into_iden()),
294 }
295 }
296
297 /// Constructs a new empty [Cycle].
298 pub fn new() -> Self {
299 Self::default()
300 }
301
302 /// The expression identifying nodes.
303 pub fn expr<EXPR>(&mut self, expr: EXPR) -> &mut Self
304 where
305 EXPR: Into<Expr>,
306 {
307 self.expr = Some(expr.into());
308 self
309 }
310
311 /// The name of the boolean column containing whether we have completed a cycle or not yet
312 /// generated by this clause.
313 pub fn set<ID>(&mut self, set: ID) -> &mut Self
314 where
315 ID: IntoIden,
316 {
317 self.set_as = Some(set.into_iden());
318 self
319 }
320
321 /// The name of the array typed column that contains the node ids (generated using the
322 /// [Self::expr]) that specify the current nodes path that will be generated by this clause.
323 pub fn using<ID>(&mut self, using: ID) -> &mut Self
324 where
325 ID: IntoIden,
326 {
327 self.using = Some(using.into_iden());
328 self
329 }
330}
331
332/// A WITH clause can contain one or multiple common table expressions ([CommonTableExpression]).
333///
334/// You can use this to generate [WithQuery] by calling [WithClause::query].
335///
336/// These named queries can act as a "query local table" that are materialized during execution and
337/// then can be used by the query prefixed with the WITH clause.
338///
339/// A WITH clause can contain multiple of these [CommonTableExpression]. (Except in the case of
340/// recursive WITH query which can only contain one [CommonTableExpression]).
341///
342/// A [CommonTableExpression] is a name, column names and a query returning data for those columns.
343///
344/// Some databases (like sqlite) restrict the acceptable kinds of queries inside of the WITH clause
345/// common table expressions. These databases only allow [SelectStatement]s to form a common table
346/// expression.
347///
348/// Other databases like postgres allow modification queries (UPDATE, DELETE) inside of the WITH
349/// clause but they have to return a table. (They must have a RETURNING clause).
350///
351/// sea-query doesn't check this or restrict the kind of [CommonTableExpression] that you can create
352/// in rust. This means that you can put an UPDATE or DELETE queries into WITH clause and sea-query
353/// will succeed in generating that kind of sql query but the execution inside the database will
354/// fail because they are invalid.
355///
356/// It is your responsibility to ensure that the kind of WITH clause that you put together makes
357/// sense and valid for that database that you are using.
358///
359/// NOTE that for recursive WITH queries (in sql: "WITH RECURSIVE") you can only have a
360/// single [CommonTableExpression] inside of the WITH clause. That query must match certain
361/// requirements:
362/// * It is a query of UNION or UNION ALL of two queries.
363/// * The first part of the query (the left side of the UNION) must be executable first in itself.
364/// It must be non-recursive. (Cannot contain self reference)
365/// * The self reference must appear in the right hand side of the UNION.
366/// * The query can only have a single self-reference.
367/// * Recursive data-modifying statements are not supported, but you can use the results of a
368/// recursive SELECT query in a data-modifying statement. (like so: WITH RECURSIVE
369/// cte_name(a,b,c,d) AS (SELECT ... UNION SELECT ... FROM ... JOIN cte_name ON ... WHERE ...)
370/// DELETE FROM table WHERE table.a = cte_name.a)
371///
372/// It is mandatory to set the [Self::cte]. With queries must have at least one CTE.
373/// Recursive with query generation will panic if you specify more than one CTE.
374///
375/// # Examples
376///
377/// ```
378/// use sea_query::{*, IntoCondition, IntoIden, tests_cfg::*};
379///
380/// let base_query = SelectStatement::new()
381/// .column("id")
382/// .expr(1i32)
383/// .column("next")
384/// .column("value")
385/// .from("table")
386/// .to_owned();
387///
388/// let cte_referencing = SelectStatement::new()
389/// .column("id")
390/// .expr(Expr::col("depth").add(1i32))
391/// .column("next")
392/// .column("value")
393/// .from("table")
394/// .join(
395/// JoinType::InnerJoin,
396/// "cte_traversal",
397/// Expr::col(("cte_traversal", "next")).equals(("table", "id"))
398/// )
399/// .to_owned();
400///
401/// let common_table_expression = CommonTableExpression::new()
402/// .query(
403/// base_query.clone().union(UnionType::All, cte_referencing).to_owned()
404/// )
405/// .column("id")
406/// .column("depth")
407/// .column("next")
408/// .column("value")
409/// .table_name("cte_traversal")
410/// .to_owned();
411///
412/// let select = SelectStatement::new()
413/// .column(Asterisk)
414/// .from("cte_traversal")
415/// .to_owned();
416///
417/// let with_clause = WithClause::new()
418/// .recursive(true)
419/// .cte(common_table_expression)
420/// .cycle(Cycle::new_from_expr_set_using(Expr::Column("id".into_column_ref()), "looped", "traversal_path"))
421/// .to_owned();
422///
423/// let query = select.with(with_clause).to_owned();
424///
425/// assert_eq!(
426/// query.to_string(MysqlQueryBuilder),
427/// r#"WITH RECURSIVE `cte_traversal` (`id`, `depth`, `next`, `value`) AS (SELECT `id`, 1, `next`, `value` FROM `table` UNION ALL (SELECT `id`, `depth` + 1, `next`, `value` FROM `table` INNER JOIN `cte_traversal` ON `cte_traversal`.`next` = `table`.`id`)) SELECT * FROM `cte_traversal`"#
428/// );
429/// assert_eq!(
430/// query.to_string(PostgresQueryBuilder),
431/// r#"WITH RECURSIVE "cte_traversal" ("id", "depth", "next", "value") AS (SELECT "id", 1, "next", "value" FROM "table" UNION ALL (SELECT "id", "depth" + 1, "next", "value" FROM "table" INNER JOIN "cte_traversal" ON "cte_traversal"."next" = "table"."id")) CYCLE "id" SET "looped" USING "traversal_path" SELECT * FROM "cte_traversal""#
432/// );
433/// assert_eq!(
434/// query.to_string(SqliteQueryBuilder),
435/// r#"WITH RECURSIVE "cte_traversal" ("id", "depth", "next", "value") AS (SELECT "id", 1, "next", "value" FROM "table" UNION ALL SELECT "id", "depth" + 1, "next", "value" FROM "table" INNER JOIN "cte_traversal" ON "cte_traversal"."next" = "table"."id") SELECT * FROM "cte_traversal""#
436/// );
437/// ```
438#[derive(Debug, Clone, Default, PartialEq)]
439pub struct WithClause {
440 pub(crate) recursive: bool,
441 pub(crate) search: Option<Search>,
442 pub(crate) cycle: Option<Cycle>,
443 pub(crate) cte_expressions: Vec<CommonTableExpression>,
444}
445
446impl WithClause {
447 /// Constructs a new [WithClause].
448 pub fn new() -> Self {
449 Self::default()
450 }
451
452 /// Sets whether this clause is a recursive with clause of not.
453 /// If set to true it will generate a 'WITH RECURSIVE' query.
454 ///
455 /// You can only specify a single [CommonTableExpression] containing a union query
456 /// if this is set to true.
457 pub fn recursive(&mut self, recursive: bool) -> &mut Self {
458 self.recursive = recursive;
459 self
460 }
461
462 /// For recursive WITH queries you can specify the [Search] clause.
463 ///
464 /// This setting is not meaningful if the query is not recursive.
465 ///
466 /// Some databases don't support this clause. In that case this option will be silently ignored.
467 pub fn search(&mut self, search: Search) -> &mut Self {
468 self.search = Some(search);
469 self
470 }
471
472 /// For recursive WITH queries you can specify the [Cycle] clause.
473 ///
474 /// This setting is not meaningful if the query is not recursive.
475 ///
476 /// Some databases don't support this clause. In that case this option will be silently ignored.
477 pub fn cycle(&mut self, cycle: Cycle) -> &mut Self {
478 self.cycle = Some(cycle);
479 self
480 }
481
482 /// Add a [CommonTableExpression] to this with clause.
483 pub fn cte(&mut self, cte: CommonTableExpression) -> &mut Self {
484 self.cte_expressions.push(cte);
485 self
486 }
487
488 /// You can turn this into a [WithQuery] using this function. The resulting WITH query will
489 /// execute the argument query with this WITH clause.
490 pub fn query<T>(self, query: T) -> WithQuery
491 where
492 T: Into<SubQueryStatement>,
493 {
494 WithQuery::new().with_clause(self).query(query).to_owned()
495 }
496}
497
498impl From<CommonTableExpression> for WithClause {
499 fn from(cte: CommonTableExpression) -> WithClause {
500 WithClause::new().cte(cte).to_owned()
501 }
502}
503
504/// A WITH query. A simple SQL query that has a WITH clause ([WithClause]).
505///
506/// The [WithClause] can contain one or multiple common table expressions ([CommonTableExpression]).
507///
508/// These named queries can act as a "query local table" that are materialized during execution and
509/// then can be used by the query prefixed with the WITH clause.
510///
511/// A WITH clause can contain multiple of these [CommonTableExpression]. (Except in the case of
512/// recursive WITH query which can only contain one [CommonTableExpression]).
513///
514/// A [CommonTableExpression] is a name, column names and a query returning data for those columns.
515///
516/// Some databases (like sqlite) restrict the acceptable kinds of queries inside of the WITH clause
517/// common table expressions. These databases only allow [SelectStatement]s to form a common table
518/// expression.
519///
520/// Other databases like postgres allow modification queries (UPDATE, DELETE) inside of the WITH
521/// clause but they have to return a table. (They must have a RETURNING clause).
522///
523/// sea-query doesn't check this or restrict the kind of [CommonTableExpression] that you can create
524/// in rust. This means that you can put an UPDATE or DELETE queries into WITH clause and sea-query
525/// will succeed in generating that kind of sql query but the execution inside the database will
526/// fail because they are invalid.
527///
528/// It is your responsibility to ensure that the kind of WITH clause that you put together makes
529/// sense and valid for that database that you are using.
530///
531/// NOTE that for recursive WITH queries (in sql: "WITH RECURSIVE") you can only have a
532/// single [CommonTableExpression] inside of the WITH clause. That query must match certain
533/// requirements:
534/// * It is a query of UNION or UNION ALL of two queries.
535/// * The first part of the query (the left side of the UNION) must be executable first in itself.
536/// It must be non-recursive. (Cannot contain self reference)
537/// * The self reference must appear in the right hand side of the UNION.
538/// * The query can only have a single self-reference.
539/// * Recursive data-modifying statements are not supported, but you can use the results of a
540/// recursive SELECT query in a data-modifying statement. (like so: WITH RECURSIVE
541/// cte_name(a,b,c,d) AS (SELECT ... UNION SELECT ... FROM ... JOIN cte_name ON ... WHERE ...)
542/// DELETE FROM table WHERE table.a = cte_name.a)
543///
544/// It is mandatory to set the [Self::cte] and the [Self::query].
545#[derive(Debug, Clone, Default, PartialEq)]
546pub struct WithQuery {
547 pub(crate) with_clause: WithClause,
548 pub(crate) query: Option<Box<SubQueryStatement>>,
549}
550
551impl WithQuery {
552 /// Constructs a new empty [WithQuery].
553 pub fn new() -> Self {
554 Self::default()
555 }
556
557 /// Set the whole [WithClause].
558 pub fn with_clause(&mut self, with_clause: WithClause) -> &mut Self {
559 self.with_clause = with_clause;
560 self
561 }
562
563 /// Set the [WithClause::recursive]. See that method for more information.
564 pub fn recursive(&mut self, recursive: bool) -> &mut Self {
565 self.with_clause.recursive = recursive;
566 self
567 }
568
569 /// Add the [WithClause::search]. See that method for more information.
570 pub fn search(&mut self, search: Search) -> &mut Self {
571 self.with_clause.search = Some(search);
572 self
573 }
574
575 /// Set the [WithClause::cycle]. See that method for more information.
576 pub fn cycle(&mut self, cycle: Cycle) -> &mut Self {
577 self.with_clause.cycle = Some(cycle);
578 self
579 }
580
581 /// Add a [CommonTableExpression] to the with clause. See [WithClause::cte].
582 pub fn cte(&mut self, cte: CommonTableExpression) -> &mut Self {
583 self.with_clause.cte_expressions.push(cte);
584 self
585 }
586
587 /// Set the query that you execute with the [WithClause].
588 pub fn query<T>(&mut self, query: T) -> &mut Self
589 where
590 T: Into<SubQueryStatement>,
591 {
592 self.query = Some(Box::new(query.into()));
593 self
594 }
595}
596
597impl QueryStatementBuilder for WithQuery {
598 fn build_collect_any_into(&self, query_builder: &impl QueryBuilder, sql: &mut impl SqlWriter) {
599 query_builder.prepare_with_query(self, sql);
600 }
601}
602
603impl From<WithQuery> for SubQueryStatement {
604 fn from(s: WithQuery) -> Self {
605 Self::WithStatement(s)
606 }
607}
608
609impl QueryStatementWriter for WithQuery {
610 fn build_collect_into<T: QueryBuilder>(&self, query_builder: T, sql: &mut impl SqlWriter) {
611 query_builder.prepare_with_query(self, sql);
612 }
613}
614
615impl WithQuery {
616 pub fn to_string<T: QueryBuilder>(&self, query_builder: T) -> String {
617 <Self as QueryStatementWriter>::to_string(self, query_builder)
618 }
619
620 pub fn build<T: QueryBuilder>(&self, query_builder: T) -> (String, Values) {
621 <Self as QueryStatementWriter>::build(self, query_builder)
622 }
623
624 pub fn build_collect<T: QueryBuilder>(
625 &self,
626 query_builder: T,
627 sql: &mut impl SqlWriter,
628 ) -> String {
629 <Self as QueryStatementWriter>::build_collect(self, query_builder, sql)
630 }
631
632 pub fn build_collect_into<T: QueryBuilder>(&self, query_builder: T, sql: &mut impl SqlWriter) {
633 <Self as QueryStatementWriter>::build_collect_into(self, query_builder, sql);
634 }
635}