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 pub generator_supports_domain: bool,
112 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 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 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 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 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 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 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}