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()
323rule 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 Ok(s)
439}