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}