1use std::mem;
2
3use itertools::{Either, Itertools};
4
5use super::sql::Sql;
6use crate::{
7 attribute::AttributeList,
8 changelist::IsCompatible,
9 column::ColumnAnnotation,
10 composite::FieldAnnotation,
11 def_name_impls, derive_is_isomorph_by_id_name,
12 diagnostics::Report,
13 index::{Check, Index, PrimaryKey, UniqueConstraint},
14 names::{
15 DbEnumItem, DbNativeType, EnumItemDefName, EnumItemKind, TypeDefName, TypeIdent, TypeKind,
16 },
17 root::Schema,
18 uid::{next_uid, OwnUid, RenameExt, RenameMap},
19 HasIdent, SchemaEnum, SchemaScalar,
20};
21
22#[derive(Debug)]
23pub struct EnumItem {
24 uid: OwnUid,
25 pub docs: Vec<String>,
26 name: EnumItemDefName,
27}
28impl EnumItem {
29 pub fn new(docs: Vec<String>, name: EnumItemDefName) -> Self {
30 Self {
31 uid: next_uid(),
32 docs,
33 name,
34 }
35 }
36}
37def_name_impls!(EnumItem, EnumItemKind);
38derive_is_isomorph_by_id_name!(EnumItem);
39
40#[derive(Debug, Clone, Copy)]
42pub struct EnumItemHandle<'a> {
43 enum_: SchemaEnum<'a>,
44 item: &'a EnumItem,
45}
46def_name_impls!(EnumItemHandle<'_>, EnumItemKind);
47derive_is_isomorph_by_id_name!(EnumItemHandle<'_>);
48
49impl std::ops::Deref for EnumItemHandle<'_> {
50 type Target = EnumItem;
51
52 fn deref(&self) -> &Self::Target {
53 self.item
54 }
55}
56
57impl IsCompatible for EnumItemHandle<'_> {
58 fn is_compatible(
59 &self,
60 _new: &Self,
61 _rn: &RenameMap,
62 _a: &mut Report,
63 _b: &mut Report,
64 ) -> bool {
65 true
66 }
67}
68
69#[derive(Debug)]
70pub struct Enum {
71 uid: OwnUid,
72 name: TypeDefName,
73 pub docs: Vec<String>,
74 pub attrlist: AttributeList,
75 pub items: Vec<EnumItem>,
76}
77impl Enum {
78 pub fn new(
79 docs: Vec<String>,
80 attrlist: AttributeList,
81 name: TypeDefName,
82 items: Vec<EnumItem>,
83 ) -> Self {
84 Self {
85 uid: next_uid(),
86 name,
87 docs,
88 attrlist,
89 items,
90 }
91 }
92 pub fn db_items(&self, rn: &RenameMap) -> Vec<DbEnumItem> {
93 self.items.iter().map(|i| i.db(rn)).collect()
94 }
95 pub fn db_type(&self, rn: &RenameMap) -> DbNativeType {
96 DbNativeType::unchecked_from(self.db(rn))
97 }
98}
99def_name_impls!(Enum, TypeKind);
100
101impl SchemaEnum<'_> {
102 pub fn items(&self) -> impl Iterator<Item = EnumItemHandle<'_>> {
103 self.items
104 .iter()
105 .map(|item| EnumItemHandle { enum_: *self, item })
106 }
107}
108
109#[derive(Debug, Default)]
110pub(crate) struct PropagatedScalarData {
111 pub annotations: Vec<ColumnAnnotation>,
112 pub field_annotations: Vec<FieldAnnotation>,
113}
114impl PropagatedScalarData {
115 pub(crate) fn extend(&mut self, other: Self) {
117 self.annotations.extend(other.annotations);
118 self.field_annotations.extend(other.field_annotations);
119 }
120 pub(crate) fn is_empty(&self) -> bool {
121 self.annotations.is_empty() && self.field_annotations.is_empty()
122 }
123}
124
125#[derive(Debug, Clone)]
126pub enum InlineSqlTypePart {
127 Raw(String),
128 TypeRef(TypeIdent),
129}
130#[derive(Debug, Clone)]
131pub struct InlineSqlType(pub Vec<InlineSqlTypePart>);
132impl InlineSqlType {
133 fn expand(&self, rn: &RenameMap, schema: &Schema, report: &mut Report) -> DbNativeType {
134 let mut out = String::new();
135 for p in &self.0 {
136 match p {
137 InlineSqlTypePart::Raw(r) => out.push_str(r),
138 InlineSqlTypePart::TypeRef(ident) => {
139 out.push_str(schema.native_type(ident, rn, report).raw())
140 }
141 }
142 }
143 DbNativeType::new(&out)
144 }
145 fn dependencies(&self, schema: &Schema, out: &mut Vec<TypeIdent>) {
146 for v in &self.0 {
147 if let InlineSqlTypePart::TypeRef(t) = v {
148 schema.schema_ty(*t).depend_on(out);
149 }
150 }
151 }
152}
153
154#[derive(Debug)]
158pub struct Scalar {
159 uid: OwnUid,
160 name: TypeDefName,
161 pub docs: Vec<String>,
162 pub attrlist: AttributeList,
163 native: InlineSqlType,
164 pub annotations: Vec<ScalarAnnotation>,
165 inlined: bool,
166}
167def_name_impls!(Scalar, TypeKind);
168impl Scalar {
169 pub fn new(
170 docs: Vec<String>,
171 attrlist: AttributeList,
172 name: TypeDefName,
173 native: InlineSqlType,
174 annotations: Vec<ScalarAnnotation>,
175 ) -> Self {
176 Self {
177 uid: next_uid(),
178 name,
179 docs,
180 attrlist,
181 native,
182 annotations,
183 inlined: false,
184 }
185 }
186 pub(crate) fn propagate(&mut self, inline: bool) -> PropagatedScalarData {
187 let annotations = mem::take(&mut self.annotations);
188 let field_annotations = annotations
189 .iter()
190 .flat_map(|a| a.propagate_to_field(inline))
191 .collect_vec();
192 let (annotations, retained) = annotations
193 .into_iter()
194 .partition_map(|a| a.propagate_to_column(inline));
195 self.annotations = retained;
196 if inline {
197 self.inlined = true;
198 }
199 PropagatedScalarData {
200 annotations,
201 field_annotations,
202 }
203 }
204 pub fn is_always_inline(&self) -> bool {
205 self.annotations
206 .iter()
207 .any(|a| matches!(a, ScalarAnnotation::Inline))
208 }
209 pub fn is_external(&self) -> bool {
210 self.annotations
211 .iter()
212 .any(|a| matches!(a, ScalarAnnotation::External))
213 }
214 pub fn inlined(&self) -> bool {
215 self.inlined
216 }
217}
218
219impl SchemaScalar<'_> {
220 pub fn depend_on(&self, out: &mut Vec<TypeIdent>) {
221 assert!(
222 !self.is_always_inline() || self.inlined,
223 "always-inline type was not inlined"
224 );
225 if self.inlined() {
226 self.type_dependencies(out);
227 } else {
228 out.push(self.id());
229 }
230 }
231 pub fn type_dependencies(&self, out: &mut Vec<TypeIdent>) {
232 assert!(
233 !self.is_always_inline() || self.inlined,
234 "always-inline type was not inlined"
235 );
236 self.native.dependencies(self.schema, out);
237 }
238 pub fn inner_type(&self, rn: &RenameMap, report: &mut Report) -> DbNativeType {
239 self.native.expand(rn, self.schema, report)
240 }
241 pub fn native(&self, rn: &RenameMap, report: &mut Report) -> DbNativeType {
242 assert!(
243 !self.is_always_inline() || self.inlined,
244 "always-inline type was not inlined"
245 );
246 if self.inlined {
247 assert!(
248 !self
249 .annotations
250 .iter()
251 .any(ScalarAnnotation::is_inline_target),
252 "inlined scalars may not have inline target scalars"
253 );
254 self.native.expand(rn, self.schema, report)
255 } else {
256 DbNativeType::unchecked_from(self.db(rn))
257 }
258 }
259}
260
261#[derive(Debug)]
262pub enum ScalarAnnotation {
263 Default(Sql),
265 Check(Check),
267 PrimaryKey(PrimaryKey),
269 Unique(UniqueConstraint),
271 Index(Index),
273 Inline,
275 External,
276}
277impl ScalarAnnotation {
278 pub fn as_check(&self) -> Option<&Check> {
279 match self {
280 Self::Check(c) => Some(c),
281 _ => None,
282 }
283 }
284 fn is_inline_target(&self) -> bool {
286 !matches!(self, Self::Inline | Self::External)
287 }
288 fn propagate_to_field(&self, inline: bool) -> Option<FieldAnnotation> {
289 Some(match self {
290 ScalarAnnotation::Check(c) if inline => FieldAnnotation::Check(c.clone_for_propagate()),
291 _ => return None,
292 })
293 }
294 fn propagate_to_column(self, inline: bool) -> Either<ColumnAnnotation, Self> {
295 Either::Left(match self {
296 ScalarAnnotation::PrimaryKey(p) => ColumnAnnotation::PrimaryKey(p),
297 ScalarAnnotation::Unique(u) => ColumnAnnotation::Unique(u),
298 ScalarAnnotation::Index(i) => ColumnAnnotation::Index(i),
299 ScalarAnnotation::Default(d) if inline => ColumnAnnotation::Default(d),
300 ScalarAnnotation::Check(c) if inline => ColumnAnnotation::Check(c),
301 _ => return Either::Right(self),
302 })
303 }
304}