1use std::collections::BTreeMap;
8
9use serde::{Deserialize, Serialize};
10
11use crate::*;
12
13#[derive(Debug, thiserror::Error, Clone, PartialEq, Eq)]
14pub enum BuilderError {
15 #[error("unknown column '{column}' on entity '{table}'")]
16 UnknownColumn { table: String, column: String },
17 #[error("builder requires at least one projection")]
18 EmptyProjection,
19 #[error("builder requires at least one value")]
20 EmptyValues,
21 #[error("ALTER TABLE builder requires exactly one action")]
22 AlterActionCardinality,
23 #[error("schema identifier '{0}' is not present in the bound descriptor")]
24 UnknownIdentifier(String),
25 #[error("invalid builder option: {0}")]
26 InvalidOption(String),
27}
28
29#[derive(Debug, thiserror::Error)]
30pub enum BuilderExecutionError<E> {
31 #[error(transparent)]
32 Build(#[from] BuilderError),
33 #[error("session execution failed")]
34 Session(E),
35}
36
37pub trait OrmBuilder {
38 fn document(&self) -> Result<IrDocument, BuilderError>;
39
40 fn to_json(&self) -> Result<String, BuilderJsonError> {
41 Ok(self.document()?.to_json()?)
42 }
43
44 fn to_sql(&self) -> Result<CompiledStatement, BuilderSqlError> {
45 Ok(self.document()?.to_sql()?)
46 }
47}
48
49async fn execute_builder_async<B, S>(
50 builder: &B,
51 session: &mut S,
52) -> Result<S::CommandOutput, BuilderExecutionError<S::Error>>
53where
54 B: OrmBuilder,
55 S: AsyncOrmSession,
56{
57 let document = builder.document()?;
58 session
59 .execute_document_async(&document)
60 .await
61 .map_err(BuilderExecutionError::Session)
62}
63
64#[derive(Debug, thiserror::Error)]
65pub enum BuilderJsonError {
66 #[error(transparent)]
67 Build(#[from] BuilderError),
68 #[error(transparent)]
69 Ir(#[from] IrError),
70}
71
72#[derive(Debug, thiserror::Error)]
73pub enum BuilderSqlError {
74 #[error(transparent)]
75 Build(#[from] BuilderError),
76 #[error(transparent)]
77 Render(#[from] RenderError),
78}
79
80#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
81#[serde(transparent)]
82pub struct Expr(pub Expression);
83
84impl Expr {
85 pub fn column(name: impl Into<String>) -> Self {
86 Self(Expression::column(name))
87 }
88
89 pub fn qualified(relation: impl Into<String>, name: impl Into<String>) -> Self {
90 Self(Expression::Column {
91 column: ColumnRef::qualified(relation, name),
92 })
93 }
94
95 pub fn value(value: impl Into<TypedValue>) -> Self {
96 Self(Expression::literal(value.into()))
97 }
98
99 pub fn star() -> Self {
100 Self(Expression::Star { relation: None })
101 }
102
103 pub fn navigation(
104 root: impl Into<String>,
105 path: impl IntoIterator<Item = impl Into<String>>,
106 ) -> Self {
107 Self(Expression::Navigation {
108 root: root.into(),
109 path: path.into_iter().map(Into::into).collect(),
110 })
111 }
112
113 pub fn function(name: impl Into<String>, arguments: impl IntoIterator<Item = Expr>) -> Self {
114 Self(Expression::Function {
115 name: name.into(),
116 arguments: arguments.into_iter().map(|value| value.0).collect(),
117 })
118 }
119
120 pub fn aggregate(
121 name: impl Into<String>,
122 arguments: impl IntoIterator<Item = Expr>,
123 distinct: bool,
124 filter: Option<Expr>,
125 order_by: Vec<Order>,
126 ) -> Self {
127 Self(Expression::Aggregate {
128 name: name.into(),
129 arguments: arguments.into_iter().map(|value| value.0).collect(),
130 distinct,
131 filter: filter.map(|value| Box::new(value.0)),
132 order_by: order_by.into_iter().map(Order::into_ir).collect(),
133 })
134 }
135
136 pub fn window(self, specification: WindowSpecification) -> Self {
137 Self(Expression::Window {
138 function: Box::new(self.0),
139 specification,
140 })
141 }
142
143 pub fn cast(self, data_type: DataTypeDescriptor) -> Self {
144 Self(Expression::Cast {
145 expression: Box::new(self.0),
146 data_type,
147 })
148 }
149
150 pub fn alias(self, alias: impl Into<String>) -> Projection {
151 Projection {
152 expression: self.0,
153 alias: Some(alias.into()),
154 }
155 }
156
157 pub fn projection(self) -> Projection {
158 Projection {
159 expression: self.0,
160 alias: None,
161 }
162 }
163
164 pub fn unary(self, operator: UnaryOperator) -> Self {
165 Self(Expression::Unary {
166 operator,
167 expression: Box::new(self.0),
168 })
169 }
170
171 pub fn binary(self, operator: BinaryOperator, right: impl Into<Expr>) -> Self {
172 Self(Expression::Binary {
173 left: Box::new(self.0),
174 operator,
175 right: Box::new(right.into().0),
176 })
177 }
178
179 pub fn eq(self, right: impl Into<Expr>) -> Self {
180 self.binary(BinaryOperator::Eq, right)
181 }
182 pub fn ne(self, right: impl Into<Expr>) -> Self {
183 self.binary(BinaryOperator::Ne, right)
184 }
185 pub fn lt(self, right: impl Into<Expr>) -> Self {
186 self.binary(BinaryOperator::Lt, right)
187 }
188 pub fn lte(self, right: impl Into<Expr>) -> Self {
189 self.binary(BinaryOperator::Lte, right)
190 }
191 pub fn gt(self, right: impl Into<Expr>) -> Self {
192 self.binary(BinaryOperator::Gt, right)
193 }
194 pub fn gte(self, right: impl Into<Expr>) -> Self {
195 self.binary(BinaryOperator::Gte, right)
196 }
197 pub fn and(self, right: impl Into<Expr>) -> Self {
198 self.binary(BinaryOperator::And, right)
199 }
200 pub fn or(self, right: impl Into<Expr>) -> Self {
201 self.binary(BinaryOperator::Or, right)
202 }
203 #[allow(clippy::should_implement_trait)]
207 pub fn add(self, right: impl Into<Expr>) -> Self {
208 self.binary(BinaryOperator::Add, right)
209 }
210 #[allow(clippy::should_implement_trait)]
211 pub fn sub(self, right: impl Into<Expr>) -> Self {
212 self.binary(BinaryOperator::Subtract, right)
213 }
214 #[allow(clippy::should_implement_trait)]
215 pub fn mul(self, right: impl Into<Expr>) -> Self {
216 self.binary(BinaryOperator::Multiply, right)
217 }
218 #[allow(clippy::should_implement_trait)]
219 pub fn div(self, right: impl Into<Expr>) -> Self {
220 self.binary(BinaryOperator::Divide, right)
221 }
222 pub fn modulo(self, right: impl Into<Expr>) -> Self {
223 self.binary(BinaryOperator::Modulo, right)
224 }
225 pub fn like(self, right: impl Into<Expr>) -> Self {
226 self.binary(BinaryOperator::Like, right)
227 }
228 pub fn not_like(self, right: impl Into<Expr>) -> Self {
229 self.binary(BinaryOperator::NotLike, right)
230 }
231 pub fn regexp(self, right: impl Into<Expr>) -> Self {
232 self.binary(BinaryOperator::Regexp, right)
233 }
234 pub fn glob(self, right: impl Into<Expr>) -> Self {
235 self.binary(BinaryOperator::Glob, right)
236 }
237 pub fn is_distinct_from(self, right: impl Into<Expr>) -> Self {
238 self.binary(BinaryOperator::IsDistinctFrom, right)
239 }
240 pub fn is_not_distinct_from(self, right: impl Into<Expr>) -> Self {
241 self.binary(BinaryOperator::IsNotDistinctFrom, right)
242 }
243 #[allow(clippy::should_implement_trait)]
244 pub fn not(self) -> Self {
245 self.unary(UnaryOperator::Not)
246 }
247
248 pub fn is_null(self) -> Self {
249 Self(Expression::IsNull {
250 expression: Box::new(self.0),
251 negated: false,
252 })
253 }
254
255 pub fn is_not_null(self) -> Self {
256 Self(Expression::IsNull {
257 expression: Box::new(self.0),
258 negated: true,
259 })
260 }
261
262 pub fn between(self, lower: impl Into<Expr>, upper: impl Into<Expr>) -> Self {
263 Self(Expression::Between {
264 expression: Box::new(self.0),
265 lower: Box::new(lower.into().0),
266 upper: Box::new(upper.into().0),
267 negated: false,
268 })
269 }
270
271 pub fn not_between(self, lower: impl Into<Expr>, upper: impl Into<Expr>) -> Self {
272 Self(Expression::Between {
273 expression: Box::new(self.0),
274 lower: Box::new(lower.into().0),
275 upper: Box::new(upper.into().0),
276 negated: true,
277 })
278 }
279
280 pub fn in_list(self, values: impl IntoIterator<Item = impl Into<Expr>>) -> Self {
281 Self(Expression::InList {
282 expression: Box::new(self.0),
283 values: values.into_iter().map(|value| value.into().0).collect(),
284 negated: false,
285 })
286 }
287
288 pub fn not_in_list(self, values: impl IntoIterator<Item = impl Into<Expr>>) -> Self {
289 Self(Expression::InList {
290 expression: Box::new(self.0),
291 values: values.into_iter().map(|value| value.into().0).collect(),
292 negated: true,
293 })
294 }
295
296 pub fn in_subquery(self, query: QueryBuilder) -> Self {
297 Self(Expression::InSubquery {
298 expression: Box::new(self.0),
299 query: Box::new(query.into_select()),
300 negated: false,
301 })
302 }
303
304 pub fn not_in_subquery(self, query: QueryBuilder) -> Self {
305 Self(Expression::InSubquery {
306 expression: Box::new(self.0),
307 query: Box::new(query.into_select()),
308 negated: true,
309 })
310 }
311
312 pub fn tuple(values: impl IntoIterator<Item = Expr>) -> Self {
313 Self(Expression::Tuple {
314 values: values.into_iter().map(|value| value.0).collect(),
315 })
316 }
317
318 pub fn exists(query: QueryBuilder) -> Self {
319 Self(Expression::Exists {
320 query: Box::new(query.into_select()),
321 negated: false,
322 })
323 }
324
325 pub fn not_exists(query: QueryBuilder) -> Self {
326 Self(Expression::Exists {
327 query: Box::new(query.into_select()),
328 negated: true,
329 })
330 }
331
332 pub fn scalar_subquery(query: QueryBuilder) -> Self {
333 Self(Expression::ScalarSubquery {
334 query: Box::new(query.into_select()),
335 })
336 }
337
338 pub fn case(
339 operand: Option<Expr>,
340 branches: impl IntoIterator<Item = (Expr, Expr)>,
341 otherwise: Option<Expr>,
342 ) -> Self {
343 Self(Expression::Case {
344 operand: operand.map(|value| Box::new(value.0)),
345 branches: branches
346 .into_iter()
347 .map(|(when, then)| CaseBranch {
348 when: when.0,
349 then: then.0,
350 })
351 .collect(),
352 otherwise: otherwise.map(|value| Box::new(value.0)),
353 })
354 }
355
356 pub fn grouping(expressions: impl IntoIterator<Item = Expr>) -> Self {
357 Self(Expression::Grouping {
358 expressions: expressions.into_iter().map(|value| value.0).collect(),
359 })
360 }
361
362 pub fn asc(self) -> Order {
363 Order::new(self, SortDirection::Asc)
364 }
365 pub fn desc(self) -> Order {
366 Order::new(self, SortDirection::Desc)
367 }
368}
369
370macro_rules! typed_value_from {
371 ($ty:ty, $variant:ident) => {
372 impl From<$ty> for TypedValue {
373 fn from(value: $ty) -> Self {
374 Self::$variant(value.into())
375 }
376 }
377 impl From<$ty> for Expr {
378 fn from(value: $ty) -> Self {
379 Self::value(value)
380 }
381 }
382 };
383}
384
385typed_value_from!(i64, Integer);
386typed_value_from!(bool, Boolean);
387typed_value_from!(String, Text);
388typed_value_from!(&str, Text);
389
390impl From<f64> for TypedValue {
391 fn from(value: f64) -> Self {
392 Self::Float(value.into())
393 }
394}
395impl From<f64> for Expr {
396 fn from(value: f64) -> Self {
397 Self::value(value)
398 }
399}
400impl From<TypedValue> for Expr {
401 fn from(value: TypedValue) -> Self {
402 Self::value(value)
403 }
404}
405impl From<Expression> for Expr {
406 fn from(value: Expression) -> Self {
407 Self(value)
408 }
409}
410
411#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
412pub struct Order(pub OrderBy);
413
414impl Order {
415 pub fn new(expression: Expr, direction: SortDirection) -> Self {
416 Self(OrderBy {
417 expression: expression.0,
418 direction,
419 nulls: None,
420 })
421 }
422 pub fn nulls_first(mut self) -> Self {
423 self.0.nulls = Some(NullPlacement::First);
424 self
425 }
426 pub fn nulls_last(mut self) -> Self {
427 self.0.nulls = Some(NullPlacement::Last);
428 self
429 }
430 fn into_ir(self) -> OrderBy {
431 self.0
432 }
433}
434
435#[derive(Debug, Clone, PartialEq, Eq)]
436pub struct DynamicColumn {
437 table: String,
438 descriptor: ColumnDescriptor,
439}
440
441impl DynamicColumn {
442 pub fn name(&self) -> &str {
443 &self.descriptor.name
444 }
445 pub fn descriptor(&self) -> &ColumnDescriptor {
446 &self.descriptor
447 }
448 pub fn expr(&self) -> Expr {
449 Expr::qualified(self.table.clone(), self.descriptor.name.clone())
450 }
451 pub fn eq(&self, value: impl Into<Expr>) -> Expr {
452 self.expr().eq(value)
453 }
454 pub fn asc(&self) -> Order {
455 self.expr().asc()
456 }
457 pub fn desc(&self) -> Order {
458 self.expr().desc()
459 }
460}
461
462impl From<DynamicColumn> for Expr {
463 fn from(value: DynamicColumn) -> Self {
464 value.expr()
465 }
466}
467impl From<&DynamicColumn> for Expr {
468 fn from(value: &DynamicColumn) -> Self {
469 value.expr()
470 }
471}
472
473#[derive(Debug, Clone, PartialEq, Eq)]
474pub struct DynamicEntity {
475 descriptor: TableDescriptor,
476 alias: Option<String>,
477}
478
479impl DynamicEntity {
480 pub fn new(descriptor: TableDescriptor) -> Self {
481 Self {
482 descriptor,
483 alias: None,
484 }
485 }
486 pub fn descriptor(&self) -> &TableDescriptor {
487 &self.descriptor
488 }
489 pub fn alias(mut self, alias: impl Into<String>) -> Self {
490 self.alias = Some(alias.into());
491 self
492 }
493 pub fn column(&self, name: &str) -> Result<DynamicColumn, BuilderError> {
494 let descriptor = self
495 .descriptor
496 .columns
497 .iter()
498 .find(|column| column.name == name)
499 .cloned()
500 .ok_or_else(|| BuilderError::UnknownColumn {
501 table: self.descriptor.name.clone(),
502 column: name.to_string(),
503 })?;
504 Ok(DynamicColumn {
505 table: self
506 .alias
507 .clone()
508 .unwrap_or_else(|| self.descriptor.name.clone()),
509 descriptor,
510 })
511 }
512 pub fn reference(
515 &self,
516 column: &str,
517 key: TypedValue,
518 ) -> Result<DynamicReference, RecordError> {
519 validate_reference_target(&self.descriptor, column)?;
520 let descriptor = self
521 .descriptor
522 .columns
523 .iter()
524 .find(|candidate| candidate.name == column)
525 .ok_or_else(|| RecordError::UnknownField(column.to_string()))?;
526 if matches!(key, TypedValue::Null(_)) {
527 return Err(RecordError::NullReferenceKey);
528 }
529 if !typed_value_matches(&key, &descriptor.data_type) {
530 return Err(RecordError::ValueTypeMismatch {
531 field: format!("{}.{}", self.descriptor.name, column),
532 expected: descriptor.data_type.clone(),
533 actual: key.data_type(),
534 });
535 }
536 Ok(DynamicReference::new(&self.descriptor.name, column, key))
537 }
538 pub fn relation(&self) -> Relation {
539 Relation::Table {
540 name: self.descriptor.name.clone(),
541 alias: self.alias.clone(),
542 }
543 }
544 pub fn query(&self) -> QueryBuilder {
545 QueryBuilder::from_relation(self.relation())
546 }
547 pub fn insert(&self) -> InsertBuilder {
548 InsertBuilder::new(self.descriptor.name.clone())
549 }
550 pub fn update(&self) -> UpdateBuilder {
551 UpdateBuilder::new(self.descriptor.name.clone())
552 }
553 pub fn delete(&self) -> DeleteBuilder {
554 DeleteBuilder::new(self.descriptor.name.clone())
555 }
556}
557
558#[derive(Debug, Clone, PartialEq)]
559pub struct QueryBuilder {
560 select: Select,
561}
562
563impl QueryBuilder {
564 pub fn from_relation(relation: Relation) -> Self {
565 Self {
566 select: Select {
567 from: Some(relation),
568 ..Select::default()
569 },
570 }
571 }
572 pub fn select(mut self, expressions: impl IntoIterator<Item = Expr>) -> Self {
573 self.select.projection = expressions.into_iter().map(Expr::projection).collect();
574 self
575 }
576 pub fn select_projections(mut self, projections: Vec<Projection>) -> Self {
577 self.select.projection = projections;
578 self
579 }
580 pub fn filter(mut self, filter: impl Into<Expr>) -> Self {
581 self.select.filter = Some(filter.into().0);
582 self
583 }
584 pub fn distinct(mut self) -> Self {
585 self.select.distinct = true;
586 self
587 }
588 pub fn distinct_on(mut self, expressions: impl IntoIterator<Item = Expr>) -> Self {
589 self.select.distinct_on = expressions.into_iter().map(|v| v.0).collect();
590 self
591 }
592 pub fn join(mut self, kind: JoinKind, right: Relation, on: Option<Expr>) -> Self {
593 let left = self
594 .select
595 .from
596 .take()
597 .expect("query relation is always present");
598 self.select.from = Some(Relation::Join {
599 left: Box::new(left),
600 right: Box::new(right),
601 kind,
602 on: on.map(|v| v.0),
603 });
604 self
605 }
606 pub fn inner_join(self, right: Relation, on: Expr) -> Self {
607 self.join(JoinKind::Inner, right, Some(on))
608 }
609 pub fn left_join(self, right: Relation, on: Expr) -> Self {
610 self.join(JoinKind::Left, right, Some(on))
611 }
612 pub fn right_join(self, right: Relation, on: Expr) -> Self {
613 self.join(JoinKind::Right, right, Some(on))
614 }
615 pub fn full_join(self, right: Relation, on: Expr) -> Self {
616 self.join(JoinKind::Full, right, Some(on))
617 }
618 pub fn cross_join(self, right: Relation) -> Self {
619 self.join(JoinKind::Cross, right, None)
620 }
621 pub fn group_by(mut self, grouping: Grouping) -> Self {
622 self.select.group_by = Some(grouping);
623 self
624 }
625 pub fn having(mut self, expression: impl Into<Expr>) -> Self {
626 self.select.having = Some(expression.into().0);
627 self
628 }
629 pub fn window(mut self, window: NamedWindow) -> Self {
630 self.select.windows.push(window);
631 self
632 }
633 pub fn order_by(mut self, values: impl IntoIterator<Item = Order>) -> Self {
634 self.select.order_by = values.into_iter().map(Order::into_ir).collect();
635 self
636 }
637 pub fn limit(mut self, value: u64) -> Self {
638 self.select.limit = Some(value);
639 self
640 }
641 pub fn offset(mut self, value: u64) -> Self {
642 self.select.offset = Some(value);
643 self
644 }
645 pub fn with_cte(mut self, cte: CommonTableExpression) -> Self {
646 self.select.ctes.push(cte);
647 self
648 }
649 pub fn recursive(mut self, value: bool) -> Self {
650 self.select.recursive = value;
651 self
652 }
653 pub fn set_operation(mut self, operator: SetOperator, query: QueryBuilder) -> Self {
654 self.select.set_operations.push(SetArm {
655 operator,
656 query: Box::new(query.select),
657 });
658 self
659 }
660 pub fn expected_shape(mut self, shape: Vec<ResultColumnDescriptor>) -> Self {
661 self.select.expected_result_shape = shape;
662 self
663 }
664 pub fn into_select(self) -> Select {
665 self.select
666 }
667 pub fn derived(self, alias: impl Into<String>) -> Relation {
668 Relation::Derived {
669 query: Box::new(self.select),
670 alias: alias.into(),
671 }
672 }
673 pub fn explain(&self, analyze: bool) -> Result<IrDocument, BuilderError> {
674 let operation = self.document()?.payload;
675 Ok(IrDocument::new(Operation::Explain {
676 statement: Explain {
677 analyze,
678 operation: Box::new(operation),
679 },
680 }))
681 }
682 pub fn fetch<S: OrmSession>(
683 self,
684 session: S,
685 ) -> Result<S::QueryOutput, BuilderExecutionError<S::Error>> {
686 let document = self.document()?;
687 session
688 .query_document(&document)
689 .map_err(BuilderExecutionError::Session)
690 }
691
692 pub async fn fetch_async<S: AsyncOrmSession>(
693 self,
694 session: &mut S,
695 ) -> Result<S::QueryOutput, BuilderExecutionError<S::Error>> {
696 let document = self.document()?;
697 session
698 .query_document_async(&document)
699 .await
700 .map_err(BuilderExecutionError::Session)
701 }
702}
703
704impl OrmBuilder for QueryBuilder {
705 fn document(&self) -> Result<IrDocument, BuilderError> {
706 if self.select.projection.is_empty() {
707 return Err(BuilderError::EmptyProjection);
708 }
709 Ok(IrDocument::new(Operation::Select {
710 query: self.select.clone(),
711 }))
712 }
713}
714
715#[derive(Debug, Clone, PartialEq)]
716pub struct InsertBuilder {
717 insert: Insert,
718 upsert: Option<(Vec<String>, Vec<Assignment>)>,
719}
720
721impl InsertBuilder {
722 pub fn new(table: impl Into<String>) -> Self {
723 Self {
724 insert: Insert {
725 table: table.into(),
726 columns: Vec::new(),
727 rows: vec![Vec::new()],
728 source: None,
729 returning: Vec::new(),
730 },
731 upsert: None,
732 }
733 }
734 pub fn value(mut self, column: impl Into<String>, value: impl Into<Expr>) -> Self {
735 self.insert.columns.push(column.into());
736 self.insert.rows[0].push(value.into().0);
737 self
738 }
739 pub fn rows(mut self, columns: Vec<String>, rows: Vec<Vec<Expr>>) -> Self {
740 self.insert.columns = columns;
741 self.insert.rows = rows
742 .into_iter()
743 .map(|row| row.into_iter().map(|v| v.0).collect())
744 .collect();
745 self
746 }
747 pub fn from_select(mut self, columns: Vec<String>, query: QueryBuilder) -> Self {
748 self.insert.columns = columns;
749 self.insert.rows.clear();
750 self.insert.source = Some(Box::new(query.select));
751 self
752 }
753 pub fn returning(mut self, values: impl IntoIterator<Item = Expr>) -> Self {
754 self.insert.returning = values.into_iter().map(Expr::projection).collect();
755 self
756 }
757 pub fn returning_all(mut self) -> Self {
758 self.insert.returning = vec![Expr::star().projection()];
759 self
760 }
761 pub fn on_conflict(mut self, columns: impl IntoIterator<Item = impl Into<String>>) -> Self {
762 self.upsert = Some((columns.into_iter().map(Into::into).collect(), Vec::new()));
763 self
764 }
765 pub fn upsert_on(self, columns: impl IntoIterator<Item = impl Into<String>>) -> Self {
766 self.on_conflict(columns)
767 }
768 pub fn do_update(mut self, column: impl Into<String>, value: impl Into<Expr>) -> Self {
769 self.upsert
770 .get_or_insert_with(|| (Vec::new(), Vec::new()))
771 .1
772 .push(Assignment {
773 column: column.into(),
774 value: value.into().0,
775 });
776 self
777 }
778 pub fn execute<S: OrmSession>(
779 self,
780 session: S,
781 ) -> Result<S::CommandOutput, BuilderExecutionError<S::Error>> {
782 let document = self.document()?;
783 session
784 .execute_document(&document)
785 .map_err(BuilderExecutionError::Session)
786 }
787
788 pub async fn execute_async<S: AsyncOrmSession>(
789 self,
790 session: &mut S,
791 ) -> Result<S::CommandOutput, BuilderExecutionError<S::Error>> {
792 execute_builder_async(&self, session).await
793 }
794}
795
796impl OrmBuilder for InsertBuilder {
797 fn document(&self) -> Result<IrDocument, BuilderError> {
798 if self.insert.columns.is_empty() {
799 return Err(BuilderError::EmptyValues);
800 }
801 let payload = match &self.upsert {
802 Some((columns, assignments)) => Operation::Upsert {
803 statement: Upsert {
804 insert: self.insert.clone(),
805 conflict_columns: columns.clone(),
806 assignments: assignments.clone(),
807 },
808 },
809 None => Operation::Insert {
810 statement: self.insert.clone(),
811 },
812 };
813 Ok(IrDocument::new(payload))
814 }
815}
816
817#[derive(Debug, Clone, PartialEq)]
818pub struct UpdateBuilder {
819 update: Update,
820}
821
822impl UpdateBuilder {
823 pub fn new(table: impl Into<String>) -> Self {
824 Self {
825 update: Update {
826 table: table.into(),
827 alias: None,
828 assignments: Vec::new(),
829 from: None,
830 filter: None,
831 returning: Vec::new(),
832 },
833 }
834 }
835 pub fn set(mut self, column: impl Into<String>, value: impl Into<Expr>) -> Self {
836 self.update.assignments.push(Assignment {
837 column: column.into(),
838 value: value.into().0,
839 });
840 self
841 }
842 pub fn alias(mut self, alias: impl Into<String>) -> Self {
843 self.update.alias = Some(alias.into());
844 self
845 }
846 pub fn filter(mut self, value: impl Into<Expr>) -> Self {
847 self.update.filter = Some(value.into().0);
848 self
849 }
850 pub fn from(mut self, relation: Relation) -> Self {
851 self.update.from = Some(relation);
852 self
853 }
854 pub fn returning(mut self, values: impl IntoIterator<Item = Expr>) -> Self {
855 self.update.returning = values.into_iter().map(Expr::projection).collect();
856 self
857 }
858 pub fn returning_all(mut self) -> Self {
859 self.update.returning = vec![Expr::star().projection()];
860 self
861 }
862 pub fn execute<S: OrmSession>(
863 self,
864 session: S,
865 ) -> Result<S::CommandOutput, BuilderExecutionError<S::Error>> {
866 let document = self.document()?;
867 session
868 .execute_document(&document)
869 .map_err(BuilderExecutionError::Session)
870 }
871
872 pub async fn execute_async<S: AsyncOrmSession>(
873 self,
874 session: &mut S,
875 ) -> Result<S::CommandOutput, BuilderExecutionError<S::Error>> {
876 execute_builder_async(&self, session).await
877 }
878}
879
880impl OrmBuilder for UpdateBuilder {
881 fn document(&self) -> Result<IrDocument, BuilderError> {
882 Ok(IrDocument::new(Operation::Update {
883 statement: self.update.clone(),
884 }))
885 }
886}
887
888#[derive(Debug, Clone, PartialEq)]
889pub struct DeleteBuilder {
890 delete: Delete,
891}
892
893impl DeleteBuilder {
894 pub fn new(table: impl Into<String>) -> Self {
895 Self {
896 delete: Delete {
897 table: table.into(),
898 alias: None,
899 using: None,
900 filter: None,
901 all_rows: false,
902 returning: Vec::new(),
903 },
904 }
905 }
906 pub fn filter(mut self, value: impl Into<Expr>) -> Self {
907 self.delete.filter = Some(value.into().0);
908 self
909 }
910 pub fn alias(mut self, alias: impl Into<String>) -> Self {
911 self.delete.alias = Some(alias.into());
912 self
913 }
914 pub fn all_rows(mut self) -> Self {
915 self.delete.all_rows = true;
916 self
917 }
918 pub fn using(mut self, relation: Relation) -> Self {
919 self.delete.using = Some(relation);
920 self
921 }
922 pub fn returning(mut self, values: impl IntoIterator<Item = Expr>) -> Self {
923 self.delete.returning = values.into_iter().map(Expr::projection).collect();
924 self
925 }
926 pub fn returning_all(mut self) -> Self {
927 self.delete.returning = vec![Expr::star().projection()];
928 self
929 }
930 pub fn execute<S: OrmSession>(
931 self,
932 session: S,
933 ) -> Result<S::CommandOutput, BuilderExecutionError<S::Error>> {
934 let document = self.document()?;
935 session
936 .execute_document(&document)
937 .map_err(BuilderExecutionError::Session)
938 }
939
940 pub async fn execute_async<S: AsyncOrmSession>(
941 self,
942 session: &mut S,
943 ) -> Result<S::CommandOutput, BuilderExecutionError<S::Error>> {
944 execute_builder_async(&self, session).await
945 }
946}
947
948impl OrmBuilder for DeleteBuilder {
949 fn document(&self) -> Result<IrDocument, BuilderError> {
950 Ok(IrDocument::new(Operation::Delete {
951 statement: self.delete.clone(),
952 }))
953 }
954}
955
956#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
957pub struct Column {
958 definition: ColumnDefinition,
959}
960
961impl Column {
962 pub fn new(name: impl Into<String>, data_type: DataTypeDescriptor) -> Self {
963 Self {
964 definition: ColumnDefinition {
965 name: name.into(),
966 data_type,
967 nullable: true,
968 primary_key: false,
969 unique: false,
970 auto_increment: false,
971 default: None,
972 check: None,
973 reference: None,
974 extensions: BTreeMap::new(),
975 },
976 }
977 }
978 pub fn integer(name: impl Into<String>) -> Self {
979 Self::new(name, DataTypeDescriptor::Integer)
980 }
981 pub fn float(name: impl Into<String>) -> Self {
982 Self::new(name, DataTypeDescriptor::Float)
983 }
984 pub fn text(name: impl Into<String>) -> Self {
985 Self::new(name, DataTypeDescriptor::Text)
986 }
987 pub fn boolean(name: impl Into<String>) -> Self {
988 Self::new(name, DataTypeDescriptor::Boolean)
989 }
990 pub fn timestamp(name: impl Into<String>) -> Self {
991 Self::new(name, DataTypeDescriptor::Timestamp)
992 }
993 pub fn date(name: impl Into<String>) -> Self {
994 Self::new(name, DataTypeDescriptor::Date)
995 }
996 pub fn json(name: impl Into<String>) -> Self {
997 Self::new(name, DataTypeDescriptor::Json)
998 }
999 pub fn uuid(name: impl Into<String>) -> Self {
1000 Self::new(name, DataTypeDescriptor::Uuid)
1001 }
1002 pub fn bytes(name: impl Into<String>) -> Self {
1003 Self::new(name, DataTypeDescriptor::Bytes)
1004 }
1005 pub fn decimal(name: impl Into<String>, precision: u8, scale: u8) -> Self {
1006 Self::new(
1007 name,
1008 DataTypeDescriptor::Decimal {
1009 precision: Some(precision),
1010 scale: Some(scale),
1011 },
1012 )
1013 }
1014 pub fn vector(name: impl Into<String>, dimensions: u16) -> Self {
1015 Self::new(name, DataTypeDescriptor::Vector { dimensions })
1016 }
1017 pub fn not_null(mut self, value: bool) -> Self {
1018 self.definition.nullable = !value;
1019 self
1020 }
1021 pub fn primary_key(mut self, value: bool) -> Self {
1022 self.definition.primary_key = value;
1023 if value {
1024 self.definition.nullable = false;
1025 }
1026 self
1027 }
1028 pub fn unique(mut self, value: bool) -> Self {
1029 self.definition.unique = value;
1030 self
1031 }
1032 pub fn auto_increment(mut self, value: bool) -> Self {
1033 self.definition.auto_increment = value;
1034 self
1035 }
1036 pub fn default(mut self, value: impl Into<Expr>) -> Self {
1037 self.definition.default = Some(value.into().0);
1038 self
1039 }
1040 pub fn check(mut self, value: impl Into<Expr>) -> Self {
1041 self.definition.check = Some(value.into().0);
1042 self
1043 }
1044 pub fn reference(mut self, reference: ReferenceDefinition) -> Self {
1045 self.definition.reference = Some(reference);
1046 self
1047 }
1048 pub fn extension(mut self, name: impl Into<String>, value: serde_json::Value) -> Self {
1049 self.definition.extensions.insert(name.into(), value);
1050 self
1051 }
1052 pub fn into_ir(self) -> ColumnDefinition {
1053 self.definition
1054 }
1055}
1056
1057pub fn reference(table: impl Into<String>, column: impl Into<String>) -> ReferenceDefinition {
1058 ReferenceDefinition {
1059 table: table.into(),
1060 column: column.into(),
1061 on_delete: ForeignKeyActionDescriptor::Restrict,
1062 on_update: ForeignKeyActionDescriptor::Restrict,
1063 }
1064}
1065
1066impl ReferenceDefinition {
1067 pub fn on_delete(mut self, action: ForeignKeyActionDescriptor) -> Self {
1068 self.on_delete = action;
1069 self
1070 }
1071
1072 pub fn on_update(mut self, action: ForeignKeyActionDescriptor) -> Self {
1073 self.on_update = action;
1074 self
1075 }
1076}
1077
1078pub fn table(name: impl Into<String>) -> Relation {
1079 Relation::Table {
1080 name: name.into(),
1081 alias: None,
1082 }
1083}
1084
1085pub fn table_as(name: impl Into<String>, alias: impl Into<String>) -> Relation {
1086 Relation::Table {
1087 name: name.into(),
1088 alias: Some(alias.into()),
1089 }
1090}
1091
1092pub fn cte(name: impl Into<String>) -> Relation {
1093 Relation::Cte {
1094 name: name.into(),
1095 alias: None,
1096 }
1097}
1098
1099pub fn values_relation(
1100 rows: Vec<Vec<Expr>>,
1101 alias: impl Into<String>,
1102 columns: impl IntoIterator<Item = impl Into<String>>,
1103) -> Relation {
1104 Relation::Values {
1105 rows: rows
1106 .into_iter()
1107 .map(|row| row.into_iter().map(|value| value.0).collect())
1108 .collect(),
1109 alias: alias.into(),
1110 columns: columns.into_iter().map(Into::into).collect(),
1111 }
1112}
1113
1114#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1115pub enum IndexMethod {
1116 BTree,
1117 Hash,
1118 Bitmap,
1119 Hnsw,
1120}
1121
1122impl IndexMethod {
1123 const fn as_sql(self) -> &'static str {
1124 match self {
1125 Self::BTree => "BTREE",
1126 Self::Hash => "HASH",
1127 Self::Bitmap => "BITMAP",
1128 Self::Hnsw => "HNSW",
1129 }
1130 }
1131}
1132
1133impl IndexDefinition {
1134 pub fn new(
1135 name: impl Into<String>,
1136 table: impl Into<String>,
1137 columns: impl IntoIterator<Item = impl Into<String>>,
1138 ) -> Self {
1139 Self {
1140 name: name.into(),
1141 table: table.into(),
1142 columns: columns.into_iter().map(Into::into).collect(),
1143 unique: false,
1144 if_not_exists: false,
1145 method: None,
1146 predicate: None,
1147 options: BTreeMap::new(),
1148 }
1149 }
1150
1151 pub fn unique(mut self, value: bool) -> Self {
1152 self.unique = value;
1153 self
1154 }
1155
1156 pub fn if_not_exists(mut self, value: bool) -> Self {
1157 self.if_not_exists = value;
1158 self
1159 }
1160
1161 pub fn method(mut self, method: IndexMethod) -> Self {
1162 self.method = Some(method.as_sql().to_string());
1163 self
1164 }
1165
1166 pub fn where_(mut self, predicate: impl Into<Expr>) -> Self {
1167 self.predicate = Some(predicate.into().0);
1168 self
1169 }
1170
1171 pub fn option(mut self, name: impl Into<String>, value: impl Into<TypedValue>) -> Self {
1172 self.options.insert(name.into(), value.into());
1173 self
1174 }
1175}
1176
1177#[derive(Debug, Clone, PartialEq)]
1178pub struct DdlBuilder {
1179 operation: DdlOperation,
1180 error: Option<BuilderError>,
1181}
1182
1183impl DdlBuilder {
1184 pub fn create_table(table: impl Into<String>) -> CreateTableBuilder {
1185 CreateTableBuilder {
1186 table: table.into(),
1187 if_not_exists: false,
1188 columns: Vec::new(),
1189 constraints: Vec::new(),
1190 }
1191 }
1192 pub fn alter_table(table: impl Into<String>) -> AlterTableBuilder {
1193 AlterTableBuilder {
1194 table: table.into(),
1195 action: None,
1196 }
1197 }
1198 pub fn create_table_as(table: impl Into<String>, query: QueryBuilder) -> CreateTableAsBuilder {
1199 CreateTableAsBuilder {
1200 table: table.into(),
1201 if_not_exists: false,
1202 query,
1203 }
1204 }
1205 pub fn drop_table(table: impl Into<String>) -> Self {
1206 Self {
1207 operation: DdlOperation::DropTable {
1208 table: table.into(),
1209 if_exists: false,
1210 },
1211 error: None,
1212 }
1213 }
1214 pub fn truncate_table(table: impl Into<String>) -> Self {
1215 Self {
1216 operation: DdlOperation::TruncateTable {
1217 table: table.into(),
1218 },
1219 error: None,
1220 }
1221 }
1222 pub fn create_index(index: IndexDefinition) -> Self {
1223 Self {
1224 operation: DdlOperation::CreateIndex { index },
1225 error: None,
1226 }
1227 }
1228 pub fn drop_index(table: impl Into<String>, index: impl Into<String>, if_exists: bool) -> Self {
1229 Self {
1230 operation: DdlOperation::DropIndex {
1231 table: table.into(),
1232 index: index.into(),
1233 if_exists,
1234 },
1235 error: None,
1236 }
1237 }
1238 pub fn alter_index(index: impl Into<String>, new_name: impl Into<String>) -> Self {
1239 Self {
1240 operation: DdlOperation::AlterIndex {
1241 index: index.into(),
1242 new_name: new_name.into(),
1243 },
1244 error: None,
1245 }
1246 }
1247 pub fn if_exists(mut self, value: bool) -> Self {
1248 match &mut self.operation {
1249 DdlOperation::DropTable { if_exists, .. }
1250 | DdlOperation::DropIndex { if_exists, .. } => {
1251 *if_exists = value;
1252 }
1253 _ => {
1254 self.error = Some(BuilderError::InvalidOption(
1255 "if_exists is valid only for DROP TABLE or DROP INDEX".to_string(),
1256 ));
1257 }
1258 }
1259 self
1260 }
1261 pub fn execute<S: OrmSession>(
1262 self,
1263 session: S,
1264 ) -> Result<S::CommandOutput, BuilderExecutionError<S::Error>> {
1265 let document = self.document()?;
1266 session
1267 .execute_document(&document)
1268 .map_err(BuilderExecutionError::Session)
1269 }
1270
1271 pub async fn execute_async<S: AsyncOrmSession>(
1272 self,
1273 session: &mut S,
1274 ) -> Result<S::CommandOutput, BuilderExecutionError<S::Error>> {
1275 execute_builder_async(&self, session).await
1276 }
1277}
1278
1279#[derive(Debug, Clone, PartialEq)]
1280pub struct CreateTableAsBuilder {
1281 table: String,
1282 if_not_exists: bool,
1283 query: QueryBuilder,
1284}
1285
1286impl CreateTableAsBuilder {
1287 pub fn if_not_exists(mut self, value: bool) -> Self {
1288 self.if_not_exists = value;
1289 self
1290 }
1291
1292 pub fn execute<S: OrmSession>(
1293 self,
1294 session: S,
1295 ) -> Result<S::CommandOutput, BuilderExecutionError<S::Error>> {
1296 let document = self.document()?;
1297 session
1298 .execute_document(&document)
1299 .map_err(BuilderExecutionError::Session)
1300 }
1301
1302 pub async fn execute_async<S: AsyncOrmSession>(
1303 self,
1304 session: &mut S,
1305 ) -> Result<S::CommandOutput, BuilderExecutionError<S::Error>> {
1306 execute_builder_async(&self, session).await
1307 }
1308}
1309
1310impl OrmBuilder for CreateTableAsBuilder {
1311 fn document(&self) -> Result<IrDocument, BuilderError> {
1312 let query = self.query.document()?;
1313 let Operation::Select { query } = query.payload else {
1314 unreachable!("QueryBuilder always produces SELECT")
1315 };
1316 Ok(IrDocument::new(Operation::Ddl {
1317 operation: DdlOperation::CreateTableAs {
1318 table: self.table.clone(),
1319 if_not_exists: self.if_not_exists,
1320 query: Box::new(query),
1321 },
1322 }))
1323 }
1324}
1325
1326impl OrmBuilder for DdlBuilder {
1327 fn document(&self) -> Result<IrDocument, BuilderError> {
1328 if let Some(error) = &self.error {
1329 return Err(error.clone());
1330 }
1331 Ok(IrDocument::new(Operation::Ddl {
1332 operation: self.operation.clone(),
1333 }))
1334 }
1335}
1336
1337#[derive(Debug, Clone, PartialEq)]
1338pub struct CreateTableBuilder {
1339 table: String,
1340 if_not_exists: bool,
1341 columns: Vec<ColumnDefinition>,
1342 constraints: Vec<ConstraintDefinitionIr>,
1343}
1344
1345impl CreateTableBuilder {
1346 pub fn if_not_exists(mut self, value: bool) -> Self {
1347 self.if_not_exists = value;
1348 self
1349 }
1350 pub fn column(mut self, column: Column) -> Self {
1351 self.columns.push(column.into_ir());
1352 self
1353 }
1354 pub fn constraint(mut self, constraint: ConstraintDefinitionIr) -> Self {
1355 self.constraints.push(constraint);
1356 self
1357 }
1358 pub fn execute<S: OrmSession>(
1359 self,
1360 session: S,
1361 ) -> Result<S::CommandOutput, BuilderExecutionError<S::Error>> {
1362 let document = self.document()?;
1363 session
1364 .execute_document(&document)
1365 .map_err(BuilderExecutionError::Session)
1366 }
1367
1368 pub async fn execute_async<S: AsyncOrmSession>(
1369 self,
1370 session: &mut S,
1371 ) -> Result<S::CommandOutput, BuilderExecutionError<S::Error>> {
1372 execute_builder_async(&self, session).await
1373 }
1374}
1375
1376impl OrmBuilder for CreateTableBuilder {
1377 fn document(&self) -> Result<IrDocument, BuilderError> {
1378 Ok(IrDocument::new(Operation::Ddl {
1379 operation: DdlOperation::CreateTable {
1380 table: self.table.clone(),
1381 if_not_exists: self.if_not_exists,
1382 columns: self.columns.clone(),
1383 constraints: self.constraints.clone(),
1384 },
1385 }))
1386 }
1387}
1388
1389#[derive(Debug, Clone, PartialEq)]
1390pub struct AlterTableBuilder {
1391 table: String,
1392 action: Option<AlterTableAction>,
1393}
1394
1395impl AlterTableBuilder {
1396 fn action(mut self, action: AlterTableAction) -> Self {
1397 self.action = Some(action);
1398 self
1399 }
1400 pub fn add_column(self, column: Column) -> Self {
1401 self.action(AlterTableAction::AddColumn {
1402 column: column.into_ir(),
1403 })
1404 }
1405 pub fn modify_column(self, column: Column) -> Self {
1406 self.action(AlterTableAction::ModifyColumn {
1407 column: column.into_ir(),
1408 })
1409 }
1410 pub fn drop_column(self, column: impl Into<String>) -> Self {
1411 self.action(AlterTableAction::DropColumn {
1412 column: column.into(),
1413 })
1414 }
1415 pub fn rename_column(self, from: impl Into<String>, to: impl Into<String>) -> Self {
1416 self.action(AlterTableAction::RenameColumn {
1417 from: from.into(),
1418 to: to.into(),
1419 })
1420 }
1421 pub fn rename_table(self, to: impl Into<String>) -> Self {
1422 self.action(AlterTableAction::RenameTable { to: to.into() })
1423 }
1424 pub fn add_constraint(self, constraint: ConstraintDefinitionIr) -> Self {
1425 self.action(AlterTableAction::AddConstraint { constraint })
1426 }
1427 pub fn drop_constraint(self, name: impl Into<String>, if_exists: bool) -> Self {
1428 self.action(AlterTableAction::DropConstraint {
1429 name: name.into(),
1430 if_exists,
1431 })
1432 }
1433 pub fn execute<S: OrmSession>(
1434 self,
1435 session: S,
1436 ) -> Result<S::CommandOutput, BuilderExecutionError<S::Error>> {
1437 let document = self.document()?;
1438 session
1439 .execute_document(&document)
1440 .map_err(BuilderExecutionError::Session)
1441 }
1442
1443 pub async fn execute_async<S: AsyncOrmSession>(
1444 self,
1445 session: &mut S,
1446 ) -> Result<S::CommandOutput, BuilderExecutionError<S::Error>> {
1447 execute_builder_async(&self, session).await
1448 }
1449}
1450
1451impl OrmBuilder for AlterTableBuilder {
1452 fn document(&self) -> Result<IrDocument, BuilderError> {
1453 let action = self
1454 .action
1455 .clone()
1456 .ok_or(BuilderError::AlterActionCardinality)?;
1457 Ok(IrDocument::new(Operation::Ddl {
1458 operation: DdlOperation::AlterTable {
1459 table: self.table.clone(),
1460 action,
1461 },
1462 }))
1463 }
1464}
1465
1466pub fn primary_key(columns: impl IntoIterator<Item = impl Into<String>>) -> ConstraintDefinitionIr {
1467 ConstraintDefinitionIr::PrimaryKey {
1468 columns: columns.into_iter().map(Into::into).collect(),
1469 }
1470}
1471pub fn unique(columns: impl IntoIterator<Item = impl Into<String>>) -> ConstraintDefinitionIr {
1472 ConstraintDefinitionIr::Unique {
1473 columns: columns.into_iter().map(Into::into).collect(),
1474 }
1475}
1476pub fn check(expression: impl Into<Expr>) -> ConstraintDefinitionIr {
1477 ConstraintDefinitionIr::Check {
1478 expression: expression.into().0,
1479 }
1480}
1481
1482#[cfg(test)]
1483mod tests {
1484 use super::*;
1485
1486 #[test]
1487 fn generated_and_dynamic_facades_produce_identical_ir() {
1488 let descriptor = TableDescriptor {
1489 catalog_id: "catalog".to_string(),
1490 name: "people".to_string(),
1491 schema_generation: 1,
1492 fingerprint: "fingerprint".to_string(),
1493 created_at: "2026-08-21T00:00:00Z".to_string(),
1494 updated_at: "2026-08-21T00:00:00Z".to_string(),
1495 columns: vec![
1496 ColumnDescriptor {
1497 ordinal: 0,
1498 name: "id".to_string(),
1499 data_type: DataTypeDescriptor::Integer,
1500 nullable: false,
1501 auto_increment: false,
1502 default_expression: None,
1503 extensions: BTreeMap::new(),
1504 },
1505 ColumnDescriptor {
1506 ordinal: 1,
1507 name: "name".to_string(),
1508 data_type: DataTypeDescriptor::Text,
1509 nullable: false,
1510 auto_increment: false,
1511 default_expression: None,
1512 extensions: BTreeMap::new(),
1513 },
1514 ],
1515 constraints: Vec::new(),
1516 indexes: Vec::new(),
1517 extensions: BTreeMap::new(),
1518 };
1519 let dynamic = DynamicEntity::new(descriptor);
1520 let dynamic_query = dynamic
1521 .query()
1522 .select([
1523 dynamic.column("id").unwrap().expr(),
1524 dynamic.column("name").unwrap().expr(),
1525 ])
1526 .filter(dynamic.column("id").unwrap().eq(7_i64));
1527
1528 let id = TypedColumn::<i64>::new("people", "id", DataTypeDescriptor::Integer, false);
1529 let name = TypedColumn::<String>::new("people", "name", DataTypeDescriptor::Text, false);
1530 let generated_query = QueryBuilder::from_relation(table("people"))
1531 .select([id.clone().expr(), name.expr()])
1532 .filter(id.eq(7_i64));
1533 assert_eq!(
1534 dynamic_query.document().unwrap(),
1535 generated_query.document().unwrap()
1536 );
1537 }
1538
1539 #[test]
1540 fn dynamic_builders_share_canonical_ir_and_reject_unsafe_delete() {
1541 let create = DdlBuilder::create_table("people")
1542 .column(Column::integer("id").primary_key(true))
1543 .column(Column::text("name").not_null(true));
1544 assert_eq!(
1545 create.to_sql().unwrap().sql,
1546 "CREATE TABLE \"people\" (\"id\" INTEGER PRIMARY KEY, \"name\" TEXT NOT NULL)"
1547 );
1548 assert_eq!(
1549 IrDocument::from_json(&create.to_json().unwrap()).unwrap(),
1550 create.document().unwrap()
1551 );
1552
1553 let source = QueryBuilder::from_relation(table("people")).select([Expr::column("id")]);
1554 assert_eq!(
1555 DdlBuilder::create_table_as("people_copy", source)
1556 .if_not_exists(true)
1557 .to_sql()
1558 .unwrap()
1559 .sql,
1560 "CREATE TABLE IF NOT EXISTS \"people_copy\" AS SELECT \"id\" FROM \"people\""
1561 );
1562 assert_eq!(
1563 DdlBuilder::alter_index("idx_people", "idx_people_new")
1564 .to_sql()
1565 .unwrap()
1566 .sql,
1567 "ALTER INDEX \"idx_people\" RENAME TO \"idx_people_new\""
1568 );
1569 assert_eq!(
1570 DdlBuilder::drop_table("people")
1571 .if_exists(true)
1572 .to_sql()
1573 .unwrap()
1574 .sql,
1575 "DROP TABLE IF EXISTS \"people\""
1576 );
1577 assert!(matches!(
1578 DdlBuilder::truncate_table("people")
1579 .if_exists(true)
1580 .document(),
1581 Err(BuilderError::InvalidOption(_))
1582 ));
1583 assert_eq!(
1584 DdlBuilder::create_index(
1585 IndexDefinition::new("idx_people_name", "people", ["name"])
1586 .if_not_exists(true)
1587 .method(IndexMethod::Hash),
1588 )
1589 .to_sql()
1590 .unwrap()
1591 .sql,
1592 "CREATE INDEX IF NOT EXISTS \"idx_people_name\" ON \"people\" (\"name\") USING \"HASH\""
1593 );
1594
1595 let descriptor = TableDescriptor {
1596 catalog_id: "00000000-0000-0000-0000-000000000001".to_string(),
1597 name: "people".to_string(),
1598 schema_generation: 1,
1599 fingerprint: "f".to_string(),
1600 created_at: "x".to_string(),
1601 updated_at: "x".to_string(),
1602 columns: vec![ColumnDescriptor {
1603 ordinal: 0,
1604 name: "id".to_string(),
1605 data_type: DataTypeDescriptor::Integer,
1606 nullable: false,
1607 auto_increment: false,
1608 default_expression: None,
1609 extensions: BTreeMap::new(),
1610 }],
1611 constraints: Vec::new(),
1612 indexes: Vec::new(),
1613 extensions: BTreeMap::new(),
1614 };
1615 let entity = DynamicEntity::new(descriptor);
1616 assert!(matches!(
1617 entity.column("missing"),
1618 Err(BuilderError::UnknownColumn { .. })
1619 ));
1620 let query = entity
1621 .query()
1622 .select([entity.column("id").unwrap().expr()])
1623 .filter(entity.column("id").unwrap().eq(7_i64));
1624 let compiled = query.to_sql().unwrap();
1625 assert_eq!(compiled.parameters, vec![TypedValue::Integer(7)]);
1626 assert!(DeleteBuilder::new("people").to_sql().is_err());
1627 }
1628}