001package gudusoft.gsqlparser.parser;
002
003import gudusoft.gsqlparser.EDbVendor;
004import gudusoft.gsqlparser.EOBTenantMode;
005import gudusoft.gsqlparser.IMetaDatabase;
006import gudusoft.gsqlparser.ISQLStatementHandle;
007import gudusoft.gsqlparser.ITokenHandle;
008import gudusoft.gsqlparser.ITokenListHandle;
009import gudusoft.gsqlparser.compiler.TFrame;
010import gudusoft.gsqlparser.sqlenv.TSQLEnv;
011import gudusoft.gsqlparser.stmt.teradata.utilities.TeradataUtilityType;
012
013import java.io.InputStream;
014import java.util.Stack;
015
016/**
017 * Immutable context carrying all parser inputs and settings.
018 *
019 * <p>This value object encapsulates all inputs required for SQL parsing,
020 * providing clean separation between input configuration and parser implementation.
021 * Once built, a ParserContext instance cannot be modified (immutable).
022 *
023 * <p><b>Design Pattern:</b> Value Object + Builder
024 * <ul>
025 *   <li>Immutable: Thread-safe, no defensive copying needed</li>
026 *   <li>Builder: Flexible construction with optional parameters</li>
027 *   <li>Clean boundaries: Input/Output separation</li>
028 * </ul>
029 *
030 * <p><b>Usage Example:</b>
031 * <pre>
032 * ParserContext context = new ParserContext.Builder(EDbVendor.dbvoracle)
033 *     .sqlText("SELECT * FROM employees WHERE salary > 50000")
034 *     .enablePartialParsing(true)
035 *     .metaDatabase(myMetaDatabase)
036 *     .build();
037 * </pre>
038 *
039 * @see SqlParser
040 * @see SqlParseResult
041 * @since 3.2.0.0
042 */
043public final class ParserContext {
044
045    // ========== Input Source ==========
046    private final EDbVendor vendor;
047    private final String sqlText;
048    private final String sqlFilename;
049    private final InputStream sqlInputStream;
050    private final String sqlCharset;
051
052    // ========== Parser Options ==========
053    private final boolean enablePartialParsing;
054    private final boolean singlePLBlock;
055    private final boolean onlyNeedRawParseTree;
056    private final boolean enableMssqlColonBindVariables;
057    private final TeradataUtilityType teradataUtilityType;
058
059    // ========== OceanBase-Specific Options ==========
060    /**
061     * Tenant compatibility mode for OceanBase. Mirrors the value set on
062     * {@link gudusoft.gsqlparser.TGSqlParser#setOBTenantMode}. Always
063     * non-null; defaults to {@link EOBTenantMode#MYSQL} for non-OceanBase
064     * vendors and for OceanBase parsers that did not explicitly set a mode.
065     * Read by {@code OceanBaseSqlParser} to choose between MySQL and Oracle
066     * delegate parsers in Phase 1 and between forked grammar instances in
067     * Phase 2/3.
068     */
069    private final EOBTenantMode oceanBaseTenantMode;
070
071    // ========== Callbacks and Hooks ==========
072    private final ISQLStatementHandle sqlStatementHandle;
073    private final ITokenHandle tokenHandle;
074    private final ITokenListHandle tokenListHandle;
075
076    // ========== Metadata and Environment ==========
077    private final IMetaDatabase metaDatabase;
078    private final TSQLEnv sqlEnv;
079
080    // ========== Diagnostic Settings ==========
081    private final boolean dumpResolverLog;
082    private final boolean enableTimeLogging;
083
084    // ========== State (for special cases) ==========
085    private final Stack<TFrame> frameStack;
086
087    // ========== Parser Reference (for backfilling) ==========
088    /**
089     * Reference to TGSqlParser facade for backfilling metadata.
090     * This allows parsed statements to access parser's vendor and other properties.
091     */
092    private final Object gsqlparser;  // Object type to avoid circular dependency
093
094    /**
095     * Private constructor - use Builder to create instances.
096     */
097    private ParserContext(Builder builder) {
098        // Input source
099        this.vendor = builder.vendor;
100        this.sqlText = builder.sqlText;
101        this.sqlFilename = builder.sqlFilename;
102        this.sqlInputStream = builder.sqlInputStream;
103        this.sqlCharset = builder.sqlCharset;
104
105        // Parser options
106        this.enablePartialParsing = builder.enablePartialParsing;
107        this.singlePLBlock = builder.singlePLBlock;
108        this.onlyNeedRawParseTree = builder.onlyNeedRawParseTree;
109        this.enableMssqlColonBindVariables = builder.enableMssqlColonBindVariables;
110        this.teradataUtilityType = builder.teradataUtilityType;
111        this.oceanBaseTenantMode = builder.oceanBaseTenantMode != null
112                ? builder.oceanBaseTenantMode : EOBTenantMode.MYSQL;
113
114        // Callbacks
115        this.sqlStatementHandle = builder.sqlStatementHandle;
116        this.tokenHandle = builder.tokenHandle;
117        this.tokenListHandle = builder.tokenListHandle;
118
119        // Metadata
120        this.metaDatabase = builder.metaDatabase;
121        this.sqlEnv = builder.sqlEnv;
122
123        // Diagnostics
124        this.dumpResolverLog = builder.dumpResolverLog;
125        this.enableTimeLogging = builder.enableTimeLogging;
126
127        // State
128        this.frameStack = builder.frameStack;
129
130        // Parser reference
131        this.gsqlparser = builder.gsqlparser;
132    }
133
134    // ========== Getters (Read-only access) ==========
135
136    public EDbVendor getVendor() {
137        return vendor;
138    }
139
140    public String getSqlText() {
141        return sqlText;
142    }
143
144    public String getSqlFilename() {
145        return sqlFilename;
146    }
147
148    public InputStream getSqlInputStream() {
149        return sqlInputStream;
150    }
151
152    public String getSqlCharset() {
153        return sqlCharset;
154    }
155
156    public boolean isEnablePartialParsing() {
157        return enablePartialParsing;
158    }
159
160    public boolean isSinglePLBlock() {
161        return singlePLBlock;
162    }
163
164    public boolean isOnlyNeedRawParseTree() {
165        return onlyNeedRawParseTree;
166    }
167
168    /**
169     * Whether the SQL Server compatibility extension for colon-prefixed bind
170     * variables is enabled.
171     *
172     * @return {@code true} when {@code :name}, {@code :1}, and {@code :@name}
173     *         are recognized as bind variables; defaults to {@code false}
174     */
175    public boolean isMssqlColonBindVariablesEnabled() {
176        return enableMssqlColonBindVariables;
177    }
178
179    public TeradataUtilityType getTeradataUtilityType() {
180        return teradataUtilityType;
181    }
182
183    /**
184     * Get the OceanBase tenant compatibility mode mirrored from the owning
185     * {@link gudusoft.gsqlparser.TGSqlParser}. Always non-null. Meaningful
186     * only when {@link #getVendor()} is {@code dbvoceanbase}; otherwise
187     * defaults to {@link EOBTenantMode#MYSQL} and is ignored by other
188     * vendor parsers.
189     *
190     * @return the OceanBase tenant mode (never null)
191     * @since 4.0.1.4
192     */
193    public EOBTenantMode getOceanBaseTenantMode() {
194        return oceanBaseTenantMode;
195    }
196
197    public ISQLStatementHandle getSqlStatementHandle() {
198        return sqlStatementHandle;
199    }
200
201    public ITokenHandle getTokenHandle() {
202        return tokenHandle;
203    }
204
205    public ITokenListHandle getTokenListHandle() {
206        return tokenListHandle;
207    }
208
209    public IMetaDatabase getMetaDatabase() {
210        return metaDatabase;
211    }
212
213    public TSQLEnv getSqlEnv() {
214        return sqlEnv;
215    }
216
217    public boolean isDumpResolverLog() {
218        return dumpResolverLog;
219    }
220
221    public boolean isEnableTimeLogging() {
222        return enableTimeLogging;
223    }
224
225    public Stack<TFrame> getFrameStack() {
226        return frameStack;
227    }
228
229    /**
230     * Get reference to TGSqlParser facade for backfilling statement metadata.
231     * @return TGSqlParser instance or null if not set
232     */
233    public Object getGsqlparser() {
234        return gsqlparser;
235    }
236
237    /**
238     * Builder for constructing ParserContext instances.
239     *
240     * <p>The Builder pattern provides:
241     * <ul>
242     *   <li>Flexible construction with optional parameters</li>
243     *   <li>Readable, fluent API</li>
244     *   <li>Default values for optional parameters</li>
245     *   <li>Immutability of final ParserContext object</li>
246     * </ul>
247     *
248     * <p><b>Example:</b>
249     * <pre>
250     * ParserContext ctx = new ParserContext.Builder(EDbVendor.dbvoracle)
251     *     .sqlText("SELECT * FROM dual")
252     *     .enablePartialParsing(true)
253     *     .metaDatabase(myMetaDb)
254     *     .build();
255     * </pre>
256     */
257    public static class Builder {
258        // Required parameters
259        private final EDbVendor vendor;
260
261        // Optional parameters with defaults
262        private String sqlText = null;
263        private String sqlFilename = "";
264        private InputStream sqlInputStream = null;
265        private String sqlCharset = null;
266
267        private boolean enablePartialParsing = true;
268        private boolean singlePLBlock = false;
269        private boolean onlyNeedRawParseTree = false;
270        private boolean enableMssqlColonBindVariables = false;
271        private TeradataUtilityType teradataUtilityType = null;
272        private EOBTenantMode oceanBaseTenantMode = EOBTenantMode.MYSQL;
273
274        private ISQLStatementHandle sqlStatementHandle = null;
275        private ITokenHandle tokenHandle = null;
276        private ITokenListHandle tokenListHandle = null;
277
278        private IMetaDatabase metaDatabase = null;
279        private TSQLEnv sqlEnv = null;
280
281        private boolean dumpResolverLog = false;
282        private boolean enableTimeLogging = false;
283
284        private Stack<TFrame> frameStack = null;
285
286        private Object gsqlparser = null;
287
288        /**
289         * Create a builder with required vendor parameter.
290         *
291         * @param vendor the database vendor (required)
292         */
293        public Builder(EDbVendor vendor) {
294            if (vendor == null) {
295                throw new IllegalArgumentException("vendor cannot be null");
296            }
297            this.vendor = vendor;
298        }
299
300        /**
301         * Set SQL text to parse.
302         *
303         * @param sqlText the SQL text
304         * @return this builder for method chaining
305         */
306        public Builder sqlText(String sqlText) {
307            this.sqlText = sqlText != null ? sqlText : "";
308            return this;
309        }
310
311        /**
312         * Set SQL filename to read from.
313         *
314         * @param sqlFilename the SQL filename
315         * @return this builder for method chaining
316         */
317        public Builder sqlFilename(String sqlFilename) {
318            this.sqlFilename = sqlFilename != null ? sqlFilename : "";
319            return this;
320        }
321
322        /**
323         * Set SQL input stream to read from.
324         *
325         * @param sqlInputStream the SQL input stream
326         * @return this builder for method chaining
327         */
328        public Builder sqlInputStream(InputStream sqlInputStream) {
329            this.sqlInputStream = sqlInputStream;
330            return this;
331        }
332
333        /**
334         * Set SQL character set for file reading.
335         *
336         * @param sqlCharset the character set name
337         * @return this builder for method chaining
338         */
339        public Builder sqlCharset(String sqlCharset) {
340            this.sqlCharset = sqlCharset;
341            return this;
342        }
343
344        /**
345         * Enable/disable partial parsing.
346         *
347         * @param enablePartialParsing true to enable partial parsing
348         * @return this builder for method chaining
349         */
350        public Builder enablePartialParsing(boolean enablePartialParsing) {
351            this.enablePartialParsing = enablePartialParsing;
352            return this;
353        }
354
355        /**
356         * Set single PL block mode.
357         *
358         * @param singlePLBlock true for single PL block
359         * @return this builder for method chaining
360         */
361        public Builder singlePLBlock(boolean singlePLBlock) {
362            this.singlePLBlock = singlePLBlock;
363            return this;
364        }
365
366        /**
367         * Set if only raw parse tree is needed.
368         *
369         * @param onlyNeedRawParseTree true if only raw parse tree needed
370         * @return this builder for method chaining
371         */
372        public Builder onlyNeedRawParseTree(boolean onlyNeedRawParseTree) {
373            this.onlyNeedRawParseTree = onlyNeedRawParseTree;
374            return this;
375        }
376
377        /**
378         * Enable or disable the SQL Server compatibility extension for
379         * colon-prefixed bind variables. The default is {@code false}.
380         *
381         * @param enabled {@code true} to recognize {@code :name}, {@code :1},
382         *                and {@code :@name} as bind variables
383         * @return this builder for method chaining
384         */
385        public Builder enableMssqlColonBindVariables(boolean enabled) {
386            this.enableMssqlColonBindVariables = enabled;
387            return this;
388        }
389
390        /**
391         * Set Teradata utility type.
392         *
393         * @param teradataUtilityType the Teradata utility type
394         * @return this builder for method chaining
395         */
396        public Builder teradataUtilityType(TeradataUtilityType teradataUtilityType) {
397            this.teradataUtilityType = teradataUtilityType;
398            return this;
399        }
400
401        /**
402         * Set the OceanBase tenant compatibility mode. Mirrors the value set
403         * via {@link gudusoft.gsqlparser.TGSqlParser#setOBTenantMode}. Null is
404         * coerced to {@link EOBTenantMode#MYSQL} on build.
405         *
406         * @param oceanBaseTenantMode the OB tenant mode
407         * @return this builder for method chaining
408         * @since 4.0.1.4
409         */
410        public Builder oceanBaseTenantMode(EOBTenantMode oceanBaseTenantMode) {
411            this.oceanBaseTenantMode = oceanBaseTenantMode;
412            return this;
413        }
414
415        /**
416         * Set SQL statement handle callback.
417         *
418         * @param sqlStatementHandle the statement handle callback
419         * @return this builder for method chaining
420         */
421        public Builder sqlStatementHandle(ISQLStatementHandle sqlStatementHandle) {
422            this.sqlStatementHandle = sqlStatementHandle;
423            return this;
424        }
425
426        /**
427         * Set token handle callback.
428         *
429         * @param tokenHandle the token handle callback
430         * @return this builder for method chaining
431         */
432        public Builder tokenHandle(ITokenHandle tokenHandle) {
433            this.tokenHandle = tokenHandle;
434            return this;
435        }
436
437        /**
438         * Set token list handle callback.
439         *
440         * @param tokenListHandle the token list handle callback
441         * @return this builder for method chaining
442         */
443        public Builder tokenListHandle(ITokenListHandle tokenListHandle) {
444            this.tokenListHandle = tokenListHandle;
445            return this;
446        }
447
448        /**
449         * Set metadata database for column/table resolution.
450         *
451         * @param metaDatabase the metadata database
452         * @return this builder for method chaining
453         */
454        public Builder metaDatabase(IMetaDatabase metaDatabase) {
455            this.metaDatabase = metaDatabase;
456            return this;
457        }
458
459        /**
460         * Set SQL environment.
461         *
462         * @param sqlEnv the SQL environment
463         * @return this builder for method chaining
464         */
465        public Builder sqlEnv(TSQLEnv sqlEnv) {
466            this.sqlEnv = sqlEnv;
467            return this;
468        }
469
470        /**
471         * Enable/disable resolver log dumping.
472         *
473         * @param dumpResolverLog true to dump resolver log
474         * @return this builder for method chaining
475         */
476        public Builder dumpResolverLog(boolean dumpResolverLog) {
477            this.dumpResolverLog = dumpResolverLog;
478            return this;
479        }
480
481        /**
482         * Enable/disable time logging.
483         *
484         * @param enableTimeLogging true to enable time logging
485         * @return this builder for method chaining
486         */
487        public Builder enableTimeLogging(boolean enableTimeLogging) {
488            this.enableTimeLogging = enableTimeLogging;
489            return this;
490        }
491
492        /**
493         * Set frame stack for compiler context.
494         *
495         * @param frameStack the frame stack
496         * @return this builder for method chaining
497         */
498        public Builder frameStack(Stack<TFrame> frameStack) {
499            this.frameStack = frameStack;
500            return this;
501        }
502
503        /**
504         * Set reference to TGSqlParser facade for backfilling metadata.
505         * @param gsqlparser TGSqlParser instance
506         * @return this builder
507         */
508        public Builder gsqlparser(Object gsqlparser) {
509            this.gsqlparser = gsqlparser;
510            return this;
511        }
512
513        /**
514         * Build the immutable ParserContext.
515         *
516         * @return a new immutable ParserContext instance
517         */
518        public ParserContext build() {
519            return new ParserContext(this);
520        }
521    }
522
523    @Override
524    public String toString() {
525        return "ParserContext{" +
526                "vendor=" + vendor +
527                ", sqlText=" + (sqlText != null && sqlText.length() > 50 ?
528                    sqlText.substring(0, 50) + "..." : sqlText) +
529                ", sqlFilename='" + sqlFilename + '\'' +
530                ", hasInputStream=" + (sqlInputStream != null) +
531                '}';
532    }
533}