immigrant_schema/
root.rs

1use itertools::Itertools;
2use std::{
3	collections::{HashMap, HashSet},
4	mem,
5};
6
7use super::{
8	scalar::{Enum, Scalar},
9	table::Table,
10};
11use crate::{
12	composite::Composite,
13	diagnostics::{self, Report},
14	ids::{DbIdent, Ident},
15	mixin::Mixin,
16	names::{DbNativeType, DbTable, DbType, TableIdent, TypeIdent},
17	process::{check_unique_identifiers, check_unique_mixin_identifiers, NamingConvention, Pgnc},
18	scalar::PropagatedScalarData,
19	sql::Sql,
20	uid::{RenameExt, RenameMap},
21	util::UniqueMap as _,
22	view::View,
23	HasIdent, SchemaComposite, SchemaEnum, SchemaItem, SchemaScalar, SchemaSql, SchemaTable,
24	SchemaTableOrView, SchemaType, SchemaView,
25};
26
27#[derive(derivative::Derivative)]
28#[derivative(Debug)]
29pub enum Item {
30	#[derivative(Debug = "transparent")]
31	Table(Table),
32	#[derivative(Debug = "transparent")]
33	Mixin(Mixin),
34	#[derivative(Debug = "transparent")]
35	Enum(Enum),
36	#[derivative(Debug = "transparent")]
37	Scalar(Scalar),
38	#[derivative(Debug = "transparent")]
39	Composite(Composite),
40	#[derivative(Debug = "transparent")]
41	View(View),
42}
43impl Item {
44	pub fn is_table(&self) -> bool {
45		matches!(self, Self::Table(_))
46	}
47	pub fn as_table(&self) -> Option<&Table> {
48		match self {
49			Self::Table(value) => Some(value),
50			_ => None,
51		}
52	}
53	pub fn as_table_mut(&mut self) -> Option<&mut Table> {
54		match self {
55			Self::Table(value) => Some(value),
56			_ => None,
57		}
58	}
59	pub fn is_enum(&self) -> bool {
60		matches!(self, Self::Enum(_))
61	}
62	pub fn as_enum(&self) -> Option<&Enum> {
63		match self {
64			Self::Enum(value) => Some(value),
65			_ => None,
66		}
67	}
68	pub fn as_enum_mut(&mut self) -> Option<&mut Enum> {
69		match self {
70			Self::Enum(value) => Some(value),
71			_ => None,
72		}
73	}
74	pub fn is_scalar(&self) -> bool {
75		matches!(self, Self::Scalar(_))
76	}
77	pub fn as_scalar(&self) -> Option<&Scalar> {
78		match self {
79			Self::Scalar(value) => Some(value),
80			_ => None,
81		}
82	}
83	pub fn as_composite(&self) -> Option<&Composite> {
84		match self {
85			Self::Composite(value) => Some(value),
86			_ => None,
87		}
88	}
89	pub fn as_view(&self) -> Option<&View> {
90		match self {
91			Self::View(value) => Some(value),
92			_ => None,
93		}
94	}
95	pub fn as_composite_mut(&mut self) -> Option<&mut Composite> {
96		match self {
97			Self::Composite(value) => Some(value),
98			_ => None,
99		}
100	}
101	pub fn as_scalar_mut(&mut self) -> Option<&mut Scalar> {
102		match self {
103			Self::Scalar(value) => Some(value),
104			_ => None,
105		}
106	}
107}
108
109pub struct SchemaProcessOptions {
110	/// If not - then every scalar is transformed to a regular type by inlining.
111	pub generator_supports_domain: bool,
112	/// How to create item names, how annotation deduplication should be handled, etc.
113	pub naming_convention: NamingConvention,
114}
115
116#[derive(Debug, Default)]
117pub struct Schema(pub Vec<Item>);
118impl Schema {
119	pub fn process(
120		&mut self,
121		options: &SchemaProcessOptions,
122		rn: &mut RenameMap,
123		report: &mut Report,
124	) {
125		self.0.sort_by_key(|i| match i {
126			Item::Table(_) => 1,
127			Item::Enum(_) => 0,
128			Item::Scalar(_) => 9997,
129			Item::Composite(_) => 9998,
130			Item::View(_) => 9999,
131			Item::Mixin(_) => 10000,
132		});
133
134		// Mixins have their own identifier namespace due to
135		// special definiton and reference syntax.
136		check_unique_mixin_identifiers(self, report);
137
138		let (_mixins, items): (Vec<Mixin>, Vec<Item>) = mem::take(&mut self.0)
139			.into_iter()
140			.partition_map(|v| match v {
141				Item::Mixin(m) => itertools::Either::Left(m),
142				i => itertools::Either::Right(i),
143			});
144		self.0 = items;
145
146		let mut mixins = HashMap::new();
147		for mixin in _mixins {
148			mixins.insert_unique(mixin.id(), mixin);
149		}
150
151		for table in self.0.iter_mut().filter_map(Item::as_table_mut) {
152			loop {
153				let mut assimilated_mixins = HashSet::new();
154				let table_mixins = std::mem::take(&mut table.mixins);
155				if table_mixins.is_empty() {
156					break;
157				}
158				for mixin in table_mixins {
159					if !assimilated_mixins.insert(mixin) {
160						panic!(
161							"mixin {} was applied twice to table {}",
162							mixin.name(),
163							table.id().name()
164						);
165					}
166					let mixin = mixins.get(&mixin).expect("unknown mixin identifier");
167					table.assimilate_mixin(mixin);
168				}
169			}
170		}
171
172		// Currently, no passes generate new items, nor
173		// alter identifiers, it should be moved to the end.
174		check_unique_identifiers(self, report);
175
176		let mut propagated_scalars = HashMap::new();
177		for s in self.0.iter_mut().filter_map(Item::as_scalar_mut) {
178			let inline = s.is_always_inline() || !options.generator_supports_domain;
179			let data = s.propagate(inline);
180			propagated_scalars.insert_unique(s.id(), data);
181		}
182
183		// Propagate scalar annotations to fields
184		for composite in self.0.iter_mut().filter_map(Item::as_composite_mut) {
185			for (id, data) in &propagated_scalars {
186				composite.propagate_scalar_data(*id, data);
187			}
188			composite.process();
189		}
190
191		// Propagate composite annotations to other composites
192		loop {
193			let mut extended_this_step = <HashMap<TypeIdent, PropagatedScalarData>>::new();
194			for s in self.0.iter_mut().filter_map(Item::as_composite_mut) {
195				s.process();
196				let to_propagate = s.propagate();
197				let existing = extended_this_step.entry(s.id()).or_default();
198				existing.extend(to_propagate);
199			}
200
201			if extended_this_step.values().all(|v| v.is_empty()) {
202				break;
203			}
204
205			// Propagate newly discovered propagations
206			for composite in self.0.iter_mut().filter_map(Item::as_composite_mut) {
207				for (id, data) in &extended_this_step {
208					composite.propagate_scalar_data(*id, data);
209				}
210				composite.process();
211			}
212
213			for (id, data) in extended_this_step {
214				let existing = propagated_scalars.entry(id).or_default();
215				existing.extend(data);
216			}
217		}
218
219		// Propagate scalar annotations to columns
220		for table in self.0.iter_mut().filter_map(Item::as_table_mut) {
221			for (id, data) in &propagated_scalars {
222				table.propagate_scalar_data(*id, data);
223			}
224			table.process();
225		}
226
227		match options.naming_convention {
228			NamingConvention::Postgres => (Pgnc(self)).process_naming(rn),
229		};
230	}
231	pub fn material_items(&self) -> Vec<SchemaItem<'_>> {
232		self.0
233			.iter()
234			.filter_map(|v| {
235				Some(match v {
236					Item::Table(table) => SchemaItem::Table(SchemaTable {
237						schema: self,
238						table,
239					}),
240					Item::Enum(en) => SchemaItem::Enum(SchemaEnum { schema: self, en }),
241					Item::Scalar(scalar) if !scalar.is_always_inline() => {
242						SchemaItem::Scalar(SchemaScalar {
243							schema: self,
244							scalar,
245						})
246					}
247					Item::Composite(composite) => SchemaItem::Composite(SchemaComposite {
248						schema: self,
249						composite,
250					}),
251					Item::View(view) => SchemaItem::View(SchemaView { schema: self, view }),
252					_ => return None,
253				})
254			})
255			.collect()
256	}
257	pub fn items(&self) -> Vec<SchemaItem<'_>> {
258		self.0
259			.iter()
260			.map(|v| match v {
261				Item::Table(table) => SchemaItem::Table(SchemaTable {
262					schema: self,
263					table,
264				}),
265				Item::Enum(en) => SchemaItem::Enum(SchemaEnum { schema: self, en }),
266				Item::Scalar(scalar) => SchemaItem::Scalar(SchemaScalar {
267					schema: self,
268					scalar,
269				}),
270				Item::Composite(composite) => SchemaItem::Composite(SchemaComposite {
271					schema: self,
272					composite,
273				}),
274				Item::View(view) => SchemaItem::View(SchemaView { schema: self, view }),
275				Item::Mixin(_) => unreachable!("mixins are assimilted at the earliest stage"),
276			})
277			.collect()
278	}
279	pub fn schema_ty(&self, name: TypeIdent) -> SchemaType<'_> {
280		if let Some(scalar) = self.scalars().find(|s| s.id() == name) {
281			return SchemaType::Scalar(SchemaScalar {
282				schema: self,
283				scalar,
284			});
285		}
286		if let Some(en) = self.enums().find(|s| s.id() == name) {
287			return SchemaType::Enum(SchemaEnum { schema: self, en });
288		}
289		if let Some(composite) = self.composites().find(|s| s.id() == name) {
290			return SchemaType::Composite(SchemaComposite {
291				schema: self,
292				composite,
293			});
294		}
295		panic!("schema type not found: {name:?}")
296	}
297	pub fn native_type(
298		&self,
299		name: &TypeIdent,
300		rn: &RenameMap,
301		report: &mut Report,
302	) -> DbNativeType {
303		for item in self.0.iter() {
304			match item {
305				Item::Enum(e) if &e.id() == name => return e.db_type(rn),
306				Item::Scalar(v) if &v.id() == name => {
307					return SchemaScalar {
308						schema: self,
309						scalar: v,
310					}
311					.native(rn, report)
312				}
313				Item::Composite(c) if &c.id() == name => return c.db_type(rn),
314				_ => continue,
315			}
316		}
317		report
318			.error("native type not found")
319			.annotate("referenced here", name.span());
320		return DbNativeType::new("ERROR");
321	}
322	pub fn tables(&self) -> impl Iterator<Item = &Table> {
323		self.0.iter().filter_map(Item::as_table)
324	}
325	pub fn views(&self) -> impl Iterator<Item = &View> {
326		self.0.iter().filter_map(Item::as_view)
327	}
328	pub fn enums(&self) -> impl Iterator<Item = &Enum> {
329		self.0.iter().filter_map(Item::as_enum)
330	}
331	pub fn scalars(&self) -> impl Iterator<Item = &Scalar> {
332		self.0.iter().filter_map(Item::as_scalar)
333	}
334	pub fn composites(&self) -> impl Iterator<Item = &Composite> {
335		self.0.iter().filter_map(Item::as_composite)
336	}
337
338	pub fn schema_table(&self, name: &TableIdent) -> Option<SchemaTable<'_>> {
339		self.tables()
340			.find(|t| &t.id() == name)
341			.map(|t| SchemaTable {
342				schema: self,
343				table: t,
344			})
345	}
346
347	pub fn schema_table_or_view(&self, name: &TableIdent) -> Option<SchemaTableOrView<'_>> {
348		if let Some(v) = self
349			.tables()
350			.find(|t| &t.id() == name)
351			.map(|t| SchemaTable {
352				schema: self,
353				table: t,
354			})
355			.map(SchemaTableOrView::Table)
356		{
357			return Some(v);
358		}
359		self.views()
360			.find(|t| t.id() == Ident::unchecked_cast(*name))
361			.map(|t| SchemaView {
362				schema: self,
363				view: t,
364			})
365			.map(SchemaTableOrView::View)
366	}
367
368	pub fn schema_scalar(&self, scalar: TypeIdent) -> SchemaScalar<'_> {
369		SchemaScalar {
370			schema: self,
371			scalar: self
372				.scalars()
373				.find(|c| c.id() == scalar)
374				.expect("scalar not found: {scalar:?}"),
375		}
376	}
377
378	pub fn db_tables(&self, rn: &RenameMap) -> Vec<DbTable> {
379		self.tables().map(|t| t.db(rn)).collect()
380	}
381	pub fn db_enums(&self, rn: &RenameMap) -> Vec<DbType> {
382		self.enums().map(|t| t.db(rn)).collect()
383	}
384	pub fn sql<'a>(&'a self, sql: &'a Sql) -> SchemaSql<'_> {
385		SchemaSql { schema: self, sql }
386	}
387}