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 double_precision(name: impl Into<String>) -> Self {
985 Self::new(name, DataTypeDescriptor::DoublePrecision)
986 }
987 pub fn text(name: impl Into<String>) -> Self {
988 Self::new(name, DataTypeDescriptor::Text { max_chars: None })
989 }
990 pub fn boolean(name: impl Into<String>) -> Self {
991 Self::new(name, DataTypeDescriptor::Boolean)
992 }
993 pub fn timestamp(name: impl Into<String>) -> Self {
994 Self::new(name, DataTypeDescriptor::Timestamp)
995 }
996 pub fn date(name: impl Into<String>) -> Self {
997 Self::new(name, DataTypeDescriptor::Date)
998 }
999 pub fn json(name: impl Into<String>) -> Self {
1000 Self::new(name, DataTypeDescriptor::Json)
1001 }
1002 pub fn uuid(name: impl Into<String>) -> Self {
1003 Self::new(name, DataTypeDescriptor::Uuid)
1004 }
1005 pub fn bytes(name: impl Into<String>) -> Self {
1006 Self::new(name, DataTypeDescriptor::Bytes)
1007 }
1008 pub fn decimal(name: impl Into<String>, precision: u8, scale: u8) -> Self {
1009 Self::new(
1010 name,
1011 DataTypeDescriptor::Decimal {
1012 precision: Some(precision),
1013 scale: Some(scale),
1014 },
1015 )
1016 }
1017 pub fn vector(name: impl Into<String>, dimensions: u16) -> Self {
1018 Self::new(name, DataTypeDescriptor::Vector { dimensions })
1019 }
1020 pub fn not_null(mut self, value: bool) -> Self {
1021 self.definition.nullable = !value;
1022 self
1023 }
1024 pub fn primary_key(mut self, value: bool) -> Self {
1025 self.definition.primary_key = value;
1026 if value {
1027 self.definition.nullable = false;
1028 }
1029 self
1030 }
1031 pub fn unique(mut self, value: bool) -> Self {
1032 self.definition.unique = value;
1033 self
1034 }
1035 pub fn auto_increment(mut self, value: bool) -> Self {
1036 self.definition.auto_increment = value;
1037 self
1038 }
1039 pub fn default(mut self, value: impl Into<Expr>) -> Self {
1040 self.definition.default = Some(value.into().0);
1041 self
1042 }
1043 pub fn check(mut self, value: impl Into<Expr>) -> Self {
1044 self.definition.check = Some(value.into().0);
1045 self
1046 }
1047 pub fn reference(mut self, reference: ReferenceDefinition) -> Self {
1048 self.definition.reference = Some(reference);
1049 self
1050 }
1051 pub fn extension(mut self, name: impl Into<String>, value: serde_json::Value) -> Self {
1052 self.definition.extensions.insert(name.into(), value);
1053 self
1054 }
1055 pub fn into_ir(self) -> ColumnDefinition {
1056 self.definition
1057 }
1058}
1059
1060pub fn reference(table: impl Into<String>, column: impl Into<String>) -> ReferenceDefinition {
1061 ReferenceDefinition {
1062 table: table.into(),
1063 column: column.into(),
1064 on_delete: ForeignKeyActionDescriptor::Restrict,
1065 on_update: ForeignKeyActionDescriptor::Restrict,
1066 }
1067}
1068
1069impl ReferenceDefinition {
1070 pub fn on_delete(mut self, action: ForeignKeyActionDescriptor) -> Self {
1071 self.on_delete = action;
1072 self
1073 }
1074
1075 pub fn on_update(mut self, action: ForeignKeyActionDescriptor) -> Self {
1076 self.on_update = action;
1077 self
1078 }
1079}
1080
1081pub fn table(name: impl Into<String>) -> Relation {
1082 Relation::Table {
1083 name: name.into(),
1084 alias: None,
1085 }
1086}
1087
1088pub fn table_as(name: impl Into<String>, alias: impl Into<String>) -> Relation {
1089 Relation::Table {
1090 name: name.into(),
1091 alias: Some(alias.into()),
1092 }
1093}
1094
1095pub fn cte(name: impl Into<String>) -> Relation {
1096 Relation::Cte {
1097 name: name.into(),
1098 alias: None,
1099 }
1100}
1101
1102pub fn values_relation(
1103 rows: Vec<Vec<Expr>>,
1104 alias: impl Into<String>,
1105 columns: impl IntoIterator<Item = impl Into<String>>,
1106) -> Relation {
1107 Relation::Values {
1108 rows: rows
1109 .into_iter()
1110 .map(|row| row.into_iter().map(|value| value.0).collect())
1111 .collect(),
1112 alias: alias.into(),
1113 columns: columns.into_iter().map(Into::into).collect(),
1114 }
1115}
1116
1117#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1118pub enum IndexMethod {
1119 BTree,
1120 Hash,
1121 Bitmap,
1122 Hnsw,
1123}
1124
1125impl IndexMethod {
1126 const fn as_sql(self) -> &'static str {
1127 match self {
1128 Self::BTree => "BTREE",
1129 Self::Hash => "HASH",
1130 Self::Bitmap => "BITMAP",
1131 Self::Hnsw => "HNSW",
1132 }
1133 }
1134}
1135
1136impl IndexDefinition {
1137 pub fn new(
1138 name: impl Into<String>,
1139 table: impl Into<String>,
1140 columns: impl IntoIterator<Item = impl Into<String>>,
1141 ) -> Self {
1142 Self {
1143 name: name.into(),
1144 table: table.into(),
1145 columns: columns.into_iter().map(Into::into).collect(),
1146 unique: false,
1147 if_not_exists: false,
1148 method: None,
1149 predicate: None,
1150 options: BTreeMap::new(),
1151 }
1152 }
1153
1154 pub fn unique(mut self, value: bool) -> Self {
1155 self.unique = value;
1156 self
1157 }
1158
1159 pub fn if_not_exists(mut self, value: bool) -> Self {
1160 self.if_not_exists = value;
1161 self
1162 }
1163
1164 pub fn method(mut self, method: IndexMethod) -> Self {
1165 self.method = Some(method.as_sql().to_string());
1166 self
1167 }
1168
1169 pub fn where_(mut self, predicate: impl Into<Expr>) -> Self {
1170 self.predicate = Some(predicate.into().0);
1171 self
1172 }
1173
1174 pub fn option(mut self, name: impl Into<String>, value: impl Into<TypedValue>) -> Self {
1175 self.options.insert(name.into(), value.into());
1176 self
1177 }
1178}
1179
1180#[derive(Debug, Clone, PartialEq)]
1181pub struct DdlBuilder {
1182 operation: DdlOperation,
1183 error: Option<BuilderError>,
1184}
1185
1186impl DdlBuilder {
1187 pub fn create_table(table: impl Into<String>) -> CreateTableBuilder {
1188 CreateTableBuilder {
1189 table: table.into(),
1190 if_not_exists: false,
1191 columns: Vec::new(),
1192 constraints: Vec::new(),
1193 }
1194 }
1195 pub fn alter_table(table: impl Into<String>) -> AlterTableBuilder {
1196 AlterTableBuilder {
1197 table: table.into(),
1198 action: None,
1199 }
1200 }
1201 pub fn create_table_as(table: impl Into<String>, query: QueryBuilder) -> CreateTableAsBuilder {
1202 CreateTableAsBuilder {
1203 table: table.into(),
1204 if_not_exists: false,
1205 query,
1206 }
1207 }
1208 pub fn drop_table(table: impl Into<String>) -> Self {
1209 Self {
1210 operation: DdlOperation::DropTable {
1211 table: table.into(),
1212 if_exists: false,
1213 },
1214 error: None,
1215 }
1216 }
1217 pub fn truncate_table(table: impl Into<String>) -> Self {
1218 Self {
1219 operation: DdlOperation::TruncateTable {
1220 table: table.into(),
1221 },
1222 error: None,
1223 }
1224 }
1225 pub fn create_index(index: IndexDefinition) -> Self {
1226 Self {
1227 operation: DdlOperation::CreateIndex { index },
1228 error: None,
1229 }
1230 }
1231 pub fn drop_index(table: impl Into<String>, index: impl Into<String>, if_exists: bool) -> Self {
1232 Self {
1233 operation: DdlOperation::DropIndex {
1234 table: table.into(),
1235 index: index.into(),
1236 if_exists,
1237 },
1238 error: None,
1239 }
1240 }
1241 pub fn alter_index(index: impl Into<String>, new_name: impl Into<String>) -> Self {
1242 Self {
1243 operation: DdlOperation::AlterIndex {
1244 index: index.into(),
1245 new_name: new_name.into(),
1246 },
1247 error: None,
1248 }
1249 }
1250 pub fn if_exists(mut self, value: bool) -> Self {
1251 match &mut self.operation {
1252 DdlOperation::DropTable { if_exists, .. }
1253 | DdlOperation::DropIndex { if_exists, .. } => {
1254 *if_exists = value;
1255 }
1256 _ => {
1257 self.error = Some(BuilderError::InvalidOption(
1258 "if_exists is valid only for DROP TABLE or DROP INDEX".to_string(),
1259 ));
1260 }
1261 }
1262 self
1263 }
1264 pub fn execute<S: OrmSession>(
1265 self,
1266 session: S,
1267 ) -> Result<S::CommandOutput, BuilderExecutionError<S::Error>> {
1268 let document = self.document()?;
1269 session
1270 .execute_document(&document)
1271 .map_err(BuilderExecutionError::Session)
1272 }
1273
1274 pub async fn execute_async<S: AsyncOrmSession>(
1275 self,
1276 session: &mut S,
1277 ) -> Result<S::CommandOutput, BuilderExecutionError<S::Error>> {
1278 execute_builder_async(&self, session).await
1279 }
1280}
1281
1282#[derive(Debug, Clone, PartialEq)]
1283pub struct CreateTableAsBuilder {
1284 table: String,
1285 if_not_exists: bool,
1286 query: QueryBuilder,
1287}
1288
1289impl CreateTableAsBuilder {
1290 pub fn if_not_exists(mut self, value: bool) -> Self {
1291 self.if_not_exists = value;
1292 self
1293 }
1294
1295 pub fn execute<S: OrmSession>(
1296 self,
1297 session: S,
1298 ) -> Result<S::CommandOutput, BuilderExecutionError<S::Error>> {
1299 let document = self.document()?;
1300 session
1301 .execute_document(&document)
1302 .map_err(BuilderExecutionError::Session)
1303 }
1304
1305 pub async fn execute_async<S: AsyncOrmSession>(
1306 self,
1307 session: &mut S,
1308 ) -> Result<S::CommandOutput, BuilderExecutionError<S::Error>> {
1309 execute_builder_async(&self, session).await
1310 }
1311}
1312
1313impl OrmBuilder for CreateTableAsBuilder {
1314 fn document(&self) -> Result<IrDocument, BuilderError> {
1315 let query = self.query.document()?;
1316 let Operation::Select { query } = query.payload else {
1317 unreachable!("QueryBuilder always produces SELECT")
1318 };
1319 Ok(IrDocument::new(Operation::Ddl {
1320 operation: DdlOperation::CreateTableAs {
1321 table: self.table.clone(),
1322 if_not_exists: self.if_not_exists,
1323 query: Box::new(query),
1324 },
1325 }))
1326 }
1327}
1328
1329impl OrmBuilder for DdlBuilder {
1330 fn document(&self) -> Result<IrDocument, BuilderError> {
1331 if let Some(error) = &self.error {
1332 return Err(error.clone());
1333 }
1334 Ok(IrDocument::new(Operation::Ddl {
1335 operation: self.operation.clone(),
1336 }))
1337 }
1338}
1339
1340#[derive(Debug, Clone, PartialEq)]
1341pub struct CreateTableBuilder {
1342 table: String,
1343 if_not_exists: bool,
1344 columns: Vec<ColumnDefinition>,
1345 constraints: Vec<ConstraintDefinitionIr>,
1346}
1347
1348impl CreateTableBuilder {
1349 pub fn if_not_exists(mut self, value: bool) -> Self {
1350 self.if_not_exists = value;
1351 self
1352 }
1353 pub fn column(mut self, column: Column) -> Self {
1354 self.columns.push(column.into_ir());
1355 self
1356 }
1357 pub fn constraint(mut self, constraint: ConstraintDefinitionIr) -> Self {
1358 self.constraints.push(constraint);
1359 self
1360 }
1361 pub fn execute<S: OrmSession>(
1362 self,
1363 session: S,
1364 ) -> Result<S::CommandOutput, BuilderExecutionError<S::Error>> {
1365 let document = self.document()?;
1366 session
1367 .execute_document(&document)
1368 .map_err(BuilderExecutionError::Session)
1369 }
1370
1371 pub async fn execute_async<S: AsyncOrmSession>(
1372 self,
1373 session: &mut S,
1374 ) -> Result<S::CommandOutput, BuilderExecutionError<S::Error>> {
1375 execute_builder_async(&self, session).await
1376 }
1377}
1378
1379impl OrmBuilder for CreateTableBuilder {
1380 fn document(&self) -> Result<IrDocument, BuilderError> {
1381 Ok(IrDocument::new(Operation::Ddl {
1382 operation: DdlOperation::CreateTable {
1383 table: self.table.clone(),
1384 if_not_exists: self.if_not_exists,
1385 columns: self.columns.clone(),
1386 constraints: self.constraints.clone(),
1387 },
1388 }))
1389 }
1390}
1391
1392#[derive(Debug, Clone, PartialEq)]
1393pub struct AlterTableBuilder {
1394 table: String,
1395 action: Option<AlterTableAction>,
1396}
1397
1398impl AlterTableBuilder {
1399 fn action(mut self, action: AlterTableAction) -> Self {
1400 self.action = Some(action);
1401 self
1402 }
1403 pub fn add_column(self, column: Column) -> Self {
1404 self.action(AlterTableAction::AddColumn {
1405 column: column.into_ir(),
1406 })
1407 }
1408 pub fn modify_column(self, column: Column) -> Self {
1409 self.action(AlterTableAction::ModifyColumn {
1410 column: column.into_ir(),
1411 })
1412 }
1413 pub fn drop_column(self, column: impl Into<String>) -> Self {
1414 self.action(AlterTableAction::DropColumn {
1415 column: column.into(),
1416 })
1417 }
1418 pub fn rename_column(self, from: impl Into<String>, to: impl Into<String>) -> Self {
1419 self.action(AlterTableAction::RenameColumn {
1420 from: from.into(),
1421 to: to.into(),
1422 })
1423 }
1424 pub fn rename_table(self, to: impl Into<String>) -> Self {
1425 self.action(AlterTableAction::RenameTable { to: to.into() })
1426 }
1427 pub fn add_constraint(self, constraint: ConstraintDefinitionIr) -> Self {
1428 self.action(AlterTableAction::AddConstraint { constraint })
1429 }
1430 pub fn drop_constraint(self, name: impl Into<String>, if_exists: bool) -> Self {
1431 self.action(AlterTableAction::DropConstraint {
1432 name: name.into(),
1433 if_exists,
1434 })
1435 }
1436 pub fn execute<S: OrmSession>(
1437 self,
1438 session: S,
1439 ) -> Result<S::CommandOutput, BuilderExecutionError<S::Error>> {
1440 let document = self.document()?;
1441 session
1442 .execute_document(&document)
1443 .map_err(BuilderExecutionError::Session)
1444 }
1445
1446 pub async fn execute_async<S: AsyncOrmSession>(
1447 self,
1448 session: &mut S,
1449 ) -> Result<S::CommandOutput, BuilderExecutionError<S::Error>> {
1450 execute_builder_async(&self, session).await
1451 }
1452}
1453
1454impl OrmBuilder for AlterTableBuilder {
1455 fn document(&self) -> Result<IrDocument, BuilderError> {
1456 let action = self
1457 .action
1458 .clone()
1459 .ok_or(BuilderError::AlterActionCardinality)?;
1460 Ok(IrDocument::new(Operation::Ddl {
1461 operation: DdlOperation::AlterTable {
1462 table: self.table.clone(),
1463 action,
1464 },
1465 }))
1466 }
1467}
1468
1469pub fn primary_key(columns: impl IntoIterator<Item = impl Into<String>>) -> ConstraintDefinitionIr {
1470 ConstraintDefinitionIr::PrimaryKey {
1471 columns: columns.into_iter().map(Into::into).collect(),
1472 }
1473}
1474pub fn unique(columns: impl IntoIterator<Item = impl Into<String>>) -> ConstraintDefinitionIr {
1475 ConstraintDefinitionIr::Unique {
1476 columns: columns.into_iter().map(Into::into).collect(),
1477 }
1478}
1479pub fn check(expression: impl Into<Expr>) -> ConstraintDefinitionIr {
1480 ConstraintDefinitionIr::Check {
1481 expression: expression.into().0,
1482 }
1483}
1484
1485#[cfg(test)]
1486mod tests {
1487 use super::*;
1488
1489 #[test]
1490 fn generated_and_dynamic_facades_produce_identical_ir() {
1491 let descriptor = TableDescriptor {
1492 catalog_id: "catalog".to_string(),
1493 name: "people".to_string(),
1494 schema_generation: 1,
1495 fingerprint: "fingerprint".to_string(),
1496 created_at: "2026-08-21T00:00:00Z".to_string(),
1497 updated_at: "2026-08-21T00:00:00Z".to_string(),
1498 columns: vec![
1499 ColumnDescriptor {
1500 ordinal: 0,
1501 name: "id".to_string(),
1502 data_type: DataTypeDescriptor::Integer,
1503 nullable: false,
1504 auto_increment: false,
1505 default_expression: None,
1506 extensions: BTreeMap::new(),
1507 },
1508 ColumnDescriptor {
1509 ordinal: 1,
1510 name: "name".to_string(),
1511 data_type: DataTypeDescriptor::Text { max_chars: None },
1512 nullable: false,
1513 auto_increment: false,
1514 default_expression: None,
1515 extensions: BTreeMap::new(),
1516 },
1517 ],
1518 constraints: Vec::new(),
1519 indexes: Vec::new(),
1520 extensions: BTreeMap::new(),
1521 };
1522 let dynamic = DynamicEntity::new(descriptor);
1523 let dynamic_query = dynamic
1524 .query()
1525 .select([
1526 dynamic.column("id").unwrap().expr(),
1527 dynamic.column("name").unwrap().expr(),
1528 ])
1529 .filter(dynamic.column("id").unwrap().eq(7_i64));
1530
1531 let id = TypedColumn::<i64>::new("people", "id", DataTypeDescriptor::Integer, false);
1532 let name = TypedColumn::<String>::new(
1533 "people",
1534 "name",
1535 DataTypeDescriptor::Text { max_chars: None },
1536 false,
1537 );
1538 let generated_query = QueryBuilder::from_relation(table("people"))
1539 .select([id.clone().expr(), name.expr()])
1540 .filter(id.eq(7_i64));
1541 assert_eq!(
1542 dynamic_query.document().unwrap(),
1543 generated_query.document().unwrap()
1544 );
1545 }
1546
1547 #[test]
1548 fn dynamic_builders_share_canonical_ir_and_reject_unsafe_delete() {
1549 let create = DdlBuilder::create_table("people")
1550 .column(Column::integer("id").primary_key(true))
1551 .column(Column::text("name").not_null(true));
1552 assert_eq!(
1553 create.to_sql().unwrap().sql,
1554 "CREATE TABLE \"people\" (\"id\" INTEGER PRIMARY KEY, \"name\" TEXT NOT NULL)"
1555 );
1556 assert_eq!(
1557 IrDocument::from_json(&create.to_json().unwrap()).unwrap(),
1558 create.document().unwrap()
1559 );
1560
1561 let source = QueryBuilder::from_relation(table("people")).select([Expr::column("id")]);
1562 assert_eq!(
1563 DdlBuilder::create_table_as("people_copy", source)
1564 .if_not_exists(true)
1565 .to_sql()
1566 .unwrap()
1567 .sql,
1568 "CREATE TABLE IF NOT EXISTS \"people_copy\" AS SELECT \"id\" FROM \"people\""
1569 );
1570 assert_eq!(
1571 DdlBuilder::alter_index("idx_people", "idx_people_new")
1572 .to_sql()
1573 .unwrap()
1574 .sql,
1575 "ALTER INDEX \"idx_people\" RENAME TO \"idx_people_new\""
1576 );
1577 assert_eq!(
1578 DdlBuilder::drop_table("people")
1579 .if_exists(true)
1580 .to_sql()
1581 .unwrap()
1582 .sql,
1583 "DROP TABLE IF EXISTS \"people\""
1584 );
1585 assert!(matches!(
1586 DdlBuilder::truncate_table("people")
1587 .if_exists(true)
1588 .document(),
1589 Err(BuilderError::InvalidOption(_))
1590 ));
1591 assert_eq!(
1592 DdlBuilder::create_index(
1593 IndexDefinition::new("idx_people_name", "people", ["name"])
1594 .if_not_exists(true)
1595 .method(IndexMethod::Hash),
1596 )
1597 .to_sql()
1598 .unwrap()
1599 .sql,
1600 "CREATE INDEX IF NOT EXISTS \"idx_people_name\" ON \"people\" (\"name\") USING \"HASH\""
1601 );
1602
1603 let descriptor = TableDescriptor {
1604 catalog_id: "00000000-0000-0000-0000-000000000001".to_string(),
1605 name: "people".to_string(),
1606 schema_generation: 1,
1607 fingerprint: "f".to_string(),
1608 created_at: "x".to_string(),
1609 updated_at: "x".to_string(),
1610 columns: vec![ColumnDescriptor {
1611 ordinal: 0,
1612 name: "id".to_string(),
1613 data_type: DataTypeDescriptor::Integer,
1614 nullable: false,
1615 auto_increment: false,
1616 default_expression: None,
1617 extensions: BTreeMap::new(),
1618 }],
1619 constraints: Vec::new(),
1620 indexes: Vec::new(),
1621 extensions: BTreeMap::new(),
1622 };
1623 let entity = DynamicEntity::new(descriptor);
1624 assert!(matches!(
1625 entity.column("missing"),
1626 Err(BuilderError::UnknownColumn { .. })
1627 ));
1628 let query = entity
1629 .query()
1630 .select([entity.column("id").unwrap().expr()])
1631 .filter(entity.column("id").unwrap().eq(7_i64));
1632 let compiled = query.to_sql().unwrap();
1633 assert_eq!(compiled.parameters, vec![TypedValue::Integer(7)]);
1634 assert!(DeleteBuilder::new("people").to_sql().is_err());
1635 }
1636}