1use std::{mem, ops::Deref};
2
3use itertools::{Either, Itertools};
4
5use crate::{
6 attribute::AttributeList,
7 column::ColumnAnnotation,
8 def_name_impls, derive_is_isomorph_by_id_name,
9 diagnostics::Report,
10 ids::DbIdent,
11 index::Check,
12 names::{
13 CompositeItemDefName, DbNativeType, FieldIdent, FieldKind, TypeDefName, TypeIdent, TypeKind,
14 },
15 scalar::PropagatedScalarData,
16 sql::Sql,
17 uid::{next_uid, OwnUid, RenameExt, RenameMap},
18 HasIdent, IsCompatible, SchemaComposite, SchemaType,
19};
20
21#[derive(Debug)]
22pub enum FieldAnnotation {
23 Check(Check),
24}
25impl FieldAnnotation {
26 fn propagate_to_composite(self, field: FieldIdent) -> Either<CompositeAnnotation, Self> {
27 Either::Left(match self {
28 FieldAnnotation::Check(c) => {
29 CompositeAnnotation::Check(c.propagate_to_composite(field))
30 }
31 #[allow(unreachable_patterns)]
32 _ => return Either::Right(self),
33 })
34 }
35 fn clone_for_propagate(&self) -> Self {
36 match self {
37 FieldAnnotation::Check(c) => Self::Check(c.clone_for_propagate()),
38 }
39 }
40}
41
42#[derive(Debug)]
43pub struct Field {
44 uid: OwnUid,
45 name: CompositeItemDefName,
46 pub docs: Vec<String>,
47 pub nullable: bool,
48 pub ty: TypeIdent,
49 pub annotations: Vec<FieldAnnotation>,
50}
51def_name_impls!(Field, FieldKind);
52derive_is_isomorph_by_id_name!(Field);
53
54impl Field {
55 pub fn new(
56 docs: Vec<String>,
57 name: CompositeItemDefName,
58 nullable: bool,
59 ty: TypeIdent,
60 annotations: Vec<FieldAnnotation>,
61 ) -> Self {
62 Self {
63 uid: next_uid(),
64 docs,
65 name,
66 nullable,
67 ty,
68 annotations,
69 }
70 }
71 pub(crate) fn propagate_scalar_data(
72 &mut self,
73 scalar: TypeIdent,
74 propagated: &PropagatedScalarData,
75 ) {
76 if self.ty == scalar {
77 self.annotations.extend(
78 propagated
79 .field_annotations
80 .iter()
81 .map(|a| a.clone_for_propagate()),
82 );
83 }
84 }
85 pub fn propagate_annotations(&mut self) -> Vec<CompositeAnnotation> {
86 let (annotations, retained) = mem::take(&mut self.annotations)
87 .into_iter()
88 .partition_map(|a| a.propagate_to_composite(self.id()));
89 self.annotations = retained;
90 annotations
91 }
92}
93
94#[derive(Debug)]
95pub enum CompositeAnnotation {
96 Check(Check),
97}
98impl CompositeAnnotation {
99 fn propagate_to_field(&self) -> Option<FieldAnnotation> {
100 Some(match self {
101 CompositeAnnotation::Check(c) => FieldAnnotation::Check(c.clone_for_propagate()),
102 })
103 }
104 fn propagate_to_column(self) -> Either<ColumnAnnotation, Self> {
105 Either::Left(match self {
106 CompositeAnnotation::Check(c) => ColumnAnnotation::Check(c),
107 })
108 }
109}
110
111#[derive(Debug)]
112pub struct Composite {
113 uid: OwnUid,
114 name: TypeDefName,
115 pub docs: Vec<String>,
116 pub attrlist: AttributeList,
117 pub fields: Vec<Field>,
118 pub annotations: Vec<CompositeAnnotation>,
119}
120def_name_impls!(Composite, TypeKind);
121
122impl Composite {
123 pub fn new(
124 docs: Vec<String>,
125 attrlist: AttributeList,
126 name: TypeDefName,
127 fields: Vec<Field>,
128 mut annotations: Vec<CompositeAnnotation>,
129 ) -> Self {
130 let mut checks = vec![];
131 for field in &fields {
132 if field.nullable {
133 continue;
134 }
135 checks.push(Sql::BinOp(
136 Box::new(Sql::GetField(Box::new(Sql::Placeholder), field.id())),
137 crate::sql::SqlOp::SNe,
138 Box::new(Sql::Null),
139 ))
140 }
141 if !checks.is_empty() {
142 annotations.push(CompositeAnnotation::Check(Check::new(
143 Some(DbIdent::new("composite_nullability_check")),
144 Sql::any([
145 Sql::BinOp(
146 Box::new(Sql::Placeholder),
147 crate::sql::SqlOp::SEq,
148 Box::new(Sql::Null),
149 ),
150 Sql::all(checks),
151 ]),
152 )));
153 }
154 Self {
155 uid: next_uid(),
156 name,
157 docs,
158 attrlist,
159 fields,
160 annotations,
161 }
162 }
163 pub fn db_type(&self, rn: &RenameMap) -> DbNativeType {
164 DbNativeType::unchecked_from(self.db(rn))
165 }
166 pub(crate) fn propagate_scalar_data(
167 &mut self,
168 scalar: TypeIdent,
169 propagated: &PropagatedScalarData,
170 ) {
171 for col in self.fields.iter_mut() {
172 col.propagate_scalar_data(scalar, propagated)
173 }
174 }
175 pub fn process(&mut self) {
176 for column in self.fields.iter_mut() {
177 let propagated = column.propagate_annotations();
178 self.annotations.extend(propagated);
179 }
180 }
181
182 pub(crate) fn propagate(&mut self) -> PropagatedScalarData {
183 let annotations = mem::take(&mut self.annotations);
184 let field_annotations = annotations
185 .iter()
186 .flat_map(CompositeAnnotation::propagate_to_field)
187 .collect_vec();
188 let (annotations, retained) = annotations
189 .into_iter()
190 .partition_map(|a| a.propagate_to_column());
191 self.annotations = retained;
192 PropagatedScalarData {
193 annotations,
194 field_annotations,
195 }
196 }
197}
198
199impl SchemaComposite<'_> {
200 pub fn field(&self, field: FieldIdent) -> CompositeField<'_> {
201 self.fields()
202 .find(|c| c.id() == field)
203 .expect("field not found")
204 }
205 pub fn fields(&self) -> impl Iterator<Item = CompositeField<'_>> {
206 self.fields.iter().map(|field| CompositeField {
207 composite: *self,
208 field,
209 })
210 }
211}
212
213#[derive(Debug, Clone, Copy)]
214pub struct CompositeField<'a> {
215 composite: SchemaComposite<'a>,
216 field: &'a Field,
217}
218def_name_impls!(CompositeField<'_>, FieldKind);
219derive_is_isomorph_by_id_name!(CompositeField<'_>);
220
221impl IsCompatible for CompositeField<'_> {
222 fn is_compatible(
223 &self,
224 new: &Self,
225 rn: &RenameMap,
226 report_self: &mut Report,
227 report_new: &mut Report,
228 ) -> bool {
229 self.name.db == new.name.db && self.db_type(rn, report_self) == new.db_type(rn, report_new)
230 }
231}
232
233impl Deref for CompositeField<'_> {
234 type Target = Field;
235
236 fn deref(&self) -> &Self::Target {
237 self.field
238 }
239}
240
241impl CompositeField<'_> {
242 pub fn ty(&self) -> SchemaType<'_> {
243 self.composite.schema.schema_ty(self.ty)
244 }
245 pub fn db_type(&self, rn: &RenameMap, report: &mut Report) -> DbNativeType {
246 self.composite.schema.native_type(&self.ty, rn, report)
247 }
248}