001package gudusoft.gsqlparser.stmt; 002 003import gudusoft.gsqlparser.EDbVendor; 004import gudusoft.gsqlparser.ESqlStatementType; 005import gudusoft.gsqlparser.TCustomSqlStatement; 006import gudusoft.gsqlparser.TGSqlParser; 007import gudusoft.gsqlparser.TSourceToken; 008import gudusoft.gsqlparser.nodes.TObjectName; 009import gudusoft.gsqlparser.nodes.TParseTreeVisitor; 010 011/** 012 * Vertica CREATE PROJECTION statement. 013 * <p> 014 * Syntax: 015 * <pre> 016 * CREATE PROJECTION [IF NOT EXISTS] [schema.]projection_name (column_list) 017 * AS SELECT ... 018 * [ORDER BY ...] 019 * [SEGMENTED BY expression ALL NODES [KSAFE [k]]] 020 * [UNSEGMENTED ALL NODES] 021 * </pre> 022 * 023 * @since 4.0.0.7 024 */ 025public class TCreateProjectionStmt extends TCustomSqlStatement { 026 027 private TObjectName projectionName = null; 028 private TSelectSqlStatement subquery = null; 029 030 public TCreateProjectionStmt(EDbVendor dbvendor) { 031 super(dbvendor); 032 sqlstatementtype = ESqlStatementType.sstCreateProjection; 033 } 034 035 /** 036 * @return the projection name (may include schema prefix) 037 */ 038 public TObjectName getProjectionName() { 039 return projectionName; 040 } 041 042 /** 043 * @return the AS SELECT subquery, or null if not present 044 */ 045 public TSelectSqlStatement getSubquery() { 046 return subquery; 047 } 048 049 /** 050 * Skip grammar-based syntax checking since there's no grammar rule 051 * for CREATE PROJECTION. We do token-based parsing instead. 052 */ 053 protected int dochecksyntax(TCustomSqlStatement psql) { 054 isparsed = true; 055 return 0; 056 } 057 058 /** 059 * Parse the CREATE PROJECTION statement from tokens. 060 * <p> 061 * Since there's no grammar-produced AST node for CREATE PROJECTION, 062 * this method extracts key information from the token stream: 063 * 1. Projection name (with optional schema prefix) 064 * 2. The AS SELECT subquery (re-parsed to expose tables and columns) 065 */ 066 public int doParseStatement(TCustomSqlStatement psql) { 067 if (sourcetokenlist == null || sourcetokenlist.size() == 0) { 068 return -1; 069 } 070 071 // Find projection name after CREATE PROJECTION keywords 072 int projectionIndex = -1; 073 for (int i = 0; i < sourcetokenlist.size() - 1; i++) { 074 TSourceToken token = sourcetokenlist.get(i); 075 if (token.toString().equalsIgnoreCase("PROJECTION")) { 076 projectionIndex = i; 077 break; 078 } 079 } 080 081 if (projectionIndex >= 0) { 082 // Find the next solid token after PROJECTION keyword (schema or name) 083 TSourceToken firstToken = sourcetokenlist.nextsolidtoken(projectionIndex, 1, false); 084 085 // Skip optional IF NOT EXISTS 086 if (firstToken != null && firstToken.toString().equalsIgnoreCase("IF")) { 087 TSourceToken notToken = sourcetokenlist.nextsolidtoken(firstToken.posinlist, 1, false); 088 if (notToken != null && notToken.toString().equalsIgnoreCase("NOT")) { 089 TSourceToken existsToken = sourcetokenlist.nextsolidtoken(notToken.posinlist, 1, false); 090 if (existsToken != null && existsToken.toString().equalsIgnoreCase("EXISTS")) { 091 firstToken = sourcetokenlist.nextsolidtoken(existsToken.posinlist, 1, false); 092 } 093 } 094 } 095 096 if (firstToken != null) { 097 // Check if next solid token is a dot (schema.name format) 098 TSourceToken afterFirst = sourcetokenlist.nextsolidtoken(firstToken.posinlist, 1, false); 099 if (afterFirst != null && afterFirst.toString().equals(".")) { 100 // schema.name format 101 TSourceToken nameToken = sourcetokenlist.nextsolidtoken(afterFirst.posinlist, 1, false); 102 if (nameToken != null) { 103 projectionName = new TObjectName(); 104 projectionName.setSchemaToken(firstToken); 105 projectionName.setObjectToken(nameToken); 106 } 107 } else { 108 // Simple name format 109 projectionName = new TObjectName(); 110 projectionName.setObjectToken(firstToken); 111 } 112 } 113 } 114 115 // Find the AS keyword followed by SELECT to extract the subquery 116 parseSubquery(); 117 118 return 0; 119 } 120 121 /** 122 * Find and parse the AS SELECT subquery from the token stream. 123 * <p> 124 * Extracts the SQL text from SELECT up to (but not including) 125 * SEGMENTED BY or UNSEGMENTED ALL NODES clauses, which are 126 * projection-specific and not part of the SELECT. 127 */ 128 private void parseSubquery() { 129 // Find the AS keyword 130 int asIndex = -1; 131 for (int i = 0; i < sourcetokenlist.size() - 1; i++) { 132 TSourceToken token = sourcetokenlist.get(i); 133 if (token.toString().equalsIgnoreCase("AS")) { 134 // Verify next solid token is SELECT or WITH 135 TSourceToken nextSolid = sourcetokenlist.nextsolidtoken(i, 1, false); 136 if (nextSolid != null && 137 (nextSolid.toString().equalsIgnoreCase("SELECT") || 138 nextSolid.toString().equalsIgnoreCase("WITH"))) { 139 asIndex = i; 140 break; 141 } 142 } 143 } 144 145 if (asIndex < 0) { 146 return; 147 } 148 149 // Find the SELECT/WITH token position 150 TSourceToken selectToken = sourcetokenlist.nextsolidtoken(asIndex, 1, false); 151 if (selectToken == null) { 152 return; 153 } 154 155 // Find the end of the SELECT portion. 156 // SEGMENTED BY and UNSEGMENTED ALL NODES are projection-specific clauses 157 // that come after the SELECT and should not be included. 158 int endIndex = sourcetokenlist.size() - 1; 159 for (int i = selectToken.posinlist; i < sourcetokenlist.size(); i++) { 160 TSourceToken token = sourcetokenlist.get(i); 161 String tokenStr = token.toString().toUpperCase(); 162 if (tokenStr.equals("SEGMENTED") || tokenStr.equals("UNSEGMENTED")) { 163 // Find the last solid token before this 164 endIndex = i - 1; 165 break; 166 } 167 } 168 169 // Build the SELECT SQL text from tokens 170 StringBuilder selectSql = new StringBuilder(); 171 for (int i = selectToken.posinlist; i <= endIndex; i++) { 172 selectSql.append(sourcetokenlist.get(i).toString()); 173 } 174 175 String selectText = selectSql.toString().trim(); 176 // Remove trailing semicolon if present 177 if (selectText.endsWith(";")) { 178 selectText = selectText.substring(0, selectText.length() - 1).trim(); 179 } 180 181 if (selectText.isEmpty()) { 182 return; 183 } 184 185 // Re-parse the SELECT portion to get a proper AST with tables 186 TGSqlParser subParser = new TGSqlParser(this.dbvendor); 187 subParser.sqltext = selectText; 188 int result = subParser.parse(); 189 if (result == 0 && subParser.sqlstatements.size() > 0 190 && subParser.sqlstatements.get(0) instanceof TSelectSqlStatement) { 191 subquery = (TSelectSqlStatement) subParser.sqlstatements.get(0); 192 subquery.setParentStmt(this); 193 } 194 } 195 196 public void accept(TParseTreeVisitor v) { 197 v.preVisit(this); 198 v.postVisit(this); 199 } 200 201 public void acceptChildren(TParseTreeVisitor v) { 202 v.preVisit(this); 203 if (subquery != null) { 204 subquery.acceptChildren(v); 205 } 206 v.postVisit(this); 207 } 208}