001package gudusoft.gsqlparser.nodes;
002
003
004import gudusoft.gsqlparser.ETokenStatus;
005import gudusoft.gsqlparser.ETokenType;
006import gudusoft.gsqlparser.TBaseType;
007import gudusoft.gsqlparser.TSourceToken;
008import gudusoft.gsqlparser.TSourceTokenList;
009
010import java.util.ArrayList;
011
012
013public class TNodeWithAliasClause extends TParseTreeNode {
014
015    /**
016     * 在 sql script 中,和该 relation 关联的 attribute。
017     * select a from t;
018     * attribute a 就是 relation t 的 referenceAttribute, 如果没有 metadata and ddl,
019     * relation t 的 relationAttributes 应该可以推断出包含 a, 但 t 是否还包含其他的 attribute 就无从得知。
020     */
021    ArrayList<TObjectName> referenceAttributes = new ArrayList<>();
022    public void setAliasClause(TAliasClause aliasClause) {
023        this.aliasClause = aliasClause;
024    }
025
026    private TAliasClause aliasClause = null;
027
028    public TAliasClause getAliasClause() {
029        return aliasClause;
030    }
031
032    /**
033     * Tf there is no alias clause of this node, then toString() is the same as {@link TParseTreeNode}
034     * <p> Otherwise, return text of  node only, don't include text of alias clause.
035     * <p> If this node was modified by {@link TParseTreeNode#setString(String)}, then node text including alias node
036     * text was replaced by new string.
037     * @return
038     */
039    public String toString() {
040        // Cache the alias clause reference to avoid repeated method calls
041        final TAliasClause aliasClause = this.getAliasClause();
042        
043        // Fast path for common case
044        if (aliasClause == null || aliasClause.isTeradataNamedAlais()) {
045            return super.toString();
046        }
047        
048        // Get required tokens for processing
049        final TSourceToken startToken = getStartToken();
050        if (startToken == null) return null;
051        
052        final TSourceTokenList tokenList = startToken.container;
053        if (tokenList == null) return null;
054        
055        // Determine the end token position in one go
056        final TSourceToken asToken = aliasClause.getAsToken();
057        final TSourceToken aliasStartToken = aliasClause.getStartToken();
058
059        // After setString() on this node or one of its children, this node's
060        // start token may live in a replacement token list while the alias
061        // still belongs to the original one; the alias token's posinlist is
062        // meaningless in that list. Walk the double-linked token chain
063        // instead, stopping before the alias tokens.
064        final TSourceToken aliasBoundary = (asToken != null) ? asToken : aliasStartToken;
065        if (aliasBoundary == null) {
066            return super.toString();
067        }
068        if (aliasBoundary.container != tokenList) {
069            return chainToStringExcludingAlias(startToken, aliasBoundary);
070        }
071
072        final TSourceToken endToken = tokenList.nextsolidtoken(aliasBoundary, -1, false);
073
074        if (endToken == null) return null;
075
076        // When the alias lies beyond this node's own token range, the tokens
077        // between this node and its alias belong to an enclosing construct —
078        // e.g. the closing parenthesis in FROM ((SELECT ...) PIVOT (...)) a,
079        // where the outer alias is attached to the inner pivot clause. Render
080        // this node's own range only (it already excludes the alias).
081        final TSourceToken ownEndToken = getEndToken();
082        if (ownEndToken != null && ownEndToken.posinlist < endToken.posinlist) {
083            return super.toString();
084        }
085        
086        // Walk the double-linked token chain rather than the list index range:
087        // after setString() on an interior child both this node's tokens and
088        // the alias stay in the original container, but the chain has been
089        // spliced — the index range would render the pre-modification text and
090        // emit tokens already marked deleted. For an unmodified statement the
091        // chain and the index range are identical.
092        return chainToStringExcludingAlias(startToken, aliasBoundary);
093    }
094
095    /**
096     * Render this node's text by walking the double-linked token chain from
097     * {@code startToken} to the node's end token, stopping before
098     * {@code aliasBoundary} (the AS keyword or the alias name). The chain —
099     * not the list index range — is the source of truth after
100     * {@link TParseTreeNode#setString(String)} splices in replacement tokens
101     * (possibly from another token list) or tokens are marked deleted; for an
102     * unmodified statement it renders exactly the index range.
103     */
104    private String chainToStringExcludingAlias(TSourceToken startToken, TSourceToken aliasBoundary) {
105        final TSourceToken stopToken = getEndToken();
106
107        // Collect the tokens to render: walk the chain, dropping deleted
108        // tokens and (when excluded) comments, and collapsing newlines made
109        // redundant by chain modifications — the same normalization
110        // TParseTreeNode.toString() applies, but without mutating token
111        // status, so the result does not depend on rendering order.
112        final ArrayList<TSourceToken> tokens = new ArrayList<TSourceToken>();
113        boolean chainWasModified = false;
114        boolean ignoreNextNewline = false;
115        TSourceToken previous = null;
116        TSourceToken current = startToken;
117        while (current != null) {
118            if (current == aliasBoundary) break;
119            if (!chainWasModified && previous != null) {
120                chainWasModified = (current.container != previous.container)
121                        || (current.posinlist - previous.posinlist != 1);
122            }
123            previous = current;
124
125            boolean skip = current.tokenstatus == ETokenStatus.tsdeleted
126                    || (!includingComment
127                        && (current.tokencode == TBaseType.cmtslashstar
128                            || current.tokencode == TBaseType.cmtdoublehyphen));
129            if (!skip) {
130                if (current.tokentype == ETokenType.ttreturn) {
131                    if (ignoreNextNewline && chainWasModified) {
132                        skip = true;
133                    } else {
134                        ignoreNextNewline = true;
135                    }
136                } else if (current.tokentype != ETokenType.ttwhitespace) {
137                    ignoreNextNewline = false;
138                }
139            }
140            if (!skip) tokens.add(current);
141
142            if (current == stopToken) break;
143            current = current.getNextTokenInChain();
144        }
145
146        // Drop the trailing whitespace/comment run left before the alias.
147        // This runs on the filtered list, so solid tokens that were marked
148        // deleted no longer shield a comment from being trimmed.
149        int last = tokens.size() - 1;
150        while (last >= 0) {
151            TSourceToken token = tokens.get(last);
152            if (token.tokentype == ETokenType.ttwhitespace
153                    || token.tokentype == ETokenType.ttreturn
154                    || token.tokencode == TBaseType.cmtslashstar
155                    || token.tokencode == TBaseType.cmtdoublehyphen) {
156                last--;
157            } else {
158                break;
159            }
160        }
161        StringBuilder sb = new StringBuilder();
162        for (int i = 0; i <= last; i++) {
163            sb.append(tokens.get(i).toString());
164        }
165        return sb.toString();
166    }
167
168}