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 | (TypedValue::Float(_), DataTypeDescriptor::Float)
1040 | (TypedValue::Text(_), DataTypeDescriptor::Text)
1041 | (TypedValue::Boolean(_), DataTypeDescriptor::Boolean)
1042 | (TypedValue::Timestamp(_), DataTypeDescriptor::Timestamp)
1043 | (TypedValue::Date(_), DataTypeDescriptor::Date)
1044 | (TypedValue::Json(_), DataTypeDescriptor::Json)
1045 | (TypedValue::Uuid(_), DataTypeDescriptor::Uuid)
1046 | (TypedValue::Bytes(_), DataTypeDescriptor::Bytes)
1047 | (TypedValue::Decimal(_), DataTypeDescriptor::Decimal { .. })
1048 | (TypedValue::Vector(_), DataTypeDescriptor::Vector { .. })
1049 )
1050}
1051
1052#[derive(Debug, thiserror::Error, Clone, Copy, PartialEq, Eq)]
1053#[error("typed value does not match the generated field type")]
1054pub struct GeneratedValueDecodeError;
1055
1056pub trait GeneratedValue: Sized {
1057 fn decode(
1058 value: &TypedValue,
1059 expected: &DataTypeDescriptor,
1060 ) -> Result<Self, GeneratedValueDecodeError>;
1061}
1062
1063impl GeneratedValue for i64 {
1064 fn decode(
1065 value: &TypedValue,
1066 expected: &DataTypeDescriptor,
1067 ) -> Result<Self, GeneratedValueDecodeError> {
1068 match (value, expected) {
1069 (TypedValue::Integer(value), DataTypeDescriptor::Integer) => Ok(*value),
1070 _ => Err(GeneratedValueDecodeError),
1071 }
1072 }
1073}
1074
1075impl GeneratedValue for f64 {
1076 fn decode(
1077 value: &TypedValue,
1078 expected: &DataTypeDescriptor,
1079 ) -> Result<Self, GeneratedValueDecodeError> {
1080 match (value, expected) {
1081 (TypedValue::Float(value), DataTypeDescriptor::Float) => Ok(value.as_f64()),
1082 _ => Err(GeneratedValueDecodeError),
1083 }
1084 }
1085}
1086
1087impl GeneratedValue for bool {
1088 fn decode(
1089 value: &TypedValue,
1090 expected: &DataTypeDescriptor,
1091 ) -> Result<Self, GeneratedValueDecodeError> {
1092 match (value, expected) {
1093 (TypedValue::Boolean(value), DataTypeDescriptor::Boolean) => Ok(*value),
1094 _ => Err(GeneratedValueDecodeError),
1095 }
1096 }
1097}
1098
1099impl GeneratedValue for serde_json::Value {
1100 fn decode(
1101 value: &TypedValue,
1102 expected: &DataTypeDescriptor,
1103 ) -> Result<Self, GeneratedValueDecodeError> {
1104 match (value, expected) {
1105 (TypedValue::Json(value), DataTypeDescriptor::Json) => Ok(value.clone()),
1106 _ => Err(GeneratedValueDecodeError),
1107 }
1108 }
1109}
1110
1111impl GeneratedValue for Vec<f32> {
1112 fn decode(
1113 value: &TypedValue,
1114 expected: &DataTypeDescriptor,
1115 ) -> Result<Self, GeneratedValueDecodeError> {
1116 match (value, expected) {
1117 (TypedValue::Vector(value), DataTypeDescriptor::Vector { dimensions })
1118 if value.len() == usize::from(*dimensions) =>
1119 {
1120 Ok(value.clone())
1121 }
1122 _ => Err(GeneratedValueDecodeError),
1123 }
1124 }
1125}
1126
1127impl GeneratedValue for String {
1128 fn decode(
1129 value: &TypedValue,
1130 expected: &DataTypeDescriptor,
1131 ) -> Result<Self, GeneratedValueDecodeError> {
1132 match (value, expected) {
1133 (TypedValue::Text(value), DataTypeDescriptor::Text)
1134 | (TypedValue::Timestamp(value), DataTypeDescriptor::Timestamp)
1135 | (TypedValue::Date(value), DataTypeDescriptor::Date)
1136 | (TypedValue::Uuid(value), DataTypeDescriptor::Uuid)
1137 | (TypedValue::Bytes(value), DataTypeDescriptor::Bytes)
1138 | (TypedValue::Decimal(value), DataTypeDescriptor::Decimal { .. }) => Ok(value.clone()),
1139 _ => Err(GeneratedValueDecodeError),
1140 }
1141 }
1142}
1143
1144pub fn validate_reference_target(table: &TableDescriptor, column: &str) -> Result<(), RecordError> {
1145 let target = table
1146 .columns
1147 .iter()
1148 .find(|candidate| candidate.name == column)
1149 .ok_or(RecordError::UnsupportedReferenceTarget)?;
1150 if target.nullable {
1151 return Err(RecordError::UnsupportedReferenceTarget);
1152 }
1153 let valid = table
1154 .constraints
1155 .iter()
1156 .any(|constraint| match &constraint.definition {
1157 crate::ConstraintDefinition::PrimaryKey { columns }
1158 | crate::ConstraintDefinition::Unique { columns, .. } => columns.as_slice() == [column],
1159 _ => false,
1160 });
1161 if valid {
1162 Ok(())
1163 } else {
1164 Err(RecordError::UnsupportedReferenceTarget)
1165 }
1166}
1167
1168fn field_expression(state: &FieldState<TypedValue>) -> Option<Expression> {
1169 match state.value() {
1170 FieldValue::Omitted => None,
1171 FieldValue::Value { value } => Some(Expression::literal(value.clone())),
1172 FieldValue::Null { data_type } => {
1173 Some(Expression::literal(TypedValue::Null(data_type.clone())))
1174 }
1175 }
1176}
1177
1178fn return_all() -> Vec<Projection> {
1179 vec![Projection {
1180 expression: Expression::Star { relation: None },
1181 alias: None,
1182 }]
1183}
1184
1185#[cfg(test)]
1186mod tests {
1187 use super::*;
1188
1189 #[derive(Debug, Default, Clone, PartialEq)]
1190 struct TestGeneratedRecord;
1191
1192 struct TestGeneratedEntity;
1193
1194 struct EmployeeEntity;
1195 struct DepartmentEntity;
1196
1197 impl GeneratedEntity for TestGeneratedEntity {
1198 type Record = TestGeneratedRecord;
1199 const TABLE: &'static str = "people";
1200 const CATALOG_ID: &'static str = "test";
1201 const SCHEMA_FINGERPRINT: &'static str = "test";
1202 }
1203
1204 impl GeneratedEntity for EmployeeEntity {
1205 type Record = TestGeneratedRecord;
1206 const TABLE: &'static str = "employees";
1207 const CATALOG_ID: &'static str = "employees";
1208 const SCHEMA_FINGERPRINT: &'static str = "employees";
1209 }
1210
1211 impl GeneratedEntity for DepartmentEntity {
1212 type Record = TestGeneratedRecord;
1213 const TABLE: &'static str = "departments";
1214 const CATALOG_ID: &'static str = "departments";
1215 const SCHEMA_FINGERPRINT: &'static str = "departments";
1216 }
1217
1218 impl GeneratedRecord for TestGeneratedRecord {
1219 type Entity = TestGeneratedEntity;
1220
1221 fn to_dynamic(
1222 &self,
1223 _descriptor: &TableDescriptor,
1224 ) -> Result<DynamicRecord, GeneratedRecordError> {
1225 unreachable!("query mock does not mutate records")
1226 }
1227
1228 fn apply_dynamic(&mut self, _record: &DynamicRecord) -> Result<(), GeneratedRecordError> {
1229 unreachable!("query mock returns already decoded records")
1230 }
1231 }
1232
1233 struct MockGeneratedQuerySession(Vec<TestGeneratedRecord>);
1234
1235 impl OrmGeneratedQuerySession for MockGeneratedQuerySession {
1236 type Error = RecordError;
1237
1238 fn query_generated_records<R: GeneratedRecord + Default>(
1239 self,
1240 _document: &IrDocument,
1241 ) -> Result<Vec<R>, Self::Error> {
1242 Ok(self.0.into_iter().map(|_| R::default()).collect())
1243 }
1244 }
1245
1246 fn descriptor() -> TableDescriptor {
1247 TableDescriptor {
1248 catalog_id: "00000000-0000-0000-0000-000000000001".to_string(),
1249 name: "people".to_string(),
1250 schema_generation: 1,
1251 fingerprint: "f".to_string(),
1252 created_at: "x".to_string(),
1253 updated_at: "x".to_string(),
1254 columns: vec![
1255 ColumnDescriptor {
1256 ordinal: 0,
1257 name: "id".to_string(),
1258 data_type: DataTypeDescriptor::Integer,
1259 nullable: false,
1260 auto_increment: false,
1261 default_expression: None,
1262 extensions: BTreeMap::new(),
1263 },
1264 ColumnDescriptor {
1265 ordinal: 1,
1266 name: "name".to_string(),
1267 data_type: DataTypeDescriptor::Text,
1268 nullable: true,
1269 auto_increment: false,
1270 default_expression: None,
1271 extensions: BTreeMap::new(),
1272 },
1273 ColumnDescriptor {
1274 ordinal: 2,
1275 name: "note".to_string(),
1276 data_type: DataTypeDescriptor::Text,
1277 nullable: true,
1278 auto_increment: false,
1279 default_expression: None,
1280 extensions: BTreeMap::new(),
1281 },
1282 ],
1283 constraints: vec![ConstraintDescriptor {
1284 id: 1,
1285 name: "pk_people".to_string(),
1286 definition: crate::ConstraintDefinition::PrimaryKey {
1287 columns: vec!["id".to_string()],
1288 },
1289 }],
1290 indexes: Vec::new(),
1291 extensions: BTreeMap::new(),
1292 }
1293 }
1294
1295 #[test]
1296 fn record_distinguishes_omitted_null_value_and_keeps_dirty_until_hydration() {
1297 let mut record = DynamicRecord::new(descriptor());
1298 record.set("id", TypedValue::Integer(7)).unwrap();
1299 record.set_null("name").unwrap();
1300 let insert = record.insert_document().unwrap().to_sql().unwrap();
1301 assert!(insert.sql.ends_with("RETURNING *"));
1302 assert_eq!(insert.parameters.len(), 2);
1303 assert!(record.is_dirty());
1304
1305 record.unset("name").unwrap();
1306 assert!(record.update_document().is_err());
1307 record
1308 .set("name", TypedValue::Text("Alice".to_string()))
1309 .unwrap();
1310 let update = record.update_document().unwrap().to_sql().unwrap();
1311 assert_eq!(
1312 update.parameters,
1313 vec![
1314 TypedValue::Text("Alice".to_string()),
1315 TypedValue::Integer(7)
1316 ]
1317 );
1318 assert!(record.is_dirty());
1319
1320 record
1321 .apply_returning(BTreeMap::from([
1322 ("id".to_string(), TypedValue::Integer(7)),
1323 ("name".to_string(), TypedValue::Text("Alice".to_string())),
1324 ("note".to_string(), TypedValue::Text("clean".to_string())),
1325 ]))
1326 .unwrap();
1327 assert!(!record.is_dirty());
1328
1329 record
1330 .set("name", TypedValue::Text("Changed".to_string()))
1331 .unwrap();
1332 let save = record.save_document().unwrap().to_sql().unwrap();
1333 assert!(save
1334 .sql
1335 .contains("DO UPDATE SET \"name\" = \"excluded\".\"name\""));
1336 assert!(!save.sql.contains("SET \"note\""));
1337 }
1338
1339 #[test]
1340 fn generated_query_cardinality_is_fail_closed() {
1341 let query = || {
1342 GeneratedQuery::<TestGeneratedRecord>::new(
1343 QueryBuilder::from_relation(crate::table("people")).select([Expr::star()]),
1344 )
1345 };
1346 assert_eq!(
1347 query()
1348 .one(MockGeneratedQuerySession(vec![TestGeneratedRecord]))
1349 .unwrap(),
1350 TestGeneratedRecord
1351 );
1352 assert!(matches!(
1353 query().one(MockGeneratedQuerySession(Vec::new())),
1354 Err(RecordError::ExpectedOneRow)
1355 ));
1356 assert!(matches!(
1357 query().optional(MockGeneratedQuerySession(vec![
1358 TestGeneratedRecord,
1359 TestGeneratedRecord,
1360 ])),
1361 Err(RecordError::ExpectedAtMostOneRow(2))
1362 ));
1363 let invalid = GeneratedQuery::<TestGeneratedRecord>::new(QueryBuilder::from_relation(
1364 crate::table("people"),
1365 ));
1366 assert!(matches!(
1367 invalid.all(MockGeneratedQuerySession(Vec::new())),
1368 Err(RecordError::Build(BuilderError::EmptyProjection))
1369 ));
1370 }
1371
1372 #[test]
1373 fn reference_targets_require_one_nonnullable_primary_or_unique_column() {
1374 let mut table = descriptor();
1375 assert!(validate_reference_target(&table, "id").is_ok());
1376 assert!(matches!(
1377 validate_reference_target(&table, "name"),
1378 Err(RecordError::UnsupportedReferenceTarget)
1379 ));
1380
1381 table.columns[1].nullable = false;
1382 table.constraints.push(ConstraintDescriptor {
1383 id: 2,
1384 name: "uq_people_name".to_string(),
1385 definition: crate::ConstraintDefinition::Unique {
1386 columns: vec!["name".to_string()],
1387 owned_index: "uq_people_name".to_string(),
1388 },
1389 });
1390 assert!(validate_reference_target(&table, "name").is_ok());
1391 table.constraints[1].definition = crate::ConstraintDefinition::Unique {
1392 columns: vec!["name".to_string(), "note".to_string()],
1393 owned_index: "uq_people_name_note".to_string(),
1394 };
1395 assert!(matches!(
1396 validate_reference_target(&table, "name"),
1397 Err(RecordError::UnsupportedReferenceTarget)
1398 ));
1399 }
1400
1401 #[test]
1402 fn dynamic_references_are_key_only_typed_and_source_checked() {
1403 let target = descriptor();
1404 let target_entity = DynamicEntity::new(target.clone());
1405 let reference = target_entity
1406 .reference("id", TypedValue::Integer(7))
1407 .unwrap();
1408 assert_eq!(reference.key(), &TypedValue::Integer(7));
1409 assert_eq!(reference.target_table(), "people");
1410 assert_eq!(reference.target_column(), "id");
1411 assert_eq!(
1412 serde_json::from_str::<serde_json::Value>(&reference.to_json().unwrap()).unwrap(),
1413 serde_json::json!({
1414 "target_table": "people",
1415 "target_column": "id",
1416 "key": { "type": "integer", "value": 7 }
1417 })
1418 );
1419 assert!(matches!(
1420 target_entity.reference("name", TypedValue::Text("x".to_string())),
1421 Err(RecordError::UnsupportedReferenceTarget)
1422 ));
1423 assert!(matches!(
1424 target_entity.reference("id", TypedValue::Float(7.0.into())),
1425 Err(RecordError::ValueTypeMismatch { .. })
1426 ));
1427
1428 let mut source = descriptor();
1429 source.name = "documents".to_string();
1430 source.constraints.push(ConstraintDescriptor {
1431 id: 2,
1432 name: "fk_documents_id___people".to_string(),
1433 definition: ConstraintDefinition::ForeignKey {
1434 columns: vec!["id".to_string()],
1435 referenced_table: "people".to_string(),
1436 referenced_columns: vec!["id".to_string()],
1437 on_delete: ForeignKeyActionDescriptor::Restrict,
1438 on_update: ForeignKeyActionDescriptor::Restrict,
1439 },
1440 });
1441 let mut record = DynamicRecord::new(source);
1442 record.set_reference("id", &reference).unwrap();
1443 assert_eq!(
1444 record.field("id").unwrap().value(),
1445 &FieldValue::Value {
1446 value: TypedValue::Integer(7)
1447 }
1448 );
1449
1450 let other = DynamicReference::new("other", "id", TypedValue::Integer(7));
1451 assert!(matches!(
1452 record.set_reference("id", &other),
1453 Err(RecordError::ReferenceSourceMismatch { .. })
1454 ));
1455 }
1456
1457 #[test]
1458 fn typed_columns_cover_predicates_and_transitive_navigation() {
1459 let employee = TypedColumn::<Reference<EmployeeEntity>, TestGeneratedEntity>::new(
1460 "payroll_documents",
1461 "employee_id",
1462 DataTypeDescriptor::Integer,
1463 false,
1464 );
1465 let department = TypedColumn::<Reference<DepartmentEntity>, EmployeeEntity>::new(
1466 "employees",
1467 "department_id",
1468 DataTypeDescriptor::Integer,
1469 false,
1470 );
1471 let department_name = TypedColumn::<String, DepartmentEntity>::new(
1472 "departments",
1473 "name",
1474 DataTypeDescriptor::Text,
1475 false,
1476 );
1477
1478 let path = employee.field(department).field(department_name);
1479 assert_eq!(
1480 path.expr().0,
1481 Expression::Navigation {
1482 root: "payroll_documents".to_string(),
1483 path: vec![
1484 "employee_id".to_string(),
1485 "department_id".to_string(),
1486 "name".to_string(),
1487 ],
1488 }
1489 );
1490
1491 let score = TypedColumn::<i64>::new("people", "score", DataTypeDescriptor::Integer, true);
1492 let query = QueryBuilder::from_relation(crate::table("people"))
1493 .select([score.clone().expr()])
1494 .filter(score.clone().between(10_i64, 20_i64).or(score.is_null()));
1495 let compiled = query.to_sql().unwrap();
1496 assert_eq!(compiled.parameters.len(), 2);
1497 assert!(compiled.sql.contains("BETWEEN $1 AND $2"));
1498 }
1499}