001package gudusoft.gsqlparser.nodes;
002
003import gudusoft.gsqlparser.EDbVendor;
004import gudusoft.gsqlparser.ESqlClause;
005import gudusoft.gsqlparser.TBaseType;
006import gudusoft.gsqlparser.TCustomSqlStatement;
007import gudusoft.gsqlparser.TSourceToken;
008import gudusoft.gsqlparser.sqlenv.TSQLEnv;
009
010import java.util.ArrayList;
011import java.util.Collections;
012
013/*
014* Date: 2010-2-3
015* Time: 11:13:19
016*/
017public class TResultColumnList extends TParseTreeNodeList <TResultColumn>{
018
019    private ArrayList<TResultColumn> sortedResultColumns = null;
020
021    private boolean isSorted = false;
022
023    public ArrayList<TResultColumn> getSortedColumns(){
024        if (isSorted) return sortedResultColumns;
025        if (sortedResultColumns == null){
026            sortedResultColumns = new ArrayList<>();
027        }
028        for(int i=0;i<size();i++){
029            TResultColumn rc = getResultColumn(i);
030            sortedResultColumns.add(rc);
031        }
032        Collections.sort(sortedResultColumns);
033        isSorted = true;
034        return sortedResultColumns;
035    }
036
037    public TResultColumnList()
038        {
039        }
040
041
042    public void removeResultColumn(int index){
043            this.removeItem(index);
044    }
045
046    public TParseTreeNode removeItem(int index){
047        TSourceToken st=null ;
048        if ((index<0)||(index>size())) return null;
049
050        if (TParseTreeNode.doubleLinkedTokenListToString){
051           // getResultColumn(index).removeTokens();
052
053        }else {
054            if (size() > 1){
055                if (index != size() - 1){
056                    st = getResultColumn(index).getEndToken().searchToken(",",1);
057                }else{
058                    st = getResultColumn(index).getStartToken().searchToken(",",-1);
059                }
060            }
061            getResultColumn(index).removeAllMyTokensFromTokenList(st);
062        }
063
064
065        return super.removeItem(index);
066    }
067
068    /**
069     * Used to add a result column manually when re-construct a select list
070     * There must exist at least one column in column list in order to this function.
071     * @param ptext
072     */
073    public void addResultColumn(String ptext){
074        if (size() == 0) return;
075
076        TResultColumn column = new TResultColumn();
077        column.setGsqlparser(this.getGsqlparser());
078        column.setString(","+ptext);
079
080        TResultColumn last_column = getResultColumn(size()-1);
081        TSourceToken last_token = last_column.getEndToken();
082
083        column.addAllMyTokensToTokenList(last_token.container, last_token.posinlist + 1);
084
085        for(int i=0;i<last_token.getNodesEndWithThisToken().size();i++){
086            TParseTreeNode node = last_token.getNodesEndWithThisToken().get(i);
087            if (!(node instanceof TResultColumn)){
088                // change all end token of parse tree node except the last result column
089                node.setEndToken(column.getEndToken());
090            }
091        }
092
093    }
094
095    /**
096         * Add a ResultColumn  to the list
097         *
098         * @param resultColumn  The ResultColumn to add to the list
099         */
100
101        public void addResultColumn(TResultColumn resultColumn)
102        {
103                /* Vectors are 0-based, ResultColumns are 1-based */
104                // resultColumn.setVirtualColumnId(size() + 1);
105                addElement(resultColumn);
106        }
107
108    /**
109     * Get a ResultColumn from a column position (0-based) in the list
110     *
111     * @param position  The ResultColumn to get from the list (1-based)
112     *
113     * @return  the column at that position.
114     */
115
116    public TResultColumn getResultColumn(int position)
117    {
118        if (position < size())
119        {
120            return (TResultColumn)elementAt(position);
121        }else{
122        return null;
123        }
124    }
125
126    void addParseTreeNode(Object arg1){
127        addResultColumn((TResultColumn)arg1);
128    }
129
130
131    /**
132     * In teradata, the whole result column can be null, so this result column
133     * should be ingored when get start token in result column list.
134     * @return
135     */
136    public TSourceToken getStartToken() {
137        TSourceToken ret = null;
138        for(int i=0;i<this.size();i++){
139            ret = this.getResultColumn(i).getStartToken();
140            if (ret != null){
141                break;
142            };
143        }
144        return ret;
145    }
146
147    public TSourceToken getEndToken() {
148        TSourceToken ret = null;
149        for(int i = this.size() -1; i >= 0; i--){
150            ret = this.getResultColumn(i).getEndToken();
151            if (ret != null){
152                break;
153            };
154        }
155        return ret;
156    }
157
158    public void doParse(TCustomSqlStatement psql, ESqlClause plocation){
159        for(int i=0;i<size();i++){
160            getResultColumn(i).doParse(psql,plocation);
161        }
162    }
163
164    /**
165     * Find the SELECT-list column alias that an unqualified reference elsewhere in the
166     * same SELECT block binds to — a "lateral column alias" reference such as
167     * Snowflake's {@code SELECT val*2 AS derived_col FROM t WHERE derived_col > 1}.
168     *
169     * <p>This is the SINGLE definition of that binding rule. Both consumers delegate
170     * here so the two paths can never drift apart:</p>
171     * <ul>
172     *   <li><b>publish</b> — Phase 1 {@code TCustomSqlStatement.linkColumnToTable()},
173     *       which sets {@code sourceColumn} and deliberately leaves
174     *       {@code sourceTable} null;</li>
175     *   <li><b>lookup</b> — resolver2
176     *       {@code TSQLResolver2.handleSelectListAliasResolution()}.</li>
177     * </ul>
178     * <p>Fixing one and not the other yields a reference whose {@code sourceColumn}
179     * is the alias while its {@code sourceTable} is a FROM-clause table that has no
180     * such column — a wrong answer with nothing to flag it (MantisBT 4659).</p>
181     *
182     * <p>The rule is vendor-gated twice: the dialect must support lateral column
183     * aliases at all ({@link TBaseType#isSupportLateralColumn}), and it must not
184     * forbid referencing a SELECT-list alias outside ORDER BY
185     * ({@link TSQLEnv#isAliasReferenceForbidden}). Oracle, MySQL, PostgreSQL,
186     * BigQuery and friends forbid it, so for them such a name is NOT an alias
187     * reference and this returns null.</p>
188     *
189     * <p>The reference must also appear textually AFTER the alias it binds to. That
190     * ordering rule is what keeps a column inside the alias's own defining
191     * expression ({@code CAST(id AS DECIMAL) AS id}) bound to the real table column
192     * rather than to the alias being defined.</p>
193     *
194     * @param vendor    the dialect of the enclosing statement
195     * @param reference an unqualified column reference from the same SELECT block
196     * @return the defining {@link TResultColumn}, or {@code null} when this is not a
197     *         lateral column alias reference
198     */
199    public TResultColumn findLateralAliasDefinition(EDbVendor vendor, TObjectName reference){
200        if ((vendor == null) || (reference == null)) return null;
201        if (!TBaseType.isSupportLateralColumn(vendor)) return null;
202        Boolean forbidden = TSQLEnv.isAliasReferenceForbidden.get(vendor);
203        if ((forbidden == null) || forbidden.booleanValue()) return null;
204        if (reference.isQualified()) return null;
205        if (reference.getStartToken() == null) return null;
206
207        for(int i=0;i<size();i++){
208            TResultColumn resultColumn = getResultColumn(i);
209            if (resultColumn == null) continue;
210            if (!resultColumn.isMatchedUsingAlias(reference)) continue;
211            TAliasClause alias = resultColumn.getAliasClause();
212            if ((alias == null) || (alias.getStartToken() == null)) continue;
213            // only a BACKWARD reference binds to the alias
214            if (reference.getStartToken().posinlist > alias.getStartToken().posinlist){
215                return resultColumn;
216            }
217        }
218        return null;
219    }
220
221    public void accept(TParseTreeVisitor v){
222        v.preVisit(this);
223        v.postVisit(this);
224    }
225//
226    public void acceptChildren(TParseTreeVisitor v){
227        v.preVisit(this);
228        for(int i=0;i<this.size();i++){
229            this.getResultColumn(i).acceptChildren(v);
230        }
231        v.postVisit(this);
232    }
233
234}