Skip to main content

nounsql_core/
resolve.rs

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
32/// 展開待ちのテーブル。名前だけ先に確定させる。
33struct Pending<'a> {
34    ast: &'a ast::Table,
35    name: String,
36    noun: Option<Compound>,
37    scope: Scope,
38    /// `name` に文字列を書いたテーブルの、識別子から最終名への対応。
39    names: HashMap<String, String>,
40    origin: ir::Origin,
41}
42
43/// blueprint 内で仮引数と `let` が束縛する名詞。
44type 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        // 名前解決を先に済ませる。belongs_to が参照先のPKを引けるようにするため。
63        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    // ---------- 定義の索引 ----------
81
82    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    // ---------- テーブルの収集 ----------
102
103    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    /// テーブルの名詞と、明示されたテーブル名を決める。
229    ///
230    /// `name` に名詞式を書くと名詞そのものが決まり、テーブル名は `table_name`
231    /// から導出される。文字列を書いた場合は**テーブル名だけ**が決まり、
232    /// 名詞は識別子のまま残る。FK 列名は名詞から作るので、既存のデータベースに
233    /// 名前を合わせても `customer_id` のままにできる。
234    ///
235    /// `name` は他のテーブルの識別子を参照できるので、解ける順に繰り返す。
236    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                // 文字列はテーブル名だけを決める。名詞は識別子のまま。
248                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                // 名詞式を書いた時点で識別子は局所的な手掛かりになる。
256                // 名詞と同じ綴りだと、どちらを指すのか決まらない。
257                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            // 1周して1つも解けなければ、残りは循環している。
291            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    /// 名詞として評価する。まだ決まっていないテーブルを参照していたら None。
306    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                // これから決まるテーブルなら次の周回に回す。
316                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    /// blueprint スコープを踏まえて識別子が指す名詞を返す。
345    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    /// `noun(...)` などの値を名詞として評価する。
372    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    /// コメント文字列の `${...}` を展開する。
416    ///
417    /// 参照できるのは名詞。blueprint の仮引数と `let` 束縛もそのまま使える。
418    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    /// 名詞の説明。複合名詞では最後の名詞のものを使う。
459    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    /// 名詞の各要素が辞書にあるか確かめる。
465    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    // ---------- テンプレート ----------
480
481    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    // ---------- テーブル本体 ----------
514
515    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        // 除外した列を参照している index を落とす。
579        indexes.retain(|i| i.columns.iter().all(|c| columns.contains_key(c)));
580
581        for col in columns.values() {
582            // 型が空なのは belongs_to が確保した仮の列。参照先の解決後に埋まる。
583            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        // `except` で消えたFK列があれば、その関連も消える。黙って消すと気づけない。
608        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                // 名詞は collect_pending で解決済み。
718                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    // ---------- relation ----------
851
852    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        // 型が埋まらなかったFK列は参照先が解決できなかったもの。落とす。
883        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    // ---------- 逆参照 ----------
1036
1037    /// FKを持つ側の関連名。名詞は単数形にする。
1038    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        // 参照先テーブル名 -> そこへ向かうFK
1061        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            // 明示されなかったFKにも既定の名前で逆参照を作る。
1162            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    /// 関連名がカラム名や他の関連名と衝突していないか。
1197    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    /// index の重複と、外すと決めたFK列の index を確かめる。
1223    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            // 自動生成を止めたなら、必要な索引は自分で張ることになる。
1243            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    // ---------- index 名 ----------
1265
1266    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
1291/// `belongs_to` の解決待ち情報。列の位置だけ先に確保しておく。
1292struct 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/// 未使用の警告を抑えるためのダミー。将来 validate で使う。
1352#[allow(dead_code)]
1353fn _unused(_: &HashSet<String>) {}
1354
1355/// `name` 文があればその値。
1356fn 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}