1use std::collections::{HashMap, HashSet};
2
3use indexmap::IndexMap;
4
5use crate::ast::{self, Document, Value};
6use crate::config::{Config, TableNameStyle, Vars};
7use crate::diag::Diagnostic;
8use crate::dialect::Dialect;
9use crate::dict::{Compound, Dict, Part};
10use crate::ir;
11use crate::span::{Span, Spanned};
12use crate::template::{Seg, Template};
13
14pub fn resolve(doc: &Document, dialect: Dialect) -> (ir::Schema, Vec<Diagnostic>) {
15 let mut diags = Vec::new();
16 let config = Config::from_document(doc, &mut diags);
17 let dict = Dict::from_block(doc.nouns.as_ref());
18 let mut r = Resolver {
19 doc,
20 dialect,
21 config,
22 dict,
23 diags,
24 top_level_names: HashMap::new(),
25 mixins: HashMap::new(),
26 blueprints: HashMap::new(),
27 };
28 let schema = r.run();
29 (schema, r.diags)
30}
31
32struct Pending<'a> {
34 ast: &'a ast::Table,
35 name: String,
36 noun: Option<Compound>,
37 scope: Scope,
38 names: HashMap<String, String>,
40 origin: ir::Origin,
41}
42
43type Scope = HashMap<String, Compound>;
45
46struct Resolver<'a> {
47 doc: &'a Document,
48 dialect: Dialect,
49 config: Config,
50 dict: Dict,
51 diags: Vec<Diagnostic>,
52 top_level_names: HashMap<String, String>,
53 mixins: HashMap<String, &'a ast::Mixin>,
54 blueprints: HashMap<String, &'a ast::Blueprint>,
55}
56
57impl<'a> Resolver<'a> {
58 fn run(&mut self) -> ir::Schema {
59 self.index_definitions();
60 let pendings = self.collect_pending();
61
62 let mut schema = ir::Schema::default();
64 let mut relations: Vec<Vec<RelationSpec>> = Vec::new();
65 let mut reverses: Vec<Vec<ast::Relation>> = Vec::new();
66 for p in &pendings {
67 let (table, rels, revs) = self.build_table(p);
68 schema.tables.push(table);
69 relations.push(rels);
70 reverses.push(revs);
71 }
72 self.attach_relations(&pendings, &relations, &mut schema);
73 self.expand_associates(&mut schema);
74 self.attach_reverses(&pendings, &reverses, &mut schema);
75 self.name_indexes(&mut schema);
76 self.check_indexes(&schema);
77 schema
78 }
79
80 fn index_definitions(&mut self) {
83 for m in &self.doc.mixins {
84 if let Some(prev) = self.mixins.insert(m.name.value.clone(), m) {
85 self.diags.push(
86 Diagnostic::error(m.name.span, format!("mixin `{}` が重複", m.name.value))
87 .with_label(prev.name.span, "最初の定義"),
88 );
89 }
90 }
91 for b in &self.doc.blueprints {
92 if let Some(prev) = self.blueprints.insert(b.name.value.clone(), b) {
93 self.diags.push(
94 Diagnostic::error(b.name.span, format!("blueprint `{}` が重複", b.name.value))
95 .with_label(prev.name.span, "最初の定義"),
96 );
97 }
98 }
99 }
100
101 fn collect_pending(&mut self) -> Vec<Pending<'a>> {
104 let mut pendings = Vec::new();
105 let tables = self.doc.tables.as_slice();
106 let mut scope = Scope::new();
107 let literals = self.resolve_table_nouns(tables, &mut scope);
108 self.top_level_names = literals.clone();
109
110 for t in tables {
111 let Some(noun) = scope.get(&t.name.value).cloned() else {
112 continue;
113 };
114 self.check_nouns_registered(&noun, t.name.span);
115 pendings.push(Pending {
116 ast: t,
117 name: literals
118 .get(&t.name.value)
119 .cloned()
120 .unwrap_or_else(|| self.table_name_of(&noun)),
121 noun: Some(noun),
122 scope: scope.clone(),
123 names: literals.clone(),
124 origin: ir::Origin::Own,
125 });
126 }
127 pendings.extend(self.expand_blueprints());
128 pendings
129 }
130
131 fn expand_blueprints(&mut self) -> Vec<Pending<'a>> {
132 let mut out = Vec::new();
133 let applies: Vec<&ast::MacroCall> = self
134 .doc
135 .macros
136 .iter()
137 .filter(|m| m.name.value == "apply_blueprint")
138 .collect();
139
140 for call in applies {
141 for (key, span) in [
142 ("name", call.table_name.as_ref().map(|v| v.span)),
143 ("comment", call.comment.as_ref().map(|c| c.span)),
144 ] {
145 if let Some(span) = span {
146 self.diags.push(Diagnostic::error(
147 span,
148 format!(
149 "`apply_blueprint` に `{key}=` は書けない。blueprint 内の `table` に書く"
150 ),
151 ));
152 }
153 }
154 let Some((bp_name, args)) = call.args.split_first() else {
155 self.diags.push(Diagnostic::error(
156 call.span,
157 "`apply_blueprint` には blueprint 名が必要",
158 ));
159 continue;
160 };
161 let Some(bp) = self.blueprints.get(bp_name.value.as_str()).copied() else {
162 self.diags.push(Diagnostic::error(
163 bp_name.span,
164 format!("blueprint `{}` が無い", bp_name.value),
165 ));
166 continue;
167 };
168 if bp.params.len() != args.len() {
169 self.diags.push(
170 Diagnostic::error(
171 call.span,
172 format!(
173 "blueprint `{}` は引数 {} 個。{} 個渡されている",
174 bp.name.value,
175 bp.params.len(),
176 args.len()
177 ),
178 )
179 .with_label(bp.name.span, "定義"),
180 );
181 continue;
182 }
183
184 let mut scope: Scope = HashMap::new();
185 for (param, arg) in bp.params.iter().zip(args) {
186 let noun = Compound::noun(arg.value.clone());
187 if self.dict.get(&arg.value).is_none() {
188 self.diags.push(Diagnostic::error(
189 arg.span,
190 format!(
191 "`{}` が `nouns` に無い。blueprint の引数は名詞に限る",
192 arg.value
193 ),
194 ));
195 }
196 self.check_shadowing(param);
197 scope.insert(param.value.clone(), noun);
198 }
199
200 let literals = self.resolve_table_nouns(&bp.tables, &mut scope);
201
202 for table in &bp.tables {
203 let Some(noun) = scope.get(&table.name.value).cloned() else {
204 continue;
205 };
206 let mut names = self.top_level_names.clone();
207 names.extend(literals.clone());
208 out.push(Pending {
209 ast: table,
210 name: literals
211 .get(&table.name.value)
212 .cloned()
213 .unwrap_or_else(|| self.table_name_of(&noun)),
214 noun: Some(noun),
215 scope: scope.clone(),
216 names,
217 origin: ir::Origin::Blueprint {
218 name: bp.name.value.clone(),
219 def_span: table.name.span,
220 apply_span: call.span,
221 },
222 });
223 }
224 }
225 out
226 }
227
228 fn resolve_table_nouns(
237 &mut self,
238 tables: &[ast::Table],
239 scope: &mut Scope,
240 ) -> HashMap<String, String> {
241 let idents: HashSet<&str> = tables.iter().map(|t| t.name.value.as_str()).collect();
242
243 let mut literal_names = HashMap::new();
244 let mut pending: Vec<(&ast::Name, Option<&Spanned<Value>>)> = Vec::new();
245 for table in tables {
246 match name_expression(table) {
247 Some(Spanned {
249 value: Value::Str(text),
250 ..
251 }) => {
252 literal_names.insert(table.name.value.clone(), text.clone());
253 pending.push((&table.name, None));
254 }
255 Some(expr) => {
258 if self.dict.get(&table.name.value).is_some() {
259 self.diags.push(Diagnostic::error(
260 table.name.span,
261 format!(
262 "`{}` は名詞と衝突している。`name` に名詞式を書くテーブルには別の名前を付ける",
263 table.name.value
264 ),
265 ));
266 continue;
267 }
268 pending.push((&table.name, Some(expr)));
269 }
270 None => pending.push((&table.name, None)),
271 }
272 }
273
274 loop {
275 let before = pending.len();
276 let mut deferred = Vec::new();
277 for (ident, expr) in pending {
278 let noun = match expr {
279 None => Some(Compound::noun(ident.value.clone())),
280 Some(value) => self.try_eval_noun(value, scope, &idents),
281 };
282 match noun {
283 Some(noun) => {
284 scope.insert(ident.value.clone(), noun);
285 }
286 None => deferred.push((ident, expr)),
287 }
288 }
289 pending = deferred;
290 if pending.is_empty() || pending.len() == before {
292 break;
293 }
294 }
295
296 for (ident, _) in pending {
297 self.diags.push(Diagnostic::error(
298 ident.span,
299 format!("`{}` の `name` が循環している", ident.value),
300 ));
301 }
302 literal_names
303 }
304
305 fn try_eval_noun(
307 &mut self,
308 value: &Spanned<Value>,
309 scope: &Scope,
310 idents: &HashSet<&str>,
311 ) -> Option<Compound> {
312 match &value.value {
313 Value::Ident(name) => match scope.get(name) {
314 Some(noun) => Some(noun.clone()),
315 None if idents.contains(name.as_str()) => None,
317 None => Some(Compound::noun(name.clone())),
318 },
319 Value::Str(text) => Some(Compound::literal(text.clone())),
320 Value::Call { name, args } if name.value == "noun" => {
321 let mut parts = Vec::new();
322 for arg in args {
323 parts.extend(self.try_eval_noun(arg, scope, idents)?.parts);
324 }
325 if parts.is_empty() {
326 self.diags
327 .push(Diagnostic::error(value.span, "`noun()` に引数が無い"));
328 }
329 Some(Compound { parts })
330 }
331 _ => self.eval_noun(value, scope),
332 }
333 }
334
335 fn check_shadowing(&mut self, name: &Spanned<String>) {
336 if self.dict.get(&name.value).is_some() {
337 self.diags.push(Diagnostic::error(
338 name.span,
339 format!("`{}` は名詞と衝突している", name.value),
340 ));
341 }
342 }
343
344 fn resolve_noun(&self, ident: &Spanned<String>, scope: &Scope) -> Compound {
346 match scope.get(&ident.value) {
347 Some(c) => c.clone(),
348 None => Compound::noun(ident.value.clone()),
349 }
350 }
351
352 fn separator(&self) -> String {
353 self.config.naming.noun_separator.clone()
354 }
355
356 fn singular_of(&self, c: &Compound) -> String {
357 c.singular(&self.dict, &self.separator())
358 }
359
360 fn plural_of(&self, c: &Compound) -> String {
361 c.plural(&self.dict, &self.separator())
362 }
363
364 fn table_name_of(&self, c: &Compound) -> String {
365 match self.config.naming.table_name {
366 TableNameStyle::Plural => self.plural_of(c),
367 TableNameStyle::Singular => self.singular_of(c),
368 }
369 }
370
371 fn eval_noun(&mut self, value: &Spanned<Value>, scope: &Scope) -> Option<Compound> {
373 match &value.value {
374 Value::Ident(name) => {
375 Some(self.resolve_noun(&Spanned::new(name.clone(), value.span), scope))
376 }
377 Value::Str(text) => Some(Compound::literal(text.clone())),
378 Value::Call { name, args } if name.value == "noun" => {
379 if args.is_empty() {
380 self.diags
381 .push(Diagnostic::error(value.span, "`noun()` に引数が無い"));
382 return None;
383 }
384 let mut parts = Vec::new();
385 for arg in args {
386 let c = self.eval_noun(arg, scope)?;
387 parts.extend(c.parts);
388 }
389 Some(Compound { parts })
390 }
391 Value::Call { name, args } if matches!(name.value.as_str(), "singular" | "plural") => {
392 let [arg] = args.as_slice() else {
393 self.diags.push(Diagnostic::error(
394 value.span,
395 format!("`{}()` は引数を1つ取る", name.value),
396 ));
397 return None;
398 };
399 let c = self.eval_noun(arg, scope)?;
400 let text = if name.value == "plural" {
401 self.plural_of(&c)
402 } else {
403 self.singular_of(&c)
404 };
405 Some(Compound::literal(text))
406 }
407 _ => {
408 self.diags
409 .push(Diagnostic::error(value.span, "名詞として評価できない"));
410 None
411 }
412 }
413 }
414
415 fn render_comment(&mut self, text: &Spanned<String>, scope: &Scope) -> String {
419 if !text.value.contains("${") {
420 return text.value.clone();
421 }
422 let tpl = match Template::parse(&text.value) {
423 Ok(tpl) => tpl,
424 Err(message) => {
425 self.diags.push(Diagnostic::error(text.span, message));
426 return text.value.clone();
427 }
428 };
429
430 let sep = self.separator();
431 let mut out = String::new();
432 for seg in &tpl.segments {
433 match seg {
434 Seg::Text(t) => out.push_str(t),
435 Seg::Var(name) => {
436 let noun = self.resolve_noun(&Spanned::new(name.clone(), text.span), scope);
437 out.push_str(&noun.singular(&self.dict, &sep));
438 }
439 Seg::Call { func, arg } => {
440 let noun = self.resolve_noun(&Spanned::new(arg.clone(), text.span), scope);
441 match func.as_str() {
442 "plural" => out.push_str(&noun.plural(&self.dict, &sep)),
443 "singular" => out.push_str(&noun.singular(&self.dict, &sep)),
444 _ => match self.noun_description(&noun) {
445 Some(desc) => out.push_str(&desc),
446 None => self.diags.push(Diagnostic::error(
447 text.span,
448 format!("`{arg}` に説明が無い。`nouns` の第3列に書く"),
449 )),
450 },
451 }
452 }
453 }
454 }
455 out
456 }
457
458 fn noun_description(&self, noun: &Compound) -> Option<String> {
460 let last = noun.nouns().last()?;
461 self.dict.get(last).and_then(|e| e.comment.clone())
462 }
463
464 fn check_nouns_registered(&mut self, c: &Compound, span: Span) {
466 let missing: Vec<String> = c
467 .nouns()
468 .filter(|n| self.dict.get(n).is_none())
469 .map(str::to_string)
470 .collect();
471 for name in missing {
472 self.diags.push(Diagnostic::warning(
473 span,
474 format!("`{name}` が `nouns` に無い。規則変化で解決する"),
475 ));
476 }
477 }
478
479 fn render(&mut self, tpl: &Template, vars: &Vars, span: Span) -> String {
482 let sep = self.separator();
483 let mut out = String::new();
484 for seg in &tpl.segments {
485 let (name, rendered) = match seg {
486 Seg::Text(t) => {
487 out.push_str(t);
488 continue;
489 }
490 Seg::Var(v) => (v, vars.get(v.as_str()).map(|c| c.as_written(&sep))),
491 Seg::Call { func, arg } => (
492 arg,
493 vars.get(arg.as_str()).map(|c| {
494 if func == "plural" {
495 c.plural(&self.dict, &sep)
496 } else {
497 c.singular(&self.dict, &sep)
498 }
499 }),
500 ),
501 };
502 match rendered {
503 Some(text) => out.push_str(&text),
504 None => self.diags.push(Diagnostic::error(
505 span,
506 format!("テンプレート変数 `{name}` はここでは使えない"),
507 )),
508 }
509 }
510 out
511 }
512
513 fn build_table(
516 &mut self,
517 p: &Pending<'a>,
518 ) -> (ir::Table, Vec<RelationSpec>, Vec<ast::Relation>) {
519 let mut columns: IndexMap<String, ir::Column> = IndexMap::new();
520 let mut pk: Vec<String> = Vec::new();
521 let mut indexes: Vec<ir::Index> = Vec::new();
522 let mut excepts: Vec<Spanned<String>> = Vec::new();
523 let mut except_indexes: Vec<Vec<String>> = Vec::new();
524 let mut overrides: Vec<&ast::Override> = Vec::new();
525
526 let mut relations: Vec<RelationSpec> = Vec::new();
527 let mut reverses: Vec<ast::Relation> = Vec::new();
528 let mut own_comment: Option<Spanned<String>> = None;
529 let mut seen = Vec::new();
530 self.walk_members(
531 &p.ast.members,
532 &ir::Origin::Own,
533 &p.scope,
534 &mut seen,
535 &mut columns,
536 &mut pk,
537 &mut indexes,
538 &mut excepts,
539 &mut except_indexes,
540 &mut overrides,
541 &mut relations,
542 &mut reverses,
543 &mut own_comment,
544 );
545
546 for name in &excepts {
547 if columns.shift_remove(&name.value).is_none() {
548 self.diags.push(Diagnostic::error(
549 name.span,
550 format!("`{}` は除外できない。定義が無い", name.value),
551 ));
552 }
553 pk.retain(|c| c != &name.value);
554 }
555 for cols in &except_indexes {
556 let before = indexes.len();
557 indexes.retain(|i| &i.columns != cols);
558 if indexes.len() == before {
559 self.diags.push(Diagnostic::error(
560 p.ast.name.span,
561 format!("除外対象の index `[{}]` が無い", cols.join(", ")),
562 ));
563 }
564 }
565 for ov in &overrides {
566 let Some(col) = columns.get_mut(&ov.name.value) else {
567 self.diags.push(Diagnostic::error(
568 ov.name.span,
569 format!("`{}` は上書きできない。定義が無い", ov.name.value),
570 ));
571 continue;
572 };
573 let mut errors = Vec::new();
574 apply_attrs(col, &ov.attrs, &mut errors);
575 self.diags.append(&mut errors);
576 }
577
578 indexes.retain(|i| i.columns.iter().all(|c| columns.contains_key(c)));
580
581 for col in columns.values() {
582 if !col.ty.is_empty() && !self.dialect.has_type(&col.ty) {
584 self.diags.push(Diagnostic::error(
585 col.span,
586 format!("`{}` は {} の型ではない", col.ty, self.dialect.name),
587 ));
588 }
589 if self.dialect.is_reserved(&col.name) {
590 self.diags.push(Diagnostic::warning(
591 col.span,
592 format!("`{}` は {} の予約語", col.name, self.dialect.name),
593 ));
594 }
595 }
596
597 let comment = match own_comment {
598 Some(text) => Some(self.render_comment(&text, &p.scope)),
599 None => p
600 .noun
601 .as_ref()
602 .and_then(Compound::as_single_noun)
603 .and_then(|n| self.dict.get(n))
604 .and_then(|n| n.comment.clone()),
605 };
606
607 for spec in &relations {
609 if !columns.contains_key(&spec.column) {
610 self.diags.push(Diagnostic::warning(
611 spec.span,
612 format!(
613 "FK列 `{}` が除外されたので `{}` の関連が消えた",
614 spec.column, spec.target.value
615 ),
616 ));
617 }
618 }
619 relations.retain(|r| columns.contains_key(&r.column));
620
621 if pk.is_empty() && !columns.is_empty() {
622 self.diags.push(Diagnostic::warning(
623 p.ast.name.span,
624 format!("`{}` に主キーが無い", p.name),
625 ));
626 }
627
628 if self.dialect.is_reserved(&p.name) {
629 self.diags.push(Diagnostic::warning(
630 p.ast.name.span,
631 format!("テーブル名 `{}` は {} の予約語", p.name, self.dialect.name),
632 ));
633 }
634
635 (
636 ir::Table {
637 name: p.name.clone(),
638 singular: p.noun.as_ref().map(|n| self.singular_of(n)),
639 plural: p.noun.as_ref().map(|n| self.plural_of(n)),
640 noun: p.noun.clone(),
641 comment,
642 columns,
643 pk,
644 indexes,
645 foreign_keys: Vec::new(),
646 reverses: Vec::new(),
647 origin: p.origin.clone(),
648 span: p.ast.name.span,
649 },
650 relations,
651 reverses,
652 )
653 }
654
655 #[allow(clippy::too_many_arguments)]
656 fn walk_members(
657 &mut self,
658 members: &'a [Spanned<ast::Member>],
659 origin: &ir::Origin,
660 scope: &Scope,
661 use_stack: &mut Vec<String>,
662 columns: &mut IndexMap<String, ir::Column>,
663 pk: &mut Vec<String>,
664 indexes: &mut Vec<ir::Index>,
665 excepts: &mut Vec<Spanned<String>>,
666 except_indexes: &mut Vec<Vec<String>>,
667 overrides: &mut Vec<&'a ast::Override>,
668 relations: &mut Vec<RelationSpec>,
669 reverses: &mut Vec<ast::Relation>,
670 comment: &mut Option<Spanned<String>>,
671 ) {
672 for m in members {
673 match &m.value {
674 ast::Member::Column(c) => {
675 let mut errors = Vec::new();
676 let col = self.make_column(c, origin.clone(), m.span, &mut errors);
677 self.diags.append(&mut errors);
678 if let Some(col) = col
679 && let Some(prev) = columns.insert(col.name.clone(), col)
680 {
681 {
682 self.diags.push(
683 Diagnostic::error(
684 m.span,
685 format!("カラム `{}` が重複している", prev.name),
686 )
687 .with_label(prev.span, "先の定義"),
688 );
689 }
690 }
691 }
692 ast::Member::Pk(cols) => {
693 *pk = cols.iter().map(|c| c.value.clone()).collect();
694 }
695 ast::Member::Index(idx) => indexes.push(ir::Index {
696 name: String::new(),
697 columns: idx.columns.iter().map(|c| c.value.clone()).collect(),
698 unique: idx.unique,
699 span: m.span,
700 }),
701 ast::Member::Use(name) => self.splice_mixin(
702 name,
703 scope,
704 use_stack,
705 columns,
706 pk,
707 indexes,
708 excepts,
709 except_indexes,
710 overrides,
711 relations,
712 reverses,
713 comment,
714 ),
715 ast::Member::Override(ov) => overrides.push(ov),
716 ast::Member::Comment(text) => *comment = Some(text.clone()),
717 ast::Member::Name(_) => {}
719 ast::Member::Except(names) => excepts.extend(names.iter().cloned()),
720 ast::Member::ExceptIndex(cols) => {
721 except_indexes.push(cols.iter().map(|c| c.value.clone()).collect())
722 }
723 ast::Member::Relation(rel) if rel.kind.owns_fk() => {
724 let fk_col = match &rel.fk {
725 Some(name) => name.value.clone(),
726 None => {
727 let target = self.resolve_noun(&rel.target, scope);
728 let vars = Vars::from([("table", target)]);
729 self.render(&self.config.naming.foreign_key.clone(), &vars, m.span)
730 }
731 };
732 let placeholder = ir::Column {
733 name: fk_col.clone(),
734 ty: String::new(),
735 null: false,
736 default: None,
737 on_update: None,
738 comment: rel.comment.as_ref().map(|c| c.value.clone()),
739 origin: ir::Origin::Generated {
740 by: rel.kind.keyword().into(),
741 },
742 span: m.span,
743 };
744 if let Some(prev) = columns.insert(fk_col.clone(), placeholder) {
745 self.diags.push(
746 Diagnostic::error(
747 m.span,
748 format!("FK列 `{fk_col}` が既存のカラムと衝突している"),
749 )
750 .with_label(prev.span, "先の定義"),
751 );
752 continue;
753 }
754 relations.push(RelationSpec {
755 target: rel.target.clone(),
756 unique: rel.kind.is_unique(),
757 column: fk_col,
758 alias: rel.alias.clone(),
759 span: m.span,
760 });
761 }
762 ast::Member::Relation(rel) => reverses.push(rel.clone()),
763 }
764 }
765 }
766
767 #[allow(clippy::too_many_arguments)]
768 fn splice_mixin(
769 &mut self,
770 name: &Spanned<String>,
771 scope: &Scope,
772 use_stack: &mut Vec<String>,
773 columns: &mut IndexMap<String, ir::Column>,
774 pk: &mut Vec<String>,
775 indexes: &mut Vec<ir::Index>,
776 excepts: &mut Vec<Spanned<String>>,
777 except_indexes: &mut Vec<Vec<String>>,
778 overrides: &mut Vec<&'a ast::Override>,
779 relations: &mut Vec<RelationSpec>,
780 reverses: &mut Vec<ast::Relation>,
781 comment: &mut Option<Spanned<String>>,
782 ) {
783 if use_stack.contains(&name.value) {
784 let path = use_stack.join(" -> ");
785 self.diags.push(Diagnostic::error(
786 name.span,
787 format!("mixin が循環している: {path} -> {}", name.value),
788 ));
789 return;
790 }
791 let Some(mixin): Option<&'a ast::Mixin> = self.mixins.get(name.value.as_str()).copied()
792 else {
793 self.diags.push(Diagnostic::error(
794 name.span,
795 format!("mixin `{}` が無い", name.value),
796 ));
797 return;
798 };
799 let origin = ir::Origin::Mixin {
800 name: mixin.name.value.clone(),
801 def_span: mixin.name.span,
802 };
803 use_stack.push(name.value.clone());
804 self.walk_members(
805 &mixin.members,
806 &origin,
807 scope,
808 use_stack,
809 columns,
810 pk,
811 indexes,
812 excepts,
813 except_indexes,
814 overrides,
815 relations,
816 reverses,
817 comment,
818 );
819 use_stack.pop();
820 }
821
822 fn make_column(
823 &self,
824 c: &ast::Column,
825 origin: ir::Origin,
826 span: Span,
827 errors: &mut Vec<Diagnostic>,
828 ) -> Option<ir::Column> {
829 let mut col = ir::Column {
830 name: c.name.value.clone(),
831 ty: String::new(),
832 null: self.config.constraints.null_default,
833 default: None,
834 on_update: None,
835 comment: None,
836 origin,
837 span,
838 };
839 apply_attrs(&mut col, &c.attrs, errors);
840 if col.ty.is_empty() {
841 errors.push(Diagnostic::error(
842 c.name.span,
843 format!("カラム `{}` に `type=` が無い", c.name.value),
844 ));
845 return None;
846 }
847 Some(col)
848 }
849
850 fn attach_relations(
853 &mut self,
854 pendings: &[Pending<'a>],
855 relations: &[Vec<RelationSpec>],
856 schema: &mut ir::Schema,
857 ) {
858 for (i, specs) in relations.iter().enumerate() {
859 let scope = pendings[i].scope.clone();
860 let names = pendings[i].names.clone();
861 let mut resolved = Vec::new();
862 for spec in specs {
863 if let Some(r) = self.resolve_relation(spec, &scope, &names, schema) {
864 resolved.push(r);
865 }
866 }
867 let Some(table) = schema.tables.get_mut(i) else {
868 continue;
869 };
870 for r in resolved {
871 if let Some(col) = table.columns.get_mut(&r.fk.columns[0])
872 && col.ty.is_empty()
873 {
874 col.ty = r.ty;
875 }
876 table.foreign_keys.push(r.fk);
877 if let Some(idx) = r.index {
878 table.indexes.push(idx);
879 }
880 }
881 }
882 for table in &mut schema.tables {
884 table.columns.retain(|_, c| !c.ty.is_empty());
885 }
886 }
887
888 fn resolve_relation(
889 &mut self,
890 spec: &RelationSpec,
891 scope: &Scope,
892 names: &HashMap<String, String>,
893 schema: &ir::Schema,
894 ) -> Option<ResolvedRelation> {
895 let target = self.resolve_noun(&spec.target, scope);
896 let ref_table_name = match names.get(&spec.target.value) {
897 Some(name) => name.clone(),
898 None => self.table_name_of(&target),
899 };
900 let Some(ref_table) = schema.table(&ref_table_name) else {
901 self.diags.push(Diagnostic::error(
902 spec.target.span,
903 format!("参照先テーブル `{ref_table_name}` が無い"),
904 ));
905 return None;
906 };
907 if ref_table.pk.len() != 1 {
908 self.diags.push(Diagnostic::error(
909 spec.target.span,
910 format!("`{ref_table_name}` の主キーが単一列でないため参照できない"),
911 ));
912 return None;
913 }
914 let ref_col_name = ref_table.pk[0].clone();
915 let ty = self
916 .dialect
917 .fk_type(&ref_table.columns.get(&ref_col_name)?.ty);
918
919 Some(ResolvedRelation {
920 ty,
921 fk: ir::ForeignKey {
922 alias: self.relation_alias(spec, &target, scope),
923 columns: vec![spec.column.clone()],
924 ref_table: ref_table_name,
925 ref_columns: vec![ref_col_name],
926 on_delete: self.config.constraints.on_delete_default.clone(),
927 on_update: self.config.constraints.on_update_default.clone(),
928 span: spec.span,
929 },
930 index: if spec.unique || self.config.constraints.foreign_key_index {
931 Some(ir::Index {
932 name: String::new(),
933 columns: vec![spec.column.clone()],
934 unique: spec.unique,
935 span: spec.span,
936 })
937 } else {
938 None
939 },
940 })
941 }
942
943 fn expand_associates(&mut self, schema: &mut ir::Schema) {
944 let calls: Vec<ast::MacroCall> = self
945 .doc
946 .macros
947 .iter()
948 .filter(|m| m.name.value == "associate")
949 .cloned()
950 .collect();
951
952 for call in calls {
953 let [a, b] = call.args.as_slice() else {
954 self.diags
955 .push(Diagnostic::error(call.span, "`associate` は引数を2つ取る"));
956 continue;
957 };
958 let joined = match &call.table_name {
959 Some(value) => {
960 let value = value.clone();
961 match self.eval_noun(&value, &Scope::new()) {
962 Some(noun) => noun,
963 None => continue,
964 }
965 }
966 None => Compound {
967 parts: vec![Part::Noun(a.value.clone()), Part::Noun(b.value.clone())],
968 },
969 };
970 self.check_nouns_registered(&joined, call.span);
971 let name = self.table_name_of(&joined);
972
973 let mut columns = IndexMap::new();
974 let mut fks = Vec::new();
975 let mut ok = true;
976 for side in [a, b] {
977 let vars = Vars::from([("table", Compound::noun(side.value.clone()))]);
978 let col_name =
979 self.render(&self.config.naming.foreign_key.clone(), &vars, call.span);
980 let spec = RelationSpec {
981 target: side.clone(),
982 unique: false,
983 column: col_name.clone(),
984 alias: None,
985 span: call.span,
986 };
987 let names = self.top_level_names.clone();
988 let Some(r) = self.resolve_relation(&spec, &Scope::new(), &names, schema) else {
989 ok = false;
990 break;
991 };
992 columns.insert(
993 col_name.clone(),
994 ir::Column {
995 name: col_name,
996 ty: r.ty,
997 null: false,
998 default: None,
999 on_update: None,
1000 comment: None,
1001 origin: ir::Origin::Generated {
1002 by: "associate".into(),
1003 },
1004 span: call.span,
1005 },
1006 );
1007 fks.push(r.fk);
1008 }
1009 if !ok {
1010 continue;
1011 }
1012 let pk: Vec<String> = columns.keys().cloned().collect();
1013 schema.tables.push(ir::Table {
1014 name,
1015 singular: Some(self.singular_of(&joined)),
1016 plural: Some(self.plural_of(&joined)),
1017 noun: Some(joined),
1018 comment: call
1019 .comment
1020 .clone()
1021 .map(|c| self.render_comment(&c, &Scope::new())),
1022 columns,
1023 pk,
1024 indexes: Vec::new(),
1025 foreign_keys: fks,
1026 reverses: Vec::new(),
1027 origin: ir::Origin::Generated {
1028 by: "associate".into(),
1029 },
1030 span: call.span,
1031 });
1032 }
1033 }
1034
1035 fn relation_alias(&mut self, spec: &RelationSpec, target: &Compound, scope: &Scope) -> String {
1039 match &spec.alias {
1040 Some(value) => {
1041 let value = value.clone();
1042 match self.eval_noun(&value, scope) {
1043 Some(c) => self.singular_of(&c),
1044 None => String::new(),
1045 }
1046 }
1047 None => {
1048 let vars = Vars::from([("table", target.clone())]);
1049 self.render(&self.config.naming.belongs_to.clone(), &vars, spec.span)
1050 }
1051 }
1052 }
1053
1054 fn attach_reverses(
1055 &mut self,
1056 pendings: &[Pending<'a>],
1057 reverses: &[Vec<ast::Relation>],
1058 schema: &mut ir::Schema,
1059 ) {
1060 let mut incoming: HashMap<String, Vec<IncomingFk>> = HashMap::new();
1062 for t in &schema.tables {
1063 for fk in &t.foreign_keys {
1064 let unique = t
1065 .indexes
1066 .iter()
1067 .any(|i| i.unique && i.columns == fk.columns);
1068 incoming
1069 .entry(fk.ref_table.clone())
1070 .or_default()
1071 .push(IncomingFk {
1072 from_table: t.name.clone(),
1073 from_noun: t.noun.clone(),
1074 columns: fk.columns.clone(),
1075 unique,
1076 });
1077 }
1078 }
1079
1080 for i in 0..schema.tables.len() {
1081 let table_name = schema.tables[i].name.clone();
1082 let candidates = incoming.get(&table_name).cloned().unwrap_or_default();
1083 let mut used = vec![false; candidates.len()];
1084 let mut out: Vec<ir::Reverse> = Vec::new();
1085
1086 let specs: Vec<ast::Relation> = reverses.get(i).cloned().unwrap_or_default();
1087 let scope = pendings.get(i).map(|p| p.scope.clone()).unwrap_or_default();
1088 let names = pendings.get(i).map(|p| p.names.clone()).unwrap_or_default();
1089
1090 for spec in &specs {
1091 let from_table = match names.get(&spec.target.value) {
1092 Some(name) => name.clone(),
1093 None => {
1094 let noun = self.resolve_noun(&spec.target, &scope);
1095 self.table_name_of(&noun)
1096 }
1097 };
1098 let hits: Vec<usize> = candidates
1099 .iter()
1100 .enumerate()
1101 .filter(|(j, c)| !used[*j] && c.from_table == from_table)
1102 .filter(|(_, c)| match &spec.via {
1103 Some(v) => c.columns == [v.value.clone()],
1104 None => true,
1105 })
1106 .map(|(j, _)| j)
1107 .collect();
1108
1109 let j = match hits.as_slice() {
1110 [j] => *j,
1111 [] => {
1112 self.diags.push(Diagnostic::error(
1113 spec.target.span,
1114 format!(
1115 "`{from_table}` から `{table_name}` へのFKが無いため `{}` を解決できない",
1116 spec.kind.keyword()
1117 ),
1118 ));
1119 continue;
1120 }
1121 _ => {
1122 self.diags.push(Diagnostic::error(
1123 spec.target.span,
1124 format!(
1125 "`{from_table}` から `{table_name}` へのFKが複数ある。`via=` で選ぶ"
1126 ),
1127 ));
1128 continue;
1129 }
1130 };
1131 let c = &candidates[j];
1132 if c.unique != spec.kind.is_unique() {
1133 let want = if c.unique { "has_one" } else { "has_many" };
1134 self.diags.push(Diagnostic::error(
1135 spec.target.span,
1136 format!("この参照は1対1ではないため `{want}` を使う"),
1137 ));
1138 continue;
1139 }
1140 used[j] = true;
1141 let alias = match &spec.alias {
1142 Some(value) => {
1143 let value = value.clone();
1144 match self.eval_noun(&value, &scope) {
1145 Some(n) if spec.kind.is_unique() => self.singular_of(&n),
1146 Some(n) => self.plural_of(&n),
1147 None => continue,
1148 }
1149 }
1150 None => self.default_reverse_alias(c, spec.target.span),
1151 };
1152 out.push(ir::Reverse {
1153 alias,
1154 from_table: c.from_table.clone(),
1155 via: c.columns.clone(),
1156 unique: c.unique,
1157 span: spec.target.span,
1158 });
1159 }
1160
1161 let span = schema.tables[i].span;
1163 for (j, c) in candidates.iter().enumerate() {
1164 if used[j] {
1165 continue;
1166 }
1167 let alias = self.default_reverse_alias(c, span);
1168 out.push(ir::Reverse {
1169 alias,
1170 from_table: c.from_table.clone(),
1171 via: c.columns.clone(),
1172 unique: c.unique,
1173 span,
1174 });
1175 }
1176
1177 self.check_relation_names(&schema.tables[i], &out);
1178 schema.tables[i].reverses = out;
1179 }
1180 }
1181
1182 fn default_reverse_alias(&mut self, c: &IncomingFk, span: Span) -> String {
1183 let base = c
1184 .from_noun
1185 .clone()
1186 .unwrap_or_else(|| Compound::literal(c.from_table.clone()));
1187 let tpl = if c.unique {
1188 self.config.naming.has_one.clone()
1189 } else {
1190 self.config.naming.has_many.clone()
1191 };
1192 let vars = Vars::from([("table", base)]);
1193 self.render(&tpl, &vars, span)
1194 }
1195
1196 fn check_relation_names(&mut self, table: &ir::Table, reverses: &[ir::Reverse]) {
1198 let mut seen: HashMap<&str, Span> = HashMap::new();
1199 for fk in &table.foreign_keys {
1200 seen.insert(fk.alias.as_str(), fk.span);
1201 }
1202 for r in reverses {
1203 if table.columns.contains_key(&r.alias) {
1204 self.diags.push(Diagnostic::error(
1205 r.span,
1206 format!("関連名 `{}` が同名のカラムと衝突している", r.alias),
1207 ));
1208 continue;
1209 }
1210 if let Some(prev) = seen.insert(r.alias.as_str(), r.span) {
1211 self.diags.push(
1212 Diagnostic::error(
1213 r.span,
1214 format!("関連名 `{}` が重複している。`alias=` で分ける", r.alias),
1215 )
1216 .with_label(prev, "先の関連"),
1217 );
1218 }
1219 }
1220 }
1221
1222 fn check_indexes(&mut self, schema: &ir::Schema) {
1224 for table in &schema.tables {
1225 let mut seen: HashMap<&[String], Span> = HashMap::new();
1226 for index in &table.indexes {
1227 if let Some(first) = seen.insert(index.columns.as_slice(), index.span) {
1228 self.diags.push(
1229 Diagnostic::warning(
1230 index.span,
1231 format!(
1232 "`{}` に同じ列組み合わせの index が2つある: [{}]",
1233 table.name,
1234 index.columns.join(", ")
1235 ),
1236 )
1237 .with_label(first, "先の index"),
1238 );
1239 }
1240 }
1241
1242 if self.config.constraints.foreign_key_index {
1244 continue;
1245 }
1246 for fk in &table.foreign_keys {
1247 let covered = table
1248 .indexes
1249 .iter()
1250 .any(|index| index.columns.starts_with(&fk.columns));
1251 if !covered {
1252 self.diags.push(Diagnostic::warning(
1253 fk.span,
1254 format!(
1255 "FK列 [{}] に index が無い。`foreign_key_index = false` なので自動では作られない",
1256 fk.columns.join(", ")
1257 ),
1258 ));
1259 }
1260 }
1261 }
1262 }
1263
1264 fn name_indexes(&mut self, schema: &mut ir::Schema) {
1267 let sep = self.config.naming.column_separator.clone();
1268 let idx_tpl = self.config.naming.index.clone();
1269 let uq_tpl = self.config.naming.unique_index.clone();
1270 for i in 0..schema.tables.len() {
1271 let table_name = schema.tables[i].name.clone();
1272 let specs: Vec<(usize, Vec<String>, bool, Span)> = schema.tables[i]
1273 .indexes
1274 .iter()
1275 .enumerate()
1276 .map(|(j, idx)| (j, idx.columns.clone(), idx.unique, idx.span))
1277 .collect();
1278 for (j, cols, unique, span) in specs {
1279 let vars = Vars::from([
1280 ("table", Compound::literal(table_name.clone())),
1281 ("columns", Compound::literal(cols.join(&sep))),
1282 ]);
1283 let tpl = if unique { &uq_tpl } else { &idx_tpl };
1284 let name = self.render(tpl, &vars, span);
1285 schema.tables[i].indexes[j].name = name;
1286 }
1287 }
1288 }
1289}
1290
1291struct RelationSpec {
1293 target: Spanned<String>,
1294 unique: bool,
1295 column: String,
1296 alias: Option<Spanned<Value>>,
1297 span: Span,
1298}
1299
1300#[derive(Debug, Clone)]
1301struct IncomingFk {
1302 from_table: String,
1303 from_noun: Option<Compound>,
1304 columns: Vec<String>,
1305 unique: bool,
1306}
1307
1308struct ResolvedRelation {
1309 ty: String,
1310 fk: ir::ForeignKey,
1311 index: Option<ir::Index>,
1312}
1313
1314fn apply_attrs(col: &mut ir::Column, attrs: &[ast::Attr], errors: &mut Vec<Diagnostic>) {
1315 for attr in attrs {
1316 let span = attr.value.span;
1317 match attr.key.value.as_str() {
1318 "type" => match &attr.value.value {
1319 Value::Ident(t) => col.ty = t.clone(),
1320 _ => errors.push(Diagnostic::error(span, "`type=` には型名を書く")),
1321 },
1322 "null" => match &attr.value.value {
1323 Value::Ident(v) if v == "true" => col.null = true,
1324 Value::Ident(v) if v == "false" => col.null = false,
1325 _ => errors.push(Diagnostic::error(span, "`null=` は `true` か `false`")),
1326 },
1327 "default" => col.default = to_val(&attr.value.value, span, errors),
1328 "on_update" => col.on_update = to_val(&attr.value.value, span, errors),
1329 "comment" => match &attr.value.value {
1330 Value::Str(s) => col.comment = Some(s.clone()),
1331 _ => errors.push(Diagnostic::error(span, "`comment=` には文字列を書く")),
1332 },
1333 _ => {}
1334 }
1335 }
1336}
1337
1338fn to_val(v: &Value, span: Span, errors: &mut Vec<Diagnostic>) -> Option<ir::Val> {
1339 match v {
1340 Value::Eval(e) => Some(ir::Val::Eval(e.clone())),
1341 Value::Str(s) => Some(ir::Val::Literal(format!("'{}'", s.replace('\'', "''")))),
1342 Value::Num(n) => Some(ir::Val::Literal(n.clone())),
1343 Value::Ident(i) => Some(ir::Val::Literal(i.clone())),
1344 _ => {
1345 errors.push(Diagnostic::error(span, "値として使えない"));
1346 None
1347 }
1348 }
1349}
1350
1351#[allow(dead_code)]
1353fn _unused(_: &HashSet<String>) {}
1354
1355fn name_expression(table: &ast::Table) -> Option<&Spanned<Value>> {
1357 table.members.iter().find_map(|m| match &m.value {
1358 ast::Member::Name(value) => Some(value),
1359 _ => None,
1360 })
1361}