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