001package gudusoft.gsqlparser.parser;
002
003import gudusoft.gsqlparser.*;
004
005import java.util.ArrayList;
006
007/**
008 * Immutable result of a parsing operation.
009 *
010 * <p>This value object encapsulates all outputs produced by SQL parsing,
011 * providing clean separation between parser implementation and result data.
012 * Once built, a SqlParseResult instance cannot be modified (immutable).
013 *
014 * <p><b>Design Pattern:</b> Value Object + Builder
015 * <ul>
016 *   <li>Immutable: Thread-safe, can be safely shared</li>
017 *   <li>Builder: Flexible construction by parser implementations</li>
018 *   <li>Clean boundaries: Input/Output separation</li>
019 * </ul>
020 *
021 * <p><b>Contents:</b>
022 * <ul>
023 *   <li><b>Parse Tree:</b> TStatementList with all parsed statements</li>
024 *   <li><b>Tokens:</b> TSourceTokenList with all source tokens</li>
025 *   <li><b>Errors:</b> Error code, message, and detailed syntax errors</li>
026 *   <li><b>Performance:</b> Timing metrics for each phase</li>
027 *   <li><b>State:</b> Lexer and parser instances (for getFlexer() compatibility)</li>
028 * </ul>
029 *
030 * <p><b>Usage Example:</b>
031 * <pre>
032 * SqlParseResult result = parser.parse(context);
033 *
034 * // Check for errors
035 * if (result.hasErrors()) {
036 *     System.err.println(result.getErrorMessage());
037 *     return;
038 * }
039 *
040 * // Access parsed statements
041 * TStatementList statements = result.getSqlStatements();
042 * for (int i = 0; i < statements.size(); i++) {
043 *     TCustomSqlStatement stmt = statements.get(i);
044 *     // Process statement...
045 * }
046 * </pre>
047 *
048 * @see SqlParser
049 * @see ParserContext
050 * @since 3.2.0.0
051 */
052public final class SqlParseResult {
053
054    // ========== Parse Results ==========
055    private final TSourceTokenList sourceTokenList;
056    private final TStatementList sqlStatements;
057
058    public TSourceToken getLastTokenOfStatementBeenValidated() {
059        return lastTokenOfStatementBeenValidated;
060    }
061
062    private TSourceToken lastTokenOfStatementBeenValidated;
063
064
065    // ========== Error Information ==========
066    private final String errorMessage;
067    private final int errorCode;
068    private final ArrayList<TSyntaxError> syntaxErrors;
069
070    // ========== Performance Metrics ==========
071    private final long tokenizationTimeMs;
072    private final long parsingTimeMs;
073    private final long semanticAnalysisTimeMs;
074    private final long interpreterTimeMs;
075
076    // ========== Vendor-Specific State ==========
077    // These are preserved for backward compatibility with getFlexer()
078    private final TCustomLexer lexer;
079    private final TCustomParser parser;
080    private final TCustomParser secondaryParser;  // For Oracle: fplsqlparser
081
082    /**
083     * Private constructor - use Builder to create instances.
084     */
085    private SqlParseResult(Builder builder) {
086        // Parse results
087        this.sourceTokenList = builder.sourceTokenList;
088        this.sqlStatements = builder.sqlStatements;
089
090        // Make defensive copy of statement list to preserve immutability
091        // This ensures that if the parser reuses its internal statement list,
092        // previous results remain independent and unchanged
093//        if (builder.sqlStatements != null) {
094//            this.sqlStatements = new TStatementList();
095//            for (int i = 0; i < builder.sqlStatements.size(); i++) {
096//                this.sqlStatements.add(builder.sqlStatements.get(i));
097//            }
098//        } else {
099//            this.sqlStatements = null;
100//        }
101
102        this.lastTokenOfStatementBeenValidated = builder.lastTokenOfStatementBeenValidated;
103
104        // Error information
105        this.errorMessage = builder.errorMessage;
106        this.errorCode = builder.errorCode;
107        this.syntaxErrors = builder.syntaxErrors != null ?
108                builder.syntaxErrors : new ArrayList<TSyntaxError>();
109
110        // Performance metrics
111        this.tokenizationTimeMs = builder.tokenizationTimeMs;
112        this.parsingTimeMs = builder.parsingTimeMs;
113        this.semanticAnalysisTimeMs = builder.semanticAnalysisTimeMs;
114        this.interpreterTimeMs = builder.interpreterTimeMs;
115
116        // Vendor state
117        this.lexer = builder.lexer;
118        this.parser = builder.parser;
119        this.secondaryParser = builder.secondaryParser;
120    }
121
122    // ========== Getters (Read-only access) ==========
123
124    /**
125     * Get the list of source tokens.
126     *
127     * @return source token list, or null if tokenization failed
128     */
129    public TSourceTokenList getSourceTokenList() {
130        return sourceTokenList;
131    }
132
133    /**
134     * Get the list of parsed SQL statements.
135     *
136     * @return statement list, or null if parsing failed
137     */
138    public TStatementList getSqlStatements() {
139        return sqlStatements;
140    }
141
142    /**
143     * Get the error message if parsing failed.
144     *
145     * @return error message, or empty string if no error
146     */
147    public String getErrorMessage() {
148        return errorMessage;
149    }
150
151    /**
152     * Get the error code.
153     *
154     * @return 0 if successful, non-zero if error occurred
155     */
156    public int getErrorCode() {
157        return errorCode;
158    }
159
160    /**
161     * Get detailed syntax errors.
162     *
163     * @return list of syntax errors, empty if no errors
164     */
165    public ArrayList<TSyntaxError> getSyntaxErrors() {
166        return syntaxErrors;
167    }
168
169    /**
170     * Get tokenization time in milliseconds.
171     *
172     * @return tokenization time in ms
173     */
174    public long getTokenizationTimeMs() {
175        return tokenizationTimeMs;
176    }
177
178    /**
179     * Get parsing time in milliseconds.
180     *
181     * @return parsing time in ms
182     */
183    public long getParsingTimeMs() {
184        return parsingTimeMs;
185    }
186
187    /**
188     * Get semantic analysis time in milliseconds.
189     *
190     * @return semantic analysis time in ms
191     */
192    public long getSemanticAnalysisTimeMs() {
193        return semanticAnalysisTimeMs;
194    }
195
196    /**
197     * Get interpreter time in milliseconds.
198     *
199     * @return interpreter time in ms
200     */
201    public long getInterpreterTimeMs() {
202        return interpreterTimeMs;
203    }
204
205    /**
206     * Get total time (all phases combined) in milliseconds.
207     *
208     * @return total time in ms
209     */
210    public long getTotalTimeMs() {
211        return tokenizationTimeMs + parsingTimeMs + semanticAnalysisTimeMs + interpreterTimeMs;
212    }
213
214    /**
215     * Get the lexer instance (for backward compatibility).
216     *
217     * @return the lexer instance
218     */
219    public TCustomLexer getLexer() {
220        return lexer;
221    }
222
223    /**
224     * Get the parser instance (for backward compatibility).
225     *
226     * @return the parser instance
227     */
228    public TCustomParser getParser() {
229        return parser;
230    }
231
232    /**
233     * Get the secondary parser instance (for Oracle PL/SQL parser).
234     *
235     * @return the secondary parser instance, or null if not applicable
236     */
237    public TCustomParser getSecondaryParser() {
238        return secondaryParser;
239    }
240
241    /**
242     * Check if parsing resulted in errors.
243     *
244     * @return true if there were errors, false otherwise
245     */
246    public boolean hasErrors() {
247        return errorCode != 0 || (syntaxErrors != null && !syntaxErrors.isEmpty());
248    }
249
250    /**
251     * Check if parsing was successful.
252     *
253     * @return true if no errors, false otherwise
254     */
255    public boolean isSuccessful() {
256        return !hasErrors();
257    }
258
259    /**
260     * Builder for constructing SqlParseResult instances.
261     *
262     * <p>Used by SqlParser implementations to build the result object
263     * as parsing progresses.
264     *
265     * <p><b>Example:</b>
266     * <pre>
267     * SqlParseResult.Builder builder = new SqlParseResult.Builder();
268     * builder.sourceTokenList(tokens);
269     * builder.sqlStatements(statements);
270     * builder.errorCode(0);
271     * builder.tokenizationTimeMs(50);
272     * builder.parsingTimeMs(120);
273     * return builder.build();
274     * </pre>
275     */
276    public static class Builder {
277        // Optional fields with defaults
278        private TSourceTokenList sourceTokenList = null;
279        private TStatementList sqlStatements = null;
280        private TSourceToken lastTokenOfStatementBeenValidated;
281
282        private String errorMessage = "";
283        private int errorCode = 0;
284        private ArrayList<TSyntaxError> syntaxErrors = new ArrayList<>();
285
286        private long tokenizationTimeMs = 0;
287        private long parsingTimeMs = 0;
288        private long semanticAnalysisTimeMs = 0;
289        private long interpreterTimeMs = 0;
290
291        private TCustomLexer lexer = null;
292        private TCustomParser parser = null;
293        private TCustomParser secondaryParser = null;
294
295        /**
296         * Create a builder with default values.
297         */
298        public Builder() {
299            // Default constructor
300        }
301
302        /**
303         * Set source token list.
304         *
305         * @param sourceTokenList the source token list
306         * @return this builder for method chaining
307         */
308        public Builder sourceTokenList(TSourceTokenList sourceTokenList) {
309            this.sourceTokenList = sourceTokenList;
310            return this;
311        }
312
313        /**
314         * Set SQL statements list.
315         *
316         * @param sqlStatements the SQL statements list
317         * @return this builder for method chaining
318         */
319        public Builder sqlStatements(TStatementList sqlStatements) {
320            this.sqlStatements = sqlStatements;
321            return this;
322        }
323
324
325        public Builder lastTokenOfStatementBeenValidated(TSourceToken lastTokenOfStatementBeenValidated) {
326            this.lastTokenOfStatementBeenValidated = lastTokenOfStatementBeenValidated;
327            return this;
328        }
329
330        /**
331         * Set error message.
332         *
333         * @param errorMessage the error message
334         * @return this builder for method chaining
335         */
336        public Builder errorMessage(String errorMessage) {
337            this.errorMessage = errorMessage != null ? errorMessage : "";
338            return this;
339        }
340
341        /**
342         * Set error code.
343         *
344         * @param errorCode the error code (0 = success)
345         * @return this builder for method chaining
346         */
347        public Builder errorCode(int errorCode) {
348            this.errorCode = errorCode;
349            return this;
350        }
351
352        /**
353         * Set syntax errors list.
354         *
355         * @param syntaxErrors the syntax errors list
356         * @return this builder for method chaining
357         */
358        public Builder syntaxErrors(ArrayList<TSyntaxError> syntaxErrors) {
359            this.syntaxErrors = syntaxErrors;
360            return this;
361        }
362
363        /**
364         * Add a single syntax error.
365         *
366         * @param syntaxError the syntax error to add
367         * @return this builder for method chaining
368         */
369        public Builder addSyntaxError(TSyntaxError syntaxError) {
370            if (this.syntaxErrors == null) {
371                this.syntaxErrors = new ArrayList<>();
372            }
373            this.syntaxErrors.add(syntaxError);
374            return this;
375        }
376
377        /**
378         * Set tokenization time in milliseconds.
379         *
380         * @param tokenizationTimeMs tokenization time in ms
381         * @return this builder for method chaining
382         */
383        public Builder tokenizationTimeMs(long tokenizationTimeMs) {
384            this.tokenizationTimeMs = tokenizationTimeMs;
385            return this;
386        }
387
388        /**
389         * Set parsing time in milliseconds.
390         *
391         * @param parsingTimeMs parsing time in ms
392         * @return this builder for method chaining
393         */
394        public Builder parsingTimeMs(long parsingTimeMs) {
395            this.parsingTimeMs = parsingTimeMs;
396            return this;
397        }
398
399        /**
400         * Set semantic analysis time in milliseconds.
401         *
402         * @param semanticAnalysisTimeMs semantic analysis time in ms
403         * @return this builder for method chaining
404         */
405        public Builder semanticAnalysisTimeMs(long semanticAnalysisTimeMs) {
406            this.semanticAnalysisTimeMs = semanticAnalysisTimeMs;
407            return this;
408        }
409
410        /**
411         * Set interpreter time in milliseconds.
412         *
413         * @param interpreterTimeMs interpreter time in ms
414         * @return this builder for method chaining
415         */
416        public Builder interpreterTimeMs(long interpreterTimeMs) {
417            this.interpreterTimeMs = interpreterTimeMs;
418            return this;
419        }
420
421        /**
422         * Set lexer instance (for backward compatibility).
423         *
424         * @param lexer the lexer instance
425         * @return this builder for method chaining
426         */
427        public Builder lexer(TCustomLexer lexer) {
428            this.lexer = lexer;
429            return this;
430        }
431
432        /**
433         * Set parser instance (for backward compatibility).
434         *
435         * @param parser the parser instance
436         * @return this builder for method chaining
437         */
438        public Builder parser(TCustomParser parser) {
439            this.parser = parser;
440            return this;
441        }
442
443        /**
444         * Set secondary parser instance (for Oracle PL/SQL parser).
445         *
446         * @param secondaryParser the secondary parser instance
447         * @return this builder for method chaining
448         */
449        public Builder secondaryParser(TCustomParser secondaryParser) {
450            this.secondaryParser = secondaryParser;
451            return this;
452        }
453
454        /**
455         * Build the immutable SqlParseResult.
456         *
457         * @return a new immutable SqlParseResult instance
458         */
459        public SqlParseResult build() {
460            return new SqlParseResult(this);
461        }
462    }
463
464    @Override
465    public String toString() {
466        return "SqlParseResult{" +
467                "errorCode=" + errorCode +
468                ", statements=" + (sqlStatements != null ? sqlStatements.size() : 0) +
469                ", tokens=" + (sourceTokenList != null ? sourceTokenList.size() : 0) +
470                ", errors=" + (syntaxErrors != null ? syntaxErrors.size() : 0) +
471                ", totalTimeMs=" + getTotalTimeMs() +
472                '}';
473    }
474}