Skip to main content

radixdb_orm/
dynamic.rs

1//! Connection-independent dynamic ORM builders.
2//!
3//! These builders only produce canonical IR. Execution is delegated to an
4//! [`OrmSession`], keeping SQL, TCP and embedded transaction ownership outside
5//! the builder graph.
6
7use 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    // These names are the deliberate fluent ORM vocabulary. Implementing the
204    // operator traits would make generic expression operands less ergonomic
205    // and would not replace the method-call API serialized in examples.
206    #[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    /// Build a key-only reference after proving that `column` is a
513    /// one-column PRIMARY KEY or UNIQUE NOT NULL key in this descriptor.
514    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}