Skip to main content

alopex_sql/planner/
typed_expr.rs

1//! Type-checked expression types for the planner.
2//!
3//! This module defines [`TypedExpr`] and related types that represent
4//! expressions after type checking. These types carry resolved type
5//! information and are used in [`crate::planner::LogicalPlan`] construction.
6//!
7//! # Overview
8//!
9//! - [`TypedExpr`]: A type-checked expression with resolved type and span
10//! - [`TypedExprKind`]: The kind of typed expression (literals, column refs, operators, etc.)
11//! - [`SortExpr`]: A sort expression for ORDER BY clauses
12//! - [`TypedAssignment`]: A typed assignment for UPDATE SET clauses
13//! - [`ProjectedColumn`]: A projected column for SELECT clauses
14//! - [`Projection`]: The projection specification for SELECT
15
16use crate::ast::expr::{BinaryOp, Literal, PatternMatchKind, UnaryOp, WindowFrame};
17use crate::ast::span::Span;
18use crate::planner::logical_plan::LogicalPlan;
19use crate::planner::types::ResolvedType;
20
21/// A type-checked expression with resolved type information.
22///
23/// This struct represents an expression that has been validated by the type checker.
24/// It contains the expression kind, the resolved type, and the source span for
25/// error reporting.
26///
27/// # Examples
28///
29/// ```
30/// use alopex_sql::planner::typed_expr::{TypedExpr, TypedExprKind};
31/// use alopex_sql::planner::types::ResolvedType;
32/// use alopex_sql::ast::expr::Literal;
33/// use alopex_sql::Span;
34///
35/// let expr = TypedExpr {
36///     kind: TypedExprKind::Literal(Literal::Number("42".to_string())),
37///     resolved_type: ResolvedType::Integer,
38///     span: Span::default(),
39/// };
40/// ```
41#[derive(Debug, Clone)]
42pub struct TypedExpr {
43    /// The kind of expression.
44    pub kind: TypedExprKind,
45    /// The resolved type of this expression.
46    pub resolved_type: ResolvedType,
47    /// Source span for error reporting.
48    pub span: Span,
49}
50
51/// The kind of a typed expression.
52///
53/// Each variant corresponds to a different expression type that has been
54/// type-checked. Unlike [`ExprKind`](crate::ast::expr::ExprKind), column
55/// references include the resolved column index for efficient access.
56#[derive(Debug, Clone)]
57pub enum TypedExprKind {
58    /// A literal value.
59    Literal(Literal),
60
61    /// A column reference with resolved table and column index.
62    ColumnRef {
63        /// The table name (resolved, never None after name resolution).
64        table: String,
65        /// The column name.
66        column: String,
67        /// The column index in the table's column list (0-based).
68        /// This allows efficient column access during execution.
69        column_index: usize,
70    },
71
72    /// A binary operation.
73    BinaryOp {
74        /// Left operand.
75        left: Box<TypedExpr>,
76        /// The operator.
77        op: BinaryOp,
78        /// Right operand.
79        right: Box<TypedExpr>,
80    },
81
82    /// A unary operation.
83    UnaryOp {
84        /// The operator.
85        op: UnaryOp,
86        /// The operand.
87        operand: Box<TypedExpr>,
88    },
89
90    /// A searched or simple CASE expression.
91    Case {
92        /// The simple CASE operand, or None for searched CASE.
93        operand: Option<Box<TypedExpr>>,
94        /// WHEN/THEN branches in source order.
95        branches: Vec<TypedCaseWhen>,
96        /// The ELSE expression, or None for the implicit NULL result.
97        else_expr: Option<Box<TypedExpr>>,
98    },
99
100    /// A function call.
101    FunctionCall {
102        /// Function name.
103        name: String,
104        /// Function arguments.
105        args: Vec<TypedExpr>,
106        /// DISTINCT modifier for aggregate functions.
107        distinct: bool,
108        /// STAR modifier for COUNT(*).
109        star: bool,
110        /// Aggregate row filter from `FILTER (WHERE predicate)` (issue #148).
111        filter: Option<Box<TypedExpr>>,
112        /// Aggregate-local ordering. `WITHIN GROUP (ORDER BY ...)` is
113        /// normalized into this list by the type checker.
114        order_by: Vec<SortExpr>,
115        /// Optional typed `OVER (...)` specification.
116        over: Option<TypedWindowSpec>,
117    },
118
119    /// An explicit type cast.
120    Cast {
121        /// Expression to cast.
122        expr: Box<TypedExpr>,
123        /// Target type.
124        target_type: ResolvedType,
125    },
126
127    /// An explicit type cast that returns NULL on conversion failure.
128    TryCast {
129        /// Expression to cast.
130        expr: Box<TypedExpr>,
131        /// Target type.
132        target_type: ResolvedType,
133    },
134
135    /// A BETWEEN expression.
136    Between {
137        /// Expression to test.
138        expr: Box<TypedExpr>,
139        /// Lower bound.
140        low: Box<TypedExpr>,
141        /// Upper bound.
142        high: Box<TypedExpr>,
143        /// Whether the expression is negated (NOT BETWEEN).
144        negated: bool,
145    },
146
147    /// A LIKE pattern match expression.
148    Like {
149        /// Expression to match.
150        expr: Box<TypedExpr>,
151        /// Pattern to match against.
152        pattern: Box<TypedExpr>,
153        /// Optional escape character.
154        escape: Option<Box<TypedExpr>>,
155        /// Whether the expression is negated (NOT LIKE).
156        negated: bool,
157        /// Pattern operator variant.
158        kind: PatternMatchKind,
159    },
160
161    /// An IN list expression.
162    InList {
163        /// Expression to test.
164        expr: Box<TypedExpr>,
165        /// List of values to check against.
166        list: Vec<TypedExpr>,
167        /// Whether the expression is negated (NOT IN).
168        negated: bool,
169    },
170
171    /// An IS NULL expression.
172    IsNull {
173        /// Expression to test.
174        expr: Box<TypedExpr>,
175        /// Whether the expression is negated (IS NOT NULL).
176        negated: bool,
177    },
178
179    /// A vector literal.
180    VectorLiteral(Vec<f64>),
181
182    /// A scalar subquery.
183    ScalarSubquery(Box<LogicalPlan>),
184
185    /// An IN subquery.
186    InSubquery {
187        /// Expression to test.
188        expr: Box<TypedExpr>,
189        /// Planned subquery.
190        subquery: Box<LogicalPlan>,
191        /// Whether this is NOT IN.
192        negated: bool,
193    },
194
195    /// An EXISTS subquery.
196    Exists {
197        /// Planned subquery.
198        subquery: Box<LogicalPlan>,
199        /// Whether this is NOT EXISTS.
200        negated: bool,
201    },
202
203    /// A quantified comparison subquery.
204    Quantified {
205        /// Expression to compare.
206        expr: Box<TypedExpr>,
207        /// Comparison operator.
208        op: BinaryOp,
209        /// ANY/ALL quantifier.
210        quantifier: Quantifier,
211        /// Planned subquery.
212        subquery: Box<LogicalPlan>,
213    },
214}
215
216/// A type-checked window specification.
217#[derive(Debug, Clone)]
218pub struct TypedWindowSpec {
219    /// Expressions identifying independent partitions.
220    pub partition_by: Vec<TypedExpr>,
221    /// Window-local ordering.
222    pub order_by: Vec<SortExpr>,
223    /// Semantically validated explicit frame, if supplied.
224    pub frame: Option<WindowFrame>,
225}
226
227#[derive(Debug, Clone)]
228pub struct TypedCaseWhen {
229    pub when: TypedExpr,
230    pub then: TypedExpr,
231}
232
233/// Quantifier for quantified subquery comparisons.
234#[derive(Debug, Clone, Copy, PartialEq, Eq)]
235pub enum Quantifier {
236    Any,
237    All,
238}
239
240/// A sort expression for ORDER BY clauses.
241///
242/// Contains a typed expression and sort direction information.
243///
244/// # Examples
245///
246/// ```
247/// use alopex_sql::planner::typed_expr::{SortExpr, TypedExpr, TypedExprKind};
248/// use alopex_sql::planner::types::ResolvedType;
249/// use alopex_sql::Span;
250///
251/// let sort_expr = SortExpr {
252///     expr: TypedExpr {
253///         kind: TypedExprKind::ColumnRef {
254///             table: "users".to_string(),
255///             column: "name".to_string(),
256///             column_index: 1,
257///         },
258///         resolved_type: ResolvedType::Text,
259///         span: Span::default(),
260///     },
261///     asc: true,
262///     nulls_first: false,
263/// };
264/// ```
265#[derive(Debug, Clone)]
266pub struct SortExpr {
267    /// The expression to sort by.
268    pub expr: TypedExpr,
269    /// Sort in ascending order (true) or descending (false).
270    pub asc: bool,
271    /// Place NULLs first (true) or last (false).
272    pub nulls_first: bool,
273}
274
275/// A typed assignment for UPDATE SET clauses.
276///
277/// Contains the column name, index, and the typed value expression.
278///
279/// # Examples
280///
281/// ```
282/// use alopex_sql::planner::typed_expr::{TypedAssignment, TypedExpr, TypedExprKind};
283/// use alopex_sql::planner::types::ResolvedType;
284/// use alopex_sql::ast::expr::Literal;
285/// use alopex_sql::Span;
286///
287/// let assignment = TypedAssignment {
288///     column: "name".to_string(),
289///     column_index: 1,
290///     value: TypedExpr {
291///         kind: TypedExprKind::Literal(Literal::String("Bob".to_string())),
292///         resolved_type: ResolvedType::Text,
293///         span: Span::default(),
294///     },
295/// };
296/// ```
297#[derive(Debug, Clone)]
298pub struct TypedAssignment {
299    /// The column name being assigned.
300    pub column: String,
301    /// The column index in the table's column list (0-based).
302    pub column_index: usize,
303    /// The value expression (type-checked against the column type).
304    pub value: TypedExpr,
305}
306
307/// A projected column for SELECT clauses.
308///
309/// Contains a typed expression and an optional alias.
310///
311/// # Examples
312///
313/// ```
314/// use alopex_sql::planner::typed_expr::{ProjectedColumn, TypedExpr, TypedExprKind};
315/// use alopex_sql::planner::types::ResolvedType;
316/// use alopex_sql::Span;
317///
318/// // SELECT name AS user_name
319/// let projected = ProjectedColumn {
320///     expr: TypedExpr {
321///         kind: TypedExprKind::ColumnRef {
322///             table: "users".to_string(),
323///             column: "name".to_string(),
324///             column_index: 1,
325///         },
326///         resolved_type: ResolvedType::Text,
327///         span: Span::default(),
328///     },
329///     alias: Some("user_name".to_string()),
330/// };
331/// ```
332#[derive(Debug, Clone)]
333pub struct ProjectedColumn {
334    /// The projected expression.
335    pub expr: TypedExpr,
336    /// Optional alias (AS name).
337    pub alias: Option<String>,
338}
339
340/// Projection specification for SELECT clauses.
341///
342/// Represents either all columns (after wildcard expansion) or specific columns.
343#[derive(Debug, Clone)]
344pub enum Projection {
345    /// All columns (expanded from `*`).
346    /// Contains the list of column names in definition order.
347    All(Vec<String>),
348
349    /// Specific columns/expressions.
350    Columns(Vec<ProjectedColumn>),
351}
352
353impl TypedExpr {
354    /// Creates a new typed expression.
355    pub fn new(kind: TypedExprKind, resolved_type: ResolvedType, span: Span) -> Self {
356        Self {
357            kind,
358            resolved_type,
359            span,
360        }
361    }
362
363    /// Creates a typed literal expression.
364    pub fn literal(lit: Literal, resolved_type: ResolvedType, span: Span) -> Self {
365        Self::new(TypedExprKind::Literal(lit), resolved_type, span)
366    }
367
368    /// Creates a typed column reference.
369    pub fn column_ref(
370        table: String,
371        column: String,
372        column_index: usize,
373        resolved_type: ResolvedType,
374        span: Span,
375    ) -> Self {
376        Self::new(
377            TypedExprKind::ColumnRef {
378                table,
379                column,
380                column_index,
381            },
382            resolved_type,
383            span,
384        )
385    }
386
387    /// Creates a typed binary operation.
388    pub fn binary_op(
389        left: TypedExpr,
390        op: BinaryOp,
391        right: TypedExpr,
392        resolved_type: ResolvedType,
393        span: Span,
394    ) -> Self {
395        Self::new(
396            TypedExprKind::BinaryOp {
397                left: Box::new(left),
398                op,
399                right: Box::new(right),
400            },
401            resolved_type,
402            span,
403        )
404    }
405
406    /// Creates a typed unary operation.
407    pub fn unary_op(
408        op: UnaryOp,
409        operand: TypedExpr,
410        resolved_type: ResolvedType,
411        span: Span,
412    ) -> Self {
413        Self::new(
414            TypedExprKind::UnaryOp {
415                op,
416                operand: Box::new(operand),
417            },
418            resolved_type,
419            span,
420        )
421    }
422
423    /// Creates a typed function call.
424    pub fn function_call(
425        name: String,
426        args: Vec<TypedExpr>,
427        distinct: bool,
428        star: bool,
429        resolved_type: ResolvedType,
430        span: Span,
431    ) -> Self {
432        Self::new(
433            TypedExprKind::FunctionCall {
434                name,
435                args,
436                distinct,
437                star,
438                filter: None,
439                order_by: Vec::new(),
440                over: None,
441            },
442            resolved_type,
443            span,
444        )
445    }
446
447    /// Creates a typed cast expression.
448    pub fn cast(expr: TypedExpr, target_type: ResolvedType, span: Span) -> Self {
449        Self::new(
450            TypedExprKind::Cast {
451                expr: Box::new(expr),
452                target_type: target_type.clone(),
453            },
454            target_type,
455            span,
456        )
457    }
458
459    /// Creates a typed TRY_CAST expression.
460    pub fn try_cast(expr: TypedExpr, target_type: ResolvedType, span: Span) -> Self {
461        Self::new(
462            TypedExprKind::TryCast {
463                expr: Box::new(expr),
464                target_type: target_type.clone(),
465            },
466            target_type,
467            span,
468        )
469    }
470
471    /// Creates a typed vector literal.
472    pub fn vector_literal(values: Vec<f64>, dimension: u32, span: Span) -> Self {
473        use crate::ast::ddl::VectorMetric;
474        Self::new(
475            TypedExprKind::VectorLiteral(values),
476            ResolvedType::Vector {
477                dimension,
478                metric: VectorMetric::Cosine,
479            },
480            span,
481        )
482    }
483}
484
485impl SortExpr {
486    /// Creates a new sort expression with ascending order.
487    pub fn asc(expr: TypedExpr) -> Self {
488        Self {
489            expr,
490            asc: true,
491            nulls_first: false,
492        }
493    }
494
495    /// Creates a new sort expression with descending order.
496    ///
497    /// Note: `nulls_first` defaults to `false` (NULLS LAST) for consistency.
498    /// Use [`SortExpr::new`] for explicit NULLS ordering.
499    pub fn desc(expr: TypedExpr) -> Self {
500        Self {
501            expr,
502            asc: false,
503            nulls_first: false,
504        }
505    }
506
507    /// Creates a new sort expression with custom settings.
508    pub fn new(expr: TypedExpr, asc: bool, nulls_first: bool) -> Self {
509        Self {
510            expr,
511            asc,
512            nulls_first,
513        }
514    }
515}
516
517impl TypedAssignment {
518    /// Creates a new typed assignment.
519    pub fn new(column: String, column_index: usize, value: TypedExpr) -> Self {
520        Self {
521            column,
522            column_index,
523            value,
524        }
525    }
526}
527
528impl ProjectedColumn {
529    /// Creates a new projected column without an alias.
530    pub fn new(expr: TypedExpr) -> Self {
531        Self { expr, alias: None }
532    }
533
534    /// Creates a new projected column with an alias.
535    pub fn with_alias(expr: TypedExpr, alias: String) -> Self {
536        Self {
537            expr,
538            alias: Some(alias),
539        }
540    }
541
542    /// Returns the output name (alias if present, otherwise derived from expression).
543    ///
544    /// Returns:
545    /// - The alias if one was specified (e.g., `SELECT name AS user_name`)
546    /// - The column name for simple column references (e.g., `SELECT name`)
547    /// - `None` for complex expressions without an alias (e.g., `SELECT 1 + 2`)
548    ///
549    /// Complex expressions (function calls, literals, binary operations) return `None`
550    /// because they don't have a natural name. Use [`with_alias`](Self::with_alias)
551    /// to give them an output name.
552    pub fn output_name(&self) -> Option<&str> {
553        if let Some(ref alias) = self.alias {
554            return Some(alias);
555        }
556        // For column references, return the column name
557        if let TypedExprKind::ColumnRef { ref column, .. } = self.expr.kind {
558            return Some(column);
559        }
560        None
561    }
562}
563
564impl Projection {
565    /// Returns the number of columns in the projection.
566    pub fn len(&self) -> usize {
567        match self {
568            Projection::All(cols) => cols.len(),
569            Projection::Columns(cols) => cols.len(),
570        }
571    }
572
573    /// Returns true if the projection has no columns.
574    pub fn is_empty(&self) -> bool {
575        self.len() == 0
576    }
577
578    /// Returns the column names in the projection.
579    ///
580    /// For [`Projection::All`], all names are present (from the wildcard expansion).
581    /// For [`Projection::Columns`], names may be `None` for complex expressions
582    /// without aliases. See [`ProjectedColumn::output_name`] for details.
583    pub fn column_names(&self) -> Vec<Option<&str>> {
584        match self {
585            Projection::All(cols) => cols.iter().map(|s| Some(s.as_str())).collect(),
586            Projection::Columns(cols) => cols.iter().map(|c| c.output_name()).collect(),
587        }
588    }
589}
590
591#[cfg(test)]
592mod tests {
593    use super::*;
594    use crate::ast::ddl::VectorMetric;
595
596    #[test]
597    fn test_typed_expr_literal() {
598        let expr = TypedExpr::literal(
599            Literal::Number("42".to_string()),
600            ResolvedType::Integer,
601            Span::default(),
602        );
603
604        assert!(matches!(
605            expr.kind,
606            TypedExprKind::Literal(Literal::Number(_))
607        ));
608        assert_eq!(expr.resolved_type, ResolvedType::Integer);
609    }
610
611    #[test]
612    fn test_typed_expr_column_ref() {
613        let expr = TypedExpr::column_ref(
614            "users".to_string(),
615            "id".to_string(),
616            0,
617            ResolvedType::Integer,
618            Span::default(),
619        );
620
621        if let TypedExprKind::ColumnRef {
622            table,
623            column,
624            column_index,
625        } = &expr.kind
626        {
627            assert_eq!(table, "users");
628            assert_eq!(column, "id");
629            assert_eq!(*column_index, 0);
630        } else {
631            panic!("Expected ColumnRef");
632        }
633    }
634
635    #[test]
636    fn test_typed_expr_binary_op() {
637        let left = TypedExpr::literal(
638            Literal::Number("1".to_string()),
639            ResolvedType::Integer,
640            Span::default(),
641        );
642        let right = TypedExpr::literal(
643            Literal::Number("2".to_string()),
644            ResolvedType::Integer,
645            Span::default(),
646        );
647
648        let expr = TypedExpr::binary_op(
649            left,
650            BinaryOp::Add,
651            right,
652            ResolvedType::Integer,
653            Span::default(),
654        );
655
656        assert!(matches!(expr.kind, TypedExprKind::BinaryOp { .. }));
657        assert_eq!(expr.resolved_type, ResolvedType::Integer);
658    }
659
660    #[test]
661    fn test_typed_expr_vector_literal() {
662        let values = vec![1.0, 2.0, 3.0];
663        let expr = TypedExpr::vector_literal(values.clone(), 3, Span::default());
664
665        if let TypedExprKind::VectorLiteral(v) = &expr.kind {
666            assert_eq!(v, &values);
667        } else {
668            panic!("Expected VectorLiteral");
669        }
670
671        if let ResolvedType::Vector { dimension, metric } = &expr.resolved_type {
672            assert_eq!(*dimension, 3);
673            assert_eq!(*metric, VectorMetric::Cosine);
674        } else {
675            panic!("Expected Vector type");
676        }
677    }
678
679    #[test]
680    fn test_sort_expr_asc() {
681        let col = TypedExpr::column_ref(
682            "users".to_string(),
683            "name".to_string(),
684            1,
685            ResolvedType::Text,
686            Span::default(),
687        );
688        let sort = SortExpr::asc(col);
689
690        assert!(sort.asc);
691        assert!(!sort.nulls_first);
692    }
693
694    #[test]
695    fn test_sort_expr_desc() {
696        let col = TypedExpr::column_ref(
697            "users".to_string(),
698            "name".to_string(),
699            1,
700            ResolvedType::Text,
701            Span::default(),
702        );
703        let sort = SortExpr::desc(col);
704
705        assert!(!sort.asc);
706        // NULLS LAST is the consistent default for both ASC and DESC
707        assert!(!sort.nulls_first);
708    }
709
710    #[test]
711    fn test_typed_assignment() {
712        let value = TypedExpr::literal(
713            Literal::String("Alice".to_string()),
714            ResolvedType::Text,
715            Span::default(),
716        );
717        let assignment = TypedAssignment::new("name".to_string(), 1, value);
718
719        assert_eq!(assignment.column, "name");
720        assert_eq!(assignment.column_index, 1);
721    }
722
723    #[test]
724    fn test_projected_column_output_name() {
725        let col = TypedExpr::column_ref(
726            "users".to_string(),
727            "name".to_string(),
728            1,
729            ResolvedType::Text,
730            Span::default(),
731        );
732
733        // Without alias, output name is the column name
734        let proj1 = ProjectedColumn::new(col.clone());
735        assert_eq!(proj1.output_name(), Some("name"));
736
737        // With alias, output name is the alias
738        let proj2 = ProjectedColumn::with_alias(col, "user_name".to_string());
739        assert_eq!(proj2.output_name(), Some("user_name"));
740    }
741
742    #[test]
743    fn test_projection_all() {
744        let columns = vec!["id".to_string(), "name".to_string(), "email".to_string()];
745        let proj = Projection::All(columns);
746
747        assert_eq!(proj.len(), 3);
748        assert!(!proj.is_empty());
749
750        let names: Vec<_> = proj.column_names();
751        assert_eq!(names, vec![Some("id"), Some("name"), Some("email")]);
752    }
753
754    #[test]
755    fn test_projection_columns() {
756        let col1 = ProjectedColumn::new(TypedExpr::column_ref(
757            "users".to_string(),
758            "id".to_string(),
759            0,
760            ResolvedType::Integer,
761            Span::default(),
762        ));
763        let col2 = ProjectedColumn::with_alias(
764            TypedExpr::column_ref(
765                "users".to_string(),
766                "name".to_string(),
767                1,
768                ResolvedType::Text,
769                Span::default(),
770            ),
771            "user_name".to_string(),
772        );
773
774        let proj = Projection::Columns(vec![col1, col2]);
775
776        assert_eq!(proj.len(), 2);
777        let names: Vec<_> = proj.column_names();
778        assert_eq!(names, vec![Some("id"), Some("user_name")]);
779    }
780
781    #[test]
782    fn test_typed_expr_cast() {
783        let inner = TypedExpr::literal(
784            Literal::Number("42".to_string()),
785            ResolvedType::Integer,
786            Span::default(),
787        );
788        let expr = TypedExpr::cast(inner, ResolvedType::Double, Span::default());
789
790        assert!(matches!(expr.kind, TypedExprKind::Cast { .. }));
791        assert_eq!(expr.resolved_type, ResolvedType::Double);
792    }
793
794    #[test]
795    fn test_typed_expr_kind_between() {
796        let expr_kind = TypedExprKind::Between {
797            expr: Box::new(TypedExpr::column_ref(
798                "t".to_string(),
799                "x".to_string(),
800                0,
801                ResolvedType::Integer,
802                Span::default(),
803            )),
804            low: Box::new(TypedExpr::literal(
805                Literal::Number("1".to_string()),
806                ResolvedType::Integer,
807                Span::default(),
808            )),
809            high: Box::new(TypedExpr::literal(
810                Literal::Number("10".to_string()),
811                ResolvedType::Integer,
812                Span::default(),
813            )),
814            negated: false,
815        };
816
817        assert!(matches!(
818            expr_kind,
819            TypedExprKind::Between { negated: false, .. }
820        ));
821    }
822
823    #[test]
824    fn test_typed_expr_kind_is_null() {
825        let expr_kind = TypedExprKind::IsNull {
826            expr: Box::new(TypedExpr::column_ref(
827                "t".to_string(),
828                "x".to_string(),
829                0,
830                ResolvedType::Integer,
831                Span::default(),
832            )),
833            negated: true,
834        };
835
836        assert!(matches!(
837            expr_kind,
838            TypedExprKind::IsNull { negated: true, .. }
839        ));
840    }
841}