immigrant_schema/
parser.rs

1use std::{
2	result,
3	sync::{self, atomic::AtomicUsize},
4};
5
6use peg::{error::ParseError, parser, str::LineCol};
7
8use crate::{
9	attribute::{Attribute, AttributeField, AttributeList, AttributeValue},
10	column::{Column, ColumnAnnotation, PartialForeignKey},
11	composite::{Composite, CompositeAnnotation, Field, FieldAnnotation},
12	diagnostics::Report,
13	ids::{in_allocator, DbIdent},
14	index::{Check, Index, OpClass, PrimaryKey, UniqueConstraint, Using, With},
15	mixin::Mixin,
16	names::{
17		ColumnIdent, DbProcedure, DefName, EnumItemDefName, FieldIdent, MixinIdent, TableDefName,
18		TableIdent, TypeDefName, TypeIdent, ViewDefName,
19	},
20	root::{Item, Schema, SchemaProcessOptions},
21	scalar::{Enum, EnumItem, InlineSqlType, InlineSqlTypePart, Scalar, ScalarAnnotation},
22	span::{register_source, SimpleSpan, SourceId},
23	sql::{Sql, SqlOp, SqlUnOp},
24	table::{ForeignKey, OnDelete, Table, TableAnnotation},
25	uid::RenameMap,
26	view::{Definition, DefinitionPart, View},
27};
28
29fn h<T>(v: T) -> Box<T> {
30	Box::new(v)
31}
32
33enum InlinePart<T> {
34	Text(String),
35	Template(T),
36}
37
38struct S {
39	id: SourceId,
40	last_inline_scalar: AtomicUsize,
41	compat: bool,
42}
43impl S {
44	fn inline_scalar(&self, span: SimpleSpan, def: InlineSqlType) -> (TypeIdent, Option<Scalar>) {
45		let name = format!(
46			"__inline_scalar_{}",
47			self.last_inline_scalar
48				.fetch_add(1, sync::atomic::Ordering::Relaxed)
49		);
50		let ident = TypeIdent::alloc((span, &name));
51		let scalar = Scalar::new(
52			vec!["Inlined scalar".to_owned()],
53			AttributeList(vec![]),
54			TypeDefName::unchecked_new(ident, None),
55			def,
56			vec![ScalarAnnotation::Inline],
57		);
58		(ident, Some(scalar))
59	}
60}
61parser! {
62grammar schema_parser() for str {
63pub(super) rule root(s:&S) -> Schema = _ t:item(s)**_ _ {
64	let mut out = Vec::new();
65	for (item, scalars) in t {
66		out.extend(scalars.into_iter().map(Item::Scalar));
67		out.push(item);
68	}
69	Schema(out)
70}
71
72rule item(s:&S) -> (Item, Vec<Scalar>)
73= t:table(s) {(Item::Table(t.0), t.1)}
74/ t:view(s) {(Item::View(t), vec![])}
75/ t:enum(s) {(Item::Enum(t), vec![])}
76/ t:scalar(s) {(Item::Scalar(t), vec![])}
77/ t:composite(s) {(Item::Composite(t), vec![])}
78/ t:mixin(s) {(Item::Mixin(t.0), t.1)}
79
80rule mixin(s:&S) -> (Mixin, Vec<Scalar>) =
81	docs:docs()
82	attrlist:attribute_list(s) _
83	"@mixin" _ name:code_ident(s) _ "{" _
84		mixins:("@mixin" _ f:code_ident(s) _ ";" {MixinIdent::alloc(f)})**_ _
85		fields:(f:table_field(s) _ ";" {f})++_ _
86		annotations:(a:table_annotation(s) _ ";" {a})**_ _
87		foreign_keys:(f:foreign_key(s) _ ";" {f})**_ _
88	"}" _ ";" {{
89	let (fields, scalars): (Vec<Column>, Vec<Option<Scalar>>) = fields.into_iter().unzip();
90	(Mixin::new(
91		docs,
92		attrlist,
93		MixinIdent::alloc(name),
94		fields,
95		annotations,
96		foreign_keys,
97		mixins,
98	), scalars.into_iter().flatten().collect())
99}};
100
101rule table(s:&S) -> (Table, Vec<Scalar>) =
102	docs:docs()
103	attrlist:attribute_list(s) _
104	"table" _ name:def_name(s) _ "{" _
105		mixins:("@mixin" _ f:code_ident(s) _ ";" {MixinIdent::alloc(f)})**_ _
106		fields:(f:table_field(s) _ ";" {f})++_ _
107		annotations:(a:table_annotation(s) _ ";" {a})**_ _
108		foreign_keys:(f:foreign_key(s) _ ";" {f})**_ _
109	"}" _ ";" {{
110	let (fields, scalars): (Vec<Column>, Vec<Option<Scalar>>) = fields.into_iter().unzip();
111	(Table::new(
112		docs,
113		attrlist,
114		TableDefName::alloc(name),
115		fields,
116		annotations,
117		foreign_keys,
118		mixins,
119	), scalars.into_iter().flatten().collect())
120}};
121
122rule definition_part(s:&S) -> DefinitionPart =
123	i:code_ident(s) _ "." _ j:code_ident(s) {DefinitionPart::ColumnRef(TableIdent::alloc(i), ColumnIdent::alloc(j))}
124/	i:code_ident(s) {DefinitionPart::TableRef(TableIdent::alloc(i))}
125rule definition(s:&S) -> Definition = parts:(
126	inline(<definition_part(s)>)
127/	compat_only(s, <custom_inline(<definition_part(s)>, <"$$">, <"$$">, <"${">, <"}">)>)
128) {
129	Definition(parts.into_iter().map(|v| match v {
130		InlinePart::Text(t) => DefinitionPart::Raw(t),
131		InlinePart::Template(t) => t,
132	}).collect())
133}
134rule inline_sql_part(s:&S) -> InlineSqlTypePart =
135	i:code_ident(s) {InlineSqlTypePart::TypeRef(TypeIdent::alloc(i))}
136rule inline_sql(s:&S) -> InlineSqlType = parts:(
137	inline(<inline_sql_part(s)>)
138	/ compat_only(s, <custom_inline(<inline_sql_part(s)>, <"$$">, <"$$">, <"${">, <"}">)>)
139) {{
140	InlineSqlType(parts.into_iter().map(|v| match v {
141		InlinePart::Text(t) => InlineSqlTypePart::Raw(t),
142		InlinePart::Template(t) => t,
143	}).collect())
144}}
145/ s:compat_only(s, <str()>) {InlineSqlType(vec![InlineSqlTypePart::Raw(s.to_string())])}
146rule view(s:&S) -> View =
147	docs:docs()
148	attrlist:attribute_list(s) _
149	"view" materialized:(_ "." _ "materialized")? _ name:def_name(s) _ "=" _ definition:definition(s) _ ";" {{
150	View::new(
151		docs,
152		attrlist,
153		ViewDefName::alloc(name),
154		materialized.is_some(),
155		definition,
156	)
157}};
158rule enum(s:&S) -> Enum =
159	docs:docs()
160	attrlist:attribute_list(s) _
161	"enum" _ name:def_name(s) _ "{" _ items:(
162		docs:docs()
163		name:def_name(s)
164	{(docs, name)})++(_ ";" _) _ ";"? _ "}" _ ";" {
165	Enum::new(docs, attrlist, TypeDefName::alloc(name), items.into_iter()
166		.map(|(docs, name)| EnumItem::new(docs, EnumItemDefName::alloc(name))).collect()
167	)
168};
169rule scalar(s:&S) -> Scalar =
170	docs:docs()
171	attrlist:attribute_list(s) _
172	"scalar" _ name:def_name(s) _ "=" _ native:inline_sql(s) _ annotations:scalar_annotation(s)**_ ";" {
173	Scalar::new(docs, attrlist, TypeDefName::alloc(name), native, annotations)
174}
175rule composite(s:&S) -> Composite =
176	docs:docs()
177	attrlist:attribute_list(s) _
178	"struct" _ name:def_name(s) _ "{" _
179		fields:(f:field(s) _ ";" {f})++_ _
180		annotations:(a:composite_annotation(s) _ ";" {a})**_ _
181	"}" _ ";" {
182	Composite::new(docs, attrlist, TypeDefName::alloc(name), fields, annotations)
183}
184rule field(s:&S) -> Field =
185	docs:docs()
186	name:def_name(s) _
187	t:(":" _ i:code_ident(s) _ {i})?
188	n:("?" _)?
189	annotations:field_annotation(s)**_ {
190	Field::new(
191		docs,
192		DefName::alloc(name),
193		n.is_some(),
194		TypeIdent::alloc(t.unwrap_or((name.0, name.1))),
195		annotations
196	)
197}
198
199rule maybe_inline_scalar(s:&S) -> (TypeIdent, Option<Scalar>) =
200	b:position!() ty:inline_sql(s) e:position!() {s.inline_scalar(
201		SimpleSpan::new(s.id, b as u32, e as u32),
202		ty,
203	)}
204/	i:code_ident(s) { (TypeIdent::alloc(i), None) }
205
206rule table_field(s:&S) -> (Column, Option<Scalar>) =
207	docs:docs()
208	attrlist:attribute_list(s) _
209	name:def_name(s) _
210	t:(":" _ i:maybe_inline_scalar(s) _ {i})?
211	n:("?" _)?
212	annotations:column_annotation(s)**_
213	foreign_key:partial_foreign_key(s)? {
214	let (ty, scalars) = match t {
215		Some(v) => v,
216		None => (TypeIdent::alloc((name.0, name.1)), None),
217	};
218	(Column::new(
219		DefName::alloc(name),
220		docs,
221		attrlist,
222		n.is_some(),
223		ty,
224		annotations,
225		foreign_key
226	), scalars)
227}
228
229rule attribute_list(s:&S) -> AttributeList
230= list:attribute(s) ** _ {AttributeList(list)}
231rule attribute(s:&S) -> Attribute
232= "#" _ name:code_ident(s) fields:(_ "(" _ f:(f:attribute_field(s)++comma() trailing_comma() {f}) _ ")" {f})? {
233	Attribute {
234		name: name.1.to_owned(),
235		fields: fields.unwrap_or_default(),
236	}
237}
238rule attribute_field(s:&S) -> AttributeField
239= key:code_ident(s) v:(_ "=" _ value:attribute_value() {value})? {AttributeField {key: key.1.to_owned(), value: v.unwrap_or(AttributeValue::Set)}}
240rule attribute_value() -> AttributeValue
241= s:str() {AttributeValue::String(s.to_owned())}
242
243rule scalar_annotation(s:&S) -> ScalarAnnotation
244= c:check(s) {ScalarAnnotation::Check(c)}
245/ u:unique(s) {ScalarAnnotation::Unique(u)}
246/ pk:primary_key(s) {ScalarAnnotation::PrimaryKey(pk)}
247/ d:default(s) {ScalarAnnotation::Default(d)}
248/ i:index(s) {ScalarAnnotation::Index(i)}
249/ "@inline" {ScalarAnnotation::Inline}
250/ "@external" {ScalarAnnotation::External}
251rule column_annotation(s:&S) -> ColumnAnnotation
252= i:index(s) {ColumnAnnotation::Index(i)}
253/ c:check(s) {ColumnAnnotation::Check(c)}
254/ u:unique(s) {ColumnAnnotation::Unique(u)}
255/ pk:primary_key(s) {ColumnAnnotation::PrimaryKey(pk)}
256/ d:default(s) {ColumnAnnotation::Default(d)}
257/ d:initialize_as(s) {ColumnAnnotation::InitializeAs(d)}
258rule table_annotation(s:&S) -> TableAnnotation
259= c:check(s) {TableAnnotation::Check(c)}
260/ u:unique(s) {TableAnnotation::Unique(u)}
261/ pk:primary_key(s) {TableAnnotation::PrimaryKey(pk)}
262/ i:index(s) {TableAnnotation::Index(i)}
263/ "@external" {TableAnnotation::External}
264rule composite_annotation(s:&S) -> CompositeAnnotation
265= c:check(s) {CompositeAnnotation::Check(c)}
266rule field_annotation(s:&S) -> FieldAnnotation
267= c:check(s) {FieldAnnotation::Check(c)}
268
269rule def_name(s:&S) -> (SimpleSpan, &'input str, Option<&'input str>)
270= c:code_ident(s) d:(_ d:db_ident() {d})? {
271	(c.0, c.1, d)
272}
273
274rule foreign_key(s:&S) -> ForeignKey
275= source_fields:index_fields(s)? _ pfk:partial_foreign_key(s) {
276	let mut fk = pfk.fk;
277	assert!(fk.source_fields.is_none());
278	fk.source_fields = source_fields;
279	fk
280}
281
282rule partial_foreign_key(s:&S) -> PartialForeignKey
283= name:db_ident()? _ "~" _ on_delete:("." _ a:on_delete() {a})? _ target:code_ident(s) _ target_fields:index_fields(s)? {
284	PartialForeignKey {
285		fk: ForeignKey::new(
286			name.map(DbIdent::new),
287			None,
288			TableIdent::alloc(target),
289			target_fields,
290			on_delete.unwrap_or(OnDelete::Noop),
291		)
292	}
293}
294rule on_delete() -> OnDelete
295= "set_null" {OnDelete::SetNull}
296/ "set_default" {OnDelete::SetDefault}
297/ "restrict" {OnDelete::Restrict}
298/ "noop" {OnDelete::Noop}
299/ "cascade" {OnDelete::Cascade}
300
301rule default(s:&S) -> Sql
302= "@default" _ "(" _ s:sql(s) _ ")" {s}
303rule initialize_as(s:&S) -> Sql
304= "@initialize_as" _ "(" _ s:sql(s) _ ")" {s}
305rule primary_key(s:&S) -> PrimaryKey
306= "@primary_key" _ name:db_ident()? _ columns:index_fields(s)? {PrimaryKey::new(name.map(DbIdent::new), columns.unwrap_or_default())}
307rule unique(s:&S) -> UniqueConstraint
308= "@unique" _ name:db_ident()? _ columns:index_fields(s)? {UniqueConstraint::new(name.map(DbIdent::new), columns.unwrap_or_default())}
309rule check(s:&S) -> Check
310= "@check" _ name:db_ident()? _ "(" _ check:sql(s) _ ")" {Check::new(name.map(DbIdent::new), check)}
311rule index(s:&S) -> Index
312= "@index" _
313	unq:("." _ "unique" _)?
314	default_opclass:("." _ "opclass" _ "(" _ opclass:code_ident(s) _ ")" _ {OpClass(opclass.1.to_owned())})?
315	using:("." _ "using" _ "(" _ using:code_ident(s) _ ")" _ {Using(using.1.to_owned())})?
316	with:("." _ "with" _ "(" _ with:str_escaping() _ ")" _ {With(with)})?
317	name:db_ident()? _ f:index_fields(s)? {Index::new(name.map(DbIdent::new), unq.is_some(), f.unwrap_or_default().into_iter().map(|v| (v, None)).collect(), using, default_opclass, with)}
318
319rule index_fields(s:&S) -> Vec<ColumnIdent> = "(" _ i:code_ident(s)**comma() trailing_comma() ")" {i.into_iter().map(ColumnIdent::alloc).collect()}
320
321rule code_ident(s:&S) -> (SimpleSpan, &'input str) = b:position!() n:$(['a'..='z' | 'A'..='Z'] ['a'..='z' | 'A'..='Z' | '_' | '0'..='9']*) e:position!() {(SimpleSpan::new(s.id, b as u32, e as u32), n)};
322rule db_ident() -> &'input str = str()
323// TODO: Replce all usages of str with str_escaping
324rule str() -> &'input str = "\"" v:$((!['"' | '\''] [_])*) "\"" {v}
325rule str_escaping() -> String = "\"" v:$((!['"'] ("\\\"" / [_]))*) "\"" {v.replace("\\\"", "\"")}
326
327rule inline_part<T>(x: rule<T>, tstart:rule<()>, tend:rule<()>, end:rule<()>) -> InlinePart<T> =
328	raw:$((!tstart() !tend() !end() [_])+) {InlinePart::Text(raw.to_string())}
329/	v:$(tstart()) tstart() {InlinePart::Text(v.to_string())}
330/	v:$(tend()) tend() {InlinePart::Text(v.to_string())}
331/	v:(tstart() v:x() tend() {v}) {InlinePart::Template(v)}
332/  (quiet!{tend()} / expected!("template or plain")) {unreachable!("unexpected inline part")}
333rule custom_inline<T>(x: rule<T>, start:rule<()>, end:rule<()>, tstart:rule<()>, tend:rule<()>) -> Vec<InlinePart<T>> =
334	start() parts:inline_part(<x()>, <tstart()>, <tend()>, <end()>)* end() { parts }
335
336rule inline<T>(x: rule<T>) -> Vec<InlinePart<T>> =
337	custom_inline(<x()>, <"sql\"\"\"">, <"\"\"\"">, <"{">, <"}">)
338/	custom_inline(<x()>, <"sql\"">, <"\"">, <"{">, <"}">)
339rule compat_only<T>(s:&S, x: rule<T>) -> T =
340	v:x() {? if s.compat {
341		Ok(v)
342	} else {
343		Err("syntax is deprecated and only allowed for old schemas")
344	}}
345
346
347rule sqlexpr(s:&S) -> Sql = "(" s:sql(s) ")" {s};
348rule sql(s:&S) -> Sql
349= precedence! {
350	a:(@) _ "||" _ b:@ {Sql::BinOp(h(a), SqlOp::Or, h(b))}
351	--
352	a:(@) _ "&&" _ b:@ {Sql::BinOp(h(a), SqlOp::And, h(b))}
353	--
354	a:(@) _ "===" _ b:@ {Sql::BinOp(h(a), SqlOp::SEq, h(b))}
355	a:(@) _ "!==" _ b:@ {Sql::BinOp(h(a), SqlOp::SNe, h(b))}
356	a:(@) _ "==" _ b:@ {Sql::BinOp(h(a), SqlOp::Eq, h(b))}
357	a:(@) _ "!=" _ b:@ {Sql::BinOp(h(a), SqlOp::Ne, h(b))}
358	a:(@) _ "~~" _ b:@ {Sql::BinOp(h(a), SqlOp::Like, h(b))}
359	--
360	a:(@) _ "<" _ b:@ {Sql::BinOp(h(a), SqlOp::Lt, h(b))}
361	a:(@) _ ">" _ b:@ {Sql::BinOp(h(a), SqlOp::Gt, h(b))}
362	a:(@) _ "<=" _ b:@ {Sql::BinOp(h(a), SqlOp::Le, h(b))}
363	a:(@) _ ">=" _ b:@ {Sql::BinOp(h(a), SqlOp::Ge, h(b))}
364	--
365	a:(@) _ "+" _ b:@ {Sql::BinOp(h(a), SqlOp::Plus, h(b))}
366	a:(@) _ "-" _ b:@ {Sql::BinOp(h(a), SqlOp::Minus, h(b))}
367	--
368	a:(@) _ "*" _ b:@ {Sql::BinOp(h(a), SqlOp::Mul, h(b))}
369	a:(@) _ "/" _ b:@ {Sql::BinOp(h(a), SqlOp::Div, h(b))}
370	a:(@) _ "%" _ b:@ {Sql::BinOp(h(a), SqlOp::Mod, h(b))}
371	--
372	"-" _ b:@ {Sql::UnOp(SqlUnOp::Minus, h(b))}
373	"+" _ b:@ {Sql::UnOp(SqlUnOp::Plus, h(b))}
374	"!" _ b:@ {Sql::UnOp(SqlUnOp::Not, h(b))}
375	--
376	a:(@) _ "::" _ ty:code_ident(s) {Sql::Cast(h(a), TypeIdent::alloc(ty))}
377	a:(@) _ "." _ f:code_ident(s) {Sql::GetField(h(a), FieldIdent::alloc(f))}
378	--
379	e:sql_basic(s) {e}
380}
381rule sql_basic(s:&S) -> Sql
382= "(" _ e:(e:sql(s) _ "," _ {e})*<2,> _ ")" {Sql::Tuple(e)}
383/ "(" _ e:sql(s) _ "," _ ")" {Sql::Tuple(vec![e])}
384/ "(" _ e:sql(s) _ ")" {Sql::Parened(h(e))}
385/ "(" _ ")" {Sql::Tuple(vec![])}
386/ "_" {Sql::Placeholder}
387/ "if" _ c:sql(s) _ "then" _ then:sql(s) _ "else" _ else_:sql(s) {Sql::If(h(c), h(then), h(else_))}
388/ "null" {Sql::Null}
389/ "true" {Sql::Boolean(true)}
390/ "false" {Sql::Boolean(false)}
391/ i:code_ident(s) _ "(" _ e:sql(s)**comma() trailing_comma() ")" {Sql::Call(DbProcedure::new(i.1), e)}
392/ i:code_ident(s) {Sql::Ident(ColumnIdent::alloc(i))}
393/ s:str() {Sql::String(s.to_owned())}
394/ n:$(['0'..='9']+) {Sql::Number(n.parse().unwrap())}
395
396rule trailing_comma() = _ ","? _;
397rule comma() = _ "," _;
398rule _() = ([' ' | '\t' | '\n'] / ("///" (!['\n'] [_])+ ['\n']))*;
399rule docs() -> Vec<String> = l:("//" s:$((!['\n'] [_])+) "\n" _ {s.to_string()})* {l}
400}
401}
402
403#[derive(thiserror::Error, Debug)]
404pub enum ParsingError {
405	#[error("parser error: {0}")]
406	Peg(#[from] ParseError<LineCol>),
407}
408
409type Result<T> = result::Result<T, ()>;
410pub fn parse(
411	v: &str,
412	compat: bool,
413	opts: &SchemaProcessOptions,
414	rn: &mut RenameMap,
415	report: &mut Report,
416) -> Result<Schema> {
417	let span = register_source(v.to_string());
418	let mut s = in_allocator(|| {
419		schema_parser::root(
420			v,
421			&S {
422				id: span,
423				last_inline_scalar: AtomicUsize::default(),
424				compat,
425			},
426		)
427		.map_err(|e| {
428			report.error("parse error").annotate(
429				e.to_string(),
430				SimpleSpan::new(span, e.location.offset as u32, e.location.offset as u32),
431			);
432		})
433	})?;
434	s.process(opts, rn, report);
435	// if report.is_error() {
436	// 	return Err(None)
437	// }
438	Ok(s)
439}