1use std::collections::BTreeSet;
2
3use itertools::Itertools;
4
5use super::column::Column;
6use crate::{
7 attribute::AttributeList,
8 db_name_impls, def_name_impls,
9 diagnostics::Report,
10 index::{Check, PrimaryKey, UniqueConstraint},
11 mixin::Mixin,
12 names::{
13 ColumnIdent, DbColumn, DbForeignKey, DbNativeType, DefName, ForeignKeyKind, MixinIdent,
14 TableDefName, TableIdent, TableKind, TypeIdent,
15 },
16 scalar::PropagatedScalarData,
17 uid::{next_uid, OwnUid, RenameExt, RenameMap, Uid},
18 w, HasIdent, Index, SchemaTable, TableColumn, TableForeignKey, TableIndex, TableItem,
19};
20
21#[derive(Debug)]
22pub struct Table {
23 uid: OwnUid,
24 name: TableDefName,
25 pub docs: Vec<String>,
26 pub attrlist: AttributeList,
27 pub columns: Vec<Column>,
28 pub annotations: Vec<TableAnnotation>,
29 pub foreign_keys: Vec<ForeignKey>,
30 pub mixins: Vec<MixinIdent>,
31}
32def_name_impls!(Table, TableKind);
33impl Table {
34 pub fn new(
35 docs: Vec<String>,
36 attrlist: AttributeList,
37 name: TableDefName,
38 columns: Vec<Column>,
39 annotations: Vec<TableAnnotation>,
40 foreign_keys: Vec<ForeignKey>,
41 mixins: Vec<MixinIdent>,
42 ) -> Self {
43 Self {
44 uid: next_uid(),
45 name,
46 docs,
47 attrlist,
48 columns,
49 annotations,
50 foreign_keys,
51 mixins,
52 }
53 }
54 pub fn primary_key(&self) -> Option<&PrimaryKey> {
55 self.annotations
56 .iter()
57 .filter_map(TableAnnotation::as_primary_key)
58 .at_most_one()
59 .expect("at most one pk")
60 }
61 pub fn is_external(&self) -> bool {
62 self.annotations
63 .iter()
64 .any(|a| matches!(a, TableAnnotation::External))
65 }
66 pub(crate) fn assimilate_mixin(&mut self, mixin: &Mixin) {
67 let Mixin {
68 docs,
69 attrlist,
70 columns,
71 annotations,
72 foreign_keys,
73 mixins,
74 ..
75 } = mixin;
76 self.docs.extend_from_slice(docs);
77 self.attrlist.0.extend_from_slice(&attrlist.0);
78 self.columns
79 .extend(columns.iter().map(|c| c.clone_for_mixin()));
80 self.annotations
81 .extend(annotations.iter().map(|a| a.clone_for_mixin()));
82 self.foreign_keys
83 .extend(foreign_keys.iter().map(|fk| fk.clone_for_mixin()));
84 self.mixins.extend(mixins.clone());
85 }
86}
87#[derive(Debug)]
88pub enum TableAnnotation {
89 Check(Check),
90 Unique(UniqueConstraint),
91 PrimaryKey(PrimaryKey),
92 Index(Index),
93 External,
94}
95
96#[derive(Debug, PartialEq, Eq, Clone, Copy)]
97pub enum OnDelete {
98 SetNull,
99 SetDefault,
100 Restrict,
101 Noop,
102 Cascade,
103}
104impl OnDelete {
105 pub fn sql(&self) -> Option<&'static str> {
106 Some(match self {
107 OnDelete::SetNull => "SET NULL",
108 OnDelete::SetDefault => "SET DEFAULT",
109 OnDelete::Restrict => "RESTRICT",
110 OnDelete::Noop => return None,
111 OnDelete::Cascade => "CASCADE",
112 })
113 }
114}
115
116#[derive(Debug)]
117pub struct ForeignKey {
118 uid: OwnUid,
119 name: Option<DbForeignKey>,
120 pub source_fields: Option<Vec<ColumnIdent>>,
121 pub target: TableIdent,
122 pub target_fields: Option<Vec<ColumnIdent>>,
123 pub on_delete: OnDelete,
124}
125db_name_impls!(ForeignKey, ForeignKeyKind);
126impl ForeignKey {
127 pub fn new(
128 name: Option<DbForeignKey>,
129 source_fields: Option<Vec<ColumnIdent>>,
130 target: TableIdent,
131 target_fields: Option<Vec<ColumnIdent>>,
132 on_delete: OnDelete,
133 ) -> Self {
134 Self {
135 uid: next_uid(),
136 name,
137 source_fields,
138 target,
139 target_fields,
140 on_delete,
141 }
142 }
143
144 pub fn clone_for_mixin(&self) -> Self {
145 Self::new(
146 self.name.clone(),
147 self.source_fields.clone(),
148 self.target.clone(),
149 self.target_fields.clone(),
150 self.on_delete,
151 )
152 }
153}
154
155impl TableAnnotation {
156 pub fn as_index(&self) -> Option<&Index> {
157 match self {
158 TableAnnotation::Index(i) => Some(i),
159 _ => None,
160 }
161 }
162
163 pub fn as_primary_key(&self) -> Option<&PrimaryKey> {
164 if let Self::PrimaryKey(v) = self {
165 Some(v)
166 } else {
167 None
168 }
169 }
170
171 pub fn as_check(&self) -> Option<&Check> {
172 if let Self::Check(c) = self {
173 Some(c)
174 } else {
175 None
176 }
177 }
178
179 pub fn as_unique_constraint(&self) -> Option<&UniqueConstraint> {
180 if let Self::Unique(u) = self {
181 Some(u)
182 } else {
183 None
184 }
185 }
186
187 pub fn clone_for_mixin(&self) -> Self {
188 match self {
189 TableAnnotation::Check(c) => Self::Check(c.clone_for_propagate()),
190 TableAnnotation::Unique(u) => Self::Unique(u.clone_for_propagate()),
191 TableAnnotation::PrimaryKey(p) => Self::PrimaryKey(p.clone_for_propagate()),
192 TableAnnotation::Index(i) => Self::Index(i.clone_for_propagate()),
193 TableAnnotation::External => Self::External,
194 }
195 }
196}
197
198impl Table {
199 pub fn process(&mut self) {
200 for column in self.columns.iter_mut() {
201 let propagated = column.propagate_annotations();
202 self.annotations.extend(propagated);
203 if let Some(fk) = column.propagate_foreign_key() {
204 self.foreign_keys.push(fk);
205 }
206 }
207 }
208 pub(crate) fn propagate_scalar_data(
209 &mut self,
210 scalar: TypeIdent,
211 propagated: &PropagatedScalarData,
212 ) {
213 for col in self.columns.iter_mut() {
214 col.propagate_scalar_data(scalar, propagated)
215 }
216 }
217
218 pub fn db_name(&self, column: &ColumnIdent, rn: &RenameMap) -> DbColumn {
219 for ele in self.columns.iter() {
220 if &ele.id() == column {
221 return ele.db(rn);
222 }
223 }
224 let table = self.db(rn);
225 unreachable!("unknown field: {table:?}.{column:?}");
226 }
227 pub fn db_names(
228 &self,
229 columns: impl IntoIterator<Item = ColumnIdent>,
230 rn: &RenameMap,
231 ) -> Vec<DbColumn> {
232 columns.into_iter().map(|c| self.db_name(&c, rn)).collect()
233 }
234 pub fn format_index_name(
235 &self,
236 columns: impl Iterator<Item = ColumnIdent>,
237 rn: &RenameMap,
238 ) -> String {
239 let mut out = String::new();
240 for (i, column) in columns.enumerate() {
241 if i != 0 {
242 w!(out, "_");
243 }
244 let db_name = self.db_name(&column, rn);
245 w!(out, "{}", db_name.raw());
246 }
247 assert!(!out.is_empty(), "passed no columns");
248 out
249 }
250 pub fn print_column_list(
251 &self,
252 out: &mut String,
253 columns: impl Iterator<Item = ColumnIdent>,
254 rn: &RenameMap,
255 ) {
256 for (i, column) in columns.enumerate() {
257 if i != 0 {
258 w!(out, ", ");
259 }
260 let db_name = self.db_name(&column, rn);
261 w!(out, "{}", db_name.raw());
262 }
263 }
264}
265impl<'a> SchemaTable<'a> {
266 fn item<I>(&self, value: &'a I) -> TableItem<'a, I> {
267 TableItem::unchecked_new(*self, value)
268 }
269 pub fn schema_column(&'a self, column: ColumnIdent) -> TableColumn<'a> {
270 self.columns()
271 .find(|c| c.id() == column)
272 .unwrap_or_else(|| panic!("column not found: {column:?}"))
273 }
274 pub fn columns(&self) -> impl Iterator<Item = TableColumn<'_>> {
275 self.columns.iter().map(|i| self.item(i))
276 }
277 pub fn indexes(&self) -> impl Iterator<Item = TableIndex<'_>> {
278 self.annotations
279 .iter()
280 .filter_map(TableAnnotation::as_index)
281 .map(|i| self.item(i))
282 }
283 pub fn pk(&self) -> Option<&PrimaryKey> {
284 self.annotations
285 .iter()
286 .filter_map(TableAnnotation::as_primary_key)
287 .at_most_one()
288 .expect("pk is not merged")
289 }
290 pub fn foreign_keys(&self) -> impl Iterator<Item = TableForeignKey<'_>> {
291 self.foreign_keys.iter().map(|i| self.item(i))
292 }
293 pub fn cardinality(&self, columns: impl IntoIterator<Item = ColumnIdent>) -> Cardinality {
294 fn is_unique_by_index(
295 indexed: impl IntoIterator<Item = ColumnIdent>,
296 columns: &BTreeSet<ColumnIdent>,
297 ) -> bool {
298 let indexed = indexed.into_iter().collect::<BTreeSet<_>>();
299 if indexed.difference(columns).next().is_none() {
300 return true;
308 }
309 false
310 }
311 let columns = columns.into_iter().collect::<BTreeSet<_>>();
312 for ele in self.annotations.iter() {
313 match ele {
314 TableAnnotation::Index(i) => {
315 if i.unique && is_unique_by_index(i.field_idents(), &columns) {
316 return Cardinality::One;
317 }
318 }
319 TableAnnotation::PrimaryKey(pk) => {
320 if is_unique_by_index(pk.columns.iter().copied(), &columns) {
321 return Cardinality::One;
322 }
323 }
324 TableAnnotation::Unique(u) => {
325 if is_unique_by_index(u.columns.iter().copied(), &columns) {
326 return Cardinality::One;
327 }
328 }
329 TableAnnotation::Check { .. } => {}
330 TableAnnotation::External => {}
331 }
332 }
333 Cardinality::Many
346 }
347}
348
349#[derive(PartialEq, Eq)]
350pub enum Cardinality {
351 One,
352 Many,
353}
354
355impl<'s> TableForeignKey<'s> {
356 pub fn source_columns(&self) -> Vec<ColumnIdent> {
357 if let Some(source) = &self.source_fields {
358 return source.clone();
359 }
360 if let Some(target) = &self.target_fields {
361 return target.clone();
362 };
363 panic!("no fields defined")
364 }
365 pub fn target_columns(&self) -> Vec<ColumnIdent> {
366 if let Some(target) = &self.target_fields {
367 return target.clone();
368 };
369 if let Some(source) = &self.source_fields {
370 return source.clone();
371 }
372 panic!("no fields defined")
373 }
374 pub fn target_table(&self) -> SchemaTable<'s> {
375 let table = &self.target;
376 self.table
377 .schema
378 .schema_table(table)
379 .expect("target table is not defined")
380 }
381 pub fn source_db_columns(&self, rn: &RenameMap) -> Vec<DbColumn> {
382 self.source_columns()
383 .into_iter()
384 .map(|f| self.table.db_name(&f, rn))
385 .collect()
386 }
387 pub fn target_db_columns(&self, rn: &RenameMap) -> Vec<DbColumn> {
388 let target_table = self.target_table();
389 self.target_columns()
390 .into_iter()
391 .map(|f| target_table.db_name(&f, rn))
392 .collect()
393 }
394 pub fn db_types(&self, rn: &RenameMap, report: &mut Report) -> Vec<DbNativeType> {
395 let db_types: Vec<_> = self
396 .source_columns()
397 .into_iter()
398 .map(|f| self.table.schema_column(f).db_type(rn, report))
399 .collect();
400 let target_table = self.target_table();
402 let target_db_types: Vec<_> = self
403 .target_columns()
404 .into_iter()
405 .map(|f| target_table.schema_column(f).db_type(rn, report))
406 .collect();
407 assert_eq!(db_types, target_db_types);
408 db_types
409 }
410 pub fn cardinality(&self) -> (Cardinality, Cardinality) {
411 (
412 self.table.cardinality(self.source_columns()),
413 self.target_table().cardinality(self.target_columns()),
414 )
415 }
416}