001package gudusoft.gsqlparser.dlineage.dataflow.model;
002
003import java.util.ArrayList;
004import java.util.List;
005import java.util.Map;
006import java.util.Set;
007
008import gudusoft.gsqlparser.EDbVendor;
009import gudusoft.gsqlparser.nodes.TObjectName;
010import gudusoft.gsqlparser.sqlenv.CanonKey;
011import gudusoft.gsqlparser.dlineage.util.DlineageUtil;
012import gudusoft.gsqlparser.sqlenv.ESQLDataObjectType;
013import gudusoft.gsqlparser.util.SQLUtil;
014
015public abstract class Column {
016
017        /**
018         * Whether star links are grouped by identity in this JVM: the default; {@code -Dgsp.dlineage.starLinks=legacy}
019         * groups them by display.
020         */
021        public static boolean starLinksGroupedByIdentity() {
022                return StarLinkStore.IDENTITY;
023        }
024        private CanonKey identifierKey;
025
026        /** Null for expressions, wildcards and other non-identifier model names. */
027        public CanonKey getIdentifierKey() {
028                return identifierKey;
029        }
030
031        protected void setIdentifierKey(CanonKey key) {
032                identifierKey = key;
033        }
034
035        public void inheritIdentifier(Column source) {
036                identifierKey = source.getIdentifierKey();
037                renamedForCollision = source.isRenamedForCollision();
038        }
039
040        private boolean renamedForCollision;
041
042        /**
043         * Whether this column publishes a name changed to tell it from another column of the same result set that has
044         * the same alias ({@code x(1)} beside {@code x}): both keep the alias's identifier key, so that key does not tell
045         * them apart.
046         */
047        public boolean isRenamedForCollision() {
048                return renamedForCollision;
049        }
050
051        void setRenamedForCollision(boolean renamedForCollision) {
052                this.renamedForCollision = renamedForCollision;
053        }
054
055        private boolean starColumn;
056
057        /**
058         * Whether this column stands for a star: {@code *} or {@code t.*} in the SQL, or a whole-row pseudo column
059         * dlineage synthesized. Its producer decides it from the parse ({@link DlineageUtil#isStarName}), never from the
060         * name: a column NAMED {@code *} (SQL Server {@code [*]}, BigQuery {@code `*`}) displays as {@code *} and is not a
061         * star.
062         */
063        public boolean isStarColumn() {
064                return starColumn;
065        }
066
067        protected void setStarColumn(boolean starColumn) {
068                this.starColumn = starColumn;
069        }
070
071        /** A column that a star expansion produces from {@code source} stands for a star when its source does. */
072        public void inheritStarColumn(Column source) {
073                starColumn = source != null && source.isStarColumn();
074        }
075
076        public boolean matchesIdentifier(TObjectName reference) {
077                return getIdentifierKey() != null && getIdentifierKey().equals(
078                                DlineageUtil.columnKey(reference));
079        }
080
081        private List<Column> starSourceColumns = new ArrayList<>();
082
083        public List<Column> getStarSourceColumns() {
084                return starSourceColumns;
085        }
086
087        public void appendStarSourceColumn(Column starSourceColumn) {
088                if (starSourceColumn != null && !starSourceColumns.contains(starSourceColumn)) {
089                        starSourceColumns.add(starSourceColumn);
090                        List<TObjectName> columns = getStarLinkColumnList();
091                        if (columns != null) {
092                                if (starSourceColumn instanceof ResultColumn) {
093                                        ResultSet parent = ((ResultColumn) starSourceColumn).getResultSet();
094                                        boolean found = false;
095                                        for (TObjectName column : columns) {
096                                                StarLinkKey nameKey = StarLinkStore.IDENTITY ? StarLinkStore.keyOf(column) : null;
097                                                for (ResultColumn resultColumn : parent.getColumns()) {
098                                                        Boolean same = StarLinkStore.identityAnswer(resultColumn, nameKey);
099                                                        if (same != null ? same.booleanValue()
100                                                                        : sameColumnRef(resultColumn.getName(), column.toString())
101                                                                                        || sameColumnName(resultColumn.getName(), column.getColumnNameOnly())) {
102                                                                found = true;
103                                                                break;
104                                                        }
105                                                }
106                                                if (!found) {
107                                                        starSourceColumn.bindStarLinkColumn(column);
108                                                }
109                                        }
110                                } else if (starSourceColumn instanceof TableColumn) {
111                                        Table parent = ((TableColumn) starSourceColumn).getTable();
112                                        boolean found = false;
113                                        for (TObjectName column : columns) {
114                                                StarLinkKey nameKey = StarLinkStore.IDENTITY ? StarLinkStore.keyOf(column) : null;
115                                                for (TableColumn resultColumn : parent.getColumns()) {
116                                                        Boolean same = StarLinkStore.identityAnswer(resultColumn, nameKey);
117                                                        if (same != null ? same.booleanValue()
118                                                                        : sameColumnRef(resultColumn.getName(), column.toString())
119                                                                                        || sameColumnName(resultColumn.getName(), column.getColumnNameOnly())) {
120                                                                found = true;
121                                                                break;
122                                                        }
123                                                }
124                                                if (!found) {
125                                                        starSourceColumn.bindStarLinkColumn(column);
126                                                }
127                                        }
128                                } 
129                        }
130                }
131        }
132
133         /** The column's name as the model publishes it (a display string, not an identity). */
134         public abstract String getName();
135
136        private final StarLinkStore starLinks = new StarLinkStore();
137
138        StarLinkStore starLinks() {
139                return starLinks;
140        }
141
142        /**
143         * Links a name a star expansion produced to this column, and passes it on to the columns this column's star was
144         * expanded from. A wildcard is never linked. Returns whether the name started a new star link rather than joining
145         * the link of its display.
146         */
147        public boolean bindStarLinkColumn(TObjectName objectName) {
148                if (objectName == null) {
149                        return false;
150                }
151                int links = starLinks.size();
152                if (starLinks.bind(objectName) == null) {
153                        return false;
154                }
155                boolean added = starLinks.size() > links;
156                propagateStarLink(objectName);
157                return added;
158        }
159
160        /** Passes a name just linked to this column on to the columns its star was expanded from. */
161        void propagateStarLink(TObjectName objectName) {
162        }
163
164        public boolean hasStarLinkColumn() {
165                return !starLinks.isEmpty();
166        }
167
168        /** Every linked name, link by link in link order. */
169        public List<TObjectName> getStarLinkColumnList() {
170                return starLinks.allNames();
171        }
172
173        /** The published name of each star link, in link order. */
174        public List<String> getStarLinkColumnNames() {
175                return starLinks.displays();
176        }
177
178        /** The number of star links. */
179        public int starLinkCount() {
180                return starLinks.size();
181        }
182
183        /** The published name of the star link at {@code ordinal}. */
184        public String starLinkName(int ordinal) {
185                return starLinks.published(ordinal);
186        }
187
188        /** The display of the star link at {@code ordinal}: its first name's legacy fold, for legacy comparisons. */
189        String starLinkDisplay(int ordinal) {
190                return starLinks.display(ordinal);
191        }
192
193        /**
194         * The identifier key of the star link at {@code ordinal}, which the column published for it carries so that
195         * output merges keep two links of one display apart; null for a label, and while links are grouped by display.
196         */
197        public CanonKey starLinkIdentifier(int ordinal) {
198                return starLinks.identifier(ordinal);
199        }
200
201        /** The first name linked by the star link at {@code ordinal}. */
202        public TObjectName getStarLinkColumnName(int ordinal) {
203                return starLinks.entries().get(ordinal).representative();
204        }
205
206        /**
207         * A key of the star link at {@code ordinal}, for remembering which links were seen: two links of any columns have
208         * equal keys when they link the same name. Nothing else about it is part of the contract.
209         */
210        public Object starLinkKey(int ordinal) {
211                return starLinks.key(ordinal);
212        }
213
214        /** Whether the star link at {@code ordinal} is published under the name {@code model} publishes. */
215        public boolean starLinkNamesColumn(int ordinal, Column model) {
216                return starLinks.displaysColumn(ordinal, model);
217        }
218
219        /**
220         * The matcher that pairs the star link at {@code ordinal}, as the target of an expansion, with source links and
221         * model columns; null when the link has no name to match by.
222         */
223        public StarLinkMatcher starLinkMatcher(int ordinal) {
224                return starLinks.matcher(ordinal);
225        }
226
227        /** The spelling a catalog is given for the column the star link at {@code ordinal} links. */
228        public String starLinkCatalogSpelling(int ordinal) {
229                return starLinks.catalogSpelling(ordinal);
230        }
231
232        /**
233         * The spelling as SQL of the column the star link at {@code ordinal} names, when links are grouped by identity and
234         * the link is an identity's; null otherwise. An output merge names two columns of one table apart from it.
235         */
236        public String starLinkSqlSpelling(int ordinal) {
237                return starLinks.sqlSpelling(ordinal);
238        }
239
240        /**
241         * A name for the star link at {@code ordinal}, spelled as it is published, that stands for that link wherever it
242         * is bound.
243         */
244        public TObjectName starLinkNameNode(int ordinal) {
245                StarLinkEntry entry = starLinks.entries().get(ordinal);
246                return DeclaredColumnName.ofLink(entry, entry.display());
247        }
248
249        /** A name built from {@code text} that publishes the name of {@code model}. */
250        public static TObjectName nameOfColumn(Column model, String text) {
251                return DeclaredColumnName.ofModel(model, text);
252        }
253
254        /** A name built from {@code text} that names no column: expression text, a literal. */
255        public static TObjectName nameOfText(String text) {
256                return DeclaredColumnName.ofLabel(text);
257        }
258
259        /** A name spelled {@code text} for the column a catalog stores as {@code storedName}. */
260        public static TObjectName nameOfCatalogColumn(String storedName, String text) {
261                return DeclaredColumnName.ofCatalog(storedName, text);
262        }
263
264        /** The ordinal of the star link {@code objectName} would join, or -1 when there is none. */
265        public int starLinkIndexOf(TObjectName objectName) {
266                return starLinks.indexOf(objectName);
267        }
268
269        /**
270         * The ordinal of the star link that names the column {@code model} (null for an element that is no column), whose
271         * name folds to {@code display}; -1 when there is none.
272         */
273        public int starLinkIndexOf(Column model, String display) {
274                return starLinks.indexOf(model, display);
275        }
276
277        /** Whether this column has a star link that {@code objectName} would join. */
278        public boolean containsStarLinkColumn(TObjectName objectName) {
279                return starLinks.indexOf(objectName) >= 0;
280        }
281
282        /**
283         * The star links as a map from each link's display (its first name's legacy fold) to its linked names, in link
284         * order. It is a live view: writing to it writes to this column's links. While links are grouped by identity, a
285         * display stands for the first link that has it; {@link #getStarLinkColumnNames()} and {@link #starLinkName(int)}
286         * give each link's published name.
287         */
288        public Map<String, Set<TObjectName>> getStarLinkColumns() {
289                return starLinks.legacyView();
290        }
291
292        /**
293         * Takes over the star links of {@code source}: each of them replaces this column's link of the same published
294         * name, or is appended. The links are shared, not copied, so a name later linked to one of them on either column
295         * is seen by both.
296         */
297        public void shareStarLinksFrom(Column source) {
298                if (source != null) {
299                        starLinks.share(source.starLinks);
300                }
301        }
302
303        /**
304         * Adds the linked names of {@code source} to this column's link of the same published name, appending a link of
305         * this column's own when there is none. Nothing is shared.
306         */
307        public void mergeStarLinksFrom(Column source) {
308                if (source != null) {
309                        starLinks.merge(source.starLinks);
310                }
311        }
312
313        /** Removes the star link that holds {@code objectName} itself, and returns whether there was one. */
314        public boolean removeStarLinkOf(TObjectName objectName) {
315                return objectName != null && starLinks.removeEntryOf(objectName);
316        }
317
318        /**
319         * Column-name equality routed through the unified façade; falls back to
320         * case-insensitive comparison when no analysis vendor is bound.
321         */
322        private static boolean sameColumnName(String a, String b) {
323                if (a == null || b == null) {
324                        return false;
325                }
326                if (DlineageUtil.configuresIdentifierRules()) {
327                        return DlineageUtil.sameName(ESQLDataObjectType.dotColumn, a, b);
328                }
329                EDbVendor vendor = ModelBindingManager.getGlobalVendor();
330                return vendor == null ? a.equalsIgnoreCase(b)
331                                : SQLUtil.sameName(vendor, ESQLDataObjectType.dotColumn, a, b);
332        }
333
334        /**
335         * Like {@link #sameColumnName} but for possibly-qualified references
336         * (TObjectName.toString() can be {@code table.column}); routes through the
337         * segmenting compare instead of the single-segment façade.
338         */
339        private static boolean sameColumnRef(String a, String b) {
340                if (a == null || b == null) {
341                        return false;
342                }
343                EDbVendor vendor = ModelBindingManager.getGlobalVendor();
344                return vendor == null ? a.equalsIgnoreCase(b)
345                                : SQLUtil.compareIdentifier(vendor, ESQLDataObjectType.dotColumn, a, b);
346        }
347
348}