immigrant_schema/
sql.rs

1use crate::{
2	diagnostics::Report,
3	ids::{DbIdent, Ident},
4	names::{ColumnIdent, DbProcedure, FieldIdent, FieldKind, TypeIdent, TypeKind},
5	uid::{RenameExt, RenameMap},
6	HasIdent, SchemaItem, SchemaType,
7};
8
9#[derive(Debug, Clone)]
10pub enum SqlUnOp {
11	Plus,
12	Minus,
13	Not,
14}
15impl SqlUnOp {
16	pub fn format(&self) -> &'static str {
17		match self {
18			SqlUnOp::Plus => "+",
19			SqlUnOp::Minus => "-",
20			SqlUnOp::Not => "NOT ",
21		}
22	}
23}
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25pub enum SqlOp {
26	Lt,
27	Gt,
28	Le,
29	Ge,
30	Eq,
31	Ne,
32	SEq,
33	SNe,
34
35	And,
36	Or,
37
38	Plus,
39	Minus,
40	Div,
41	Mul,
42	Mod,
43
44	Like,
45}
46impl SqlOp {
47	pub fn format(&self) -> &'static str {
48		match self {
49			SqlOp::Lt => "<",
50			SqlOp::Gt => ">",
51			SqlOp::Le => "<=",
52			SqlOp::Ge => ">=",
53			SqlOp::Eq => "=",
54			SqlOp::Ne => "<>",
55			SqlOp::SEq => "IS NOT DISTINCT FROM",
56			SqlOp::SNe => "IS DISTINCT FROM",
57			SqlOp::And => "AND",
58			SqlOp::Or => "OR",
59			SqlOp::Plus => "+",
60			SqlOp::Minus => "-",
61			SqlOp::Div => "/",
62			SqlOp::Mul => "*",
63			SqlOp::Mod => "%",
64			SqlOp::Like => "LIKE",
65		}
66	}
67}
68#[derive(Debug, Clone)]
69pub enum Sql {
70	Cast(Box<Sql>, TypeIdent),
71	Call(DbProcedure, Vec<Sql>),
72	String(String),
73	Number(i128),
74	Ident(ColumnIdent),
75	UnOp(SqlUnOp, Box<Sql>),
76	BinOp(Box<Sql>, SqlOp, Box<Sql>),
77	GetField(Box<Sql>, FieldIdent),
78	Parened(Box<Sql>),
79	Tuple(Vec<Sql>),
80	If(Box<Sql>, Box<Sql>, Box<Sql>),
81	Boolean(bool),
82	Placeholder,
83	Null,
84}
85impl Sql {
86	pub fn replace_placeholder(&mut self, to: Sql) {
87		struct ReplacePlaceholder {
88			to: Sql,
89		}
90		impl SqlVisitor for ReplacePlaceholder {
91			fn handle_placeholder(&mut self, placeholder: &mut Sql) {
92				*placeholder = self.to.clone()
93			}
94		}
95		self.visit(&mut ReplacePlaceholder { to })
96	}
97	pub fn affected_columns(&self) -> Vec<ColumnIdent> {
98		struct ColumnCollector {
99			columns: Vec<ColumnIdent>,
100		}
101		impl SqlVisitor for ColumnCollector {
102			fn handle_column(&mut self, column: &mut ColumnIdent) {
103				if self.columns.contains(column) {
104					return;
105				}
106				self.columns.push(*column)
107			}
108			fn handle_placeholder(&mut self, _placeholder: &mut Sql) {
109				panic!("placeholder was not normalized")
110			}
111		}
112		let mut collector = ColumnCollector { columns: vec![] };
113		let mut copy = self.clone();
114		copy.visit(&mut collector);
115		collector.columns
116	}
117	fn visit(&mut self, v: &mut impl SqlVisitor) {
118		v.handle(self);
119		match self {
120			Sql::Cast(s, t) => {
121				s.visit(v);
122				v.handle_type(t);
123			}
124			Sql::Call(_p, args) => {
125				for arg in args {
126					arg.visit(v);
127				}
128			}
129			Sql::String(_s) => {}
130			Sql::Number(_n) => {}
131			Sql::Ident(i) => v.handle_column(i),
132			Sql::UnOp(_o, s) => s.visit(v),
133			Sql::BinOp(a, _o, b) => {
134				a.visit(v);
135				b.visit(v);
136			}
137			Sql::Parened(s) => s.visit(v),
138			Sql::Placeholder => v.handle_placeholder(self),
139			Sql::Boolean(_) => {}
140			Sql::GetField(s, _) => s.visit(v),
141			Sql::Null => {}
142			Sql::Tuple(els) => {
143				for ele in els.iter_mut() {
144					ele.visit(v)
145				}
146			}
147			Sql::If(a, b, c) => {
148				a.visit(v);
149				b.visit(v);
150				c.visit(v);
151			}
152		}
153	}
154	pub fn all(s: impl IntoIterator<Item = Self>) -> Self {
155		let mut s = s.into_iter();
156		let mut v = s.next().unwrap_or(Self::Boolean(true));
157		for i in s {
158			v = Sql::BinOp(Box::new(v), SqlOp::And, Box::new(i))
159		}
160		v
161	}
162	pub fn any(s: impl IntoIterator<Item = Self>) -> Self {
163		let mut s = s.into_iter();
164		let mut v = s.next().unwrap_or(Self::Boolean(false));
165		for i in s {
166			v = Sql::BinOp(Box::new(v), SqlOp::Or, Box::new(i))
167		}
168		v
169	}
170	pub fn field_name(
171		&self,
172		context: &SchemaItem<'_>,
173		field: Ident<FieldKind>,
174		rn: &RenameMap,
175		report: &mut Report,
176	) -> Option<DbIdent<FieldKind>> {
177		let this = self.type_ident_of_expr(context, report)?;
178		let ty = context.schema().schema_ty(this);
179		Some(match ty {
180			SchemaType::Enum(e) => {
181				report
182					.error("enum variants can't be observed yet")
183					.annotate("happened here", field.span())
184					.annotate("enum defined here", e.id().span());
185				return None;
186			}
187			SchemaType::Scalar(s) => {
188				report
189					.error("invalid field access")
190					.annotate("happened here", field.span())
191					.annotate("scalars can't have fields", s.id().span());
192				return None;
193			}
194			SchemaType::Composite(c) => c.field(field).db(rn),
195		})
196	}
197	pub fn context_ident_name<'s>(
198		context: &'s SchemaItem<'s>,
199		ident: ColumnIdent,
200		rn: &RenameMap,
201		report: &mut Report,
202	) -> Option<DbIdent<FieldKind>> {
203		Some(match context {
204			SchemaItem::Table(t) => {
205				let column = t.schema_column(ident);
206				DbIdent::unchecked_from(column.db(rn))
207			}
208			SchemaItem::Enum(e) => {
209				report
210					.error("enum variants can't be observed yet")
211					.annotate("happened here", ident.span())
212					.annotate("enum defined here", e.id().span());
213				return None;
214			}
215			SchemaItem::Scalar(s) => {
216				report
217					.error("invalid field access")
218					.annotate("happened here", ident.span())
219					.annotate("scalars can't have fields", s.id().span());
220				return None;
221			}
222			SchemaItem::Composite(c) => {
223				let field = c.field(Ident::unchecked_cast(ident));
224				field.db(rn)
225			}
226			SchemaItem::View(v) => {
227				report
228					.error("invalid field access")
229					.annotate("happened here", ident.span())
230					.annotate("views are opaque", v.id().span());
231				return None;
232			}
233		})
234	}
235	/// If self == Sql::Ident(name), convert name to native
236	pub fn ident_name<'s>(
237		&self,
238		context: &'s SchemaItem<'s>,
239		rn: &RenameMap,
240		report: &mut Report,
241	) -> Option<DbIdent<FieldKind>> {
242		let Sql::Ident(f) = self else {
243			panic!("not ident");
244		};
245		Self::context_ident_name(context, *f, rn, report)
246	}
247	fn type_ident_of_expr<'s>(
248		&self,
249		context: &'s SchemaItem<'s>,
250		report: &mut Report,
251	) -> Option<Ident<TypeKind>> {
252		Some(match self {
253			Sql::Cast(_, t) => *t,
254			Sql::UnOp(_, _)
255			| Sql::BinOp(_, _, _)
256			| Sql::Call(_, _)
257			| Sql::String(_)
258			| Sql::Number(_)
259			| Sql::Boolean(_)
260			| Sql::Null
261			| Sql::Tuple(_)
262			| Sql::If(_, _, _) => {
263				panic!("cannot determine call return type")
264			}
265			Sql::Ident(f) => match context {
266				SchemaItem::Table(t) => {
267					let column = t.schema_column(*f);
268					column.ty
269				}
270				SchemaItem::Enum(e) => {
271					report
272						.error("invalid value reference")
273						.annotate("enums can't contain fields", f.span())
274						.annotate("enum defined here", e.id().span());
275					return None;
276				}
277				SchemaItem::Scalar(s) => {
278					report
279						.error("invalid value reference")
280						.annotate("scalars can't contain fields", f.span())
281						.annotate("scalar defined here", s.id().span());
282					return None;
283				}
284				SchemaItem::Composite(c) => {
285					let field = c.field(Ident::unchecked_cast(*f));
286					field.ty
287				}
288				SchemaItem::View(v) => {
289					report
290						.error("invalid value reference")
291						.annotate("views can't contain fields", f.span())
292						.annotate("scalar defined here", v.id().span());
293					return None;
294				}
295			},
296			Sql::GetField(f, t) => {
297				let ty_id = f.type_ident_of_expr(context, report)?;
298				let ty = context.schema().schema_ty(ty_id);
299				Sql::Ident(Ident::unchecked_cast(*t))
300					.type_ident_of_expr(&ty.as_schema_item(), report)?
301			}
302			Sql::Parened(v) => v.type_ident_of_expr(context, report)?,
303			Sql::Placeholder => match context {
304				SchemaItem::Table(_) | SchemaItem::View(_) => {
305					panic!("can't refer to table fields using this notation")
306				}
307				SchemaItem::Enum(e) => e.id(),
308				SchemaItem::Scalar(s) => s.id(),
309				SchemaItem::Composite(c) => c.id(),
310			},
311		})
312	}
313}
314#[allow(unused_variables)]
315pub trait SqlVisitor {
316	fn handle(&mut self, sql: &mut Sql) {}
317	fn handle_type(&mut self, ty: &mut TypeIdent) {}
318	fn handle_column(&mut self, column: &mut ColumnIdent) {}
319	fn handle_placeholder(&mut self, placeholder: &mut Sql) {}
320}