001package gudusoft.gsqlparser.nodes;
002
003import gudusoft.gsqlparser.*;
004import gudusoft.gsqlparser.compiler.TSymbolTableManager;
005import gudusoft.gsqlparser.compiler.TVariable;
006import gudusoft.gsqlparser.sqlenv.TSQLTable;
007import gudusoft.gsqlparser.stmt.TSelectSqlStatement;
008
009import java.util.ArrayList;
010
011
012public class TIntoClause extends TParseTreeNode {
013
014    public void setBulkCollect(boolean bulkCollect) {
015        this.bulkCollect = bulkCollect;
016    }
017
018    public boolean isBulkCollect() {
019
020        return bulkCollect;
021    }
022
023
024    public TObjectName getTableName() {
025        return tableName;
026    }
027
028    private TObjectName tableName;
029
030    private boolean bulkCollect = false;
031    private TSourceToken filename;
032    private TExpressionList exprList = null;
033    private TObjectName intoName = null;
034    private TObjectNameList variableList = null;
035    private ETableKind tableKind = ETableKind.etkBase;
036    private boolean tableKindDetected = false;
037
038    /**
039     * Returns the table kind for SELECT INTO targets (TEMPORARY, TEMP, LOCAL TEMPORARY, etc.).
040     * <p>
041     * This is consistent with {@link gudusoft.gsqlparser.stmt.TCreateTableSqlStatement#getTableKinds()}
042     * but returns a single ETableKind value since SELECT INTO can only specify one kind.
043     * <p>
044     * For SQL Server, table kind is detected from the # or ## prefix on the table name.
045     * For PostgreSQL, Redshift, Greenplum, and Netezza, table kind is detected from
046     * TEMPORARY/TEMP/LOCAL/GLOBAL/UNLOGGED keywords in the INTO clause.
047     *
048     * @return the table kind, or {@link ETableKind#etkBase} if no special kind is specified
049     */
050    public ETableKind getTableKind() {
051        if (!tableKindDetected) {
052            detectTableKindFromTokens();
053        }
054        return tableKind;
055    }
056
057    public void setTableKind(ETableKind tableKind) {
058        this.tableKind = tableKind;
059        this.tableKindDetected = true;
060    }
061
062    public TObjectNameList getVariableList() {
063        return variableList;
064    }
065
066    public void setIntoName(TObjectName intoName) {
067        this.intoName = intoName;
068    }
069
070    public TObjectName getIntoName() {
071        if (intoName != null){
072            return intoName;
073        }else{
074           return exprList.getExpression(0).getObjectOperand();
075        }
076    }
077
078    public TExpressionList getExprList() {
079        return exprList;
080    }
081
082    public TSourceToken getFilename() {
083        return filename;
084    }
085
086    public void init(Object arg1)
087    {
088        if (arg1 instanceof TExpressionList){
089            exprList = (TExpressionList)arg1;
090        }else if (arg1 instanceof TSourceToken){
091            filename = (TSourceToken)arg1;
092        }else if (arg1 instanceof TObjectNameList){
093            variableList = (TObjectNameList)arg1;
094            for(int i=0;i<variableList.size();i++){
095                if (variableList.getObjectName(i).toString().startsWith("#")){
096                    // #temp_table, not a variable
097                    variableList.getObjectName(i).setDbObjectType(EDbObjectType.TEMP_TABLE);
098                }else{
099                    variableList.getObjectName(i).setDbObjectType(EDbObjectType.variable);
100                }
101
102            }
103            intoName = variableList.getObjectName(0);
104        }else if (arg1 instanceof TObjectName){
105            tableName = (TObjectName)arg1;
106        }
107    }
108
109   /**
110    * Detects the table kind (TEMPORARY, TEMP, LOCAL, GLOBAL, UNLOGGED, VOLATILE)
111    * by scanning the tokens between the INTO keyword (startToken) and the table name.
112    * This handles SELECT INTO TEMPORARY TABLE, SELECT INTO LOCAL TEMP TABLE, etc.
113    */
114   private void detectTableKindFromTokens() {
115       tableKindDetected = true;
116       TSourceToken st = getStartToken();
117       if (st == null) return;
118
119       boolean hasLocal = false;
120       boolean hasGlobal = false;
121
122       // Scan tokens after startToken (INTO) looking for table-kind keywords
123       st = st.getNextTokenInChain();
124       TSourceToken endSt = getEndToken();
125       while (st != null && st != endSt) {
126           if (st.tokencode == TBaseType.lexspace || st.tokencode == TBaseType.lexnewline
127                   || st.tokencode == TBaseType.cmtslashstar || st.tokencode == TBaseType.cmtdoublehyphen) {
128               st = st.getNextTokenInChain();
129               continue;
130           }
131           String text = st.toString().toUpperCase(java.util.Locale.ROOT);
132           if (text.equals("LOCAL")) {
133               hasLocal = true;
134           } else if (text.equals("GLOBAL")) {
135               hasGlobal = true;
136           } else if (text.equals("TEMPORARY")) {
137               if (hasLocal) tableKind = ETableKind.etkLocalTemporary;
138               else if (hasGlobal) tableKind = ETableKind.etkGlobalTemporary;
139               else tableKind = ETableKind.etkTemporary;
140               return;
141           } else if (text.equals("TEMP")) {
142               if (hasLocal) tableKind = ETableKind.etkLocalTemp;
143               else if (hasGlobal) tableKind = ETableKind.etkGlobalTemp;
144               else tableKind = ETableKind.etkTemp;
145               return;
146           } else if (text.equals("UNLOGGED")) {
147               tableKind = ETableKind.etkUnlogged;
148               return;
149           } else if (text.equals("VOLATILE")) {
150               if (hasLocal) tableKind = ETableKind.etkLocalVolatile;
151               else if (hasGlobal) tableKind = ETableKind.etkGlobalVolatile;
152               else tableKind = ETableKind.etkVolatile;
153               return;
154           } else if (text.equals("STRICT") || text.equals("TABLE")) {
155               // Skip known non-kind keywords in INTO clause
156           } else {
157               // Reached the table name or unknown token, stop scanning
158               break;
159           }
160           st = st.getNextTokenInChain();
161       }
162   }
163
164   public void doParse(TCustomSqlStatement psql, ESqlClause plocation){
165       // Detect table kind from tokens (TEMPORARY, TEMP, LOCAL, GLOBAL, etc.)
166       if (!tableKindDetected) {
167           detectTableKindFromTokens();
168       }
169
170       if (exprList != null){
171           for(int i=0;i<exprList.size();i++){
172               TExpression expr = exprList.getExpression(i);
173               if (expr.getExpressionType() == EExpressionType.simple_object_name_t){
174                   TObjectName dbObject = expr.getObjectOperand();
175                   if ((psql.dbvendor == EDbVendor.dbvmssql)
176                           ||(psql.dbvendor == EDbVendor.dbvgreenplum)
177                           ||(psql.dbvendor == EDbVendor.dbvpostgresql)
178                           ||(psql.dbvendor == EDbVendor.dbvsybase || psql.dbvendor == EDbVendor.dbvsybasease || psql.dbvendor == EDbVendor.dbvsqlanywhere || psql.dbvendor == EDbVendor.dbvsybaseiq)
179                           ||(psql.dbvendor == EDbVendor.dbvodbc)
180                           ||(psql.dbvendor == EDbVendor.dbvredshift)
181                           ||(psql.dbvendor == EDbVendor.dbvnetezza)
182                           ){  // database support: select .. into table
183                       if (dbObject.toString().startsWith("@")){
184                           // table variable
185                           dbObject.setObjectType(TObjectName.ttobjVariable);
186                       }else {
187                           TVariable symbolVariable =  TSymbolTableManager.searchSymbolVariable(psql.getFrameStack(),dbObject.toString()); //psql.searchSymbolVariable(dbObject.toString());
188                           if (symbolVariable != null){
189                               dbObject.setDbObjectType(EDbObjectType.variable);
190                           }else{
191                               declareTable(psql, dbObject);
192                           }
193                       }
194                   }else { // database support: select .. into variable
195                       if (dbObject.toString().startsWith(":")){
196                           // oracle, INTO :NEW.DI_RESID
197                           expr.doParse(psql, ESqlClause.selectInto);
198                       }else{
199                           if (dbObject.toString().startsWith("#")){
200                                // #temp_table, not a variable
201                               dbObject.setDbObjectType(EDbObjectType.TEMP_TABLE);
202                           }else{
203                               dbObject.setObjectType(TObjectName.ttobjVariable);
204                               dbObject.setDbObjectType(EDbObjectType.variable);
205                           }
206                       }
207                   }
208               }else{
209                   expr.doParse(psql, ESqlClause.selectInto);
210               }
211           }
212       }else if ((variableList != null) && createsTable(psql)){
213           declareTable(psql, variableList.getObjectName(0));
214       }
215    }
216
217    /**
218     * Whether PostgreSQL's {@code SELECT ... INTO name} creates the table {@code name}: it does as a statement of its
219     * own, a branch of a set operation included, and in the body of a LANGUAGE SQL function, which holds SQL; in a
220     * PL/pgSQL body INTO assigns variables (Mantis 4857).
221     */
222    private boolean createsTable(TCustomSqlStatement psql){
223        if ((psql.dbvendor != EDbVendor.dbvpostgresql) || (variableList.size() != 1)) return false;
224        TCustomSqlStatement statement = psql;
225        while ((statement.getParentStmt() instanceof TSelectSqlStatement)
226                && (((TSelectSqlStatement) statement.getParentStmt()).getSetOperatorType() != ESetOperatorType.none)){
227            statement = statement.getParentStmt();
228        }
229        TCustomSqlStatement parent = statement.getParentStmt();
230        if (parent instanceof gudusoft.gsqlparser.stmt.TCreateFunctionStmt) {
231            TConstant language = ((gudusoft.gsqlparser.stmt.TCreateFunctionStmt) parent).getProcedureLanguage();
232            return (language != null) && "sql".equalsIgnoreCase(language.toString()); // non-identifier-compare: language name
233        }
234        return parent == null;
235    }
236
237    /** The table {@code SELECT ... INTO dbObject} creates: one of the statement's tables, and in its catalog. */
238    private void declareTable(TCustomSqlStatement psql, TObjectName dbObject){
239        TTable table = new TTable();
240        dbObject.setObjectType(TObjectName.ttobjTable);
241        if (tableKind != ETableKind.etkBase) {
242            // Table kind detected from TEMPORARY/TEMP/etc. keywords
243            dbObject.setTableKind(tableKind);
244        } else if (dbObject.toString().startsWith("##")){
245            // SQL Server global temporary table (##tablename)
246            dbObject.setTableKind(ETableKind.etkGlobalTemporary);
247        } else if (dbObject.toString().startsWith("#")){
248            // SQL Server local temporary table (#tablename)
249            dbObject.setTableKind(ETableKind.etkTemporary);
250        }
251        table.setTableName(dbObject);
252        table.setTableType(ETableSource.objectname);
253        table.setEffectType(ETableEffectType.tetSelectInto);
254        psql.addToTables(table);
255        if (psql instanceof TSelectSqlStatement){
256            TSelectSqlStatement subQuery = ((TSelectSqlStatement)psql);
257            table.setColumnListInTempTable(subQuery.getResultColumnList());
258
259            TSQLTable sqlTmpTable = null;
260            TGSqlParser parser = psql.getGsqlparser();
261            // A named routine's own temporary SELECT INTO target stays out of the parser's
262            // catalog: resolver2 registers it in the routine's catalog at its position, and
263            // with no resolver the routine's later statements read it from the parse-time
264            // catalog (Mantis 4797). The older resolver reads the parser's catalog.
265            boolean scopedTemporary = (parser != null)
266                    && (parser.getEffectiveResolverType() != EResolverType.RESOLVER)
267                    && gudusoft.gsqlparser.resolver2.metadata.ParseTimeCatalog
268                           .isRoutineTransient(subQuery, dbObject);
269            if (scopedTemporary) table.dbvendor = psql.dbvendor;
270            // The routine's later statements read it at parse time, in every resolver type (Mantis 4797).
271            gudusoft.gsqlparser.resolver2.metadata.ParseTimeCatalog.declareSelectInto(subQuery, dbObject);
272            if (psql.getSqlEnv() != null && !scopedTemporary)  {
273                // Resolver2 collects local SELECT INTO targets at their statement position.
274                // addTable returns null when enableGetMetadataFromDDL is off
275                sqlTmpTable = psql.getSqlEnv().addTable(dbObject.toString(),true);
276            }
277            // PostgreSQL's SELECT INTO t FROM x creates t with no columns
278            if ((sqlTmpTable != null) && (subQuery.getResultColumnList() != null)
279                    && (subQuery.getResultColumnList().size() > 0))  {
280                if (subQuery.getResultColumnList().getResultColumn(0).toString().endsWith("*")){
281                    // select * into #tmp, we need to find real columns in from clause
282                    // if (subQuery.getTables().getTable())
283                    ArrayList<String> columns = subQuery.getColumnsInFromClause();
284                    if (columns.size() > 0){
285                        for(String c:columns){
286                            sqlTmpTable.addColumn(c);
287                        }
288                    }
289                }else{
290                    subQuery.addColumnInSelectListToSQLEnv(sqlTmpTable);
291                }
292            }
293        }
294    }
295
296    //public void addTableToList()
297
298    public void accept(TParseTreeVisitor v){
299        v.preVisit(this);
300        v.postVisit(this);
301
302    }
303
304    public void acceptChildren(TParseTreeVisitor v){
305        v.preVisit(this);
306        v.postVisit(this);
307    }
308
309    public void setExprList(TExpressionList exprList) {
310        this.exprList = exprList;
311    }
312}