001
002package gudusoft.gsqlparser.dlineage.dataflow.model;
003
004import java.util.*;
005import java.util.concurrent.atomic.AtomicInteger;
006
007import gudusoft.gsqlparser.EDbVendor;
008import gudusoft.gsqlparser.EExpressionType;
009import gudusoft.gsqlparser.EFunctionType;
010import gudusoft.gsqlparser.TSourceToken;
011import gudusoft.gsqlparser.dlineage.util.DlineageUtil;
012import gudusoft.gsqlparser.dlineage.util.Pair;
013import gudusoft.gsqlparser.dlineage.util.Pair3;
014import gudusoft.gsqlparser.nodes.TExpression;
015import gudusoft.gsqlparser.nodes.TFunctionCall;
016import gudusoft.gsqlparser.nodes.TObjectName;
017import gudusoft.gsqlparser.nodes.TParseTreeNode;
018import gudusoft.gsqlparser.nodes.TResultColumn;
019import gudusoft.gsqlparser.nodes.TResultColumnList;
020import gudusoft.gsqlparser.nodes.TTable;
021import gudusoft.gsqlparser.util.SQLUtil;
022
023public class ResultColumn extends Column{
024
025        protected ResultSet resultSet;
026
027        protected long id;
028
029        protected String alias;
030        protected Pair3<Long, Long, String> aliasStartPosition;
031        protected Pair3<Long, Long, String> aliasEndPosition;
032
033        protected String fullName;
034        protected String name;
035
036        protected Pair3<Long, Long, String> startPosition;
037        protected Pair3<Long, Long, String> endPosition;
038
039        protected TParseTreeNode columnObject;
040        
041        protected String refColumnName;
042
043        /**
044         * A live view of this column's star links ({@link #getStarLinkColumns()}).
045         *
046         * @deprecated read and write the star links through the column's methods; assigning this field does not
047         * change them.
048         */
049        @Deprecated
050        protected Map<String, Set<TObjectName>> starLinkColumns = starLinks().legacyView();
051
052        private boolean showStar = true;
053
054        protected boolean isFunction = false;
055        
056        private boolean isPseduo = false;
057        
058        private boolean isStruct = false;
059        
060        private List<Transform> transforms;
061
062        public ResultColumn() {
063
064        }
065
066        public ResultColumn(ResultSet resultSet, TParseTreeNode columnObject) {
067                if (columnObject == null || resultSet == null)
068                        throw new IllegalArgumentException("ResultColumn arguments can't be null.");
069
070                id = ++ModelBindingManager.get().TABLE_COLUMN_ID;
071
072                this.resultSet = resultSet;
073                resultSet.addColumn(this);
074
075                this.columnObject = columnObject;
076
077                TSourceToken startToken = columnObject.getStartToken();
078                TSourceToken endToken = columnObject.getEndToken();
079
080                if (columnObject instanceof TObjectName) {
081                        setStarColumn(DlineageUtil.isStarName((TObjectName) columnObject));
082                } else if (columnObject instanceof TResultColumn) {
083                        setStarColumn(isStarItem((TResultColumn) columnObject));
084                }
085                if (columnObject instanceof TObjectName) {
086                        setIdentifierKey(DlineageUtil.columnKey((TObjectName) columnObject));
087                        TObjectName reference = (TObjectName) columnObject;
088                        if (gudusoft.gsqlparser.nodes.structured.NestedAccessProfile.forVendor(ModelBindingManager.getGlobalVendor()).buildsDeclaredTree() && reference.getFieldPathRead() != null
089                                        && reference.getFieldPathBaseToken() != null) {
090                                // STRUCT tree: a field read through a CTE or derived table, customer.address.city, reads the column
091                                // customer of its result set; the grammar's column slot (address) names no column (Mantis 4830)
092                                this.name = DlineageUtil.decodeColumnIdentifier(reference.getFieldPathBaseToken().toString());
093                        } else if (((TObjectName) columnObject).getColumnNameOnly() != null
094                                        && !"".equals(((TObjectName) columnObject).getColumnNameOnly())) {
095                                this.name = DlineageUtil.decodeColumnIdentifier(((TObjectName) columnObject).getColumnNameOnly());
096                        } else {
097                                // Empty columnNameOnly means a null part token, which is what an
098                                // ALIAS TObjectName looks like (dbObjectType=column_alias). This is
099                                // the path MSSQL's "alias = expression" form takes, and leaving it
100                                // raw published RS-1."[Alias]" beside a view column "Alias" (#704).
101                                this.name = DlineageUtil.decodeAliasIdentifier(((TObjectName) columnObject).toString());
102                        }
103                } else
104                        this.name = columnObject.toString();
105                if (this.name == null) {
106                        this.name = "";
107                }
108
109                this.fullName = columnObject.toString();
110                if (this.fullName == null) {
111                        this.fullName = this.name;
112                }
113
114                if (startToken != null) {
115                        this.startPosition = new Pair3<Long, Long, String>(startToken.lineNo, startToken.columnNo,
116                                        ModelBindingManager.getGlobalHash());
117                }
118                if (endToken != null) {
119                        this.endPosition = new Pair3<Long, Long, String>(endToken.lineNo,
120                                        endToken.columnNo + SQLUtil.endTrim(endToken.getAstext()).length(),
121                                        ModelBindingManager.getGlobalHash());
122                }
123        }
124
125        public ResultColumn(ResultSet resultSet, TResultColumn resultColumnObject) {
126                if (resultColumnObject == null || resultSet == null)
127                        throw new IllegalArgumentException("ResultColumn arguments can't be null.");
128
129                id = ++ModelBindingManager.get().TABLE_COLUMN_ID;
130
131                this.resultSet = resultSet;
132                resultSet.addColumn(this);
133
134                this.columnObject = resultColumnObject;
135                setStarColumn(isStarItem(resultColumnObject));
136                if (resultColumnObject.getAliasClause() != null
137                                && resultColumnObject.getAliasClause().getAliasName() != null) {
138                        setIdentifierKey(DlineageUtil.columnKey(resultColumnObject.getAliasClause().getAliasName()));
139                } else if (resultColumnObject.getExpr() != null
140                                && resultColumnObject.getExpr().getExpressionType() == EExpressionType.simple_object_name_t) {
141                        setIdentifierKey(DlineageUtil.columnKey(resultColumnObject.getExpr().getObjectOperand()));
142                }
143
144                if (resultColumnObject.getAliasClause() != null) {
145                        this.alias = resultColumnObject.getAliasClause().toString();
146                        TSourceToken startToken = resultColumnObject.getAliasClause().getStartToken();
147                        TSourceToken endToken = resultColumnObject.getAliasClause().getEndToken();
148                        this.aliasStartPosition = new Pair3<Long, Long, String>(startToken.lineNo, startToken.columnNo,
149                                        ModelBindingManager.getGlobalHash());
150                        this.aliasEndPosition = new Pair3<Long, Long, String>(endToken.lineNo,
151                                        endToken.columnNo + SQLUtil.endTrim(endToken.getAstext()).length(), ModelBindingManager.getGlobalHash());
152
153                        // The alias is an identifier: publish the name a catalog stores, not
154                        // the syntactic spelling. Replaces a vendor-blind apostrophe strip
155                        // that left [x] / "x" / `x` delimited (GitHub issue #704).
156                        // Collision detection must key on the name we PUBLISH, not on the
157                        // spelling. `SELECT a AS [x], b AS x` decodes both to x, and keying
158                        // on the raw text would emit two indistinguishable RS-1.x targets —
159                        // the canonical lineage walker keys nodes by (parent, column), so a
160                        // trace of x would conflate two different sources.
161                        String decodedAlias = DlineageUtil.decodeAliasIdentifier(this.alias);
162                        String aliasKey = decodedAlias.toLowerCase(Locale.ROOT);
163                        if(resultSet.getAliasMap().containsKey(aliasKey)){
164                                int index = resultSet.getAliasMap().get(aliasKey).incrementAndGet();
165                                this.name = decodedAlias + "(" + index + ")";
166                                setRenamedForCollision(true);
167                        }
168                        else {
169                                resultSet.getAliasMap().put(aliasKey, new AtomicInteger(0));
170                                this.name = decodedAlias;
171                        }
172                        
173                        if(resultColumnObject.getExpr().getFunctionCall()!=null) {
174                                TFunctionCall functionObj = resultColumnObject.getExpr().getFunctionCall();
175                                if (functionObj.getFunctionType() == EFunctionType.array_agg_t
176                                                && ModelBindingManager.getGlobalVendor() == EDbVendor.dbvbigquery
177                                                && functionObj.getArgs() != null
178                                                && functionObj.getArgs().size() == 1) {
179
180                                        TExpression arg0 = functionObj.getArgs().getExpression(0);
181                                        if (arg0 != null
182                                                        && arg0.getExpressionType() == EExpressionType.simple_object_name_t) {
183
184                                                TObjectName on = arg0.getObjectOperand();
185                                                if (on.getSourceTable()!=null) {
186                                                        // The aggregated row, a whole-row star of the table it reads.
187                                                        this.setStruct(true);
188                                                        this.name = "*";
189                                                        setStarColumn(true);
190                                                }
191                                        }
192                                }
193                        }
194                } else if (resultColumnObject.getExpr() != null) {
195                        this.name = getColumnName(resultColumnObject.getExpr());
196                } 
197                
198                if(this.name == null){
199                        if (resultColumnObject.getColumnNameOnly() != null
200                                        && !"".equals(resultColumnObject.getColumnNameOnly())) {
201                                if (resultColumnObject.getExpr().getObjectOperand() != null
202                                                && resultColumnObject.getExpr().getObjectOperand().getPropertyToken() != null) {
203                                        this.name = DlineageUtil.decodeColumnIdentifier(DlineageUtil.fieldLeafText(resultColumnObject.getExpr().getObjectOperand()));
204                                } else {
205                                        this.name = DlineageUtil.decodeColumnIdentifier(resultColumnObject.getColumnNameOnly());
206                                }
207                        } else {
208                                this.name = resultColumnObject.toString();
209                        }
210                }
211                
212                if (resultColumnObject.getExpr().getExpressionType() == EExpressionType.function_t) {
213                        this.fullName = resultColumnObject.getExpr().getFunctionCall().toString();
214                } else if (resultColumnObject.getExpr().getExpressionType() == EExpressionType.typecast_t) {
215                        this.fullName = resultColumnObject.getExpr().getLeftOperand().toString();
216                } else {
217                        this.fullName = resultColumnObject.toString();
218                }
219
220                TSourceToken startToken = resultColumnObject.getStartToken();
221                TSourceToken endToken = resultColumnObject.getEndToken();
222                this.startPosition = new Pair3<Long, Long, String>(startToken.lineNo, startToken.columnNo,
223                                ModelBindingManager.getGlobalHash());
224                this.endPosition = new Pair3<Long, Long, String>(endToken.lineNo, endToken.columnNo + SQLUtil.endTrim(endToken.getAstext()).length(),
225                                ModelBindingManager.getGlobalHash());
226        }
227        
228        public ResultColumn(ResultSet resultSet, TResultColumn resultColumnObject, String refColumnName) {
229                if (resultColumnObject == null || resultSet == null)
230                        throw new IllegalArgumentException("ResultColumn arguments can't be null.");
231
232                id = ++ModelBindingManager.get().TABLE_COLUMN_ID;
233
234                this.resultSet = resultSet;
235                resultSet.addColumn(this);
236
237                this.columnObject = resultColumnObject;
238                this.refColumnName = refColumnName;
239                
240                this.name = refColumnName;
241
242                TSourceToken startToken = resultColumnObject.getStartToken();
243                TSourceToken endToken = resultColumnObject.getEndToken();
244                this.startPosition = new Pair3<Long, Long, String>(startToken.lineNo, startToken.columnNo,
245                                ModelBindingManager.getGlobalHash());
246                this.endPosition = new Pair3<Long, Long, String>(endToken.lineNo, endToken.columnNo + SQLUtil.endTrim(endToken.getAstext()).length(),
247                                ModelBindingManager.getGlobalHash());
248        }
249
250        /**
251         * Whether a select-list item stands for a star, by the parse: {@code *}, {@code t.*}, or an item the model names
252         * after an inner star ({@code (*)}, a scalar {@code (SELECT * ...)}), following {@link #getColumnName(TExpression)}.
253         * An item with an alias is named by it: a column.
254         */
255        private static boolean isStarItem(TResultColumn item) {
256                return item.getAliasClause() == null && item.getExpr() != null && isStarExpr(item.getExpr());
257        }
258
259        private static boolean isStarExpr(TExpression expr) {
260                while (expr != null) {
261                        switch (expr.getExpressionType()) {
262                        case parenthesis_t:
263                                expr = expr.getLeftOperand();
264                                break;
265                        case simple_object_name_t:
266                                return DlineageUtil.isStarName(expr.getObjectOperand());
267                        case subquery_t:
268                                TResultColumnList items = expr.getSubQuery() == null ? null : expr.getSubQuery().getResultColumnList();
269                                if (items == null || items.size() != 1 || !SQLUtil.isEmpty(items.getResultColumn(0).getColumnAlias())) {
270                                        return false;
271                                }
272                                expr = items.getResultColumn(0).getExpr();
273                                break;
274                        default:
275                                return false;
276                        }
277                }
278                return false;
279        }
280
281        protected String getColumnName(TExpression expr) {
282                if (expr.getExpressionType() == EExpressionType.simple_constant_t) {
283                        return expr.toString();
284                } else if (expr.getExpressionType() == EExpressionType.sqlserver_proprietary_column_alias_t) {
285                        this.alias = expr.getLeftOperand().toString();
286                        TSourceToken startToken = expr.getLeftOperand().getStartToken();
287                        TSourceToken endToken = expr.getLeftOperand().getEndToken();
288                        this.aliasStartPosition = new Pair3<Long, Long, String>(startToken.lineNo, startToken.columnNo,
289                                        ModelBindingManager.getGlobalHash());
290                        this.aliasEndPosition = new Pair3<Long, Long, String>(endToken.lineNo,
291                                        endToken.columnNo + SQLUtil.endTrim(endToken.getAstext()).length(), ModelBindingManager.getGlobalHash());
292
293                        return DlineageUtil.decodeAliasIdentifier(this.alias);
294                } else if (expr.getExpressionType() == EExpressionType.function_t) {
295                        return expr.getFunctionCall().toString();
296                } else if (expr.getExpressionType() == EExpressionType.typecast_t) {
297                        return expr.getLeftOperand().toString();
298                } else if (expr.getExpressionType() == EExpressionType.parenthesis_t) {
299                        TExpression inner = expr.getLeftOperand();
300                        while (inner != null && inner.getExpressionType() == EExpressionType.parenthesis_t) {
301                                inner = inner.getLeftOperand();
302                        }
303                        return getColumnName(inner);
304                } else if (expr.getExpressionType() == EExpressionType.simple_object_name_t) {
305                        TObjectName columnName = expr.getObjectOperand();
306                        if (columnName.getPropertyToken() != null) {
307                                return DlineageUtil.decodeColumnIdentifier(DlineageUtil.fieldLeafText(columnName));
308                        } else {
309                                if (SQLUtil.isEmpty(columnName.getColumnNameOnly())) {
310                                        return columnName.toString();
311                                } else {
312                                        return DlineageUtil.decodeColumnIdentifier(columnName.getColumnNameOnly());
313                                }
314                        }
315                } else if (expr.getExpressionType() == EExpressionType.subquery_t) {
316                        TResultColumnList resultset = expr.getSubQuery().getResultColumnList();
317                        if (resultset != null && resultset.size() == 1) {
318                                TResultColumn resultColumn = resultset.getResultColumn(0);
319                                if (!SQLUtil.isEmpty(resultColumn.getColumnAlias())) {
320                                        return resultColumn.getColumnAlias();
321                                } else if (resultColumn.getExpr() != null) {
322                                        return getColumnName(resultColumn.getExpr());
323                                } else if (resultColumn.getColumnNameOnly() != null
324                                                && !"".equals(resultColumn.getColumnNameOnly())) {
325                                        if (resultColumn.getExpr().getObjectOperand() != null
326                                                        && resultColumn.getExpr().getObjectOperand().getPropertyToken() != null) {
327                                                this.name = DlineageUtil.decodeColumnIdentifier(DlineageUtil.fieldLeafText(resultColumn.getExpr().getObjectOperand()));
328                                        } else {
329                                                this.name = DlineageUtil.decodeColumnIdentifier(resultColumn.getColumnNameOnly());
330                                        }
331                                } else {
332                                        this.name = resultColumn.toString();
333                                }
334                        }
335                }
336                return null;
337        }
338
339        public ResultColumn(SelectResultSet resultSet, Pair<TResultColumn, TObjectName> starColumnPair) {
340                if (starColumnPair == null || resultSet == null)
341                        throw new IllegalArgumentException("ResultColumn arguments can't be null.");
342
343                id = ++ModelBindingManager.get().TABLE_COLUMN_ID;
344
345                this.resultSet = resultSet;
346                resultSet.addColumn(this);
347
348                this.columnObject = starColumnPair.first;
349
350                TSourceToken startToken = columnObject.getStartToken();
351                TSourceToken endToken = columnObject.getEndToken();
352
353                this.name = DlineageUtil.decodeColumnIdentifier(((TObjectName) columnObject).getColumnNameOnly());
354                if(name == null){
355                        name = DlineageUtil.decodeAliasIdentifier(((TObjectName) columnObject).toString());
356                }
357                this.fullName = columnObject.toString();
358
359                this.startPosition = new Pair3<Long, Long, String>(startToken.lineNo, startToken.columnNo,
360                                ModelBindingManager.getGlobalHash());
361                this.endPosition = new Pair3<Long, Long, String>(endToken.lineNo, endToken.columnNo + SQLUtil.endTrim(endToken.getAstext()).length(),
362                                ModelBindingManager.getGlobalHash());
363        }
364
365        // private int getIndexOf(TResultColumnList resultColumnList,
366        // TResultColumn resultColumnObject) {
367        // for (int i = 0; i < resultColumnList.size(); i++) {
368        // if (resultColumnList.getResultColumn(i) == resultColumnObject)
369        // return i;
370        // }
371        // return -1;
372        // }
373
374        public ResultSet getResultSet() {
375                return resultSet;
376        }
377
378        public long getId() {
379                return id;
380        }
381
382        public String getAlias() {
383                return alias;
384        }
385
386        public Pair3<Long, Long, String> getAliasStartPosition() {
387                return aliasStartPosition;
388        }
389
390        public Pair3<Long, Long, String> getAliasEndPosition() {
391                return aliasEndPosition;
392        }
393
394        public String getFullName() {
395                return fullName;
396        }
397
398        public Pair3<Long, Long, String> getStartPosition() {
399                return startPosition;
400        }
401
402        public Pair3<Long, Long, String> getEndPosition() {
403                return endPosition;
404        }
405
406        public TParseTreeNode getColumnObject() {
407                return columnObject;
408        }
409
410        public String getName() {
411                return name;
412        }
413
414        /**
415         * Names a column of an UPDATE or MERGE action after the attribute it writes. {@code SET tgt.objcol.a1 = v}
416         * writes {@code objcol.a1}, but the column segment alone names it {@code objcol}. Two attributes of one column
417         * set by the same action then share a name, and the output merge of same-named columns cross-wires their
418         * values (Mantis #4747). The name comes from {@link DlineageUtil#propertyColumnPath}, which also names the
419         * written {@link TableColumn} outside the STRUCT-path vendors; for those, it is the display of the field path
420         * written, {@code customer.address.city}, the name of the field path node the action writes.
421         */
422        void nameAfterWrittenAttribute(TObjectName target) {
423                gudusoft.gsqlparser.common.structured.StructuredColumnPath fieldPath =
424                                gudusoft.gsqlparser.nodes.structured.NestedAccessProfile.forVendor(ModelBindingManager.getGlobalVendor()).buildsDeclaredTree() ? target.getFieldPathRead() : null;
425                String path = fieldPath != null ? fieldPath.toDisplayString() : DlineageUtil.propertyColumnPath(target);
426                if (path != null) {
427                        this.name = path;
428                }
429        }
430
431        @Override
432        void propagateStarLink(TObjectName objectName) {
433                if (this.getStarSourceColumns() != null) {
434                        StarLinkKey nameKey = StarLinkStore.IDENTITY && !this.getStarSourceColumns().isEmpty()
435                                        ? StarLinkStore.keyOf(objectName) : null;
436                        for (Column column : this.getStarSourceColumns()) {
437                                boolean find = false;
438                                if (column instanceof ResultColumn) {
439                                        ResultSet resultset = ((ResultColumn) column).getResultSet();
440                                        for (ResultColumn resultColumn : resultset.getColumns()) {
441                                                Boolean same = StarLinkStore.identityAnswer(resultColumn, nameKey);
442                                                if (same != null ? same.booleanValue() : DlineageUtil.sameColumnDisplay(resultColumn.name, objectName.toString())) {
443                                                        find = true;
444                                                        break;
445                                                }
446                                        }
447                                }
448                                if (!find) {
449                                        column.bindStarLinkColumn(objectName);
450                                }
451                        }
452                }
453        }
454        
455        
456        /**
457         * The ordinal of the star link {@code objectName} would join. A name no link has gives the last ordinal (-1
458         * when there is no link).
459         */
460        public int indexOfStarLinkColumn(TObjectName objectName) {
461                if (objectName == null) {
462                        return -1;
463                }
464
465                String columnName = StarLinkStore.legacyDisplay(objectName);
466
467                if (DlineageUtil.isStarName(objectName)) {
468                        return -1;
469                }
470
471                return starLinks().legacyIndexOf(objectName, columnName);
472        }
473        
474        /** Removes the star link {@code objectName} would join, with every name it holds. */
475        public void unbindStarLinkColumn(TObjectName objectName) {
476                if (objectName == null) {
477                        return;
478                }
479
480                starLinks().unbind(objectName);
481        }
482        
483        /**
484         * {@link #bindStarLinkColumn(TObjectName)} that places a new star link at ordinal {@code index}, without passing
485         * the name on to the star's source columns.
486         */
487        public void bindStarLinkColumn(TObjectName objectName, int index) {
488                if (objectName == null) {
489                        return;
490                }
491
492                starLinks().bindAt(objectName, index);
493        }
494
495
496        public boolean isShowStar() {
497                return showStar;
498        }
499
500        public void setShowStar(boolean showStar) {
501                this.showStar = showStar;
502        }
503
504        public boolean isFunction() {
505                return isFunction;
506        }
507
508        public void setFunction(boolean isFunction) {
509                this.isFunction = isFunction;
510        }
511        
512        public boolean isPseduo() {
513                return isPseduo;
514        }
515
516        public void setPseduo(boolean isPseduo) {
517                this.isPseduo = isPseduo;
518        }
519        
520        public String getRefColumnName() {
521                return refColumnName;
522        }
523        
524        @Override
525        public String toString() {
526                return this.name;
527        }
528
529        public boolean isStruct() {
530                return isStruct;
531        }
532
533        public void setStruct(boolean isStruct) {
534                this.isStruct = isStruct;
535        }
536
537        public List<Transform> getTransforms() {
538                return transforms;
539        }
540
541        public void setTransform(Transform transform) {
542                if(transform==null) {
543                        return;
544                }
545                if(transforms == null) {
546                        transforms = new ArrayList<Transform>();
547                }
548                if(!transforms.contains(transform)) {
549                        transforms.add(transform);
550                }
551        }
552}