001package gudusoft.gsqlparser.catalog.input.model;
002
003import java.util.ArrayList;
004import java.util.Collections;
005import java.util.List;
006import java.util.Objects;
007
008/**
009 * Table constraint entry. Plan §6.
010 *
011 * <p>{@link #type()} is a string (PK / FK / UNIQUE / CHECK / NOT_NULL) so the
012 * model stays forward-compatible with vendor-specific constraint flavors.</p>
013 *
014 * <p><b>Foreign-key targets.</b> {@link #referencedTable()} and
015 * {@link #referencedColumns()} carry the FK target side and mirror
016 * {@link gudusoft.gsqlparser.nodes.TConstraint#getReferencedObject()} /
017 * {@link gudusoft.gsqlparser.nodes.TConstraint#getReferencedColumnList()} on
018 * the AST side. The model is permissive: both fields may be set on any
019 * constraint type, but {@link gudusoft.gsqlparser.catalog.input.CatalogModelValidator}
020 * enforces FK-specific semantics (required target table, matching column counts,
021 * resolvable target table + columns).</p>
022 *
023 * <p>{@link #referencedTable()} is a single string and may carry a qualified
024 * name (e.g. {@code "schema.table"}). It is the catalog-side counterpart of
025 * {@link gudusoft.gsqlparser.nodes.TObjectName#toString()}.</p>
026 */
027public final class ConstraintModel {
028
029    private final String name;
030    private final String type;
031    private final List<String> columns;
032    private final String referencedTable;
033    private final List<String> referencedColumns;
034
035    private ConstraintModel(Builder b) {
036        if (b.type == null || b.type.isEmpty()) {
037            throw new IllegalArgumentException("ConstraintModel.type is required");
038        }
039        this.name = b.name;
040        this.type = b.type;
041        this.columns = Collections.unmodifiableList(new ArrayList<String>(b.columns));
042        this.referencedTable = b.referencedTable;
043        this.referencedColumns =
044            Collections.unmodifiableList(new ArrayList<String>(b.referencedColumns));
045    }
046
047    public static Builder builder() {
048        return new Builder();
049    }
050
051    public String name() {
052        return name;
053    }
054
055    /** PK / FK / UNIQUE / CHECK / NOT_NULL — string for forward compatibility. */
056    public String type() {
057        return type;
058    }
059
060    public List<String> columns() {
061        return columns;
062    }
063
064    /**
065     * Target table for a foreign-key constraint, or {@code null} for non-FK
066     * constraints. May be a single name ({@code "orders"}) or qualified
067     * ({@code "shop.orders"} / {@code "catalog.shop.orders"}). Resolution is
068     * up to the validator / runtime; the model itself does not interpret dots.
069     */
070    public String referencedTable() {
071        return referencedTable;
072    }
073
074    /**
075     * Target columns on the foreign-key {@link #referencedTable()}, ordered
076     * to align positionally with {@link #columns()}. Empty (never {@code null})
077     * for non-FK constraints.
078     */
079    public List<String> referencedColumns() {
080        return referencedColumns;
081    }
082
083    @Override
084    public boolean equals(Object o) {
085        if (this == o) return true;
086        if (!(o instanceof ConstraintModel)) return false;
087        ConstraintModel that = (ConstraintModel) o;
088        return Objects.equals(name, that.name)
089            && type.equals(that.type)
090            && columns.equals(that.columns)
091            && Objects.equals(referencedTable, that.referencedTable)
092            && referencedColumns.equals(that.referencedColumns);
093    }
094
095    @Override
096    public int hashCode() {
097        return Objects.hash(name, type, columns, referencedTable, referencedColumns);
098    }
099
100    @Override
101    public String toString() {
102        return "ConstraintModel{name=" + name + ", type=" + type
103            + ", columns=" + columns
104            + ", referencedTable=" + referencedTable
105            + ", referencedColumns=" + referencedColumns + '}';
106    }
107
108    public static final class Builder {
109
110        private String name;
111        private String type;
112        private final List<String> columns = new ArrayList<String>();
113        private String referencedTable;
114        private final List<String> referencedColumns = new ArrayList<String>();
115
116        private Builder() {
117        }
118
119        public Builder name(String v) {
120            this.name = v;
121            return this;
122        }
123
124        public Builder type(String v) {
125            this.type = v;
126            return this;
127        }
128
129        public Builder addColumn(String v) {
130            if (v == null) {
131                throw new IllegalArgumentException("ConstraintModel column may not be null");
132            }
133            this.columns.add(v);
134            return this;
135        }
136
137        public Builder referencedTable(String v) {
138            this.referencedTable = v;
139            return this;
140        }
141
142        public Builder addReferencedColumn(String v) {
143            if (v == null) {
144                throw new IllegalArgumentException(
145                    "ConstraintModel referenced column may not be null");
146            }
147            this.referencedColumns.add(v);
148            return this;
149        }
150
151        public ConstraintModel build() {
152            return new ConstraintModel(this);
153        }
154    }
155}