Skip to main content

diesel/query_builder/select_statement/
boxed.rs

1use std::marker::PhantomData;
2
3use crate::backend::{sql_dialect, DieselReserveSpecialization};
4use crate::dsl::AsExprOf;
5use crate::expression::subselect::ValidSubselect;
6use crate::expression::*;
7use crate::insertable::Insertable;
8use crate::query_builder::combination_clause::*;
9use crate::query_builder::distinct_clause::DistinctClause;
10use crate::query_builder::group_by_clause::ValidGroupByClause;
11use crate::query_builder::having_clause::HavingClause;
12use crate::query_builder::insert_statement::InsertFromSelect;
13use crate::query_builder::limit_clause::LimitClause;
14use crate::query_builder::limit_offset_clause::BoxedLimitOffsetClause;
15use crate::query_builder::offset_clause::OffsetClause;
16use crate::query_builder::order_clause::OrderClause;
17use crate::query_builder::where_clause::{BoxedWhereClause, WhereAnd, WhereOr};
18use crate::query_builder::*;
19use crate::query_dsl::methods::*;
20use crate::query_dsl::*;
21use crate::query_source::joins::*;
22use crate::query_source::{QuerySource, Table};
23use crate::sql_types::{BigInt, BoolOrNullableBool, IntoNullable};
24
25// This is used by the table macro internally
26/// This type represents a boxed select query
27///
28/// Using this type directly is only meaningful for custom backends
29/// that need to provide a custom [`QueryFragment`] implementation
30#[allow(missing_debug_implementations)]
31#[doc = " This type represents a boxed select query"]
#[doc = ""]
#[doc = " Using this type directly is only meaningful for custom backends"]
#[doc = " that need to provide a custom [`QueryFragment`] implementation"]
#[allow(missing_debug_implementations)]
#[non_exhaustive]
pub struct BoxedSelectStatement<'a, ST, QS, DB, GB = ()> {
    #[doc = " The select clause of the query"]
    pub select: Box<dyn QueryFragment<DB> + Send + 'a>,
    #[doc = " The from clause of the query"]
    pub from: QS,
    #[doc = " The distinct clause of the query"]
    pub distinct: Box<dyn QueryFragment<DB> + Send + 'a>,
    #[doc = " The where clause of the query"]
    pub where_clause: BoxedWhereClause<'a, DB>,
    #[doc = " The order clause of the query"]
    pub order: Option<Box<dyn QueryFragment<DB> + Send + 'a>>,
    #[doc = " The combined limit/offset clause of the query"]
    pub limit_offset: BoxedLimitOffsetClause<'a, DB>,
    #[doc = " The group by clause of the query"]
    pub group_by: Box<dyn QueryFragment<DB> + Send + 'a>,
    #[doc = " The having clause of the query"]
    pub having: Box<dyn QueryFragment<DB> + Send + 'a>,
    _marker: PhantomData<(ST, GB)>,
}#[diesel_derives::__diesel_public_if(
32    feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes",
33    public_fields(
34        select,
35        from,
36        distinct,
37        where_clause,
38        order,
39        limit_offset,
40        group_by,
41        having
42    )
43)]
44pub struct BoxedSelectStatement<'a, ST, QS, DB, GB = ()> {
45    /// The select clause of the query
46    select: Box<dyn QueryFragment<DB> + Send + 'a>,
47    /// The from clause of the query
48    from: QS,
49    /// The distinct clause of the query
50    distinct: Box<dyn QueryFragment<DB> + Send + 'a>,
51    /// The where clause of the query
52    where_clause: BoxedWhereClause<'a, DB>,
53    /// The order clause of the query
54    order: Option<Box<dyn QueryFragment<DB> + Send + 'a>>,
55    /// The combined limit/offset clause of the query
56    limit_offset: BoxedLimitOffsetClause<'a, DB>,
57    /// The group by clause of the query
58    group_by: Box<dyn QueryFragment<DB> + Send + 'a>,
59    /// The having clause of the query
60    having: Box<dyn QueryFragment<DB> + Send + 'a>,
61    _marker: PhantomData<(ST, GB)>,
62}
63
64impl<'a, ST, QS: QuerySource, DB, GB> BoxedSelectStatement<'a, ST, FromClause<QS>, DB, GB> {
65    #[allow(clippy::too_many_arguments)]
66    pub(crate) fn new<S, G>(
67        select: S,
68        from: FromClause<QS>,
69        distinct: Box<dyn QueryFragment<DB> + Send + 'a>,
70        where_clause: BoxedWhereClause<'a, DB>,
71        order: Option<Box<dyn QueryFragment<DB> + Send + 'a>>,
72        limit_offset: BoxedLimitOffsetClause<'a, DB>,
73        group_by: G,
74        having: Box<dyn QueryFragment<DB> + Send + 'a>,
75    ) -> Self
76    where
77        DB: Backend,
78        G: ValidGroupByClause<Expressions = GB> + QueryFragment<DB> + Send + 'a,
79        S: SelectClauseExpression<FromClause<QS>, SelectClauseSqlType = ST>
80            + QueryFragment<DB>
81            + Send
82            + 'a,
83        S::Selection: ValidGrouping<GB>,
84    {
85        BoxedSelectStatement {
86            select: Box::new(select),
87            from,
88            distinct,
89            where_clause,
90            order,
91            limit_offset,
92            group_by: Box::new(group_by),
93            having,
94            _marker: PhantomData,
95        }
96    }
97}
98
99impl<'a, ST, DB, GB> BoxedSelectStatement<'a, ST, NoFromClause, DB, GB> {
100    #[allow(clippy::too_many_arguments)]
101    pub(crate) fn new_no_from_clause<S, G>(
102        select: S,
103        from: NoFromClause,
104        distinct: Box<dyn QueryFragment<DB> + Send + 'a>,
105        where_clause: BoxedWhereClause<'a, DB>,
106        order: Option<Box<dyn QueryFragment<DB> + Send + 'a>>,
107        limit_offset: BoxedLimitOffsetClause<'a, DB>,
108        group_by: G,
109        having: Box<dyn QueryFragment<DB> + Send + 'a>,
110    ) -> Self
111    where
112        DB: Backend,
113        G: ValidGroupByClause<Expressions = GB> + QueryFragment<DB> + Send + 'a,
114        S: SelectClauseExpression<NoFromClause, SelectClauseSqlType = ST>
115            + QueryFragment<DB>
116            + Send
117            + 'a,
118        S::Selection: ValidGrouping<GB>,
119    {
120        BoxedSelectStatement {
121            select: Box::new(select),
122            from,
123            distinct,
124            where_clause,
125            order,
126            limit_offset,
127            group_by: Box::new(group_by),
128            having,
129            _marker: PhantomData,
130        }
131    }
132}
133
134// that's a trait to control who can access these methods
135#[doc(hidden)] // exported via internal::derives::multiconnection
136pub trait BoxedQueryHelper<'a, QS, DB> {
137    fn build_query<'b, 'c>(
138        &'b self,
139        out: AstPass<'_, 'c, DB>,
140        where_clause_handler: impl Fn(
141            &'b BoxedWhereClause<'a, DB>,
142            AstPass<'_, 'c, DB>,
143        ) -> QueryResult<()>,
144    ) -> QueryResult<()>
145    where
146        DB: Backend + 'b,
147        QS: QueryFragment<DB>,
148        BoxedLimitOffsetClause<'a, DB>: QueryFragment<DB>,
149        'a: 'b,
150        'b: 'c;
151}
152
153impl<'a, ST, QS, DB, GB> BoxedQueryHelper<'a, QS, DB> for BoxedSelectStatement<'a, ST, QS, DB, GB> {
154    fn build_query<'b, 'c>(
155        &'b self,
156        mut out: AstPass<'_, 'c, DB>,
157        where_clause_handler: impl Fn(
158            &'b BoxedWhereClause<'a, DB>,
159            AstPass<'_, 'c, DB>,
160        ) -> QueryResult<()>,
161    ) -> QueryResult<()>
162    where
163        DB: Backend,
164        QS: QueryFragment<DB>,
165        BoxedLimitOffsetClause<'a, DB>: QueryFragment<DB>,
166        'b: 'c,
167    {
168        out.push_sql("SELECT ");
169        self.distinct.walk_ast(out.reborrow())?;
170        self.select.walk_ast(out.reborrow())?;
171        self.from.walk_ast(out.reborrow())?;
172        where_clause_handler(&self.where_clause, out.reborrow())?;
173        self.group_by.walk_ast(out.reborrow())?;
174        self.having.walk_ast(out.reborrow())?;
175
176        if let Some(ref order) = self.order {
177            out.push_sql(" ORDER BY ");
178            order.walk_ast(out.reborrow())?;
179        }
180        self.limit_offset.walk_ast(out.reborrow())?;
181        Ok(())
182    }
183}
184
185impl<ST, QS, DB, GB> Query for BoxedSelectStatement<'_, ST, QS, DB, GB>
186where
187    DB: Backend,
188{
189    type SqlType = ST;
190}
191
192impl<ST, QS, DB, GB> SelectQuery for BoxedSelectStatement<'_, ST, QS, DB, GB>
193where
194    DB: Backend,
195{
196    type SqlType = ST;
197}
198
199impl<ST, QS, QS2, DB, GB> ValidSubselect<QS2> for BoxedSelectStatement<'_, ST, QS, DB, GB> where
200    Self: Query<SqlType = ST>
201{
202}
203
204impl<ST, QS, DB, GB> QueryFragment<DB> for BoxedSelectStatement<'_, ST, QS, DB, GB>
205where
206    DB: Backend,
207    Self: QueryFragment<DB, DB::SelectStatementSyntax>,
208{
209    fn walk_ast<'b>(&'b self, pass: AstPass<'_, 'b, DB>) -> QueryResult<()> {
210        <Self as QueryFragment<DB, DB::SelectStatementSyntax>>::walk_ast(self, pass)
211    }
212}
213
214impl<'a, ST, QS, DB, GB>
215    QueryFragment<DB, sql_dialect::select_statement_syntax::AnsiSqlSelectStatement>
216    for BoxedSelectStatement<'a, ST, QS, DB, GB>
217where
218    DB: Backend<
219            SelectStatementSyntax = sql_dialect::select_statement_syntax::AnsiSqlSelectStatement,
220        > + DieselReserveSpecialization,
221    QS: QueryFragment<DB>,
222    BoxedLimitOffsetClause<'a, DB>: QueryFragment<DB>,
223{
224    fn walk_ast<'b>(&'b self, out: AstPass<'_, 'b, DB>) -> QueryResult<()> {
225        self.build_query(out, |where_clause, out| where_clause.walk_ast(out))
226    }
227}
228
229impl<ST, QS, DB, GB> QueryId for BoxedSelectStatement<'_, ST, QS, DB, GB> {
230    type QueryId = ();
231
232    const HAS_STATIC_QUERY_ID: bool = false;
233}
234
235impl<'a, ST, QS, DB, Rhs, Kind, On, GB> InternalJoinDsl<Rhs, Kind, On>
236    for BoxedSelectStatement<'a, ST, FromClause<QS>, DB, GB>
237where
238    QS: QuerySource,
239    Rhs: QuerySource,
240    JoinOn<Join<QS, Rhs, Kind>, On>: QuerySource,
241    BoxedSelectStatement<'a, ST, FromClause<JoinOn<Join<QS, Rhs, Kind>, On>>, DB, GB>: AsQuery,
242{
243    type Output = BoxedSelectStatement<'a, ST, FromClause<JoinOn<Join<QS, Rhs, Kind>, On>>, DB, GB>;
244
245    fn join(self, rhs: Rhs, kind: Kind, on: On) -> Self::Output {
246        BoxedSelectStatement {
247            select: self.select,
248            from: FromClause::new(Join::new(self.from.source, rhs, kind).on(on)),
249            distinct: self.distinct,
250            where_clause: self.where_clause,
251            order: self.order,
252            limit_offset: self.limit_offset,
253            group_by: self.group_by,
254            having: self.having,
255            _marker: PhantomData,
256        }
257    }
258}
259
260impl<ST, QS, DB, GB> DistinctDsl for BoxedSelectStatement<'_, ST, QS, DB, GB>
261where
262    DB: Backend,
263    DistinctClause: QueryFragment<DB>,
264{
265    type Output = Self;
266
267    fn distinct(mut self) -> Self::Output {
268        self.distinct = Box::new(DistinctClause);
269        self
270    }
271}
272
273impl<'a, ST, QS, DB, Selection, GB> SelectDsl<Selection>
274    for BoxedSelectStatement<'a, ST, FromClause<QS>, DB, GB>
275where
276    DB: Backend,
277    QS: QuerySource,
278    Selection: SelectableExpression<QS> + QueryFragment<DB> + ValidGrouping<GB> + Send + 'a,
279{
280    type Output = BoxedSelectStatement<'a, Selection::SqlType, FromClause<QS>, DB, GB>;
281
282    fn select(self, selection: Selection) -> Self::Output {
283        BoxedSelectStatement {
284            select: Box::new(selection),
285            from: self.from,
286            distinct: self.distinct,
287            where_clause: self.where_clause,
288            order: self.order,
289            limit_offset: self.limit_offset,
290            group_by: self.group_by,
291            having: self.having,
292            _marker: PhantomData,
293        }
294    }
295}
296
297impl<'a, ST, DB, Selection, GB> SelectDsl<Selection>
298    for BoxedSelectStatement<'a, ST, NoFromClause, DB, GB>
299where
300    DB: Backend,
301    Selection:
302        SelectableExpression<NoFromClause> + QueryFragment<DB> + ValidGrouping<GB> + Send + 'a,
303{
304    type Output = BoxedSelectStatement<'a, Selection::SqlType, NoFromClause, DB, GB>;
305
306    fn select(self, selection: Selection) -> Self::Output {
307        BoxedSelectStatement {
308            select: Box::new(selection),
309            from: self.from,
310            distinct: self.distinct,
311            where_clause: self.where_clause,
312            order: self.order,
313            limit_offset: self.limit_offset,
314            group_by: self.group_by,
315            having: self.having,
316            _marker: PhantomData,
317        }
318    }
319}
320
321impl<'a, ST, QS, DB, Predicate, GB> FilterDsl<Predicate>
322    for BoxedSelectStatement<'a, ST, FromClause<QS>, DB, GB>
323where
324    QS: QuerySource,
325    BoxedWhereClause<'a, DB>: WhereAnd<Predicate, Output = BoxedWhereClause<'a, DB>>,
326    Predicate: AppearsOnTable<QS> + NonAggregate,
327    Predicate::SqlType: BoolOrNullableBool,
328{
329    type Output = Self;
330
331    fn filter(mut self, predicate: Predicate) -> Self::Output {
332        self.where_clause = self.where_clause.and(predicate);
333        self
334    }
335}
336
337impl<'a, ST, DB, Predicate, GB> FilterDsl<Predicate>
338    for BoxedSelectStatement<'a, ST, NoFromClause, DB, GB>
339where
340    BoxedWhereClause<'a, DB>: WhereAnd<Predicate, Output = BoxedWhereClause<'a, DB>>,
341    Predicate: AppearsOnTable<NoFromClause> + NonAggregate,
342    Predicate::SqlType: BoolOrNullableBool,
343{
344    type Output = Self;
345
346    fn filter(mut self, predicate: Predicate) -> Self::Output {
347        self.where_clause = self.where_clause.and(predicate);
348        self
349    }
350}
351
352impl<'a, ST, QS, DB, Predicate, GB> OrFilterDsl<Predicate>
353    for BoxedSelectStatement<'a, ST, FromClause<QS>, DB, GB>
354where
355    QS: QuerySource,
356    BoxedWhereClause<'a, DB>: WhereOr<Predicate, Output = BoxedWhereClause<'a, DB>>,
357    Predicate: AppearsOnTable<QS> + NonAggregate,
358    Predicate::SqlType: BoolOrNullableBool,
359{
360    type Output = Self;
361
362    fn or_filter(mut self, predicate: Predicate) -> Self::Output {
363        self.where_clause = self.where_clause.or(predicate);
364        self
365    }
366}
367
368impl<'a, ST, DB, Predicate, GB> OrFilterDsl<Predicate>
369    for BoxedSelectStatement<'a, ST, NoFromClause, DB, GB>
370where
371    BoxedWhereClause<'a, DB>: WhereOr<Predicate, Output = BoxedWhereClause<'a, DB>>,
372    Predicate: AppearsOnTable<NoFromClause> + NonAggregate,
373    Predicate::SqlType: BoolOrNullableBool,
374{
375    type Output = Self;
376
377    fn or_filter(mut self, predicate: Predicate) -> Self::Output {
378        self.where_clause = self.where_clause.or(predicate);
379        self
380    }
381}
382
383impl<ST, QS, DB, GB> LimitDsl for BoxedSelectStatement<'_, ST, QS, DB, GB>
384where
385    DB: Backend,
386    LimitClause<AsExprOf<i64, BigInt>>: QueryFragment<DB>,
387{
388    type Output = Self;
389
390    fn limit(mut self, limit: i64) -> Self::Output {
391        self.limit_offset.limit = Some(Box::new(LimitClause(limit.into_sql::<BigInt>())));
392        self
393    }
394}
395
396impl<ST, QS, DB, GB> OffsetDsl for BoxedSelectStatement<'_, ST, QS, DB, GB>
397where
398    DB: Backend,
399    OffsetClause<AsExprOf<i64, BigInt>>: QueryFragment<DB>,
400{
401    type Output = Self;
402
403    fn offset(mut self, offset: i64) -> Self::Output {
404        self.limit_offset.offset = Some(Box::new(OffsetClause(offset.into_sql::<BigInt>())));
405        self
406    }
407}
408
409// no impls for `NoFromClause` here because order is not really supported there yet
410impl<'a, ST, QS, DB, Order, GB> OrderDsl<Order>
411    for BoxedSelectStatement<'a, ST, FromClause<QS>, DB, GB>
412where
413    DB: Backend,
414    QS: QuerySource,
415    Order: QueryFragment<DB> + AppearsOnTable<QS> + Send + 'a,
416{
417    type Output = Self;
418
419    fn order(mut self, order: Order) -> Self::Output {
420        self.order = OrderClause(order).into();
421        self
422    }
423}
424
425impl<'a, ST, QS, DB, Order, GB> ThenOrderDsl<Order>
426    for BoxedSelectStatement<'a, ST, FromClause<QS>, DB, GB>
427where
428    DB: Backend + 'a,
429    QS: QuerySource,
430    Order: QueryFragment<DB> + AppearsOnTable<QS> + Send + 'a,
431{
432    type Output = Self;
433
434    fn then_order_by(mut self, order: Order) -> Self::Output {
435        self.order = match self.order {
436            Some(old) => Some(Box::new((old, order))),
437            None => Some(Box::new(order)),
438        };
439        self
440    }
441}
442
443impl<ST, QS, DB, Rhs> JoinTo<Rhs> for BoxedSelectStatement<'_, ST, FromClause<QS>, DB, ()>
444where
445    QS: JoinTo<Rhs> + QuerySource,
446{
447    type FromClause = <QS as JoinTo<Rhs>>::FromClause;
448    type OnClause = QS::OnClause;
449
450    fn join_target(rhs: Rhs) -> (Self::FromClause, Self::OnClause) {
451        QS::join_target(rhs)
452    }
453}
454
455impl<ST, QS, DB, GB> QueryDsl for BoxedSelectStatement<'_, ST, QS, DB, GB> {}
456
457impl<ST, QS, DB, Conn, GB> RunQueryDsl<Conn> for BoxedSelectStatement<'_, ST, QS, DB, GB> {}
458
459impl<ST, QS, DB, T, GB> Insertable<T> for BoxedSelectStatement<'_, ST, QS, DB, GB>
460where
461    T: Table,
462    Self: Query,
463    <T::AllColumns as ValidGrouping<()>>::IsAggregate:
464        MixedAggregates<is_aggregate::No, Output = is_aggregate::No>,
465{
466    type Values = InsertFromSelect<Self, T::AllColumns>;
467
468    fn values(self) -> Self::Values {
469        InsertFromSelect::new(self)
470    }
471}
472
473impl<ST, QS, DB, T, GB> Insertable<T> for &BoxedSelectStatement<'_, ST, QS, DB, GB>
474where
475    T: Table,
476    Self: Query,
477    <T::AllColumns as ValidGrouping<()>>::IsAggregate:
478        MixedAggregates<is_aggregate::No, Output = is_aggregate::No>,
479{
480    type Values = InsertFromSelect<Self, T::AllColumns>;
481
482    fn values(self) -> Self::Values {
483        InsertFromSelect::new(self)
484    }
485}
486
487impl<'a, ST, QS, DB, GB> SelectNullableDsl for BoxedSelectStatement<'a, ST, QS, DB, GB>
488where
489    ST: IntoNullable,
490{
491    type Output = BoxedSelectStatement<'a, ST::Nullable, QS, DB>;
492
493    fn nullable(self) -> Self::Output {
494        BoxedSelectStatement {
495            select: self.select,
496            from: self.from,
497            distinct: self.distinct,
498            where_clause: self.where_clause,
499            order: self.order,
500            limit_offset: self.limit_offset,
501            group_by: self.group_by,
502            having: self.having,
503            _marker: PhantomData,
504        }
505    }
506}
507
508impl<'a, ST, QS, DB, GB, Predicate> HavingDsl<Predicate>
509    for BoxedSelectStatement<'a, ST, FromClause<QS>, DB, GB>
510where
511    QS: QuerySource,
512    DB: Backend,
513    GB: Expression,
514    HavingClause<Predicate>: QueryFragment<DB> + Send + 'a,
515    Predicate: AppearsOnTable<QS>,
516    Predicate::SqlType: BoolOrNullableBool,
517{
518    type Output = Self;
519
520    fn having(mut self, predicate: Predicate) -> Self::Output {
521        self.having = Box::new(HavingClause(predicate));
522        self
523    }
524}
525
526impl<ST, QS, DB, GB> CombineDsl for BoxedSelectStatement<'_, ST, QS, DB, GB>
527where
528    Self: Query,
529{
530    type Query = Self;
531
532    fn union<Rhs>(self, rhs: Rhs) -> crate::dsl::Union<Self, Rhs>
533    where
534        Rhs: AsQuery<SqlType = <Self::Query as Query>::SqlType>,
535    {
536        CombinationClause::new(Union, Distinct, self, rhs.as_query())
537    }
538
539    fn union_all<Rhs>(self, rhs: Rhs) -> crate::dsl::UnionAll<Self, Rhs>
540    where
541        Rhs: AsQuery<SqlType = <Self::Query as Query>::SqlType>,
542    {
543        CombinationClause::new(Union, All, self, rhs.as_query())
544    }
545
546    fn intersect<Rhs>(self, rhs: Rhs) -> crate::dsl::Intersect<Self, Rhs>
547    where
548        Rhs: AsQuery<SqlType = <Self::Query as Query>::SqlType>,
549    {
550        CombinationClause::new(Intersect, Distinct, self, rhs.as_query())
551    }
552
553    fn intersect_all<Rhs>(self, rhs: Rhs) -> crate::dsl::IntersectAll<Self, Rhs>
554    where
555        Rhs: AsQuery<SqlType = <Self::Query as Query>::SqlType>,
556    {
557        CombinationClause::new(Intersect, All, self, rhs.as_query())
558    }
559
560    fn except<Rhs>(self, rhs: Rhs) -> crate::dsl::Except<Self, Rhs>
561    where
562        Rhs: AsQuery<SqlType = <Self::Query as Query>::SqlType>,
563    {
564        CombinationClause::new(Except, Distinct, self, rhs.as_query())
565    }
566
567    fn except_all<Rhs>(self, rhs: Rhs) -> crate::dsl::ExceptAll<Self, Rhs>
568    where
569        Rhs: AsQuery<SqlType = <Self::Query as Query>::SqlType>,
570    {
571        CombinationClause::new(Except, All, self, rhs.as_query())
572    }
573}
574
575#[cfg(test)]
576mod tests {
577    use crate::prelude::*;
578
579    table! {
580        users {
581            id -> Integer,
582        }
583    }
584
585    fn assert_send<T>(_: T)
586    where
587        T: Send,
588    {
589    }
590
591    macro_rules! assert_boxed_query_send {
592        ($backend:ty) => {{
593            assert_send(users::table.into_boxed::<$backend>());
594            assert_send(
595                users::table
596                    .filter(users::id.eq(10))
597                    .into_boxed::<$backend>(),
598            );
599        };};
600    }
601
602    #[diesel_test_helper::test]
603    fn boxed_is_send() {
604        #[cfg(feature = "postgres")]
605        assert_boxed_query_send!(crate::pg::Pg);
606
607        #[cfg(feature = "sqlite")]
608        assert_boxed_query_send!(crate::sqlite::Sqlite);
609
610        #[cfg(feature = "mysql")]
611        assert_boxed_query_send!(crate::mysql::Mysql);
612    }
613}