1use std::collections::{BTreeMap, BTreeSet};
4use std::marker::PhantomData;
5
6use serde::{Deserialize, Serialize};
7
8use crate::*;
9
10macro_rules! typed_expression_methods {
11 () => {
12 pub fn eq(self, value: impl Into<Expr>) -> Expr {
13 self.expr().eq(value)
14 }
15 pub fn ne(self, value: impl Into<Expr>) -> Expr {
16 self.expr().ne(value)
17 }
18 pub fn lt(self, value: impl Into<Expr>) -> Expr {
19 self.expr().lt(value)
20 }
21 pub fn lte(self, value: impl Into<Expr>) -> Expr {
22 self.expr().lte(value)
23 }
24 pub fn gt(self, value: impl Into<Expr>) -> Expr {
25 self.expr().gt(value)
26 }
27 pub fn gte(self, value: impl Into<Expr>) -> Expr {
28 self.expr().gte(value)
29 }
30 pub fn is_null(self) -> Expr {
31 self.expr().is_null()
32 }
33 pub fn is_not_null(self) -> Expr {
34 self.expr().is_not_null()
35 }
36 pub fn between(self, lower: impl Into<Expr>, upper: impl Into<Expr>) -> Expr {
37 self.expr().between(lower, upper)
38 }
39 pub fn not_between(self, lower: impl Into<Expr>, upper: impl Into<Expr>) -> Expr {
40 self.expr().not_between(lower, upper)
41 }
42 pub fn in_(self, values: impl IntoIterator<Item = impl Into<Expr>>) -> Expr {
43 self.expr().in_list(values)
44 }
45 pub fn not_in(self, values: impl IntoIterator<Item = impl Into<Expr>>) -> Expr {
46 self.expr().not_in_list(values)
47 }
48 pub fn in_subquery(self, query: QueryBuilder) -> Expr {
49 self.expr().in_subquery(query)
50 }
51 pub fn not_in_subquery(self, query: QueryBuilder) -> Expr {
52 self.expr().not_in_subquery(query)
53 }
54 pub fn like(self, value: impl Into<Expr>) -> Expr {
55 self.expr().like(value)
56 }
57 pub fn not_like(self, value: impl Into<Expr>) -> Expr {
58 self.expr().not_like(value)
59 }
60 pub fn regexp(self, value: impl Into<Expr>) -> Expr {
61 self.expr().regexp(value)
62 }
63 pub fn glob(self, value: impl Into<Expr>) -> Expr {
64 self.expr().glob(value)
65 }
66 pub fn is_distinct_from(self, value: impl Into<Expr>) -> Expr {
67 self.expr().is_distinct_from(value)
68 }
69 pub fn is_not_distinct_from(self, value: impl Into<Expr>) -> Expr {
70 self.expr().is_not_distinct_from(value)
71 }
72 #[allow(clippy::should_implement_trait)]
73 pub fn add(self, value: impl Into<Expr>) -> Expr {
74 self.expr().add(value)
75 }
76 #[allow(clippy::should_implement_trait)]
77 pub fn sub(self, value: impl Into<Expr>) -> Expr {
78 self.expr().sub(value)
79 }
80 #[allow(clippy::should_implement_trait)]
81 pub fn mul(self, value: impl Into<Expr>) -> Expr {
82 self.expr().mul(value)
83 }
84 #[allow(clippy::should_implement_trait)]
85 pub fn div(self, value: impl Into<Expr>) -> Expr {
86 self.expr().div(value)
87 }
88 pub fn modulo(self, value: impl Into<Expr>) -> Expr {
89 self.expr().modulo(value)
90 }
91 pub fn cast(self, data_type: DataTypeDescriptor) -> Expr {
92 self.expr().cast(data_type)
93 }
94 pub fn alias(self, alias: impl Into<String>) -> Projection {
95 self.expr().alias(alias)
96 }
97 pub fn projection(self) -> Projection {
98 self.expr().projection()
99 }
100 pub fn asc(self) -> Order {
101 self.expr().asc()
102 }
103 pub fn desc(self) -> Order {
104 self.expr().desc()
105 }
106 };
107}
108
109#[derive(Debug, Clone, PartialEq, Eq)]
110pub struct TypedColumn<T, Owner = ()> {
111 pub table: &'static str,
112 pub name: &'static str,
113 pub data_type: DataTypeDescriptor,
114 pub nullable: bool,
115 marker: PhantomData<fn() -> (T, Owner)>,
116}
117
118impl<T, Owner> TypedColumn<T, Owner> {
119 pub const fn new(
120 table: &'static str,
121 name: &'static str,
122 data_type: DataTypeDescriptor,
123 nullable: bool,
124 ) -> Self {
125 Self {
126 table,
127 name,
128 data_type,
129 nullable,
130 marker: PhantomData,
131 }
132 }
133 pub fn expr(self) -> Expr {
134 Expr::qualified(self.table, self.name)
135 }
136
137 typed_expression_methods!();
138}
139
140impl<T, Owner> From<TypedColumn<T, Owner>> for Expr {
141 fn from(value: TypedColumn<T, Owner>) -> Self {
142 value.expr()
143 }
144}
145
146impl<T, Owner> From<&TypedColumn<T, Owner>> for Expr {
147 fn from(value: &TypedColumn<T, Owner>) -> Self {
148 Expr::qualified(value.table, value.name)
149 }
150}
151
152impl<T, Owner> From<TypedColumn<T, Owner>> for String {
153 fn from(value: TypedColumn<T, Owner>) -> Self {
154 value.name.to_string()
155 }
156}
157
158#[derive(Debug, Clone, PartialEq, Eq)]
162pub struct TypedNavigation<T, Root = ()> {
163 root: &'static str,
164 path: Vec<&'static str>,
165 marker: PhantomData<fn() -> (T, Root)>,
166}
167
168impl<Source, Target: GeneratedEntity> TypedColumn<Reference<Target>, Source> {
169 pub fn field<U>(self, target: TypedColumn<U, Target>) -> TypedNavigation<U, Source> {
170 TypedNavigation {
171 root: self.table,
172 path: vec![self.name, target.name],
173 marker: PhantomData,
174 }
175 }
176}
177
178impl<Root, Target: GeneratedEntity> TypedNavigation<Reference<Target>, Root> {
179 pub fn field<U>(mut self, target: TypedColumn<U, Target>) -> TypedNavigation<U, Root> {
180 self.path.push(target.name);
181 TypedNavigation {
182 root: self.root,
183 path: self.path,
184 marker: PhantomData,
185 }
186 }
187}
188
189impl<T, Root> TypedNavigation<T, Root> {
190 pub fn expr(self) -> Expr {
191 Expr::navigation(self.root, self.path)
192 }
193
194 typed_expression_methods!();
195}
196
197impl<T, Root> From<TypedNavigation<T, Root>> for Expr {
198 fn from(value: TypedNavigation<T, Root>) -> Self {
199 value.expr()
200 }
201}
202
203pub trait GeneratedEntity {
204 type Record;
205 const TABLE: &'static str;
206 const CATALOG_ID: &'static str;
207 const SCHEMA_FINGERPRINT: &'static str;
208}
209
210#[derive(Debug, Clone)]
216pub struct TypedKey<Owner, T> {
217 column: TypedColumn<T, Owner>,
218 primary: bool,
219 encoder: fn(T) -> TypedValue,
220}
221
222impl<Owner, T> TypedKey<Owner, T> {
223 pub const fn new(
224 column: TypedColumn<T, Owner>,
225 primary: bool,
226 encoder: fn(T) -> TypedValue,
227 ) -> Self {
228 Self {
229 column,
230 primary,
231 encoder,
232 }
233 }
234
235 pub fn column(&self) -> &TypedColumn<T, Owner> {
236 &self.column
237 }
238
239 pub const fn is_primary(&self) -> bool {
240 self.primary
241 }
242
243 pub fn reference(&self, key: T) -> Reference<Owner> {
244 Reference::new(self.column.table, self.column.name, (self.encoder)(key))
245 }
246}
247
248pub trait GeneratedRecord: Sized {
252 type Entity: GeneratedEntity<Record = Self>;
253
254 fn to_dynamic(
255 &self,
256 descriptor: &TableDescriptor,
257 ) -> Result<DynamicRecord, GeneratedRecordError>;
258
259 fn apply_dynamic(&mut self, record: &DynamicRecord) -> Result<(), GeneratedRecordError>;
260}
261
262#[derive(Debug, thiserror::Error, Clone, PartialEq, Eq)]
263#[error("generated schema changed: expected {expected}, actual {actual}")]
264pub struct SchemaChanged {
265 pub expected: String,
266 pub actual: String,
267}
268
269#[derive(Debug, thiserror::Error, Clone, PartialEq, Eq)]
270pub enum GeneratedRecordError {
271 #[error(transparent)]
272 Schema(#[from] SchemaChanged),
273 #[error(transparent)]
274 Record(#[from] RecordError),
275}
276
277pub fn ensure_schema_fingerprint(expected: &str, actual: &str) -> Result<(), SchemaChanged> {
278 if expected == actual {
279 Ok(())
280 } else {
281 Err(SchemaChanged {
282 expected: expected.to_string(),
283 actual: actual.to_string(),
284 })
285 }
286}
287
288#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
289#[serde(tag = "state", rename_all = "snake_case")]
290pub enum FieldValue<T> {
291 Omitted,
292 Value { value: T },
293 Null { data_type: DataTypeDescriptor },
294}
295
296#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
297pub struct FieldState<T> {
298 value: FieldValue<T>,
299 dirty: bool,
300 #[serde(default, skip_serializing_if = "Option::is_none")]
301 declared_type: Option<DataTypeDescriptor>,
302}
303
304impl<T> Default for FieldState<T> {
305 fn default() -> Self {
306 Self::omitted()
307 }
308}
309
310impl<T> FieldState<T> {
311 pub const fn omitted() -> Self {
312 Self {
313 value: FieldValue::Omitted,
314 dirty: false,
315 declared_type: None,
316 }
317 }
318 pub fn typed(data_type: DataTypeDescriptor) -> Self {
319 Self {
320 value: FieldValue::Omitted,
321 dirty: false,
322 declared_type: Some(data_type),
323 }
324 }
325 pub fn value(&self) -> &FieldValue<T> {
326 &self.value
327 }
328 pub fn is_dirty(&self) -> bool {
329 self.dirty
330 }
331 pub fn is_omitted(&self) -> bool {
332 matches!(self.value, FieldValue::Omitted)
333 }
334 pub fn set(&mut self, value: impl Into<T>) {
335 self.value = FieldValue::Value {
336 value: value.into(),
337 };
338 self.dirty = true;
339 }
340 pub fn set_null(&mut self) {
341 self.value = FieldValue::Null {
342 data_type: self
343 .declared_type
344 .clone()
345 .unwrap_or(DataTypeDescriptor::Null),
346 };
347 self.dirty = true;
348 }
349 pub fn set_null_as(&mut self, data_type: DataTypeDescriptor) {
350 self.declared_type = Some(data_type.clone());
351 self.value = FieldValue::Null { data_type };
352 self.dirty = true;
353 }
354 pub fn unset(&mut self) {
357 self.value = FieldValue::Omitted;
358 self.dirty = false;
359 }
360 pub fn hydrate(&mut self, value: FieldValue<T>) {
361 if let FieldValue::Null { data_type } = &value {
362 self.declared_type = Some(data_type.clone());
363 }
364 self.value = value;
365 self.dirty = false;
366 }
367 pub fn mark_clean(&mut self) {
368 self.dirty = false;
369 }
370}
371
372#[derive(Debug, Clone, PartialEq, Serialize)]
373pub struct Reference<T> {
374 target_table: String,
375 target_column: String,
376 key: TypedValue,
377 #[serde(skip)]
378 marker: PhantomData<T>,
379}
380
381#[derive(Debug, Clone, PartialEq, Serialize)]
384pub struct DynamicReference {
385 target_table: String,
386 target_column: String,
387 key: TypedValue,
388}
389
390impl DynamicReference {
391 pub(crate) fn new(
392 target_table: impl Into<String>,
393 target_column: impl Into<String>,
394 key: TypedValue,
395 ) -> Self {
396 Self {
397 target_table: target_table.into(),
398 target_column: target_column.into(),
399 key,
400 }
401 }
402
403 pub fn key(&self) -> &TypedValue {
404 &self.key
405 }
406
407 pub fn target_table(&self) -> &str {
408 &self.target_table
409 }
410
411 pub fn target_column(&self) -> &str {
412 &self.target_column
413 }
414
415 pub fn to_json(&self) -> Result<String, serde_json::Error> {
416 serde_json::to_string(self)
417 }
418}
419
420impl<T> Reference<T> {
421 pub(crate) fn new(
422 target_table: impl Into<String>,
423 target_column: impl Into<String>,
424 key: TypedValue,
425 ) -> Self {
426 Self {
427 target_table: target_table.into(),
428 target_column: target_column.into(),
429 key,
430 marker: PhantomData,
431 }
432 }
433 pub fn key(&self) -> &TypedValue {
434 &self.key
435 }
436
437 pub fn target_table(&self) -> &str {
438 &self.target_table
439 }
440
441 pub fn target_column(&self) -> &str {
442 &self.target_column
443 }
444
445 pub fn to_json(&self) -> Result<String, serde_json::Error> {
446 serde_json::to_string(self)
447 }
448}
449
450impl<T> From<Reference<T>> for TypedValue {
451 fn from(reference: Reference<T>) -> Self {
452 reference.key
453 }
454}
455
456impl<T> From<Reference<T>> for Expr {
457 fn from(reference: Reference<T>) -> Self {
458 Expr::value(reference.key)
459 }
460}
461
462#[derive(Debug, thiserror::Error, Clone, PartialEq, Eq)]
463pub enum RecordError {
464 #[error(transparent)]
465 Build(#[from] BuilderError),
466 #[error("unknown record field '{0}'")]
467 UnknownField(String),
468 #[error("table '{0}' has no primary key")]
469 MissingPrimaryKey(String),
470 #[error("record primary-key field '{0}' is omitted or NULL")]
471 MissingPrimaryKeyValue(String),
472 #[error("record UPDATE has no dirty non-primary-key fields")]
473 EmptyUpdate,
474 #[error("reference target must be a one-column PRIMARY KEY or UNIQUE NOT NULL key")]
475 UnsupportedReferenceTarget,
476 #[error("field '{field}' expects {expected:?}, got {actual:?}")]
477 ValueTypeMismatch {
478 field: String,
479 expected: DataTypeDescriptor,
480 actual: DataTypeDescriptor,
481 },
482 #[error("NULL reference keys are not valid; use an explicit typed NULL on the source field")]
483 NullReferenceKey,
484 #[error(
485 "field '{table}.{column}' is not a one-column reference to '{target_table}.{target_column}'"
486 )]
487 ReferenceSourceMismatch {
488 table: String,
489 column: String,
490 target_table: String,
491 target_column: String,
492 },
493 #[error("hydrated row is missing expected field '{0}'")]
494 IncompleteHydration(String),
495 #[error("hydrated field '{field}' does not match generated type {expected:?}")]
496 HydrationTypeMismatch {
497 field: String,
498 expected: DataTypeDescriptor,
499 },
500 #[error("generated query expected one row but returned none")]
501 ExpectedOneRow,
502 #[error("generated query expected at most one row but returned {0}")]
503 ExpectedAtMostOneRow(usize),
504}
505
506#[derive(Debug, Clone, PartialEq)]
507pub struct GeneratedQuery<R> {
508 builder: QueryBuilder,
509 marker: PhantomData<fn() -> R>,
510}
511
512impl<R> GeneratedQuery<R> {
513 pub fn new(builder: QueryBuilder) -> Self {
514 Self {
515 builder,
516 marker: PhantomData,
517 }
518 }
519
520 pub fn builder(&self) -> &QueryBuilder {
521 &self.builder
522 }
523
524 pub fn document(&self) -> Result<IrDocument, BuilderError> {
525 self.builder.document()
526 }
527
528 pub fn to_json(&self) -> Result<String, BuilderJsonError> {
529 self.builder.to_json()
530 }
531
532 pub fn to_sql(&self) -> Result<CompiledStatement, BuilderSqlError> {
533 self.builder.to_sql()
534 }
535}
536
537impl<R: GeneratedRecord + Default> GeneratedQuery<R> {
538 pub fn all<S>(self, session: S) -> Result<Vec<R>, S::Error>
539 where
540 S: OrmGeneratedQuerySession,
541 S::Error: From<BuilderError>,
542 {
543 let document = self.document().map_err(S::Error::from)?;
544 session.query_generated_records(&document)
545 }
546
547 pub fn one<S>(self, session: S) -> Result<R, S::Error>
548 where
549 S: OrmGeneratedQuerySession,
550 S::Error: From<RecordError> + From<BuilderError>,
551 {
552 let mut rows = self.all(session)?;
553 match rows.len() {
554 1 => Ok(rows.pop().expect("one row")),
555 0 => Err(RecordError::ExpectedOneRow.into()),
556 count => Err(RecordError::ExpectedAtMostOneRow(count).into()),
557 }
558 }
559
560 pub fn optional<S>(self, session: S) -> Result<Option<R>, S::Error>
561 where
562 S: OrmGeneratedQuerySession,
563 S::Error: From<RecordError> + From<BuilderError>,
564 {
565 let mut rows = self.all(session)?;
566 match rows.len() {
567 0 => Ok(None),
568 1 => Ok(rows.pop()),
569 count => Err(RecordError::ExpectedAtMostOneRow(count).into()),
570 }
571 }
572
573 pub async fn all_async<S>(self, session: &mut S) -> Result<Vec<R>, S::Error>
574 where
575 S: AsyncOrmGeneratedQuerySession,
576 S::Error: From<BuilderError>,
577 {
578 let document = self.document().map_err(S::Error::from)?;
579 session.query_generated_records_async(&document).await
580 }
581
582 pub async fn one_async<S>(self, session: &mut S) -> Result<R, S::Error>
583 where
584 S: AsyncOrmGeneratedQuerySession,
585 S::Error: From<RecordError> + From<BuilderError>,
586 {
587 let mut rows = self.all_async(session).await?;
588 match rows.len() {
589 1 => Ok(rows.pop().expect("one row")),
590 0 => Err(RecordError::ExpectedOneRow.into()),
591 count => Err(RecordError::ExpectedAtMostOneRow(count).into()),
592 }
593 }
594
595 pub async fn optional_async<S>(self, session: &mut S) -> Result<Option<R>, S::Error>
596 where
597 S: AsyncOrmGeneratedQuerySession,
598 S::Error: From<RecordError> + From<BuilderError>,
599 {
600 let mut rows = self.all_async(session).await?;
601 match rows.len() {
602 0 => Ok(None),
603 1 => Ok(rows.pop()),
604 count => Err(RecordError::ExpectedAtMostOneRow(count).into()),
605 }
606 }
607}
608
609#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
610pub struct DynamicRecord {
611 descriptor: TableDescriptor,
612 fields: BTreeMap<String, FieldState<TypedValue>>,
613}
614
615impl DynamicRecord {
616 pub fn new(descriptor: TableDescriptor) -> Self {
617 let fields = descriptor
618 .columns
619 .iter()
620 .map(|column| (column.name.clone(), FieldState::omitted()))
621 .collect();
622 Self { descriptor, fields }
623 }
624 pub fn descriptor(&self) -> &TableDescriptor {
625 &self.descriptor
626 }
627 pub fn field(&self, name: &str) -> Result<&FieldState<TypedValue>, RecordError> {
628 self.fields
629 .get(name)
630 .ok_or_else(|| RecordError::UnknownField(name.to_string()))
631 }
632 pub fn field_mut(&mut self, name: &str) -> Result<&mut FieldState<TypedValue>, RecordError> {
633 self.fields
634 .get_mut(name)
635 .ok_or_else(|| RecordError::UnknownField(name.to_string()))
636 }
637 pub fn set(&mut self, name: &str, value: TypedValue) -> Result<(), RecordError> {
638 let column = self.column_descriptor(name)?;
639 if !typed_value_matches(&value, &column.data_type) {
640 return Err(RecordError::ValueTypeMismatch {
641 field: name.to_string(),
642 expected: column.data_type.clone(),
643 actual: value.data_type(),
644 });
645 }
646 self.field_mut(name)?.set(value);
647 Ok(())
648 }
649 pub fn set_reference(
650 &mut self,
651 name: &str,
652 reference: &DynamicReference,
653 ) -> Result<(), RecordError> {
654 let matches = self.descriptor.constraints.iter().any(|constraint| {
655 let ConstraintDefinition::ForeignKey {
656 columns,
657 referenced_table,
658 referenced_columns,
659 ..
660 } = &constraint.definition
661 else {
662 return false;
663 };
664 columns.as_slice() == [name]
665 && referenced_table == &reference.target_table
666 && referenced_columns.as_slice() == [reference.target_column.as_str()]
667 });
668 if !matches {
669 return Err(RecordError::ReferenceSourceMismatch {
670 table: self.descriptor.name.clone(),
671 column: name.to_string(),
672 target_table: reference.target_table.clone(),
673 target_column: reference.target_column.clone(),
674 });
675 }
676 self.set(name, reference.key.clone())
677 }
678 pub fn set_null(&mut self, name: &str) -> Result<(), RecordError> {
679 let data_type = self
680 .descriptor
681 .columns
682 .iter()
683 .find(|column| column.name == name)
684 .map(|column| column.data_type.clone())
685 .ok_or_else(|| RecordError::UnknownField(name.to_string()))?;
686 self.field_mut(name)?.set_null_as(data_type);
687 Ok(())
688 }
689 pub fn unset(&mut self, name: &str) -> Result<(), RecordError> {
690 self.field_mut(name)?.unset();
691 Ok(())
692 }
693
694 pub fn hydrate(&mut self, name: &str, value: TypedValue) -> Result<(), RecordError> {
698 let column = self.column_descriptor(name)?;
699 let hydrated = match value {
700 TypedValue::Null(data_type) if data_type == column.data_type => {
701 FieldValue::Null { data_type }
702 }
703 TypedValue::Null(data_type) => {
704 return Err(RecordError::ValueTypeMismatch {
705 field: name.to_string(),
706 expected: column.data_type.clone(),
707 actual: data_type,
708 });
709 }
710 value if typed_value_matches(&value, &column.data_type) => FieldValue::Value { value },
711 value => {
712 return Err(RecordError::ValueTypeMismatch {
713 field: name.to_string(),
714 expected: column.data_type.clone(),
715 actual: value.data_type(),
716 });
717 }
718 };
719 self.field_mut(name)?.hydrate(hydrated);
720 Ok(())
721 }
722 pub fn is_dirty(&self) -> bool {
723 self.fields.values().any(FieldState::is_dirty)
724 }
725
726 pub fn insert<S: OrmRecordSession>(&mut self, session: S) -> Result<(), S::Error> {
727 session.mutate_record(self, RecordMutation::Insert)
728 }
729
730 pub fn save<S: OrmRecordSession>(&mut self, session: S) -> Result<(), S::Error> {
731 session.mutate_record(self, RecordMutation::Save)
732 }
733
734 pub fn update<S: OrmRecordSession>(&mut self, session: S) -> Result<(), S::Error> {
735 session.mutate_record(self, RecordMutation::Update)
736 }
737
738 pub fn delete<S: OrmRecordSession>(&mut self, session: S) -> Result<(), S::Error> {
739 session.mutate_record(self, RecordMutation::Delete)
740 }
741
742 pub async fn insert_async<S: AsyncOrmRecordSession>(
743 &mut self,
744 session: &mut S,
745 ) -> Result<(), S::Error> {
746 session
747 .mutate_record_async(self, RecordMutation::Insert)
748 .await
749 }
750
751 pub async fn save_async<S: AsyncOrmRecordSession>(
752 &mut self,
753 session: &mut S,
754 ) -> Result<(), S::Error> {
755 session
756 .mutate_record_async(self, RecordMutation::Save)
757 .await
758 }
759
760 pub async fn update_async<S: AsyncOrmRecordSession>(
761 &mut self,
762 session: &mut S,
763 ) -> Result<(), S::Error> {
764 session
765 .mutate_record_async(self, RecordMutation::Update)
766 .await
767 }
768
769 pub async fn delete_async<S: AsyncOrmRecordSession>(
770 &mut self,
771 session: &mut S,
772 ) -> Result<(), S::Error> {
773 session
774 .mutate_record_async(self, RecordMutation::Delete)
775 .await
776 }
777
778 pub fn insert_document(&self) -> Result<IrDocument, RecordError> {
779 let (columns, values) = self.present_fields(false)?;
780 Ok(IrDocument::new(Operation::Insert {
781 statement: Insert {
782 table: self.descriptor.name.clone(),
783 columns,
784 rows: vec![values],
785 source: None,
786 returning: return_all(),
787 },
788 }))
789 }
790
791 pub fn save_document(&self) -> Result<IrDocument, RecordError> {
792 let primary_key = self.primary_key_columns()?;
793 self.primary_key_predicate(&primary_key)?;
794 let (columns, values) = self.present_fields(false)?;
795 let primary: BTreeSet<_> = primary_key.iter().cloned().collect();
796 let assignments = self
797 .fields
798 .iter()
799 .filter(|(column, state)| state.is_dirty() && !primary.contains(*column))
800 .map(|(column, _)| Assignment {
801 column: column.clone(),
802 value: Expression::Column {
803 column: ColumnRef::qualified("excluded", column.clone()),
804 },
805 })
806 .collect();
807 Ok(IrDocument::new(Operation::Upsert {
808 statement: Upsert {
809 insert: Insert {
810 table: self.descriptor.name.clone(),
811 columns,
812 rows: vec![values],
813 source: None,
814 returning: return_all(),
815 },
816 conflict_columns: primary_key,
817 assignments,
818 },
819 }))
820 }
821
822 pub fn update_document(&self) -> Result<IrDocument, RecordError> {
823 let primary_key = self.primary_key_columns()?;
824 let filter = self.primary_key_predicate(&primary_key)?;
825 let primary: BTreeSet<_> = primary_key.iter().cloned().collect();
826 let assignments: Vec<_> = self
827 .fields
828 .iter()
829 .filter(|(name, state)| state.is_dirty() && !primary.contains(*name))
830 .filter_map(|(name, state)| {
831 field_expression(state).map(|value| Assignment {
832 column: name.clone(),
833 value,
834 })
835 })
836 .collect();
837 if assignments.is_empty() {
838 return Err(RecordError::EmptyUpdate);
839 }
840 Ok(IrDocument::new(Operation::Update {
841 statement: Update {
842 table: self.descriptor.name.clone(),
843 alias: None,
844 assignments,
845 from: None,
846 filter: Some(filter),
847 returning: return_all(),
848 },
849 }))
850 }
851
852 pub fn delete_document(&self) -> Result<IrDocument, RecordError> {
853 let primary_key = self.primary_key_columns()?;
854 let filter = self.primary_key_predicate(&primary_key)?;
855 Ok(IrDocument::new(Operation::Delete {
856 statement: Delete {
857 table: self.descriptor.name.clone(),
858 alias: None,
859 using: None,
860 filter: Some(filter),
861 all_rows: false,
862 returning: Vec::new(),
863 },
864 }))
865 }
866
867 pub fn apply_returning(
870 &mut self,
871 values: BTreeMap<String, TypedValue>,
872 ) -> Result<(), RecordError> {
873 for column in &self.descriptor.columns {
874 if !values.contains_key(&column.name) {
875 return Err(RecordError::IncompleteHydration(column.name.clone()));
876 }
877 }
878 for column in &self.descriptor.columns {
879 let value = values.get(&column.name).expect("checked complete").clone();
880 let hydrated = match value {
881 TypedValue::Null(data_type) if data_type == column.data_type => {
882 FieldValue::Null { data_type }
883 }
884 TypedValue::Null(data_type) => {
885 return Err(RecordError::ValueTypeMismatch {
886 field: column.name.clone(),
887 expected: column.data_type.clone(),
888 actual: data_type,
889 });
890 }
891 value if typed_value_matches(&value, &column.data_type) => {
892 FieldValue::Value { value }
893 }
894 value => {
895 return Err(RecordError::ValueTypeMismatch {
896 field: column.name.clone(),
897 expected: column.data_type.clone(),
898 actual: value.data_type(),
899 });
900 }
901 };
902 self.fields
903 .get_mut(&column.name)
904 .expect("descriptor owns field")
905 .hydrate(hydrated);
906 }
907 Ok(())
908 }
909
910 fn column_descriptor(&self, name: &str) -> Result<&ColumnDescriptor, RecordError> {
911 self.descriptor
912 .columns
913 .iter()
914 .find(|column| column.name == name)
915 .ok_or_else(|| RecordError::UnknownField(name.to_string()))
916 }
917
918 fn present_fields(
919 &self,
920 dirty_only: bool,
921 ) -> Result<(Vec<String>, Vec<Expression>), RecordError> {
922 let mut columns = Vec::new();
923 let mut values = Vec::new();
924 for column in &self.descriptor.columns {
925 let state = self
926 .fields
927 .get(&column.name)
928 .expect("descriptor owns field");
929 if dirty_only && !state.is_dirty() {
930 continue;
931 }
932 if let Some(value) = field_expression(state) {
933 columns.push(column.name.clone());
934 values.push(value);
935 }
936 }
937 if columns.is_empty() {
938 return Err(RecordError::EmptyUpdate);
939 }
940 Ok((columns, values))
941 }
942
943 fn primary_key_columns(&self) -> Result<Vec<String>, RecordError> {
944 self.descriptor
945 .constraints
946 .iter()
947 .find_map(|constraint| match &constraint.definition {
948 crate::ConstraintDefinition::PrimaryKey { columns } => Some(columns.clone()),
949 _ => None,
950 })
951 .filter(|columns| !columns.is_empty())
952 .ok_or_else(|| RecordError::MissingPrimaryKey(self.descriptor.name.clone()))
953 }
954
955 fn primary_key_predicate(&self, columns: &[String]) -> Result<Expression, RecordError> {
956 let mut predicates = Vec::new();
957 for column in columns {
958 let state = self
959 .fields
960 .get(column)
961 .ok_or_else(|| RecordError::UnknownField(column.clone()))?;
962 let FieldValue::Value { value } = state.value() else {
963 return Err(RecordError::MissingPrimaryKeyValue(column.clone()));
964 };
965 predicates.push(Expression::Binary {
966 left: Box::new(Expression::column(column)),
967 operator: BinaryOperator::Eq,
968 right: Box::new(Expression::literal(value.clone())),
969 });
970 }
971 Ok(predicates
972 .into_iter()
973 .reduce(|left, right| Expression::Binary {
974 left: Box::new(left),
975 operator: BinaryOperator::And,
976 right: Box::new(right),
977 })
978 .expect("primary key is nonempty"))
979 }
980}
981
982pub fn decode_generated_field<T: GeneratedValue>(
985 field: &str,
986 state: &FieldState<TypedValue>,
987 expected: &DataTypeDescriptor,
988) -> Result<FieldValue<T>, RecordError> {
989 match state.value() {
990 FieldValue::Omitted => Ok(FieldValue::Omitted),
991 FieldValue::Null { data_type } if data_type == expected => Ok(FieldValue::Null {
992 data_type: data_type.clone(),
993 }),
994 FieldValue::Null { .. } => Err(RecordError::HydrationTypeMismatch {
995 field: field.to_string(),
996 expected: expected.clone(),
997 }),
998 FieldValue::Value { value } => T::decode(value, expected)
999 .map(|value| FieldValue::Value { value })
1000 .map_err(|_| RecordError::HydrationTypeMismatch {
1001 field: field.to_string(),
1002 expected: expected.clone(),
1003 }),
1004 }
1005}
1006
1007pub fn decode_generated_reference_field<T>(
1008 field: &str,
1009 state: &FieldState<TypedValue>,
1010 expected: &DataTypeDescriptor,
1011 target_table: &str,
1012 target_column: &str,
1013) -> Result<FieldValue<Reference<T>>, RecordError> {
1014 match state.value() {
1015 FieldValue::Omitted => Ok(FieldValue::Omitted),
1016 FieldValue::Null { data_type } if data_type == expected => Ok(FieldValue::Null {
1017 data_type: data_type.clone(),
1018 }),
1019 FieldValue::Null { .. } => Err(RecordError::HydrationTypeMismatch {
1020 field: field.to_string(),
1021 expected: expected.clone(),
1022 }),
1023 FieldValue::Value { value } if typed_value_matches(value, expected) => {
1024 Ok(FieldValue::Value {
1025 value: Reference::new(target_table, target_column, value.clone()),
1026 })
1027 }
1028 FieldValue::Value { .. } => Err(RecordError::HydrationTypeMismatch {
1029 field: field.to_string(),
1030 expected: expected.clone(),
1031 }),
1032 }
1033}
1034
1035pub(crate) fn typed_value_matches(value: &TypedValue, expected: &DataTypeDescriptor) -> bool {
1036 matches!(
1037 (value, expected),
1038 (TypedValue::Integer(_), DataTypeDescriptor::Integer)
1039 | (
1040 TypedValue::Float(_),
1041 DataTypeDescriptor::Float | DataTypeDescriptor::DoublePrecision
1042 )
1043 | (TypedValue::Text(_), DataTypeDescriptor::Text { .. })
1044 | (TypedValue::Boolean(_), DataTypeDescriptor::Boolean)
1045 | (TypedValue::Timestamp(_), DataTypeDescriptor::Timestamp)
1046 | (
1047 TypedValue::CivilTimestamp(_),
1048 DataTypeDescriptor::CivilTimestamp
1049 )
1050 | (TypedValue::Time(_), DataTypeDescriptor::Time)
1051 | (TypedValue::Date(_), DataTypeDescriptor::Date)
1052 | (TypedValue::Json(_), DataTypeDescriptor::Json)
1053 | (TypedValue::Uuid(_), DataTypeDescriptor::Uuid)
1054 | (TypedValue::Bytes(_), DataTypeDescriptor::Bytes)
1055 | (TypedValue::Decimal(_), DataTypeDescriptor::Decimal { .. })
1056 | (TypedValue::Vector(_), DataTypeDescriptor::Vector { .. })
1057 )
1058}
1059
1060#[derive(Debug, thiserror::Error, Clone, Copy, PartialEq, Eq)]
1061#[error("typed value does not match the generated field type")]
1062pub struct GeneratedValueDecodeError;
1063
1064pub trait GeneratedValue: Sized {
1065 fn decode(
1066 value: &TypedValue,
1067 expected: &DataTypeDescriptor,
1068 ) -> Result<Self, GeneratedValueDecodeError>;
1069}
1070
1071impl GeneratedValue for i64 {
1072 fn decode(
1073 value: &TypedValue,
1074 expected: &DataTypeDescriptor,
1075 ) -> Result<Self, GeneratedValueDecodeError> {
1076 match (value, expected) {
1077 (TypedValue::Integer(value), DataTypeDescriptor::Integer) => Ok(*value),
1078 _ => Err(GeneratedValueDecodeError),
1079 }
1080 }
1081}
1082
1083impl GeneratedValue for f64 {
1084 fn decode(
1085 value: &TypedValue,
1086 expected: &DataTypeDescriptor,
1087 ) -> Result<Self, GeneratedValueDecodeError> {
1088 match (value, expected) {
1089 (
1090 TypedValue::Float(value),
1091 DataTypeDescriptor::Float | DataTypeDescriptor::DoublePrecision,
1092 ) => Ok(value.as_f64()),
1093 _ => Err(GeneratedValueDecodeError),
1094 }
1095 }
1096}
1097
1098impl GeneratedValue for bool {
1099 fn decode(
1100 value: &TypedValue,
1101 expected: &DataTypeDescriptor,
1102 ) -> Result<Self, GeneratedValueDecodeError> {
1103 match (value, expected) {
1104 (TypedValue::Boolean(value), DataTypeDescriptor::Boolean) => Ok(*value),
1105 _ => Err(GeneratedValueDecodeError),
1106 }
1107 }
1108}
1109
1110impl GeneratedValue for serde_json::Value {
1111 fn decode(
1112 value: &TypedValue,
1113 expected: &DataTypeDescriptor,
1114 ) -> Result<Self, GeneratedValueDecodeError> {
1115 match (value, expected) {
1116 (TypedValue::Json(value), DataTypeDescriptor::Json) => Ok(value.clone()),
1117 _ => Err(GeneratedValueDecodeError),
1118 }
1119 }
1120}
1121
1122impl GeneratedValue for Vec<f32> {
1123 fn decode(
1124 value: &TypedValue,
1125 expected: &DataTypeDescriptor,
1126 ) -> Result<Self, GeneratedValueDecodeError> {
1127 match (value, expected) {
1128 (TypedValue::Vector(value), DataTypeDescriptor::Vector { dimensions })
1129 if value.len() == usize::from(*dimensions) =>
1130 {
1131 Ok(value.clone())
1132 }
1133 _ => Err(GeneratedValueDecodeError),
1134 }
1135 }
1136}
1137
1138impl GeneratedValue for String {
1139 fn decode(
1140 value: &TypedValue,
1141 expected: &DataTypeDescriptor,
1142 ) -> Result<Self, GeneratedValueDecodeError> {
1143 match (value, expected) {
1144 (TypedValue::Text(value), DataTypeDescriptor::Text { .. })
1145 | (TypedValue::Timestamp(value), DataTypeDescriptor::Timestamp)
1146 | (TypedValue::CivilTimestamp(value), DataTypeDescriptor::CivilTimestamp)
1147 | (TypedValue::Time(value), DataTypeDescriptor::Time)
1148 | (TypedValue::Date(value), DataTypeDescriptor::Date)
1149 | (TypedValue::Uuid(value), DataTypeDescriptor::Uuid)
1150 | (TypedValue::Bytes(value), DataTypeDescriptor::Bytes)
1151 | (TypedValue::Decimal(value), DataTypeDescriptor::Decimal { .. }) => Ok(value.clone()),
1152 _ => Err(GeneratedValueDecodeError),
1153 }
1154 }
1155}
1156
1157pub fn validate_reference_target(table: &TableDescriptor, column: &str) -> Result<(), RecordError> {
1158 let target = table
1159 .columns
1160 .iter()
1161 .find(|candidate| candidate.name == column)
1162 .ok_or(RecordError::UnsupportedReferenceTarget)?;
1163 if target.nullable {
1164 return Err(RecordError::UnsupportedReferenceTarget);
1165 }
1166 let valid = table
1167 .constraints
1168 .iter()
1169 .any(|constraint| match &constraint.definition {
1170 crate::ConstraintDefinition::PrimaryKey { columns }
1171 | crate::ConstraintDefinition::Unique { columns, .. } => columns.as_slice() == [column],
1172 _ => false,
1173 });
1174 if valid {
1175 Ok(())
1176 } else {
1177 Err(RecordError::UnsupportedReferenceTarget)
1178 }
1179}
1180
1181fn field_expression(state: &FieldState<TypedValue>) -> Option<Expression> {
1182 match state.value() {
1183 FieldValue::Omitted => None,
1184 FieldValue::Value { value } => Some(Expression::literal(value.clone())),
1185 FieldValue::Null { data_type } => {
1186 Some(Expression::literal(TypedValue::Null(data_type.clone())))
1187 }
1188 }
1189}
1190
1191fn return_all() -> Vec<Projection> {
1192 vec![Projection {
1193 expression: Expression::Star { relation: None },
1194 alias: None,
1195 }]
1196}
1197
1198#[cfg(test)]
1199mod tests {
1200 use super::*;
1201
1202 #[derive(Debug, Default, Clone, PartialEq)]
1203 struct TestGeneratedRecord;
1204
1205 struct TestGeneratedEntity;
1206
1207 struct EmployeeEntity;
1208 struct DepartmentEntity;
1209
1210 impl GeneratedEntity for TestGeneratedEntity {
1211 type Record = TestGeneratedRecord;
1212 const TABLE: &'static str = "people";
1213 const CATALOG_ID: &'static str = "test";
1214 const SCHEMA_FINGERPRINT: &'static str = "test";
1215 }
1216
1217 impl GeneratedEntity for EmployeeEntity {
1218 type Record = TestGeneratedRecord;
1219 const TABLE: &'static str = "employees";
1220 const CATALOG_ID: &'static str = "employees";
1221 const SCHEMA_FINGERPRINT: &'static str = "employees";
1222 }
1223
1224 impl GeneratedEntity for DepartmentEntity {
1225 type Record = TestGeneratedRecord;
1226 const TABLE: &'static str = "departments";
1227 const CATALOG_ID: &'static str = "departments";
1228 const SCHEMA_FINGERPRINT: &'static str = "departments";
1229 }
1230
1231 impl GeneratedRecord for TestGeneratedRecord {
1232 type Entity = TestGeneratedEntity;
1233
1234 fn to_dynamic(
1235 &self,
1236 _descriptor: &TableDescriptor,
1237 ) -> Result<DynamicRecord, GeneratedRecordError> {
1238 unreachable!("query mock does not mutate records")
1239 }
1240
1241 fn apply_dynamic(&mut self, _record: &DynamicRecord) -> Result<(), GeneratedRecordError> {
1242 unreachable!("query mock returns already decoded records")
1243 }
1244 }
1245
1246 struct MockGeneratedQuerySession(Vec<TestGeneratedRecord>);
1247
1248 impl OrmGeneratedQuerySession for MockGeneratedQuerySession {
1249 type Error = RecordError;
1250
1251 fn query_generated_records<R: GeneratedRecord + Default>(
1252 self,
1253 _document: &IrDocument,
1254 ) -> Result<Vec<R>, Self::Error> {
1255 Ok(self.0.into_iter().map(|_| R::default()).collect())
1256 }
1257 }
1258
1259 fn descriptor() -> TableDescriptor {
1260 TableDescriptor {
1261 catalog_id: "00000000-0000-0000-0000-000000000001".to_string(),
1262 name: "people".to_string(),
1263 schema_generation: 1,
1264 fingerprint: "f".to_string(),
1265 created_at: "x".to_string(),
1266 updated_at: "x".to_string(),
1267 columns: vec![
1268 ColumnDescriptor {
1269 ordinal: 0,
1270 name: "id".to_string(),
1271 data_type: DataTypeDescriptor::Integer,
1272 nullable: false,
1273 auto_increment: false,
1274 default_expression: None,
1275 extensions: BTreeMap::new(),
1276 },
1277 ColumnDescriptor {
1278 ordinal: 1,
1279 name: "name".to_string(),
1280 data_type: DataTypeDescriptor::Text { max_chars: None },
1281 nullable: true,
1282 auto_increment: false,
1283 default_expression: None,
1284 extensions: BTreeMap::new(),
1285 },
1286 ColumnDescriptor {
1287 ordinal: 2,
1288 name: "note".to_string(),
1289 data_type: DataTypeDescriptor::Text { max_chars: None },
1290 nullable: true,
1291 auto_increment: false,
1292 default_expression: None,
1293 extensions: BTreeMap::new(),
1294 },
1295 ],
1296 constraints: vec![ConstraintDescriptor {
1297 id: 1,
1298 name: "pk_people".to_string(),
1299 definition: crate::ConstraintDefinition::PrimaryKey {
1300 columns: vec!["id".to_string()],
1301 },
1302 }],
1303 indexes: Vec::new(),
1304 extensions: BTreeMap::new(),
1305 }
1306 }
1307
1308 #[test]
1309 fn record_distinguishes_omitted_null_value_and_keeps_dirty_until_hydration() {
1310 let mut record = DynamicRecord::new(descriptor());
1311 record.set("id", TypedValue::Integer(7)).unwrap();
1312 record.set_null("name").unwrap();
1313 let insert = record.insert_document().unwrap().to_sql().unwrap();
1314 assert!(insert.sql.ends_with("RETURNING *"));
1315 assert_eq!(insert.parameters.len(), 2);
1316 assert!(record.is_dirty());
1317
1318 record.unset("name").unwrap();
1319 assert!(record.update_document().is_err());
1320 record
1321 .set("name", TypedValue::Text("Alice".to_string()))
1322 .unwrap();
1323 let update = record.update_document().unwrap().to_sql().unwrap();
1324 assert_eq!(
1325 update.parameters,
1326 vec![
1327 TypedValue::Text("Alice".to_string()),
1328 TypedValue::Integer(7)
1329 ]
1330 );
1331 assert!(record.is_dirty());
1332
1333 record
1334 .apply_returning(BTreeMap::from([
1335 ("id".to_string(), TypedValue::Integer(7)),
1336 ("name".to_string(), TypedValue::Text("Alice".to_string())),
1337 ("note".to_string(), TypedValue::Text("clean".to_string())),
1338 ]))
1339 .unwrap();
1340 assert!(!record.is_dirty());
1341
1342 record
1343 .set("name", TypedValue::Text("Changed".to_string()))
1344 .unwrap();
1345 let save = record.save_document().unwrap().to_sql().unwrap();
1346 assert!(save
1347 .sql
1348 .contains("DO UPDATE SET \"name\" = \"excluded\".\"name\""));
1349 assert!(!save.sql.contains("SET \"note\""));
1350 }
1351
1352 #[test]
1353 fn generated_query_cardinality_is_fail_closed() {
1354 let query = || {
1355 GeneratedQuery::<TestGeneratedRecord>::new(
1356 QueryBuilder::from_relation(crate::table("people")).select([Expr::star()]),
1357 )
1358 };
1359 assert_eq!(
1360 query()
1361 .one(MockGeneratedQuerySession(vec![TestGeneratedRecord]))
1362 .unwrap(),
1363 TestGeneratedRecord
1364 );
1365 assert!(matches!(
1366 query().one(MockGeneratedQuerySession(Vec::new())),
1367 Err(RecordError::ExpectedOneRow)
1368 ));
1369 assert!(matches!(
1370 query().optional(MockGeneratedQuerySession(vec![
1371 TestGeneratedRecord,
1372 TestGeneratedRecord,
1373 ])),
1374 Err(RecordError::ExpectedAtMostOneRow(2))
1375 ));
1376 let invalid = GeneratedQuery::<TestGeneratedRecord>::new(QueryBuilder::from_relation(
1377 crate::table("people"),
1378 ));
1379 assert!(matches!(
1380 invalid.all(MockGeneratedQuerySession(Vec::new())),
1381 Err(RecordError::Build(BuilderError::EmptyProjection))
1382 ));
1383 }
1384
1385 #[test]
1386 fn reference_targets_require_one_nonnullable_primary_or_unique_column() {
1387 let mut table = descriptor();
1388 assert!(validate_reference_target(&table, "id").is_ok());
1389 assert!(matches!(
1390 validate_reference_target(&table, "name"),
1391 Err(RecordError::UnsupportedReferenceTarget)
1392 ));
1393
1394 table.columns[1].nullable = false;
1395 table.constraints.push(ConstraintDescriptor {
1396 id: 2,
1397 name: "uq_people_name".to_string(),
1398 definition: crate::ConstraintDefinition::Unique {
1399 columns: vec!["name".to_string()],
1400 owned_index: "uq_people_name".to_string(),
1401 },
1402 });
1403 assert!(validate_reference_target(&table, "name").is_ok());
1404 table.constraints[1].definition = crate::ConstraintDefinition::Unique {
1405 columns: vec!["name".to_string(), "note".to_string()],
1406 owned_index: "uq_people_name_note".to_string(),
1407 };
1408 assert!(matches!(
1409 validate_reference_target(&table, "name"),
1410 Err(RecordError::UnsupportedReferenceTarget)
1411 ));
1412 }
1413
1414 #[test]
1415 fn dynamic_references_are_key_only_typed_and_source_checked() {
1416 let target = descriptor();
1417 let target_entity = DynamicEntity::new(target.clone());
1418 let reference = target_entity
1419 .reference("id", TypedValue::Integer(7))
1420 .unwrap();
1421 assert_eq!(reference.key(), &TypedValue::Integer(7));
1422 assert_eq!(reference.target_table(), "people");
1423 assert_eq!(reference.target_column(), "id");
1424 assert_eq!(
1425 serde_json::from_str::<serde_json::Value>(&reference.to_json().unwrap()).unwrap(),
1426 serde_json::json!({
1427 "target_table": "people",
1428 "target_column": "id",
1429 "key": { "type": "integer", "value": 7 }
1430 })
1431 );
1432 assert!(matches!(
1433 target_entity.reference("name", TypedValue::Text("x".to_string())),
1434 Err(RecordError::UnsupportedReferenceTarget)
1435 ));
1436 assert!(matches!(
1437 target_entity.reference("id", TypedValue::Float(7.0.into())),
1438 Err(RecordError::ValueTypeMismatch { .. })
1439 ));
1440
1441 let mut source = descriptor();
1442 source.name = "documents".to_string();
1443 source.constraints.push(ConstraintDescriptor {
1444 id: 2,
1445 name: "fk_documents_id___people".to_string(),
1446 definition: ConstraintDefinition::ForeignKey {
1447 columns: vec!["id".to_string()],
1448 referenced_table: "people".to_string(),
1449 referenced_columns: vec!["id".to_string()],
1450 on_delete: ForeignKeyActionDescriptor::Restrict,
1451 on_update: ForeignKeyActionDescriptor::Restrict,
1452 },
1453 });
1454 let mut record = DynamicRecord::new(source);
1455 record.set_reference("id", &reference).unwrap();
1456 assert_eq!(
1457 record.field("id").unwrap().value(),
1458 &FieldValue::Value {
1459 value: TypedValue::Integer(7)
1460 }
1461 );
1462
1463 let other = DynamicReference::new("other", "id", TypedValue::Integer(7));
1464 assert!(matches!(
1465 record.set_reference("id", &other),
1466 Err(RecordError::ReferenceSourceMismatch { .. })
1467 ));
1468 }
1469
1470 #[test]
1471 fn typed_columns_cover_predicates_and_transitive_navigation() {
1472 let employee = TypedColumn::<Reference<EmployeeEntity>, TestGeneratedEntity>::new(
1473 "payroll_documents",
1474 "employee_id",
1475 DataTypeDescriptor::Integer,
1476 false,
1477 );
1478 let department = TypedColumn::<Reference<DepartmentEntity>, EmployeeEntity>::new(
1479 "employees",
1480 "department_id",
1481 DataTypeDescriptor::Integer,
1482 false,
1483 );
1484 let department_name = TypedColumn::<String, DepartmentEntity>::new(
1485 "departments",
1486 "name",
1487 DataTypeDescriptor::Text { max_chars: None },
1488 false,
1489 );
1490
1491 let path = employee.field(department).field(department_name);
1492 assert_eq!(
1493 path.expr().0,
1494 Expression::Navigation {
1495 root: "payroll_documents".to_string(),
1496 path: vec![
1497 "employee_id".to_string(),
1498 "department_id".to_string(),
1499 "name".to_string(),
1500 ],
1501 }
1502 );
1503
1504 let score = TypedColumn::<i64>::new("people", "score", DataTypeDescriptor::Integer, true);
1505 let query = QueryBuilder::from_relation(crate::table("people"))
1506 .select([score.clone().expr()])
1507 .filter(score.clone().between(10_i64, 20_i64).or(score.is_null()));
1508 let compiled = query.to_sql().unwrap();
1509 assert_eq!(compiled.parameters.len(), 2);
1510 assert!(compiled.sql.contains("BETWEEN $1 AND $2"));
1511 }
1512}