001package gudusoft.gsqlparser.ir.semantic.catalog; 002 003import java.util.ArrayList; 004import java.util.Collections; 005import java.util.List; 006 007/** 008 * Slice 76 — minimal catalog DTO. Lets external callers supply catalog 009 * metadata to 010 * {@link gudusoft.gsqlparser.ir.semantic.SqlSemanticAnalyzer#analyze(String, 011 * gudusoft.gsqlparser.EDbVendor, 012 * gudusoft.gsqlparser.ir.semantic.catalog.Catalog) 013 * SqlSemanticAnalyzer.analyze(sql, vendor, Catalog)} without depending on 014 * the internal {@code gudusoft.gsqlparser.sqlenv.TSQLEnv} type. 015 * 016 * <p>Construct via {@link #builder()}; the returned {@code Catalog} is 017 * immutable and safe to share across threads. 018 * 019 * <p>Identifier semantics (case folding, quoted vs unquoted, vendor 020 * rules) are delegated to the analyzer's internal {@code TSQLEnv} bridge 021 * — the DTO stores names exactly as written by the caller. A later slice 022 * may introduce explicit identifier handling. 023 * 024 * <p><b>Failure-mode note</b> (slice 75 contract preservation): 025 * passing a {@code null} {@code Catalog} as the third argument of 026 * {@link gudusoft.gsqlparser.ir.semantic.SqlSemanticAnalyzer#analyze(String, 027 * gudusoft.gsqlparser.EDbVendor, 028 * gudusoft.gsqlparser.ir.semantic.catalog.Catalog)} is equivalent to 029 * calling the 2-arg {@code analyze(sql, vendor)} overload. Passing a 030 * literal {@code null} third argument WITHOUT a cast is ambiguous at 031 * compile time (the 3-arg {@code analyze} overloads accept either 032 * {@code Catalog} or {@code TSQLEnv}, which are unrelated reference 033 * types). Callers should use the 2-arg overload when no catalog is 034 * available. 035 * 036 * <p><b>API status: supported input DTO.</b> Its builder and read-only getters 037 * are part of Join Analysis Consumption Profile v1. 038 */ 039public final class Catalog { 040 041 private final List<CatalogTable> tables; 042 043 private Catalog(List<CatalogTable> tables) { 044 this.tables = Collections.unmodifiableList(tables); 045 } 046 047 public static Builder builder() { 048 return new Builder(); 049 } 050 051 /** 052 * @return an unmodifiable view of the tables in registration order. 053 */ 054 public List<CatalogTable> getTables() { 055 return tables; 056 } 057 058 /** 059 * Package-private: bare-name lookup used by the analyzer bridge. 060 * 061 * <p>Kept non-public for slice 76 because the public matching 062 * semantics (qualifier expansion, case sensitivity, quoting) are 063 * delegated to {@code TSQLEnv} and are not yet part of the 064 * {@code Catalog} contract. Promoting this to public freezes a 065 * matching rule before those concerns are designed. 066 * 067 * <p>Matching: bare-name {@link String#equals(Object)} on 068 * {@link CatalogTable#getName()}. First-match wins. 069 * 070 * @return the matching table, or {@code null} if none. 071 */ 072 CatalogTable findTable(String name) { 073 if (name == null) { 074 return null; 075 } 076 for (CatalogTable t : tables) { 077 if (name.equals(t.getName())) { 078 return t; 079 } 080 } 081 return null; 082 } 083 084 @Override 085 public String toString() { 086 return "Catalog{tables=" + tables + "}"; 087 } 088 089 /** 090 * Mutable builder for {@link Catalog}. Single-use: 091 * {@link #build()} returns one {@code Catalog} and the builder should 092 * not be reused after that. 093 */ 094 public static final class Builder { 095 private final List<CatalogTable> tables = new ArrayList<>(); 096 097 private Builder() {} 098 099 public Builder addTable(CatalogTable table) { 100 if (table == null) { 101 throw new IllegalArgumentException("table must not be null"); 102 } 103 tables.add(table); 104 return this; 105 } 106 107 public Catalog build() { 108 return new Catalog(new ArrayList<>(tables)); 109 } 110 } 111}