001package gudusoft.gsqlparser.parser; 002 003import gudusoft.gsqlparser.EDbObjectType; 004import gudusoft.gsqlparser.EDbVendor; 005import gudusoft.gsqlparser.ETokenType; 006import gudusoft.gsqlparser.TBaseType; 007import gudusoft.gsqlparser.TCustomSqlStatement; 008import gudusoft.gsqlparser.TSourceToken; 009import gudusoft.gsqlparser.TSourceTokenList; 010import gudusoft.gsqlparser.nodes.TObjectName; 011import gudusoft.gsqlparser.stmt.TCreateTableSqlStatement; 012 013/** 014 * The location of an Athena CREATE TABLE whose Hive clauses the grammar cannot read. 015 * 016 * <p>Athena creates tables with Hive DDL, as in {@code CREATE EXTERNAL TABLE events (...) PARTITIONED BY (...) 017 * STORED AS PARQUET LOCATION 's3://bucket/events/'}. The Athena grammar reads the column list but none of the 018 * clauses after it, so the general create-table error recovery ignores those clauses and parses the statement 019 * again. {@code LOCATION} names the files of the table, which lineage shows as the path the table comes from, as it 020 * does for Hive. This recovery reads it before the clauses are ignored and gives it to the parsed statement, so 021 * that {@link TCreateTableSqlStatement#getTableLocation()} returns it as it does for Hive.</p> 022 * 023 * <p>Used by both parse paths, {@code TGSqlParser} and the vendor {@code SqlParser}, so that they recover in the 024 * same way. Not intended for use outside the parser.</p> 025 */ 026public final class CreateTableLocationRecovery { 027 028 private CreateTableLocationRecovery() { 029 } 030 031 /** 032 * The string of the LOCATION clause after the column list of a CREATE TABLE: {@code 's3://bucket/events/'} in 033 * {@code CREATE EXTERNAL TABLE events (...) STORED AS PARQUET LOCATION 's3://bucket/events/'}. Only a clause at 034 * parenthesis depth zero counts, so a string in a property list, such as 035 * {@code TBLPROPERTIES ('location' = '...')}, is not one. Read the tokens before the recovery ignores them. 036 * 037 * @param tokens the tokens of one CREATE TABLE statement 038 * @param locationKeyword the lexer's token code of the keyword LOCATION, 0 when the lexer has none 039 * @return the string token after LOCATION, or null when there is no such clause 040 */ 041 public static TSourceToken locationAfterColumnList(TSourceTokenList tokens, int locationKeyword) { 042 if (tokens == null || locationKeyword == 0) { 043 return null; 044 } 045 int nested = 0; 046 boolean afterColumnList = false; 047 boolean afterKeyword = false; 048 for (int k = 0; k < tokens.size(); k++) { 049 TSourceToken st = tokens.get(k); 050 if (!st.issolidtoken()) { 051 continue; 052 } 053 if (afterKeyword) { 054 return st.tokentype == ETokenType.ttsqstring ? st : null; 055 } 056 if (afterColumnList && nested == 0 && isLocationKeyword(st, locationKeyword)) { 057 afterKeyword = true; 058 } else if (st.tokencode == (int) '(' || st.tokencode == TBaseType.left_parenthesis_2) { 059 nested++; 060 } else if (st.tokencode == (int) ')') { 061 nested--; 062 if (nested == 0) { 063 afterColumnList = true; 064 } 065 } 066 } 067 return null; 068 } 069 070 /** 071 * The keyword LOCATION. When it is the token where the syntax error is, as in 072 * {@code CREATE EXTERNAL TABLE events (...) LOCATION 's3://bucket/events/'}, the parser has tried it again as an 073 * identifier: its token code is then {@link TBaseType#ident}, while its token type is still a keyword. 074 */ 075 private static boolean isLocationKeyword(TSourceToken st, int locationKeyword) { 076 if (st.tokencode == locationKeyword) { 077 return true; 078 } 079 return st.tokencode == TBaseType.ident && st.tokentype == ETokenType.ttkeyword 080 && "LOCATION".equalsIgnoreCase(st.toString()); // non-identifier-compare: the keyword LOCATION 081 } 082 083 /** 084 * Gives the location found by {@link #locationAfterColumnList} to the statement that the recovery parsed again. 085 * Nothing happens when {@code location} is null or the statement is not a CREATE TABLE. 086 */ 087 public static void setLocation(TCustomSqlStatement stmt, TSourceToken location, EDbVendor vendor) { 088 if (location != null && stmt instanceof TCreateTableSqlStatement) { 089 ((TCreateTableSqlStatement) stmt).setTableLocation( 090 TObjectName.createObjectName(vendor, EDbObjectType.unknown, location)); 091 } 092 } 093}