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}