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 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}