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}