immigrant_schema/
composite.rs

1use std::{mem, ops::Deref};
2
3use itertools::{Either, Itertools};
4
5use crate::{
6	attribute::AttributeList,
7	column::ColumnAnnotation,
8	def_name_impls, derive_is_isomorph_by_id_name,
9	diagnostics::Report,
10	ids::DbIdent,
11	index::Check,
12	names::{
13		CompositeItemDefName, DbNativeType, FieldIdent, FieldKind, TypeDefName, TypeIdent, TypeKind,
14	},
15	scalar::PropagatedScalarData,
16	sql::Sql,
17	uid::{next_uid, OwnUid, RenameExt, RenameMap},
18	HasIdent, IsCompatible, SchemaComposite, SchemaType,
19};
20
21#[derive(Debug)]
22pub enum FieldAnnotation {
23	Check(Check),
24}
25impl FieldAnnotation {
26	fn propagate_to_composite(self, field: FieldIdent) -> Either<CompositeAnnotation, Self> {
27		Either::Left(match self {
28			FieldAnnotation::Check(c) => {
29				CompositeAnnotation::Check(c.propagate_to_composite(field))
30			}
31			#[allow(unreachable_patterns)]
32			_ => return Either::Right(self),
33		})
34	}
35	fn clone_for_propagate(&self) -> Self {
36		match self {
37			FieldAnnotation::Check(c) => Self::Check(c.clone_for_propagate()),
38		}
39	}
40}
41
42#[derive(Debug)]
43pub struct Field {
44	uid: OwnUid,
45	name: CompositeItemDefName,
46	pub docs: Vec<String>,
47	pub nullable: bool,
48	pub ty: TypeIdent,
49	pub annotations: Vec<FieldAnnotation>,
50}
51def_name_impls!(Field, FieldKind);
52derive_is_isomorph_by_id_name!(Field);
53
54impl Field {
55	pub fn new(
56		docs: Vec<String>,
57		name: CompositeItemDefName,
58		nullable: bool,
59		ty: TypeIdent,
60		annotations: Vec<FieldAnnotation>,
61	) -> Self {
62		Self {
63			uid: next_uid(),
64			docs,
65			name,
66			nullable,
67			ty,
68			annotations,
69		}
70	}
71	pub(crate) fn propagate_scalar_data(
72		&mut self,
73		scalar: TypeIdent,
74		propagated: &PropagatedScalarData,
75	) {
76		if self.ty == scalar {
77			self.annotations.extend(
78				propagated
79					.field_annotations
80					.iter()
81					.map(|a| a.clone_for_propagate()),
82			);
83		}
84	}
85	pub fn propagate_annotations(&mut self) -> Vec<CompositeAnnotation> {
86		let (annotations, retained) = mem::take(&mut self.annotations)
87			.into_iter()
88			.partition_map(|a| a.propagate_to_composite(self.id()));
89		self.annotations = retained;
90		annotations
91	}
92}
93
94#[derive(Debug)]
95pub enum CompositeAnnotation {
96	Check(Check),
97}
98impl CompositeAnnotation {
99	fn propagate_to_field(&self) -> Option<FieldAnnotation> {
100		Some(match self {
101			CompositeAnnotation::Check(c) => FieldAnnotation::Check(c.clone_for_propagate()),
102		})
103	}
104	fn propagate_to_column(self) -> Either<ColumnAnnotation, Self> {
105		Either::Left(match self {
106			CompositeAnnotation::Check(c) => ColumnAnnotation::Check(c),
107		})
108	}
109}
110
111#[derive(Debug)]
112pub struct Composite {
113	uid: OwnUid,
114	name: TypeDefName,
115	pub docs: Vec<String>,
116	pub attrlist: AttributeList,
117	pub fields: Vec<Field>,
118	pub annotations: Vec<CompositeAnnotation>,
119}
120def_name_impls!(Composite, TypeKind);
121
122impl Composite {
123	pub fn new(
124		docs: Vec<String>,
125		attrlist: AttributeList,
126		name: TypeDefName,
127		fields: Vec<Field>,
128		mut annotations: Vec<CompositeAnnotation>,
129	) -> Self {
130		let mut checks = vec![];
131		for field in &fields {
132			if field.nullable {
133				continue;
134			}
135			checks.push(Sql::BinOp(
136				Box::new(Sql::GetField(Box::new(Sql::Placeholder), field.id())),
137				crate::sql::SqlOp::SNe,
138				Box::new(Sql::Null),
139			))
140		}
141		if !checks.is_empty() {
142			annotations.push(CompositeAnnotation::Check(Check::new(
143				Some(DbIdent::new("composite_nullability_check")),
144				Sql::any([
145					Sql::BinOp(
146						Box::new(Sql::Placeholder),
147						crate::sql::SqlOp::SEq,
148						Box::new(Sql::Null),
149					),
150					Sql::all(checks),
151				]),
152			)));
153		}
154		Self {
155			uid: next_uid(),
156			name,
157			docs,
158			attrlist,
159			fields,
160			annotations,
161		}
162	}
163	pub fn db_type(&self, rn: &RenameMap) -> DbNativeType {
164		DbNativeType::unchecked_from(self.db(rn))
165	}
166	pub(crate) fn propagate_scalar_data(
167		&mut self,
168		scalar: TypeIdent,
169		propagated: &PropagatedScalarData,
170	) {
171		for col in self.fields.iter_mut() {
172			col.propagate_scalar_data(scalar, propagated)
173		}
174	}
175	pub fn process(&mut self) {
176		for column in self.fields.iter_mut() {
177			let propagated = column.propagate_annotations();
178			self.annotations.extend(propagated);
179		}
180	}
181
182	pub(crate) fn propagate(&mut self) -> PropagatedScalarData {
183		let annotations = mem::take(&mut self.annotations);
184		let field_annotations = annotations
185			.iter()
186			.flat_map(CompositeAnnotation::propagate_to_field)
187			.collect_vec();
188		let (annotations, retained) = annotations
189			.into_iter()
190			.partition_map(|a| a.propagate_to_column());
191		self.annotations = retained;
192		PropagatedScalarData {
193			annotations,
194			field_annotations,
195		}
196	}
197}
198
199impl SchemaComposite<'_> {
200	pub fn field(&self, field: FieldIdent) -> CompositeField<'_> {
201		self.fields()
202			.find(|c| c.id() == field)
203			.expect("field not found")
204	}
205	pub fn fields(&self) -> impl Iterator<Item = CompositeField<'_>> {
206		self.fields.iter().map(|field| CompositeField {
207			composite: *self,
208			field,
209		})
210	}
211}
212
213#[derive(Debug, Clone, Copy)]
214pub struct CompositeField<'a> {
215	composite: SchemaComposite<'a>,
216	field: &'a Field,
217}
218def_name_impls!(CompositeField<'_>, FieldKind);
219derive_is_isomorph_by_id_name!(CompositeField<'_>);
220
221impl IsCompatible for CompositeField<'_> {
222	fn is_compatible(
223		&self,
224		new: &Self,
225		rn: &RenameMap,
226		report_self: &mut Report,
227		report_new: &mut Report,
228	) -> bool {
229		self.name.db == new.name.db && self.db_type(rn, report_self) == new.db_type(rn, report_new)
230	}
231}
232
233impl Deref for CompositeField<'_> {
234	type Target = Field;
235
236	fn deref(&self) -> &Self::Target {
237		self.field
238	}
239}
240
241impl CompositeField<'_> {
242	pub fn ty(&self) -> SchemaType<'_> {
243		self.composite.schema.schema_ty(self.ty)
244	}
245	pub fn db_type(&self, rn: &RenameMap, report: &mut Report) -> DbNativeType {
246		self.composite.schema.native_type(&self.ty, rn, report)
247	}
248}