001package gudusoft.gsqlparser.resolver2;
002
003import gudusoft.gsqlparser.EDbVendor;
004import gudusoft.gsqlparser.TBaseType;
005import gudusoft.gsqlparser.resolver2.binding.BindingDiagnosticCode;
006import gudusoft.gsqlparser.resolver2.binding.BindingDiagnosticSeverity;
007import gudusoft.gsqlparser.resolver2.format.DisplayNameMode;
008import gudusoft.gsqlparser.resolver2.format.DisplayNamePolicy;
009import gudusoft.gsqlparser.resolver2.matcher.DefaultNameMatcher;
010import gudusoft.gsqlparser.resolver2.matcher.INameMatcher;
011import gudusoft.gsqlparser.resolver2.matcher.VendorNameMatcher;
012
013import java.util.EnumMap;
014import java.util.Map;
015
016/**
017 * Configuration for TSQLResolver2.
018 * Controls various aspects of name resolution behavior.
019 */
020public class TSQLResolverConfig {
021
022    /** Name matcher for case sensitivity and matching rules */
023    private INameMatcher nameMatcher = new DefaultNameMatcher();
024
025    /** Database vendor for vendor-specific name matching */
026    private EDbVendor vendor = null;
027
028    /** Whether to enable legacy compatibility mode (sync results to TTable.linkedColumns) */
029    private boolean legacyCompatibilityEnabled = true;
030
031    /**
032     * Minimum confidence threshold for syncing to legacy structures.
033     *
034     * Recommended values:
035     * - 1.0 (default): Only sync definite results (safest, for SQL validation)
036     * - 0.7: Include high-confidence inferences (for data lineage analysis)
037     * - 0.5: Include all inferences (may mislead legacy code, not recommended)
038     *
039     * Use cases:
040     * - Data lineage tools may want 0.7 inference results
041     * - SQL formatters only need 1.0 definite results
042     */
043    private double legacySyncMinConfidence = 1.0;
044
045    /** Maximum iterations for iterative resolution */
046    private int maxIterations = 10;
047
048    /** Minimum progress rate to continue iteration (0.0 - 1.0) */
049    private double minProgressRate = 0.01;  // 1%
050
051    /** Number of stable passes required to declare convergence */
052    private int stablePassesForConvergence = 2;
053
054    /** Whether to collect full candidates for ambiguous columns */
055    private boolean collectFullCandidates = true;
056
057    /** Whether to enable legacy evidence collection (deprecated, use NamespaceEnhancer) */
058    private boolean evidenceCollectionEnabled = false;
059
060    /**
061     * Whether to show datatype information for columns from CREATE TABLE statements.
062     * When enabled, columns from CREATE TABLE will include datatype info in format:
063     * columnName:datatypeName:length or columnName:datatypeName:precision:scale
064     */
065    private boolean showDatatype = false;
066
067    /**
068     * Whether to show CTE (Common Table Expression) tables and their columns in output.
069     * When enabled, CTE tables are included in the tables list and CTE columns
070     * are included in the fields list with "(CTE)" suffix.
071     * Default is false for backward compatibility.
072     */
073    private boolean showCTE = false;
074
075    // ========== DISPLAY NAME Configuration ==========
076    // Controls how identifier names are rendered for output
077
078    /**
079     * Display name mode - controls how identifiers are rendered.
080     * <ul>
081     *   <li>DISPLAY: Strip delimiters, preserve original case (default, recommended for debugging)</li>
082     *   <li>SQL_RENDER: Preserve delimiters for valid SQL regeneration</li>
083     *   <li>CANONICAL: Apply vendor-specific case folding</li>
084     * </ul>
085     */
086    private DisplayNameMode displayNameMode = DisplayNameMode.DISPLAY;
087
088    /**
089     * Display name policy - controls which occurrence to use when same object
090     * appears multiple times with different spellings.
091     * <ul>
092     *   <li>PREFER_DEFINITION_SITE: Use spelling from definition (CTE def, CREATE TABLE, etc.)</li>
093     *   <li>PREFER_FIRST_OCCURRENCE: Use first occurrence in SQL text</li>
094     *   <li>PREFER_METADATA: Use spelling from database metadata</li>
095     * </ul>
096     */
097    private DisplayNamePolicy displayNamePolicy = DisplayNamePolicy.PREFER_DEFINITION_SITE;
098
099    /**
100     * Whether to strip delimiters (quotes, backticks, brackets) from identifier display.
101     * Only applies when displayNameMode is DISPLAY.
102     * Default is true.
103     */
104    private boolean stripDelimitersForDisplay = true;
105
106    // ========== GUESS_COLUMN_STRATEGY Configuration ==========
107    // Strategy for handling ambiguous columns (when a column could belong to multiple tables)
108
109    /** Pick the first candidate table (nearest in FROM clause order) */
110    public static final int GUESS_COLUMN_STRATEGY_NEAREST = TBaseType.GUESS_COLUMN_STRATEGY_NEAREST;
111
112    /** Pick the last candidate table (farthest in FROM clause order) */
113    public static final int GUESS_COLUMN_STRATEGY_FARTHEST = TBaseType.GUESS_COLUMN_STRATEGY_FARTHEST;
114
115    /** Do not pick any candidate, leave as unresolved/ambiguous */
116    public static final int GUESS_COLUMN_STRATEGY_NOT_PICKUP = TBaseType.GUESS_COLUMN_STRATEGY_NOT_PICKUP;
117
118    /** Human-readable names for strategy values */
119    public static final String[] GUESS_COLUMN_STRATEGY_NAMES = TBaseType.GUESS_COLUMN_STRATEGY_MSG;
120
121    /**
122     * Strategy for handling ambiguous columns.
123     * Default: reads from TBaseType.GUESS_COLUMN_STRATEGY for backward compatibility.
124     * Can be overridden per-config instance.
125     */
126    private Integer guessColumnStrategy = null; // null means use TBaseType default
127
128    // ========== CONFIDENCE THRESHOLD Configuration ==========
129    // Controls when resolutions are considered "definite" vs "inferred" and when guessing is allowed
130
131    /**
132     * Minimum confidence threshold for a resolution to be considered "definite".
133     *
134     * <p>Resolutions with confidence >= this threshold are treated as having
135     * strong evidence (e.g., DDL metadata, qualified references). Resolutions
136     * below this threshold are considered "inferred" or "uncertain".</p>
137     *
138     * <p>Recommended values:</p>
139     * <ul>
140     *   <li>0.9 (default): High bar - only high-confidence resolutions are definite</li>
141     *   <li>0.7: Include more inferred resolutions as definite</li>
142     *   <li>1.0: Only metadata-backed resolutions are definite</li>
143     * </ul>
144     */
145    private double minDefiniteConfidence = 0.9;
146
147    /**
148     * Minimum confidence threshold to allow guessing from ambiguous candidates.
149     *
150     * <p>When multiple candidates exist and GUESS_COLUMN_STRATEGY is not NOT_PICKUP,
151     * at least one candidate must have confidence >= this threshold for guessing
152     * to be allowed. If all candidates are below this threshold, the column
153     * remains AMBIGUOUS regardless of strategy.</p>
154     *
155     * <p>Recommended values:</p>
156     * <ul>
157     *   <li>0.95 (default): Very high bar - only guess with strong evidence</li>
158     *   <li>0.9: Allow guessing with high-confidence candidates</li>
159     *   <li>0.7: Allow guessing with inferred candidates (not recommended)</li>
160     * </ul>
161     */
162    private double minConfidenceToGuess = 0.95;
163
164    /**
165     * Whether to allow guessing when all candidates are inferred (no DDL metadata).
166     *
167     * <p>When false (default), if all candidates have confidence below
168     * {@link #minDefiniteConfidence}, the column remains AMBIGUOUS even if
169     * GUESS_COLUMN_STRATEGY would normally pick one. This prevents guessing
170     * based on uncertain evidence.</p>
171     *
172     * <p>When true, guessing is allowed even when all candidates are inferred,
173     * as long as the GUESS_COLUMN_STRATEGY is not NOT_PICKUP. Use with caution
174     * as this may produce misleading lineage results.</p>
175     */
176    private boolean allowGuessWhenAllInferred = false;
177
178    // ========== Binding Diagnostic Configuration (plan §5.3, S2) ==========
179
180    /**
181     * When {@code true}, the resolver2 binding-diagnostic post-pass runs once
182     * after iterative convergence and populates {@link
183     * gudusoft.gsqlparser.resolver2.binding.BindingResult}. Default {@code
184     * false} — accessors return the {@link
185     * gudusoft.gsqlparser.resolver2.binding.BindingResult#empty()} singleton
186     * with zero parse-time overhead.
187     */
188    private boolean emitBindingDiagnostics = false;
189
190    /**
191     * When {@code true} (and {@link #emitBindingDiagnostics} is on), strict
192     * catalog mode emits {@code UNKNOWN_TABLE} / {@code UNKNOWN_ALIAS} /
193     * {@code CATALOG_METADATA_UNAVAILABLE}. Default {@code false} so that
194     * tables without authoritative metadata are silent (plan §5.5).
195     */
196    private boolean bindingStrictCatalogValidation = false;
197
198    /**
199     * When {@code true}, successful {@link
200     * gudusoft.gsqlparser.resolver2.binding.BindingReference} entries are
201     * included in the result. Default {@code false} — IDE/lint consumers opt
202     * in; non-IDE consumers avoid the payload bloat.
203     */
204    private boolean bindingIncludeSuccessfulReferences = false;
205
206    /**
207     * Lazy map of per-code severity overrides. Allocated only when {@link
208     * #setBindingSeverityFor(BindingDiagnosticCode, BindingDiagnosticSeverity)}
209     * is first called so that the default-off path stays allocation-free.
210     */
211    private EnumMap<BindingDiagnosticCode, BindingDiagnosticSeverity> bindingSeverityOverrides;
212
213    public TSQLResolverConfig() {
214        // Default configuration
215    }
216
217    public INameMatcher getNameMatcher() {
218        return nameMatcher;
219    }
220
221    public void setNameMatcher(INameMatcher nameMatcher) {
222        if (nameMatcher == null) {
223            throw new IllegalArgumentException("Name matcher cannot be null");
224        }
225        this.nameMatcher = nameMatcher;
226    }
227
228    public boolean isLegacyCompatibilityEnabled() {
229        return legacyCompatibilityEnabled;
230    }
231
232    public void setLegacyCompatibilityEnabled(boolean enabled) {
233        this.legacyCompatibilityEnabled = enabled;
234    }
235
236    public double getLegacySyncMinConfidence() {
237        return legacySyncMinConfidence;
238    }
239
240    public void setLegacySyncMinConfidence(double threshold) {
241        if (threshold < 0.0 || threshold > 1.0) {
242            throw new IllegalArgumentException("Confidence threshold must be in [0.0, 1.0]");
243        }
244        this.legacySyncMinConfidence = threshold;
245    }
246
247    public int getMaxIterations() {
248        return maxIterations;
249    }
250
251    public void setMaxIterations(int maxIterations) {
252        if (maxIterations < 1) {
253            throw new IllegalArgumentException("Max iterations must be at least 1");
254        }
255        this.maxIterations = maxIterations;
256    }
257
258    public double getMinProgressRate() {
259        return minProgressRate;
260    }
261
262    public void setMinProgressRate(double minProgressRate) {
263        if (minProgressRate < 0.0 || minProgressRate > 1.0) {
264            throw new IllegalArgumentException("Progress rate must be in [0.0, 1.0]");
265        }
266        this.minProgressRate = minProgressRate;
267    }
268
269    public int getStablePassesForConvergence() {
270        return stablePassesForConvergence;
271    }
272
273    public void setStablePassesForConvergence(int stablePasses) {
274        if (stablePasses < 1) {
275            throw new IllegalArgumentException("Stable passes must be at least 1");
276        }
277        this.stablePassesForConvergence = stablePasses;
278    }
279
280    public boolean isCollectFullCandidates() {
281        return collectFullCandidates;
282    }
283
284    public void setCollectFullCandidates(boolean collectFullCandidates) {
285        this.collectFullCandidates = collectFullCandidates;
286    }
287
288    /**
289     * @deprecated Use NamespaceEnhancer instead
290     */
291    public boolean isEvidenceCollectionEnabled() {
292        return evidenceCollectionEnabled;
293    }
294
295    /**
296     * @deprecated Use NamespaceEnhancer instead
297     */
298    public void setEvidenceCollectionEnabled(boolean enabled) {
299        this.evidenceCollectionEnabled = enabled;
300    }
301
302    /**
303     * Check if datatype information should be shown for columns from CREATE TABLE statements.
304     *
305     * @return true if datatype information should be included in column names
306     */
307    public boolean isShowDatatype() {
308        return showDatatype;
309    }
310
311    /**
312     * Set whether to show datatype information for columns from CREATE TABLE statements.
313     * When enabled, columns from CREATE TABLE will include datatype info in format:
314     * columnName:datatypeName:length or columnName:datatypeName:precision:scale
315     *
316     * @param showDatatype true to include datatype information
317     */
318    public void setShowDatatype(boolean showDatatype) {
319        this.showDatatype = showDatatype;
320    }
321
322    /**
323     * Check if CTE (Common Table Expression) tables and columns should be shown in output.
324     *
325     * @return true if CTE tables and columns should be included
326     */
327    public boolean isShowCTE() {
328        return showCTE;
329    }
330
331    /**
332     * Set whether to show CTE (Common Table Expression) tables and columns in output.
333     * When enabled, CTE tables are included in the tables list and CTE columns
334     * are included in the fields list with "(CTE)" suffix.
335     *
336     * @param showCTE true to include CTE tables and columns
337     */
338    public void setShowCTE(boolean showCTE) {
339        this.showCTE = showCTE;
340    }
341
342    // ========== Display Name Getters and Setters ==========
343
344    /**
345     * Get the display name mode.
346     *
347     * @return the current display name mode
348     */
349    public DisplayNameMode getDisplayNameMode() {
350        return displayNameMode;
351    }
352
353    /**
354     * Set the display name mode.
355     *
356     * @param mode the display name mode
357     */
358    public void setDisplayNameMode(DisplayNameMode mode) {
359        this.displayNameMode = mode != null ? mode : DisplayNameMode.DISPLAY;
360    }
361
362    /**
363     * Get the display name policy.
364     *
365     * @return the current display name policy
366     */
367    public DisplayNamePolicy getDisplayNamePolicy() {
368        return displayNamePolicy;
369    }
370
371    /**
372     * Set the display name policy.
373     *
374     * @param policy the display name policy
375     */
376    public void setDisplayNamePolicy(DisplayNamePolicy policy) {
377        this.displayNamePolicy = policy != null ? policy : DisplayNamePolicy.PREFER_DEFINITION_SITE;
378    }
379
380    /**
381     * Check if delimiters should be stripped for display.
382     *
383     * @return true if delimiters should be stripped
384     */
385    public boolean isStripDelimitersForDisplay() {
386        return stripDelimitersForDisplay;
387    }
388
389    /**
390     * Set whether to strip delimiters for display.
391     *
392     * @param stripDelimiters true to strip delimiters
393     */
394    public void setStripDelimitersForDisplay(boolean stripDelimiters) {
395        this.stripDelimitersForDisplay = stripDelimiters;
396    }
397
398    /**
399     * Get the strategy for handling ambiguous columns.
400     * Returns the configured value, or TBaseType.GUESS_COLUMN_STRATEGY if not set.
401     *
402     * @return One of GUESS_COLUMN_STRATEGY_NEAREST, GUESS_COLUMN_STRATEGY_FARTHEST,
403     *         or GUESS_COLUMN_STRATEGY_NOT_PICKUP
404     */
405    public int getGuessColumnStrategy() {
406        return guessColumnStrategy != null ? guessColumnStrategy : TBaseType.GUESS_COLUMN_STRATEGY;
407    }
408
409    /**
410     * Set the strategy for handling ambiguous columns.
411     *
412     * @param strategy One of GUESS_COLUMN_STRATEGY_NEAREST, GUESS_COLUMN_STRATEGY_FARTHEST,
413     *                 or GUESS_COLUMN_STRATEGY_NOT_PICKUP
414     */
415    public void setGuessColumnStrategy(int strategy) {
416        if (strategy < GUESS_COLUMN_STRATEGY_NEAREST || strategy > GUESS_COLUMN_STRATEGY_NOT_PICKUP) {
417            throw new IllegalArgumentException("Invalid strategy: " + strategy +
418                ". Must be GUESS_COLUMN_STRATEGY_NEAREST (0), GUESS_COLUMN_STRATEGY_FARTHEST (1), " +
419                "or GUESS_COLUMN_STRATEGY_NOT_PICKUP (2)");
420        }
421        this.guessColumnStrategy = strategy;
422    }
423
424    /**
425     * Check if a custom guess column strategy has been set on this config.
426     * If false, the strategy from TBaseType.GUESS_COLUMN_STRATEGY will be used.
427     *
428     * @return true if a custom strategy is set
429     */
430    public boolean hasCustomGuessColumnStrategy() {
431        return guessColumnStrategy != null;
432    }
433
434    /**
435     * Clear any custom guess column strategy, reverting to TBaseType.GUESS_COLUMN_STRATEGY.
436     */
437    public void clearGuessColumnStrategy() {
438        this.guessColumnStrategy = null;
439    }
440
441    /**
442     * Get the human-readable name for the current strategy.
443     *
444     * @return Strategy name (e.g., "GUESS_COLUMN_STRATEGY_NEAREST")
445     */
446    public String getGuessColumnStrategyName() {
447        int strategy = getGuessColumnStrategy();
448        if (strategy >= 0 && strategy < GUESS_COLUMN_STRATEGY_NAMES.length) {
449            return GUESS_COLUMN_STRATEGY_NAMES[strategy];
450        }
451        return "UNKNOWN(" + strategy + ")";
452    }
453
454    // ========== Confidence Threshold Getters and Setters ==========
455
456    /**
457     * Get the minimum confidence threshold for definite resolutions.
458     *
459     * @return Threshold value [0.0, 1.0]
460     */
461    public double getMinDefiniteConfidence() {
462        return minDefiniteConfidence;
463    }
464
465    /**
466     * Set the minimum confidence threshold for definite resolutions.
467     *
468     * @param threshold Threshold value [0.0, 1.0]
469     */
470    public void setMinDefiniteConfidence(double threshold) {
471        if (threshold < 0.0 || threshold > 1.0) {
472            throw new IllegalArgumentException("Confidence threshold must be in [0.0, 1.0]");
473        }
474        this.minDefiniteConfidence = threshold;
475    }
476
477    /**
478     * Get the minimum confidence threshold to allow guessing.
479     *
480     * @return Threshold value [0.0, 1.0]
481     */
482    public double getMinConfidenceToGuess() {
483        return minConfidenceToGuess;
484    }
485
486    /**
487     * Set the minimum confidence threshold to allow guessing.
488     *
489     * @param threshold Threshold value [0.0, 1.0]
490     */
491    public void setMinConfidenceToGuess(double threshold) {
492        if (threshold < 0.0 || threshold > 1.0) {
493            throw new IllegalArgumentException("Confidence threshold must be in [0.0, 1.0]");
494        }
495        this.minConfidenceToGuess = threshold;
496    }
497
498    /**
499     * Check if guessing is allowed when all candidates are inferred.
500     *
501     * @return true if guessing is allowed with inferred candidates
502     */
503    public boolean isAllowGuessWhenAllInferred() {
504        return allowGuessWhenAllInferred;
505    }
506
507    /**
508     * Set whether to allow guessing when all candidates are inferred.
509     *
510     * @param allow true to allow guessing with inferred candidates
511     */
512    public void setAllowGuessWhenAllInferred(boolean allow) {
513        this.allowGuessWhenAllInferred = allow;
514    }
515
516    /**
517     * Check if a confidence value represents a definite resolution.
518     *
519     * @param confidence The confidence value to check
520     * @return true if the confidence is >= minDefiniteConfidence
521     */
522    public boolean isDefiniteConfidence(double confidence) {
523        return confidence >= minDefiniteConfidence;
524    }
525
526    /**
527     * Check if a confidence value is sufficient to allow guessing.
528     *
529     * @param confidence The confidence value to check
530     * @return true if the confidence is >= minConfidenceToGuess
531     */
532    public boolean canGuessWithConfidence(double confidence) {
533        return confidence >= minConfidenceToGuess;
534    }
535
536    // ========== Binding Diagnostic Getters and Setters (plan §5.3, S2) ==========
537
538    /**
539     * @return whether the binding-diagnostic post-pass is enabled
540     */
541    public boolean isEmitBindingDiagnostics() {
542        return emitBindingDiagnostics;
543    }
544
545    /**
546     * Enable or disable the binding-diagnostic post-pass.
547     *
548     * <p>Default {@code false}. When off, the post-pass is not invoked and
549     * accessors return {@link
550     * gudusoft.gsqlparser.resolver2.binding.BindingResult#empty()}.</p>
551     *
552     * @param on whether to emit binding diagnostics
553     * @return this config (fluent)
554     */
555    public TSQLResolverConfig setEmitBindingDiagnostics(boolean on) {
556        this.emitBindingDiagnostics = on;
557        return this;
558    }
559
560    /**
561     * @return whether strict catalog validation is enabled
562     */
563    public boolean isBindingStrictCatalogValidation() {
564        return bindingStrictCatalogValidation;
565    }
566
567    /**
568     * Enable or disable strict catalog validation. Default {@code false}.
569     *
570     * @param on whether to enable strict mode
571     * @return this config (fluent)
572     */
573    public TSQLResolverConfig setBindingStrictCatalogValidation(boolean on) {
574        this.bindingStrictCatalogValidation = on;
575        return this;
576    }
577
578    /**
579     * @return whether successful binding references are included in the
580     *         result payload
581     */
582    public boolean isBindingIncludeSuccessfulReferences() {
583        return bindingIncludeSuccessfulReferences;
584    }
585
586    /**
587     * Enable or disable successful-reference emission. Default {@code false}
588     * (payload bloat for non-IDE consumers).
589     *
590     * @param on whether to include successful references
591     * @return this config (fluent)
592     */
593    public TSQLResolverConfig setBindingIncludeSuccessfulReferences(boolean on) {
594        this.bindingIncludeSuccessfulReferences = on;
595        return this;
596    }
597
598    /**
599     * Override the severity emitted for a given binding diagnostic code.
600     *
601     * <p>The override map is allocated lazily so callers that never customize
602     * severities pay no allocation cost.</p>
603     *
604     * @param code the diagnostic code (must not be null)
605     * @param severity the severity to emit for this code (must not be null)
606     * @return this config (fluent)
607     * @throws IllegalArgumentException if either argument is null
608     */
609    public TSQLResolverConfig setBindingSeverityFor(BindingDiagnosticCode code,
610                                                   BindingDiagnosticSeverity severity) {
611        if (code == null) {
612            throw new IllegalArgumentException("BindingDiagnosticCode is required");
613        }
614        if (severity == null) {
615            throw new IllegalArgumentException("BindingDiagnosticSeverity is required");
616        }
617        if (bindingSeverityOverrides == null) {
618            bindingSeverityOverrides = new EnumMap<BindingDiagnosticCode, BindingDiagnosticSeverity>(
619                BindingDiagnosticCode.class);
620        }
621        bindingSeverityOverrides.put(code, severity);
622        return this;
623    }
624
625    /**
626     * Resolve the configured severity for a diagnostic code. Returns the
627     * override when one is set, otherwise the code's frozen default
628     * severity (plan §5.4).
629     *
630     * @param code the diagnostic code (must not be null)
631     * @return the configured or default severity for this code
632     */
633    public BindingDiagnosticSeverity getBindingSeverityFor(BindingDiagnosticCode code) {
634        if (code == null) {
635            throw new IllegalArgumentException("BindingDiagnosticCode is required");
636        }
637        if (bindingSeverityOverrides != null) {
638            BindingDiagnosticSeverity override = bindingSeverityOverrides.get(code);
639            if (override != null) {
640                return override;
641            }
642        }
643        return code.defaultSeverity();
644    }
645
646    /**
647     * @return whether any per-code severity override has been configured.
648     *         Used to keep the default-off path allocation-free in tests.
649     */
650    public boolean hasBindingSeverityOverrides() {
651        return bindingSeverityOverrides != null && !bindingSeverityOverrides.isEmpty();
652    }
653
654    /**
655     * @return an immutable view of the configured severity overrides, or an
656     *         empty map when none are configured. Never null.
657     */
658    public Map<BindingDiagnosticCode, BindingDiagnosticSeverity> getBindingSeverityOverrides() {
659        if (bindingSeverityOverrides == null || bindingSeverityOverrides.isEmpty()) {
660            return java.util.Collections.<BindingDiagnosticCode, BindingDiagnosticSeverity>emptyMap();
661        }
662        return java.util.Collections.unmodifiableMap(bindingSeverityOverrides);
663    }
664
665    /**
666     * Create a default configuration
667     */
668    public static TSQLResolverConfig createDefault() {
669        return new TSQLResolverConfig();
670    }
671
672    /**
673     * Create configuration for case-sensitive matching
674     */
675    public static TSQLResolverConfig createCaseSensitive() {
676        TSQLResolverConfig config = new TSQLResolverConfig();
677        config.setNameMatcher(new DefaultNameMatcher(true));
678        return config;
679    }
680
681    /**
682     * Create configuration for standalone mode (no legacy sync)
683     */
684    public static TSQLResolverConfig createStandalone() {
685        TSQLResolverConfig config = new TSQLResolverConfig();
686        config.setLegacyCompatibilityEnabled(false);
687        return config;
688    }
689
690    /**
691     * Create configuration with showDatatype enabled.
692     * This configuration includes datatype information for columns from CREATE TABLE statements.
693     */
694    public static TSQLResolverConfig createWithDatatype() {
695        TSQLResolverConfig config = new TSQLResolverConfig();
696        config.setShowDatatype(true);
697        return config;
698    }
699
700    /**
701     * Create configuration with showCTE enabled.
702     * This configuration includes CTE tables and columns in the output.
703     */
704    public static TSQLResolverConfig createWithCTE() {
705        TSQLResolverConfig config = new TSQLResolverConfig();
706        config.setShowCTE(true);
707        return config;
708    }
709
710    /**
711     * Create configuration for a specific database vendor.
712     *
713     * <p>This factory method creates a configuration with vendor-specific
714     * name matching rules. The VendorNameMatcher uses IdentifierService
715     * to properly handle case sensitivity and quote handling for each vendor.</p>
716     *
717     * <p>Example vendor behaviors:</p>
718     * <ul>
719     *   <li>Oracle: Unquoted identifiers fold to UPPER, quoted are case-sensitive</li>
720     *   <li>PostgreSQL: Unquoted identifiers fold to LOWER, quoted are case-sensitive</li>
721     *   <li>MySQL: Depends on lower_case_table_names setting</li>
722     *   <li>BigQuery: Table names are case-sensitive, column names are case-insensitive</li>
723     * </ul>
724     *
725     * @param vendor the database vendor
726     * @return configuration with vendor-specific name matcher
727     */
728    public static TSQLResolverConfig createForVendor(EDbVendor vendor) {
729        TSQLResolverConfig config = new TSQLResolverConfig();
730        config.vendor = vendor;
731        config.nameMatcher = new VendorNameMatcher(vendor);
732        return config;
733    }
734
735    /**
736     * Create configuration for a specific database vendor with datatype display enabled.
737     *
738     * @param vendor the database vendor
739     * @return configuration with vendor-specific name matcher and datatype display
740     */
741    public static TSQLResolverConfig createForVendorWithDatatype(EDbVendor vendor) {
742        TSQLResolverConfig config = createForVendor(vendor);
743        config.setShowDatatype(true);
744        return config;
745    }
746
747    /**
748     * Get the database vendor, if set.
749     *
750     * @return the database vendor, or null if not set
751     */
752    public EDbVendor getVendor() {
753        return vendor;
754    }
755
756    /**
757     * Set the database vendor and update name matcher accordingly.
758     *
759     * @param vendor the database vendor
760     */
761    public void setVendor(EDbVendor vendor) {
762        this.vendor = vendor;
763        if (vendor != null) {
764            this.nameMatcher = new VendorNameMatcher(vendor);
765        }
766    }
767
768    /**
769     * Check if vendor-specific name matching is enabled.
770     *
771     * @return true if a vendor is configured
772     */
773    public boolean hasVendor() {
774        return vendor != null;
775    }
776
777    @Override
778    public String toString() {
779        return String.format(
780            "TSQLResolverConfig{vendor=%s, nameMatcher=%s, legacyCompat=%s, minConfidence=%.2f, maxIter=%d, guessStrategy=%s, " +
781            "minDefiniteConf=%.2f, minGuessConf=%.2f, allowGuessInferred=%s, showDatatype=%s, showCTE=%s, displayMode=%s, displayPolicy=%s}",
782            vendor,
783            nameMatcher,
784            legacyCompatibilityEnabled,
785            legacySyncMinConfidence,
786            maxIterations,
787            getGuessColumnStrategyName(),
788            minDefiniteConfidence,
789            minConfidenceToGuess,
790            allowGuessWhenAllInferred,
791            showDatatype,
792            showCTE,
793            displayNameMode,
794            displayNamePolicy
795        );
796    }
797}