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        self.check_identifier_lengths(&schema);
78        schema
79    }
80
81    // ---------- 定義の索引 ----------
82
83    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    // ---------- テーブルの収集 ----------
103
104    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    /// テーブルの名詞と、明示されたテーブル名を決める。
230    ///
231    /// `name` に名詞式を書くと名詞そのものが決まり、テーブル名は `table_name`
232    /// から導出される。文字列を書いた場合は**テーブル名だけ**が決まり、
233    /// 名詞は識別子のまま残る。FK 列名は名詞から作るので、既存のデータベースに
234    /// 名前を合わせても `customer_id` のままにできる。
235    ///
236    /// `name` は他のテーブルの識別子を参照できるので、解ける順に繰り返す。
237    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                // 文字列はテーブル名だけを決める。名詞は識別子のまま。
249                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                // 名詞式を書いた時点で識別子は局所的な手掛かりになる。
257                // 名詞と同じ綴りだと、どちらを指すのか決まらない。
258                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            // 1周して1つも解けなければ、残りは循環している。
292            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    /// 名詞として評価する。まだ決まっていないテーブルを参照していたら None。
307    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                // これから決まるテーブルなら次の周回に回す。
317                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    /// blueprint スコープを踏まえて識別子が指す名詞を返す。
346    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    /// `noun(...)` などの値を名詞として評価する。
373    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    /// コメント文字列の `${...}` を展開する。
417    ///
418    /// 参照できるのは名詞。blueprint の仮引数と `let` 束縛もそのまま使える。
419    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    /// 名詞の説明。複合名詞では最後の名詞のものを使う。
461    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    /// 出力する識別子が方言の上限に収まるか確かめる。
467    ///
468    /// 超えた分は DB が黙って切り詰めるので、切り詰めた結果が衝突するまで
469    /// 誰も気付かない。制約名は表名と列名から作られるぶん先に溢れる。
470    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    /// 名詞の各要素が辞書にあるか確かめる。
506    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    // ---------- テンプレート ----------
521
522    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                // 裸の変数はテーブル名の綴りで出す。識別子は出力に出さない。
532                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    // ---------- テーブル本体 ----------
559
560    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        // 除外した列を参照している index を落とす。
624        indexes.retain(|i| i.columns.iter().all(|c| columns.contains_key(c)));
625
626        for col in columns.values() {
627            // 型が空なのは belongs_to が確保した仮の列。参照先の解決後に埋まる。
628            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        // `except` で消えたFK列があれば、その関連も消える。黙って消すと気づけない。
653        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                // 名詞は collect_pending で解決済み。
763                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    // ---------- relation ----------
896
897    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        // 型が埋まらなかったFK列は参照先が解決できなかったもの。落とす。
928        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            // 書かれた識別子で話す。生成後のテーブル名は書き手が書いていない名前なので。
947            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    // ---------- 逆参照 ----------
1086
1087    /// FKを持つ側の関連名。名詞は単数形にする。
1088    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        // 参照先テーブル名 -> そこへ向かうFK
1111        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            // 明示されなかったFKにも既定の名前で逆参照を作る。
1212            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    /// 関連名がカラム名や他の関連名と衝突していないか。
1247    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    /// index の重複と、外すと決めたFK列の index を確かめる。
1273    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            // 自動生成を止めたなら、必要な索引は自分で張ることになる。
1293            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    // ---------- index 名 ----------
1315
1316    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            // 名詞を束ねておくと singular / plural / short が索引名でも使える。
1323            // 名詞から名前が付いていない表だけ、確定した名前で代用する。
1324            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
1347/// `belongs_to` の解決待ち情報。列の位置だけ先に確保しておく。
1348struct 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/// 未使用の警告を抑えるためのダミー。将来 validate で使う。
1408#[allow(dead_code)]
1409fn _unused(_: &HashSet<String>) {}
1410
1411/// `name` 文があればその値。
1412fn 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}