001package gudusoft.gsqlparser.parser; 002 003import gudusoft.gsqlparser.TBaseType; 004import gudusoft.gsqlparser.TSourceToken; 005import gudusoft.gsqlparser.TSourceTokenList; 006 007/** 008 * Error recovery for a Doris or StarRocks {@code CREATE TABLE ... AS SELECT} whose table clauses 009 * the grammar cannot read. 010 * 011 * <p>When a CREATE TABLE fails to parse, the general recovery ignores everything after the first 012 * closing parenthesis at depth zero and parses the statement again, so a table whose column list 013 * is followed by an unsupported option still yields its columns. For a CREATE TABLE AS SELECT that 014 * ignores the query too, and with it every lineage edge of the statement, while the statement 015 * reports success. When the first closing parenthesis belongs to an option, as in 016 * {@code DUPLICATE KEY(k1)}, or to the query itself, the result is a query cut short.</p> 017 * 018 * <p>This recovery keeps the header and the query and ignores only the table clauses between 019 * them: {@code CREATE TABLE t DUPLICATE KEY(k1) DISTRIBUTED BY HASH(k1) AS SELECT ...} is parsed as 020 * {@code CREATE TABLE t AS SELECT ...}. When nothing stands between the header and the query, the 021 * error lies in one of them and is kept.</p> 022 * 023 * <p>Used by both parse paths, {@code TGSqlParser} and the vendor {@code SqlParser}, so that they 024 * recover in the same way. Not intended for use outside the parser.</p> 025 */ 026public final class CreateTableAsSelectRecovery { 027 028 private CreateTableAsSelectRecovery() { 029 } 030 031 /** 032 * Position of the AS that starts the query of a CREATE TABLE AS SELECT: the first AS at 033 * parenthesis depth zero that is followed by SELECT, WITH or an opening parenthesis, or -1 when 034 * the statement has no query. A generated column ({@code k2 INT AS k1 + 1}) sits inside the 035 * column list, one level deep, so it is never taken for the query. 036 * 037 * @param tokens the tokens of one CREATE TABLE statement 038 * @return the position of the AS in {@code tokens}, or -1 039 */ 040 public static int queryStart(TSourceTokenList tokens) { 041 int depth = 0; 042 for (int k = 0; k < tokens.size(); k++) { 043 TSourceToken st = tokens.get(k); 044 if (st.tokencode == '(') { 045 depth++; 046 } else if (st.tokencode == ')') { 047 depth--; 048 } else if ((depth == 0) && (st.tokencode == TBaseType.rrw_as)) { 049 int next = nextSolidTokenPos(tokens, k); 050 if (next >= 0) { 051 int code = tokens.get(next).tokencode; 052 if ((code == TBaseType.rrw_select) || (code == TBaseType.rrw_with) || (code == '(')) { 053 return k; 054 } 055 } 056 } 057 } 058 return -1; 059 } 060 061 /** 062 * Ignores the table clauses between the header and the query, so that the grammar reads both 063 * even when it cannot read a clause in between (an OLAP key, a partition or distribution 064 * clause, properties, ...). 065 * 066 * @param tokens the tokens of one CREATE TABLE statement 067 * @param queryStart the position returned by {@link #queryStart} 068 * @return true when a clause was ignored and the statement must be parsed again; false when 069 * nothing stands between the header and the query, or the header was not found, so the 070 * tokens are unchanged and the error is kept 071 */ 072 public static boolean ignoreClausesBeforeQuery(TSourceTokenList tokens, int queryStart) { 073 int headerEnd = headerEnd(tokens, queryStart); 074 if (headerEnd < 0) { 075 return false; 076 } 077 boolean hasClause = false; 078 for (int k = headerEnd + 1; k < queryStart; k++) { 079 if (tokens.get(k).issolidtoken()) { 080 hasClause = true; 081 break; 082 } 083 } 084 if (!hasClause) { 085 return false; 086 } 087 for (int k = headerEnd + 1; k < queryStart; k++) { 088 tokens.get(k).tokencode = TBaseType.sqlpluscmd; 089 } 090 return true; 091 } 092 093 /** 094 * Position of the last token of the header {@code CREATE ... TABLE [IF NOT EXISTS] name}, or of 095 * the closing parenthesis of the column list that follows the name, or -1 when the header is not 096 * found before the query. 097 */ 098 private static int headerEnd(TSourceTokenList tokens, int queryStart) { 099 int table = -1; 100 for (int k = 0; k < queryStart; k++) { 101 if (tokens.get(k).tokencode == TBaseType.rrw_table) { 102 table = k; 103 break; 104 } 105 } 106 if (table < 0) { 107 return -1; 108 } 109 int beforeName = table; 110 int next = nextSolidTokenPos(tokens, table); 111 if ((next >= 0) && (tokens.get(next).tokencode == TBaseType.rrw_if)) { 112 // IF NOT EXISTS: the name follows EXISTS, the second solid token after IF 113 beforeName = nextSolidTokenPos(tokens, nextSolidTokenPos(tokens, next)); 114 if (beforeName < 0) { 115 return -1; 116 } 117 } 118 int nameEnd = tokens.nextObjectNameToken(beforeName, 1, false); 119 if ((nameEnd < 0) || (nameEnd >= queryStart)) { 120 return -1; 121 } 122 int after = nextSolidTokenPos(tokens, nameEnd); 123 if ((after < 0) || (after >= queryStart) || (tokens.get(after).tokencode != '(')) { 124 return nameEnd; 125 } 126 int depth = 0; 127 for (int k = after; k < queryStart; k++) { 128 int code = tokens.get(k).tokencode; 129 if (code == '(') { 130 depth++; 131 } else if ((code == ')') && (--depth == 0)) { 132 return k; 133 } 134 } 135 return -1; 136 } 137 138 private static int nextSolidTokenPos(TSourceTokenList tokens, int pos) { 139 if (pos < 0) { 140 return -1; 141 } 142 for (int k = pos + 1; k < tokens.size(); k++) { 143 if (tokens.get(k).issolidtoken()) { 144 return k; 145 } 146 } 147 return -1; 148 } 149}