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}