001package gudusoft.gsqlparser.stmt.snowflake; 002 003 004import gudusoft.gsqlparser.EDbVendor; 005import gudusoft.gsqlparser.ESqlClause; 006import gudusoft.gsqlparser.ESqlStatementType; 007import gudusoft.gsqlparser.TCustomSqlStatement; 008import gudusoft.gsqlparser.ETableSource; 009import gudusoft.gsqlparser.nodes.TCreateTableOption; 010import gudusoft.gsqlparser.nodes.TObjectName; 011import gudusoft.gsqlparser.nodes.TObjectNameList; 012import gudusoft.gsqlparser.nodes.TParseTreeVisitor; 013import gudusoft.gsqlparser.nodes.TPathSqlNode; 014import gudusoft.gsqlparser.nodes.TStageLocation; 015import gudusoft.gsqlparser.nodes.TTable; 016import gudusoft.gsqlparser.nodes.snowflake.TCopyIntoNode; 017import gudusoft.gsqlparser.nodes.snowflake.TStageReference; 018import gudusoft.gsqlparser.stmt.TSelectSqlStatement; 019 020import java.util.ArrayList; 021 022public class TSnowflakeCopyIntoStmt extends TCustomSqlStatement { 023 024 private ArrayList<String> fileList = new ArrayList<>(); 025 026 027 public ArrayList<String> getFileList() { 028 return fileList; 029 } 030 031 private String fileFormatName = null; 032 private String fileFormatType = null; 033 034 public void setFileFormatName(String fileFormatName) { 035 this.fileFormatName = fileFormatName; 036 } 037 038 public void setFileFormatType(String fileFormatType) { 039 this.fileFormatType = fileFormatType; 040 } 041 042 public String getFileFormatName() { 043 return fileFormatName; 044 } 045 046 public String getFileFormatType() { 047 return fileFormatType; 048 } 049 050 public static int COPY_INTO_TABLE = 0; 051 public static int COPY_INTO_LOCATION = 1; 052 053 private String regex_pattern; 054 055 public void setRegex_pattern(String regex_pattern) { 056 this.regex_pattern = regex_pattern; 057 } 058 059 public String getRegex_pattern() { 060 return regex_pattern; 061 } 062 063 private int copyIntoType = TSnowflakeCopyIntoStmt.COPY_INTO_TABLE; 064 065 public int getCopyIntoType() { 066 return copyIntoType; 067 } 068 069 private TStageLocation stageLocation; 070 071 public TStageLocation getStageLocation() { 072 return stageLocation; 073 } 074 075 private TObjectName tableName; 076 private TSelectSqlStatement subQuery; 077 public TSnowflakeCopyIntoStmt(EDbVendor dbvendor) { 078 super(dbvendor); 079 sqlstatementtype = ESqlStatementType.sstCopyInto; 080 } 081 082 public void setTableName(TObjectName tableName) { 083 this.tableName = tableName; 084 } 085 086 public void setSubQuery(TSelectSqlStatement subQuery) { 087 this.subQuery = subQuery; 088 } 089 090 public TObjectName getTableName() { 091 092 return tableName; 093 } 094 095 public TSelectSqlStatement getSubQuery() { 096 return subQuery; 097 } 098 099 private TObjectName fromSourceLocation; 100 public TObjectName getFromSourceLocation() { 101 return fromSourceLocation; 102 } 103 104 private TObjectNameList tableColumnList; 105 106 public void setTableColumnList(TObjectNameList tableColumnList) { 107 this.tableColumnList = tableColumnList; 108 } 109 110 public TObjectNameList getTableColumnList() { 111 return tableColumnList; 112 } 113 114 115 public int doParseStatement(TCustomSqlStatement psql) { 116 if (rootNode == null) return -1; 117 super.doParseStatement(psql); 118 TCopyIntoNode node = (TCopyIntoNode)(rootNode); 119 tableName = node.getTableName(); 120 this.copyIntoType = node.getCopyIntoType(); 121 this.stageLocation = node.getStageLocation(); 122 123 if (node.getSubquery() != null){ 124 subQuery = new TSelectSqlStatement(this.dbvendor); 125 subQuery.rootNode = node.getSubquery(); 126 subQuery.doParseStatement(this); 127 128 // When the stage location is inside the subquery's FROM clause 129 // (e.g., COPY INTO t FROM (SELECT ... FROM @stage/path)), 130 // propagate it to the COPY INTO statement for API accessibility. 131 if (this.stageLocation == null && subQuery.getTables() != null) { 132 for (int i = 0; i < subQuery.getTables().size(); i++) { 133 TTable table = subQuery.getTables().getTable(i); 134 if (table.getTableType() == ETableSource.stageReference 135 && table.getStageReference() != null) { 136 TStageReference ref = table.getStageReference(); 137 this.stageLocation = new TStageLocation(); 138 this.stageLocation.setStage(true); 139 if (ref.getStageName() != null) { 140 String name = ref.getStageName().toString(); 141 if (name.startsWith("~")) { 142 this.stageLocation.init(TStageLocation.EStageLocationType.internalUser); 143 } else if (name.startsWith("%")) { 144 this.stageLocation.init(TStageLocation.EStageLocationType.internalTable); 145 TObjectName tblName = new TObjectName(); 146 tblName.setString(name.substring(1)); 147 this.stageLocation.setTableName(tblName); 148 } else { 149 this.stageLocation.init(TStageLocation.EStageLocationType.internalNamed, ref.getStageName()); 150 } 151 } 152 if (ref.getStagePath() != null) { 153 this.stageLocation.setPath(ref.getStagePath()); 154 } 155 break; 156 } 157 } 158 } 159 } 160 161 ArrayList<TCreateTableOption> tableOptions = node.tableOptions; 162 if (tableOptions != null){ 163 for(int i=0;i<tableOptions.size();i++){ 164 tableOptions.get(i).doParse(this, ESqlClause.unknown); 165 } 166 } 167 168 fromSourceLocation = node.getFromSourceLocation(); 169 tableColumnList = node.getTableColumnList(); 170 171 // Mantis #4425: when the FROM clause uses a quoted string literal 172 // (e.g. FROM '@db.schema.stage/path' or FROM 's3://bucket/path'), 173 // the grammar stores it as fromSourceLocation rather than as a 174 // structured stage location, leaving stageLocation null. 175 // Parse the literal and populate stageLocation so the public API 176 // is consistent with the unquoted form. 177 if (this.stageLocation == null && fromSourceLocation != null) { 178 populateStageLocationFromLiteral(fromSourceLocation); 179 } 180 181 return 0; 182 } 183 184 private void populateStageLocationFromLiteral(TObjectName source) { 185 String raw = source.toString(); 186 if (raw == null || raw.isEmpty()) return; 187 188 // Strip surrounding single quotes, if any. 189 String content = raw; 190 if (content.length() >= 2 && content.startsWith("'") && content.endsWith("'")) { 191 content = content.substring(1, content.length() - 1); 192 } 193 if (content.isEmpty()) return; 194 195 if (content.startsWith("@")) { 196 populateInternalStageLocation(content); 197 } else if (looksLikeExternalUrl(content)) { 198 TStageLocation loc = new TStageLocation(); 199 loc.init(TStageLocation.EStageLocationType.location); 200 loc.setStage(false); 201 TObjectName extLoc = new TObjectName(); 202 extLoc.setString(raw); 203 loc.setExternalLocation(extLoc); 204 this.stageLocation = loc; 205 } 206 } 207 208 private void populateInternalStageLocation(String content) { 209 // content begins with '@' 210 String afterAt = content.substring(1); 211 212 // Split into name part and path part at the first '/'. 213 String namePart = afterAt; 214 String pathPart = null; 215 int slashIdx = afterAt.indexOf('/'); 216 if (slashIdx >= 0) { 217 namePart = afterAt.substring(0, slashIdx); 218 pathPart = afterAt.substring(slashIdx); 219 } 220 221 TStageLocation loc = new TStageLocation(); 222 loc.setStage(true); 223 224 if (namePart.isEmpty() || namePart.equals("~") || namePart.startsWith("~")) { 225 // @~ or @~/path -> user stage 226 loc.init(TStageLocation.EStageLocationType.internalUser); 227 } else if (namePart.startsWith("%")) { 228 // @%table or @%table/path -> table stage 229 loc.init(TStageLocation.EStageLocationType.internalTable); 230 TObjectName tblName = new TObjectName(); 231 tblName.setString(namePart.substring(1)); 232 loc.setTableName(tblName); 233 } else { 234 // @stage, @schema.stage, or @db.schema.stage (with optional path) 235 TObjectName stageName = new TObjectName(); 236 stageName.setString(namePart); 237 loc.init(TStageLocation.EStageLocationType.internalNamed, stageName); 238 } 239 240 if (pathPart != null && !pathPart.isEmpty()) { 241 TPathSqlNode path = new TPathSqlNode(); 242 path.setString(pathPart); 243 loc.setPath(path); 244 } 245 246 this.stageLocation = loc; 247 } 248 249 private boolean looksLikeExternalUrl(String content) { 250 String lower = content.toLowerCase(); 251 return lower.startsWith("s3://") 252 || lower.startsWith("azure://") 253 || lower.startsWith("gcs://") 254 || lower.startsWith("https://") 255 || lower.startsWith("http://") 256 || lower.startsWith("file://"); 257 } 258 259 public void accept(TParseTreeVisitor v){ 260 v.preVisit(this); 261 v.postVisit(this); 262 } 263 264 public void acceptChildren(TParseTreeVisitor v){ 265 v.preVisit(this); 266 if (this.subQuery != null){ 267 this.subQuery.acceptChildren(v); 268 } 269 v.postVisit(this); 270 } 271 272}