001package gudusoft.gsqlparser.stmt.mysql; 002 003import gudusoft.gsqlparser.EDbObjectType; 004import gudusoft.gsqlparser.EDbVendor; 005import gudusoft.gsqlparser.ESqlStatementType; 006import gudusoft.gsqlparser.ETokenType; 007import gudusoft.gsqlparser.TBaseType; 008import gudusoft.gsqlparser.TCustomSqlStatement; 009import gudusoft.gsqlparser.TSourceToken; 010import gudusoft.gsqlparser.nodes.TObjectName; 011import gudusoft.gsqlparser.nodes.TObjectNameList; 012import gudusoft.gsqlparser.nodes.TParseTreeVisitor; 013import gudusoft.gsqlparser.sqlenv.ESQLDataObjectType; 014import gudusoft.gsqlparser.util.SQLUtil; 015 016import java.util.ArrayList; 017import java.util.HashSet; 018import java.util.List; 019import java.util.Locale; 020import java.util.Set; 021 022/** 023 * MySQL {@code LOAD DATA [LOCAL] INFILE 'file' [REPLACE | IGNORE] INTO TABLE t [PARTITION (...)] [options] 024 * [(col_or_@var, ...)] [SET col = expr, ...]}. 025 * <p> 026 * The grammar keeps the statement as text, so the parts that lineage needs are read from its tokens (Mantis 4963): 027 * the file ({@link #getFileName()}), the table ({@link #getTableName()}), the columns the file's fields are loaded into 028 * ({@link #getColumnList()}, user variables left out), the columns a {@code SET} assigns from that list 029 * ({@link #getSetColumnList()}), and the columns that end up holding file data ({@link #getLoadedColumnList()}). 030 */ 031public class TLoadDataStmt extends TCustomMySQLStmt { 032 033 private TSourceToken fileName; 034 private TObjectName tableName; 035 private TObjectNameList columnList; 036 private TObjectNameList setColumnList; 037 private TObjectNameList loadedColumnList; 038 /** The SET columns whose value reads nothing of the column list, which replaces the field loaded into them. */ 039 private final TObjectNameList overwrittenColumns = new TObjectNameList(); 040 041 public TLoadDataStmt(EDbVendor dbvendor) { 042 super(dbvendor); 043 sqlstatementtype = ESqlStatementType.sstLoadData; 044 } 045 046 /** 047 * @return the string literal after INFILE, as written, or null when it was not found 048 */ 049 public TSourceToken getFileName() { 050 return fileName; 051 } 052 053 /** 054 * @return the table after INTO TABLE, or null when it was not found 055 */ 056 public TObjectName getTableName() { 057 return tableName; 058 } 059 060 /** 061 * @return the columns of the column list, without its user variables; null when the statement has no column list 062 */ 063 public TObjectNameList getColumnList() { 064 return columnList; 065 } 066 067 /** 068 * @return the columns of the SET clause whose value reads a user variable or a column of the column list; empty 069 * when none 070 */ 071 public TObjectNameList getSetColumnList() { 072 return setColumnList; 073 } 074 075 /** 076 * @return the columns whose new value comes from the file: those of the column list that no SET assigns a value 077 * reading nothing of the list, and {@link #getSetColumnList()}; null when the statement has no column list 078 */ 079 public TObjectNameList getLoadedColumnList() { 080 return loadedColumnList; 081 } 082 083 public int doParseStatement(TCustomSqlStatement psql) { 084 if (rootNode == null) return -1; 085 super.doParseStatement(psql); 086 readLoadParts(); 087 return 0; 088 } 089 090 private void readLoadParts() { 091 setColumnList = new TObjectNameList(); 092 List<TSourceToken> tokens = new ArrayList<TSourceToken>(); 093 for (int i = 0; i < sourcetokenlist.size(); i++) { 094 TSourceToken token = sourcetokenlist.get(i); 095 if (!token.isnonsolidtoken()) { 096 tokens.add(token); 097 } 098 } 099 int i = indexOfWord(tokens, 0, "INFILE"); 100 // INFILE takes a string, "/data/x.csv" as well: a double-quoted text is a string there whatever ANSI_QUOTES says 101 if ((i < 0) || (i + 1 >= tokens.size()) || ((tokens.get(i + 1).tokencode != TBaseType.sconst) 102 && (tokens.get(i + 1).tokentype != ETokenType.ttdqstring))) return; 103 fileName = tokens.get(i + 1); 104 i = indexOfWord(tokens, i + 2, "INTO"); 105 if ((i < 0) || !isWord(tokens, i + 1, "TABLE")) return; 106 i += 2; 107 List<TSourceToken> parts = new ArrayList<TSourceToken>(); 108 i = readName(tokens, i, parts); 109 if (parts.isEmpty() || (parts.size() > 2)) return; 110 tableName = parts.size() == 1 ? TObjectName.createObjectName(dbvendor, EDbObjectType.table, parts.get(0)) 111 : TObjectName.createObjectName(dbvendor, EDbObjectType.table, parts.get(0), parts.get(1)); 112 113 Set<String> variables = new HashSet<String>(); 114 while (i < tokens.size()) { 115 TSourceToken token = tokens.get(i); 116 if (isWord(tokens, i, "PARTITION") && isTokenType(tokens, i + 1, ETokenType.ttleftparenthesis)) { 117 i = closingParenthesis(tokens, i + 1) + 1; 118 } else if ((token.tokentype == ETokenType.ttleftparenthesis) && (columnList == null)) { 119 columnList = new TObjectNameList(); 120 i = readColumnList(tokens, i + 1, variables); 121 } else if (isWord(tokens, i, "SET") && !isWord(tokens, i - 1, "CHARACTER")) { 122 readSetClause(tokens, i + 1, variables); 123 break; 124 } else { 125 i++; 126 } 127 } 128 if (columnList != null) { 129 loadedColumnList = new TObjectNameList(); 130 for (TObjectName column : columnList) { 131 if (!contains(overwrittenColumns, column)) { 132 loadedColumnList.addObjectName(column); 133 } 134 } 135 for (TObjectName column : setColumnList) { 136 if (!contains(loadedColumnList, column)) { 137 loadedColumnList.addObjectName(column); 138 } 139 } 140 } 141 } 142 143 private boolean contains(TObjectNameList columns, TObjectName column) { 144 for (TObjectName listed : columns) { 145 if (SQLUtil.sameName(dbvendor, ESQLDataObjectType.dotColumn, listed.toString(), column.toString())) { 146 return true; 147 } 148 } 149 return false; 150 } 151 152 /** 153 * A user variable at i, {@code @v} or quoted {@code @`v`}, {@code @'v'}, {@code @"v"}: its key and the index after 154 * it, or null. MySQL user variable names are not case-sensitive and the quotes only allow any character, so the 155 * key is the unquoted name in upper case; user variables are no database objects. 156 */ 157 private static Object[] readVariable(List<TSourceToken> tokens, int i) { 158 TSourceToken token = tokens.get(i); 159 if (token.tokentype == ETokenType.ttsqlvar && token.toString().startsWith("@")) { 160 return new Object[] {token.toString().substring(1).toUpperCase(Locale.ROOT), i + 1}; 161 } 162 if ((token.tokentype == ETokenType.ttatsign) && (i + 1 < tokens.size())) { 163 String quoted = tokens.get(i + 1).toString(); 164 if (quoted.length() >= 2 && (quoted.charAt(0) == '`' || quoted.charAt(0) == '\'' || quoted.charAt(0) == '"') 165 && quoted.charAt(quoted.length() - 1) == quoted.charAt(0)) { 166 return new Object[] {quoted.substring(1, quoted.length() - 1).toUpperCase(Locale.ROOT), i + 2}; 167 } 168 } 169 return null; 170 } 171 172 /** The names of the list up to its closing parenthesis; a user variable is remembered, not listed. */ 173 private int readColumnList(List<TSourceToken> tokens, int i, Set<String> variables) { 174 while (i < tokens.size()) { 175 TSourceToken token = tokens.get(i); 176 if (token.tokentype == ETokenType.ttrightparenthesis) { 177 return i + 1; 178 } 179 Object[] variable = readVariable(tokens, i); 180 if (variable != null) { 181 variables.add((String) variable[0]); 182 i = (Integer) variable[1]; 183 } else if (isNamePart(token)) { 184 List<TSourceToken> parts = new ArrayList<TSourceToken>(); 185 i = readName(tokens, i, parts); 186 columnList.addObjectName(TObjectName.createObjectName(dbvendor, EDbObjectType.column, parts.get(parts.size() - 1))); 187 } else { 188 i++; 189 } 190 } 191 return i; 192 } 193 194 /** 195 * SET col = expr, ...: a column whose value reads a user variable or a column of the list is loaded from the file; 196 * a listed column assigned a value that reads neither no longer holds its field. 197 */ 198 private void readSetClause(List<TSourceToken> tokens, int i, Set<String> variables) { 199 while (i < tokens.size()) { 200 List<TSourceToken> parts = new ArrayList<TSourceToken>(); 201 i = readName(tokens, i, parts); 202 if (parts.isEmpty() || !isTokenType(tokens, i, ETokenType.ttequals)) return; 203 i++; 204 boolean readsFile = false; 205 int depth = 0; 206 while (i < tokens.size()) { 207 TSourceToken token = tokens.get(i); 208 if (token.tokentype == ETokenType.ttleftparenthesis) { 209 depth++; 210 } else if (token.tokentype == ETokenType.ttrightparenthesis) { 211 depth--; 212 } else if ((depth == 0) && ((token.tokentype == ETokenType.ttcomma) || (token.tokentype == ETokenType.ttsemicolon))) { 213 break; 214 } else { 215 Object[] variable = readVariable(tokens, i); 216 if (variable != null) { 217 readsFile |= variables.contains((String) variable[0]); 218 i = (Integer) variable[1]; 219 continue; 220 } 221 // a name before ( calls a function, NOW(), it reads no column 222 if (isNamePart(token) && (columnList != null) && !isTokenType(tokens, i + 1, ETokenType.ttleftparenthesis) 223 && contains(columnList, TObjectName.createObjectName(dbvendor, EDbObjectType.column, token))) { 224 readsFile = true; 225 } 226 } 227 i++; 228 } 229 TObjectName target = TObjectName.createObjectName(dbvendor, EDbObjectType.column, parts.get(parts.size() - 1)); 230 if (readsFile) { 231 setColumnList.addObjectName(target); 232 } else { 233 overwrittenColumns.addObjectName(target); 234 } 235 if ((i >= tokens.size()) || (tokens.get(i).tokentype != ETokenType.ttcomma)) return; 236 i++; 237 } 238 } 239 240 /** A dotted name starting at i: its parts go to parts, the index after it is returned. */ 241 private static int readName(List<TSourceToken> tokens, int i, List<TSourceToken> parts) { 242 while ((i < tokens.size()) && isNamePart(tokens.get(i))) { 243 parts.add(tokens.get(i)); 244 i++; 245 if (!isTokenType(tokens, i, ETokenType.ttperiod)) break; 246 i++; 247 } 248 return i; 249 } 250 251 /** Where only a name can stand, a keyword is a name too: (id, date) lists a column named date. */ 252 private static boolean isNamePart(TSourceToken token) { 253 return (token.tokencode == TBaseType.ident) || (token.tokentype == ETokenType.ttidentifier) 254 || (token.tokentype == ETokenType.ttdqstring) || (token.tokentype == ETokenType.ttkeyword); 255 } 256 257 private static int closingParenthesis(List<TSourceToken> tokens, int open) { 258 int depth = 0; 259 for (int i = open; i < tokens.size(); i++) { 260 if (tokens.get(i).tokentype == ETokenType.ttleftparenthesis) depth++; 261 if ((tokens.get(i).tokentype == ETokenType.ttrightparenthesis) && (--depth == 0)) return i; 262 } 263 return tokens.size(); 264 } 265 266 private static int indexOfWord(List<TSourceToken> tokens, int from, String word) { 267 for (int i = from; i < tokens.size(); i++) { 268 if (isWord(tokens, i, word)) return i; 269 } 270 return -1; 271 } 272 273 private static boolean isWord(List<TSourceToken> tokens, int i, String word) { 274 return (i >= 0) && (i < tokens.size()) && (tokens.get(i).tokentype == ETokenType.ttkeyword) 275 && tokens.get(i).toString().equalsIgnoreCase(word); // non-identifier-compare: LOAD DATA keyword 276 } 277 278 private static boolean isTokenType(List<TSourceToken> tokens, int i, ETokenType type) { 279 return (i >= 0) && (i < tokens.size()) && (tokens.get(i).tokentype == type); 280 } 281 282 public void accept(TParseTreeVisitor v){ 283 v.preVisit(this); 284 v.postVisit(this); 285 } 286 287 public void acceptChildren(TParseTreeVisitor v){ 288 v.preVisit(this); 289 v.postVisit(this); 290 } 291 292}