immigrant_schema/
column.rs

1use std::mem;
2
3use itertools::{Either, Itertools};
4
5use super::{
6	index::Index,
7	sql::Sql,
8	table::{ForeignKey, TableAnnotation},
9};
10use crate::{
11	attribute::AttributeList, changelist::IsCompatible, def_name_impls, derive_is_isomorph_by_id_name, diagnostics::Report, index::{Check, PrimaryKey, UniqueConstraint}, names::{ColumnDefName, ColumnIdent, ColumnKind, DbNativeType, DefName, TypeIdent}, scalar::PropagatedScalarData, uid::{next_uid, OwnUid, RenameMap}, HasIdent, SchemaType, TableColumn
12};
13
14#[derive(Debug)]
15pub enum ColumnAnnotation {
16	/// Moved to table.
17	Check(Check),
18	/// Moved to table.
19	Unique(UniqueConstraint),
20	/// Moved to table.
21	PrimaryKey(PrimaryKey),
22	/// Moved to table.
23	Index(Index),
24	/// Column default annotation.
25	Default(Sql),
26	/// If column is created, then this attribute is used to prefill it, treat it as one-time, much more powerful
27	/// DEFAULT value.
28	/// It can be implemented as an UPDATE statement, or with SET TYPE ... USING, but not as one-time DEFAULT, because
29	/// it can reference other columns.
30	///
31	/// If column is updated, then it is just passed to USING expression, allowing to refernce older column value,
32	/// just be careful with that. Initially this expression was only usable for new column creation, but
33	/// after further consideration I decided there is no harm of allowing it for upgrades.
34	/// Yes, this is a semi-imperative action, however it is pretty isolated and subtle to make it actually work.
35	///
36	/// After all, it doesn't allow access to old schema version fields directly (except for the current field),
37	/// thus not breaking isolation of a standalone schema definition.
38	InitializeAs(Sql),
39}
40impl ColumnAnnotation {
41	fn as_default(&self) -> Option<&Sql> {
42		match self {
43			Self::Default(s) => Some(s),
44			_ => None,
45		}
46	}
47	fn as_initialize_as(&self) -> Option<&Sql> {
48		match self {
49			Self::InitializeAs(s) => Some(s),
50			_ => None,
51		}
52	}
53	fn propagate_to_table(self, column: ColumnIdent) -> Either<TableAnnotation, Self> {
54		Either::Left(match self {
55			ColumnAnnotation::Check(c) => TableAnnotation::Check(c.propagate_to_table(column)),
56			ColumnAnnotation::Unique(u) => TableAnnotation::Unique(u.propagate_to_table(column)),
57			ColumnAnnotation::PrimaryKey(p) => {
58				TableAnnotation::PrimaryKey(p.propagate_to_table(column))
59			}
60			ColumnAnnotation::Index(i) => TableAnnotation::Index(i.propagate_to_table(column)),
61			_ => return Either::Right(self),
62		})
63	}
64	fn clone_for_mixin(&self) -> Self {
65		self.clone_for_propagate()
66	}
67	fn clone_for_propagate(&self) -> Self {
68		match self {
69			ColumnAnnotation::Check(c) => Self::Check(c.clone_for_propagate()),
70			ColumnAnnotation::Unique(u) => Self::Unique(u.clone_for_propagate()),
71			ColumnAnnotation::PrimaryKey(p) => Self::PrimaryKey(p.clone_for_propagate()),
72			ColumnAnnotation::Index(i) => Self::Index(i.clone_for_propagate()),
73			ColumnAnnotation::Default(d) => Self::Default(d.clone()),
74			ColumnAnnotation::InitializeAs(i) => Self::InitializeAs(i.clone()),
75		}
76	}
77}
78
79#[derive(Debug)]
80pub struct Column {
81	uid: OwnUid,
82	name: ColumnDefName,
83	pub docs: Vec<String>,
84	pub attrs: AttributeList,
85	pub nullable: bool,
86	pub ty: TypeIdent,
87	pub annotations: Vec<ColumnAnnotation>,
88	pub foreign_key: Option<PartialForeignKey>,
89}
90def_name_impls!(Column, ColumnKind);
91derive_is_isomorph_by_id_name!(Column);
92impl Column {
93	pub fn new(
94		name: ColumnDefName,
95		docs: Vec<String>,
96		attrs: AttributeList,
97		nullable: bool,
98		ty: TypeIdent,
99		annotations: Vec<ColumnAnnotation>,
100		foreign_key: Option<PartialForeignKey>,
101	) -> Self {
102		Self {
103			uid: next_uid(),
104			attrs,
105			name,
106			docs,
107			nullable,
108			ty,
109			annotations,
110			foreign_key,
111		}
112	}
113}
114impl IsCompatible for Column {
115	fn is_compatible(&self, _new: &Self, _rn: &RenameMap, a: &mut Report, b: &mut Report) -> bool {
116		true
117	}
118}
119
120#[derive(Debug)]
121pub struct PartialForeignKey {
122	pub fk: ForeignKey,
123}
124impl PartialForeignKey {
125	fn clone_for_mixin(&self) -> Self {
126		Self {
127			fk: self.fk.clone_for_mixin(),
128		}
129	}
130}
131
132impl Column {
133	pub(crate) fn propagate_scalar_data(
134		&mut self,
135		scalar: TypeIdent,
136		propagated: &PropagatedScalarData,
137	) {
138		if self.ty == scalar {
139			self.annotations.extend(
140				propagated
141					.annotations
142					.iter()
143					.map(|v| v.clone_for_propagate()),
144			);
145		}
146	}
147	pub fn propagate_annotations(&mut self) -> Vec<TableAnnotation> {
148		let (annotations, retained) = mem::take(&mut self.annotations)
149			.into_iter()
150			.partition_map(|a| a.propagate_to_table(self.id()));
151		self.annotations = retained;
152		annotations
153	}
154	pub fn propagate_foreign_key(&mut self) -> Option<ForeignKey> {
155		let mut fk = self.foreign_key.take()?;
156		fk.fk.source_fields = Some(vec![self.id()]);
157		Some(fk.fk)
158	}
159
160	pub fn clone_for_mixin(&self) -> Column {
161		Column::new(
162			DefName::unchecked_new(
163				self.name.code,
164				// Makes little sense to support RenameMap here, columns renamed in Mixins are not propagated.
165				self.name.db.clone(),
166			),
167			self.docs.clone(),
168			self.attrs.clone(),
169			self.nullable,
170			self.ty,
171			self.annotations
172				.iter()
173				.map(|a| a.clone_for_mixin())
174				.collect(),
175			self.foreign_key.as_ref().map(|fk| fk.clone_for_mixin()),
176		)
177	}
178}
179
180impl<'a> TableColumn<'a> {
181	pub fn db_type(&self, rn: &RenameMap, report: &mut Report) -> DbNativeType {
182		self.table.schema.native_type(&self.ty, rn, report)
183	}
184	pub fn ty(&'a self) -> SchemaType<'a> {
185		self.table.schema.schema_ty(self.ty)
186	}
187	/// Only returns column default, if the underlying type has default value -
188	/// it needs to be handled manually.
189	/// If you only want to check if default exists - use has_default.
190	pub fn default(&self) -> Option<&Sql> {
191		self.annotations
192			.iter()
193			.filter_map(|v| v.as_default())
194			.at_most_one()
195			.unwrap()
196	}
197	pub fn initialize_as(&self) -> Option<&Sql> {
198		self.annotations
199			.iter()
200			.filter_map(|v| v.as_initialize_as())
201			.at_most_one()
202			.unwrap()
203	}
204	pub fn is_pk_part(&self) -> bool {
205		let Some(pk) = self.table.pk() else {
206			return false;
207		};
208		pk.columns.contains(&self.id())
209	}
210	pub fn has_default(&self) -> bool {
211		self.default().is_some() || self.ty().has_default()
212	}
213	pub fn is_pk_full(&self) -> bool {
214		let Some(pk) = self.table.pk() else {
215			return false;
216		};
217		pk.columns == [self.id()]
218	}
219}