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