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 */ 036public final class Catalog { 037 038 private final List<CatalogTable> tables; 039 040 private Catalog(List<CatalogTable> tables) { 041 this.tables = Collections.unmodifiableList(tables); 042 } 043 044 public static Builder builder() { 045 return new Builder(); 046 } 047 048 /** 049 * @return an unmodifiable view of the tables in registration order. 050 */ 051 public List<CatalogTable> getTables() { 052 return tables; 053 } 054 055 /** 056 * Package-private: bare-name lookup used by the analyzer bridge. 057 * 058 * <p>Kept non-public for slice 76 because the public matching 059 * semantics (qualifier expansion, case sensitivity, quoting) are 060 * delegated to {@code TSQLEnv} and are not yet part of the 061 * {@code Catalog} contract. Promoting this to public freezes a 062 * matching rule before those concerns are designed. 063 * 064 * <p>Matching: bare-name {@link String#equals(Object)} on 065 * {@link CatalogTable#getName()}. First-match wins. 066 * 067 * @return the matching table, or {@code null} if none. 068 */ 069 CatalogTable findTable(String name) { 070 if (name == null) { 071 return null; 072 } 073 for (CatalogTable t : tables) { 074 if (name.equals(t.getName())) { 075 return t; 076 } 077 } 078 return null; 079 } 080 081 @Override 082 public String toString() { 083 return "Catalog{tables=" + tables + "}"; 084 } 085 086 /** 087 * Mutable builder for {@link Catalog}. Single-use: 088 * {@link #build()} returns one {@code Catalog} and the builder should 089 * not be reused after that. 090 */ 091 public static final class Builder { 092 private final List<CatalogTable> tables = new ArrayList<>(); 093 094 private Builder() {} 095 096 public Builder addTable(CatalogTable table) { 097 if (table == null) { 098 throw new IllegalArgumentException("table must not be null"); 099 } 100 tables.add(table); 101 return this; 102 } 103 104 public Catalog build() { 105 return new Catalog(new ArrayList<>(tables)); 106 } 107 } 108}