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}