001package gudusoft.gsqlparser.dlineage.dataflow.model;
002
003import java.util.Arrays;
004import java.util.HashSet;
005import java.util.Set;
006
007import gudusoft.gsqlparser.EDbVendor;
008import gudusoft.gsqlparser.dlineage.dataflow.listener.DataFlowHandleListener;
009import gudusoft.gsqlparser.dlineage.dynamicsql.DynamicSqlTrustMode;
010import gudusoft.gsqlparser.sqlenv.IdentifierProfile;
011
012public class Option implements Cloneable {
013
014    private boolean collectCommentBindings;
015    private int commentBindingMaxRawLength = 8192;
016    private int commentBindingMaxCommentsPerSourceUnit = 50000;
017    private int commentBindingMaxTotalRawLength = 4 * 1024 * 1024;
018
019    /** Collect source comment occurrences without changing legacy comments or lineage. */
020    public boolean isCollectCommentBindings() { return collectCommentBindings; }
021    public void setCollectCommentBindings(boolean value) { collectCommentBindings = value; }
022    public int getCommentBindingMaxRawLength() { return commentBindingMaxRawLength; }
023    public void setCommentBindingMaxRawLength(int value) {
024        if (value < 0) throw new IllegalArgumentException("Comment limit must be nonnegative");
025        commentBindingMaxRawLength = value;
026    }
027    public int getCommentBindingMaxCommentsPerSourceUnit() { return commentBindingMaxCommentsPerSourceUnit; }
028    public void setCommentBindingMaxCommentsPerSourceUnit(int value) {
029        if (value < 0) throw new IllegalArgumentException("Comment limit must be nonnegative");
030        commentBindingMaxCommentsPerSourceUnit = value;
031    }
032    public int getCommentBindingMaxTotalRawLength() { return commentBindingMaxTotalRawLength; }
033    public void setCommentBindingMaxTotalRawLength(int value) {
034        if (value < 0) throw new IllegalArgumentException("Comment limit must be nonnegative");
035        commentBindingMaxTotalRawLength = value;
036    }
037
038    private EDbVendor vendor;
039    private EDbVendor powerQueryInnerVendor;
040    private DataFlowHandleListener handleListener;
041    private boolean simpleOutput;
042    private boolean textFormat = false;
043    private boolean showJoin = false;
044    private boolean ignoreRecordSet = false;
045    private boolean ignoreTemporaryTable = false;
046    private boolean ignoreTopSelect = false;
047    private boolean ignoreUnusedSynonym = true;
048    private boolean showCallRelation = false;
049    private boolean simpleShowFunction = false;
050    private boolean simpleShowSynonym = false;
051    private boolean simpleShowUdfFunctionOnly = false;
052    private boolean simpleShowCursor = false;
053    private boolean simpleShowVariable = false;
054    private boolean simpleRetainIntermediate = false;
055    private boolean simpleShowTopSelectResultSet = false;
056    private boolean linkOrphanColumnToFirstTable = false;
057    private boolean ignoreCoordinate = false;
058    private boolean transform = false;
059    private boolean transformCoordinate = false;
060    /**
061     * Whether SQL Server parsers created by {@code DataFlowAnalyzer} accept
062     * colon-prefixed bind variables such as {@code :name}, {@code :1}, and
063     * {@code :@name}. This compatibility extension is disabled by default.
064     */
065    private boolean enableMssqlColonBindVariables = false;
066    /**
067     * Whether {@code DataFlowAnalyzer} should collect the immutable semantic
068     * sidecar consumed by the SLPC adapter.
069     *
070     * <p>This is deliberately opt-in. The established dlineage XML/JSON model
071     * remains the default execution path, with no AST traversal or sidecar
072     * bookkeeping added for callers that do not request SLPC projection.
073     * Parallel analysis currently forces this option off because merged legacy
074     * IDs are remapped and cannot yet be correlated safely to worker evidence.</p>
075     */
076    private boolean collectAuthoritativeLineageEvidence = false;
077    private boolean showImplicitSchema = false;
078    private boolean showCountTableColumn = true;
079    private boolean showConstantTable = false;
080    private boolean showCaseWhenAsDirect = true;
081    private String filePathDatabase;
082    private String filePathSchema;
083    private long startId = 0;
084    private boolean output = true;
085    private boolean traceSQL = false;
086    private boolean showComment = false;
087    private boolean traceProcedure = false;
088    private int parallel = Runtime.getRuntime().availableProcessors() / 2 - 1;
089    private String defaultServer;
090    private String defaultDatabase;
091    private String defaultSchema;
092    private boolean showERDiagram = false;
093    private Set<ResultSetType> targetResultSetTypes = new HashSet<ResultSetType>(); 
094    private Set<String> filterRelationTypes = new HashSet<String>();
095    private Set<String> simpleShowRelationTypes = new HashSet<String>();
096    private boolean sqlflowIgnoreFunction = false;
097    private DynamicSqlTrustMode dynamicSqlTrustMode = DynamicSqlTrustMode.LEGACY;
098    /** Whether execution statements may analyze embedded/generated SQL text.
099     *  Defaults true for compatibility. Routine-summary extraction disables
100     *  it so dynamic relationships cannot lose their site provenance when
101     *  collapsed into an ordinary base-summary edge. */
102    private boolean analyzeDynamicSql = true;
103
104    private Set<String> excludedProcedureNames = new HashSet<String>();
105
106    /**
107     * Wildcard patterns to exclude procedures from analysis.
108     * <p>
109     * Supports the following wildcard characters:
110     * <ul>
111     *   <li><code>*</code> - Matches any sequence of characters (e.g., <code>SCHEMA1.*</code> matches all procedures in SCHEMA1)</li>
112     *   <li><code>?</code> - Matches any single character (e.g., <code>SCHEMA?.PROC?</code> matches SCHEMA1.PROC1, SCHEMA2.PROC2, etc.)</li>
113     *   <li><code>.</code> - Reserved as schema/procedure name separator (e.g., <code>SCHEMA.PROC</code> matches a two-segment qualified name)</li>
114     * </ul>
115     * <p>
116     * Examples:
117     * <ul>
118     *   <li><code>"SCHEMA1.*"</code> - Excludes all procedures in SCHEMA1</li>
119     *   <li><code>"*.SP_*"</code> - Excludes all procedures starting with SP_ in any schema</li>
120     *   <li><code>"*_TEST"</code> - Excludes all procedures ending with _TEST in any schema</li>
121     *   <li><code>"SCHEMA?.PROC?"</code> - Excludes procedures like SCHEMA1.PROC1, SCHEMA2.PROC2, etc.</li>
122     *   <li><code>"DB.SCHEMA.PROC"</code> - Excludes a specific three-segment procedure</li>
123     * </ul>
124     * <p>
125     * Note: Pattern matching is case-insensitive. Quoted identifiers (double quotes, square brackets, backticks, single quotes)
126     * will be automatically stripped before matching.
127     */
128    private Set<String> excludedProcedurePatterns = new HashSet<String>();
129    private boolean showCandidateTable = true;
130    private boolean ignoreInsertIntoValues = true;
131    private boolean normalizeOutput = false;
132    /*
133     * Whether to trace all table positions. Default is false.
134     */
135    private boolean traceTablePosition = false;
136
137    private boolean enablePipelinedStitching = true;
138
139    /**
140     * B3 (routine-summary-scc-design §3): object-level per-overload variable
141     * pools — the identity-first createVariable path. OFF by default: LEGACY
142     * keeps the historical shared-key merge of same-named overload formals;
143     * only the SHADOW/ENHANCED pipelines switch this on. Folds into
144     * ProceduralLineageOptions in B6.
145     */
146    private boolean identityFirstVariablePools = false;
147    private int maxPipelinedExpansionDepth = 8;
148    private int maxStitchedSourcesPerColumn = 64;
149
150    private AnalyzeMode analyzeMode;
151
152    public EDbVendor getVendor() {
153        return vendor;
154    }
155
156    public void setVendor(EDbVendor vendor) {
157        this.vendor = vendor;
158    }
159
160    /**
161     * Identifier configuration for this analysis, per object category; null means the vendor
162     * default.
163     */
164    private IdentifierProfile identifierProfile;
165
166    /**
167     * The identifier configuration this analysis compares, normalizes and keys names under.
168     *
169     * @return the profile, or null when the vendor default applies
170     */
171    public IdentifierProfile getIdentifierProfile() {
172        return identifierProfile;
173    }
174
175    /**
176     * Configure how identifiers are compared for this analysis, per object category — for
177     * example BigQuery table names compared case-insensitively while catalog, schema and column
178     * keep their documented rules:
179     *
180     * <pre>
181     * Option option = new Option();
182     * option.setVendor(EDbVendor.dbvbigquery);
183     * option.setIdentifierProfile(IdentifierProfile.builder(EDbVendor.dbvbigquery)
184     *         .withObjectRules(ESQLDataObjectType.dotTable,
185     *                 new IdentifierRules(CaseFold.NONE, CaseCompare.INSENSITIVE,
186     *                                     CaseFold.NONE, CaseCompare.INSENSITIVE))
187     *         .build());
188     * new DataFlowAnalyzer(sql, option).generateDataFlow();
189     * </pre>
190     *
191     * <p>Set it BEFORE {@code generateDataFlow()}. The analyzer takes a snapshot at entry and the
192     * whole run — catalog registration and lookup, the parser and resolver, lineage model keys and
193     * output merging — uses that snapshot, so mutating this option mid-run changes nothing.
194     * Two analyzers may run concurrently under opposite configurations.
195     *
196     * <p>This is the analysis assumption the CALLER states. GSP does not read it from a live
197     * server, and {@code CREATE SCHEMA ... OPTIONS(is_case_insensitive=...)} in the script is
198     * parsed but NOT interpreted as a configuration change.
199     *
200     * @param identifierProfile the configuration, or null for the vendor default
201     */
202    public void setIdentifierProfile(IdentifierProfile identifierProfile) {
203        this.identifierProfile = identifierProfile;
204    }
205
206    /**
207     * Explicit SQL dialect for the inner SQL embedded in Power Query
208     * {@code Value.NativeQuery()} calls and for navigation-chain synthetic
209     * SELECTs. When set, this takes precedence over the connector-based
210     * inference performed by {@code TPowerQueryAnalyzer}; when {@code null}
211     * (default), inference is used.
212     */
213    public EDbVendor getPowerQueryInnerVendor() {
214        return powerQueryInnerVendor;
215    }
216
217    public void setPowerQueryInnerVendor(EDbVendor powerQueryInnerVendor) {
218        this.powerQueryInnerVendor = powerQueryInnerVendor;
219    }
220
221    public DataFlowHandleListener getHandleListener() {
222        return handleListener;
223    }
224
225    public void setHandleListener(DataFlowHandleListener handleListener) {
226        this.handleListener = handleListener;
227    }
228
229    public boolean isSimpleOutput() {
230        return simpleOutput;
231    }
232
233    public void setSimpleOutput(boolean simpleOutput) {
234        this.simpleOutput = simpleOutput;
235    }
236
237    public boolean isTextFormat() {
238        return textFormat;
239    }
240
241    public void setTextFormat(boolean textFormat) {
242        this.textFormat = textFormat;
243    }
244
245    public boolean isShowJoin() {
246        return showJoin;
247    }
248
249    public void setShowJoin(boolean showJoin) {
250        this.showJoin = showJoin;
251    }
252
253    /**
254     * When true, every non-RESOLVED dynamic-SQL site (including SHADOW's PARTIAL sites) is also
255     * mirrored into an {@link ErrorInfo} (type {@link ErrorInfo#DYNAMIC_SQL_UNRESOLVED}) so callers reading
256     * {@code getErrorMessages()} can see them. Default false, so existing callers are unaffected;
257     * the structured list is always available via {@code DataFlowAnalyzer.getDynamicSqlSites()}.
258     */
259    private boolean reportDynamicSqlSitesAsErrors = false;
260
261    public boolean isReportDynamicSqlSitesAsErrors() {
262        return reportDynamicSqlSitesAsErrors;
263    }
264
265    public void setReportDynamicSqlSitesAsErrors(boolean reportDynamicSqlSitesAsErrors) {
266        this.reportDynamicSqlSitesAsErrors = reportDynamicSqlSitesAsErrors;
267    }
268
269    /**
270     * Observation policy for incompletely materialized dynamic SQL. The default
271     * {@link DynamicSqlTrustMode#LEGACY} retains historical output. SHADOW keeps
272     * the exact same model and adds incompleteness diagnostics.
273     */
274    public DynamicSqlTrustMode getDynamicSqlTrustMode() {
275        return dynamicSqlTrustMode;
276    }
277
278    public void setDynamicSqlTrustMode(DynamicSqlTrustMode dynamicSqlTrustMode) {
279        this.dynamicSqlTrustMode = dynamicSqlTrustMode == null
280                ? DynamicSqlTrustMode.LEGACY : dynamicSqlTrustMode;
281    }
282
283    /**
284     * Controls inner analysis of dynamic SQL text. When false, ordinary
285     * static statements and static routine calls are still analyzed, but
286     * EXEC-string, sp_executesql, EXECUTE IMMEDIATE, DBMS_SQL.PARSE, and
287     * similar embedded-text expansions publish no relationships.
288     */
289    public boolean isAnalyzeDynamicSql() {
290        return analyzeDynamicSql;
291    }
292
293    public void setAnalyzeDynamicSql(boolean analyzeDynamicSql) {
294        this.analyzeDynamicSql = analyzeDynamicSql;
295    }
296
297    /**
298     * Whether the dynamic SQL evaluation compares strings case-sensitively when it runs a query over a table
299     * variable or temporary table the procedure fills with literal rows (Mantis 4962). Default false, as SQL Server's
300     * default {@code _CI_AS} collations do; trailing spaces are ignored either way.
301     */
302    private boolean dynamicSqlCaseSensitiveCompare = false;
303
304    public boolean isDynamicSqlCaseSensitiveCompare() {
305        return dynamicSqlCaseSensitiveCompare;
306    }
307
308    public void setDynamicSqlCaseSensitiveCompare(boolean dynamicSqlCaseSensitiveCompare) {
309        this.dynamicSqlCaseSensitiveCompare = dynamicSqlCaseSensitiveCompare;
310    }
311
312    /**
313     * Recover T-SQL routines whose body holds statements the grammar rejects (see
314     * {@code TGSqlParser.setRecoverRoutineBodyErrors}): the statements holding syntax errors are skipped and the
315     * routine is analyzed with the rest of its body, instead of yielding no lineage at all. The skipped statements
316     * are still reported as syntax errors. It applies to the analyzed SQL itself; SQL parsed on the way (dynamic SQL,
317     * routines found elsewhere) is parsed as written. Off by default.
318     */
319    public boolean isRecoverRoutineBodyErrors() {
320        return recoverRoutineBodyErrors;
321    }
322
323    public void setRecoverRoutineBodyErrors(boolean recoverRoutineBodyErrors) {
324        this.recoverRoutineBodyErrors = recoverRoutineBodyErrors;
325    }
326
327    private boolean recoverRoutineBodyErrors = false;
328
329    public boolean isIgnoreRecordSet() {
330        return ignoreRecordSet;
331    }
332
333    public void setIgnoreRecordSet(boolean ignoreRecordSet) {
334        this.ignoreRecordSet = ignoreRecordSet;
335    }
336
337    public boolean isLinkOrphanColumnToFirstTable() {
338        return linkOrphanColumnToFirstTable;
339    }
340
341    public void setLinkOrphanColumnToFirstTable(boolean linkOrphanColumnToFirstTable) {
342        this.linkOrphanColumnToFirstTable = linkOrphanColumnToFirstTable;
343    }
344
345    /**
346     * When true, dlineage ASSUMES that an external script
347     * ({@code sp_execute_external_script}) passes its input dataset through to the
348     * declared {@code WITH RESULT SETS} output positionally, emitting
349     * {@link gudusoft.gsqlparser.dlineage.dataflow.model.EffectType#external_script_passthrough}
350     * edges from each @input_data_1 column to the corresponding output column.
351     *
352     * <p>The script body (Python/R/…) is opaque and may reorder, drop or
353     * synthesize columns, so these edges are an assumption, not proven lineage —
354     * hence the dedicated effect type. Default {@code true}: column-level lineage
355     * flows THROUGH the script (e.g. across an {@code INSERT ... EXEC} that
356     * consumes the procedure's result set), with every assumed edge marked
357     * {@code external_script_passthrough} so it stays distinguishable from proven
358     * lineage. Set to {@code false} to suppress the assumption: the input query's
359     * source lineage is still emitted, but it stops at the script boundary (it is
360     * not connected to the output columns).
361     */
362    private boolean assumeExternalScriptPassthrough = true;
363
364    public boolean isAssumeExternalScriptPassthrough() {
365        return assumeExternalScriptPassthrough;
366    }
367
368    public void setAssumeExternalScriptPassthrough(boolean assumeExternalScriptPassthrough) {
369        this.assumeExternalScriptPassthrough = assumeExternalScriptPassthrough;
370    }
371
372    public boolean isIgnoreCoordinate() {
373        return ignoreCoordinate;
374    }
375
376    public void setIgnoreCoordinate(boolean ignoreCoordinate) {
377        this.ignoreCoordinate = ignoreCoordinate;
378    }
379
380    public boolean isTransform() {
381        return transform;
382    }
383
384    public void setTransform(boolean transform) {
385        this.transform = transform;
386    }
387
388    public boolean isTransformCoordinate() {
389        return transformCoordinate;
390    }
391
392    public void setTransformCoordinate(boolean transformCoordinate) {
393        this.transformCoordinate = transformCoordinate;
394    }
395
396    public boolean isMssqlColonBindVariablesEnabled() {
397        return enableMssqlColonBindVariables;
398    }
399
400    public void setEnableMssqlColonBindVariables(boolean enabled) {
401        this.enableMssqlColonBindVariables = enabled;
402    }
403
404    public boolean isCollectAuthoritativeLineageEvidence() {
405        return collectAuthoritativeLineageEvidence;
406    }
407
408    public void setCollectAuthoritativeLineageEvidence(boolean collectAuthoritativeLineageEvidence) {
409        this.collectAuthoritativeLineageEvidence = collectAuthoritativeLineageEvidence;
410    }
411
412    public boolean isSimpleShowFunction() {
413        return simpleShowFunction;
414    }
415
416    public void setSimpleShowFunction(boolean simpleShowFunction) {
417        this.simpleShowFunction = simpleShowFunction;
418    }
419
420    public boolean isSimpleShowSynonym() {
421        return simpleShowSynonym;
422    }
423
424    public void setSimpleShowSynonym(boolean simpleShowSynonym) {
425        this.simpleShowSynonym = simpleShowSynonym;
426    }
427
428    public boolean isSimpleShowTopSelectResultSet() {
429        return simpleShowTopSelectResultSet;
430    }
431
432    public void setSimpleShowTopSelectResultSet(boolean simpleShowTopSelectResultSet) {
433        this.simpleShowTopSelectResultSet = simpleShowTopSelectResultSet;
434    }
435
436    public boolean isShowImplicitSchema() {
437        return showImplicitSchema;
438    }
439
440    public void setShowImplicitSchema(boolean showImplicitSchema) {
441        this.showImplicitSchema = showImplicitSchema;
442    }
443
444    public boolean isShowCountTableColumn() {
445        return showCountTableColumn;
446    }
447
448    public void setShowCountTableColumn(boolean showCountTableColumn) {
449        this.showCountTableColumn = showCountTableColumn;
450    }
451
452    public boolean isShowConstantTable() {
453        return showConstantTable;
454    }
455
456    public void setShowConstantTable(boolean showConstantTable) {
457        this.showConstantTable = showConstantTable;
458    }
459
460    public long getStartId() {
461        return startId;
462    }
463
464    public void setStartId(long startId) {
465        this.startId = startId;
466    }
467
468    public boolean isOutput() {
469        return output;
470    }
471
472    public void setOutput(boolean output) {
473        this.output = output;
474    }
475
476    public int getParallel() {
477        return parallel;
478    }
479
480    public void setParallel(int parallel) {
481        this.parallel = parallel;
482    }
483
484    public boolean isTraceSQL() {
485        return traceSQL;
486    }
487
488    /**
489     * Whether the comment a script author wrote against an object is carried into the output:
490     * the leading comment of a procedure and of a package, and the comment on the statement
491     * behind a call relationship.
492     *
493     * <p>Off by default, so output that consumers already parse does not grow a field. It is
494     * separate from {@link #isTraceSQL()} because a caller can want the author's label without
495     * the SQL text and hash that tracing attaches. When tracing is on, relationship comments keep
496     * the exact text they have always had; this option adds the blank-line-bounded comment
497     * instead, which is the one that belongs to the statement rather than to the section above it.
498     */
499    public boolean isShowComment() {
500        return showComment;
501    }
502
503    public void setShowComment(boolean showComment) {
504        this.showComment = showComment;
505    }
506
507    public void setTraceSQL(boolean traceSQL) {
508        this.traceSQL = traceSQL;
509    }
510
511    public boolean isIgnoreTopSelect() {
512                return ignoreTopSelect;
513        }
514
515        public void setIgnoreTopSelect(boolean ignoreTopSelect) {
516                this.ignoreTopSelect = ignoreTopSelect;
517        }
518        
519        public void setShowCallRelation(boolean showCallRelation) {
520                this.showCallRelation = showCallRelation;
521        }
522        
523        public boolean isShowCallRelation() {
524                return showCallRelation;
525        }
526
527        public String getFilePathDatabase() {
528                return filePathDatabase;
529        }
530
531        public void setFilePathDatabase(String filePathDatabase) {
532                this.filePathDatabase = filePathDatabase;
533        }
534
535        public String getFilePathSchema() {
536                return filePathSchema;
537        }
538
539        public void setFilePathSchema(String filePathSchema) {
540                this.filePathSchema = filePathSchema;
541        }
542
543        public String getDefaultServer() {
544                return defaultServer;
545        }
546
547        public void setDefaultServer(String defaultServer) {
548                this.defaultServer = defaultServer;
549        }
550
551        public String getDefaultDatabase() {
552                return defaultDatabase;
553        }
554
555        public void setDefaultDatabase(String defaultDatabase) {
556                this.defaultDatabase = defaultDatabase;
557        }
558
559        public String getDefaultSchema() {
560                return defaultSchema;
561        }
562
563        public void setDefaultSchema(String defaultSchema) {
564                this.defaultSchema = defaultSchema;
565        }
566
567    public boolean isShowERDiagram() {
568        return showERDiagram;
569    }
570
571    public void setShowERDiagram(boolean showERDiagram) {
572        this.showERDiagram = showERDiagram;
573    }
574
575    public boolean isIgnoreTemporaryTable() {
576        return ignoreTemporaryTable;
577    }
578
579    public void setIgnoreTemporaryTable(boolean ignoreTemporaryTable) {
580        this.ignoreTemporaryTable = ignoreTemporaryTable;
581    }
582    
583    public void showResultSetTypes(ResultSetType... types) {
584        if(types!=null) {
585                targetResultSetTypes.addAll(Arrays.asList(types));
586        }
587    }
588    
589        public void showResultSetTypes(String... types) {
590                if (types != null) {
591                        for (String type : types) {
592                                ResultSetType resultSetType = ResultSetType.of(type);
593                                if (resultSetType != null) {
594                                        targetResultSetTypes.add(resultSetType);
595                                }
596                        }
597                }
598        }
599    
600    public boolean containsResultSetType(ResultSetType type) {
601        return targetResultSetTypes.contains(type);
602    }
603
604    @Override
605    public Object clone() throws CloneNotSupportedException {
606        return super.clone();
607    }
608    
609        public void filterRelationTypes(String... types) {
610                if (types != null) {
611                        for (String type : types) {
612                                RelationshipType relationshipType = RelationshipType.of(type);
613                                if (relationshipType != null) {
614                                        filterRelationTypes.add(relationshipType.name());
615                                }
616                        }
617                }
618        }
619
620        public Set<String> getFilterRelationTypes() {
621                return filterRelationTypes;
622        }
623
624    public boolean isSimpleShowCursor() {
625        return simpleShowCursor;
626    }
627
628    public void setSimpleShowCursor(boolean simpleShowCursor) {
629        this.simpleShowCursor = simpleShowCursor;
630    }
631
632    public boolean isSimpleShowVariable() {
633        return simpleShowVariable;
634    }
635
636    public void setSimpleShowVariable(boolean simpleShowVariable) {
637        this.simpleShowVariable = simpleShowVariable;
638    }
639
640    public boolean isSimpleRetainIntermediate() {
641        return simpleRetainIntermediate;
642    }
643
644    public void setSimpleRetainIntermediate(boolean simpleRetainIntermediate) {
645        this.simpleRetainIntermediate = simpleRetainIntermediate;
646    }
647
648    public boolean isSimpleShowUdfFunctionOnly() {
649        return simpleShowUdfFunctionOnly;
650    }
651
652    public void setSimpleShowUdfFunctionOnly(boolean simpleShowUdfFunctionOnly) {
653        this.simpleShowUdfFunctionOnly = simpleShowUdfFunctionOnly;
654    }
655
656        public boolean isTraceProcedure() {
657                return traceProcedure;
658        }
659
660        public void setTraceProcedure(boolean traceProcedure) {
661                this.traceProcedure = traceProcedure;
662        }
663
664    public boolean isSqlflowIgnoreFunction() {
665        return sqlflowIgnoreFunction;
666    }
667
668    public void setSqlflowIgnoreFunction(boolean sqlflowIgnoreFunction) {
669        this.sqlflowIgnoreFunction = sqlflowIgnoreFunction;
670    }
671
672    public boolean isShowCaseWhenAsDirect() {
673        return showCaseWhenAsDirect;
674    }
675
676    public void setShowCaseWhenAsDirect(boolean showCaseWhenAsDirect) {
677        this.showCaseWhenAsDirect = showCaseWhenAsDirect;
678    }
679
680        public boolean isIgnoreUnusedSynonym() {
681                return ignoreUnusedSynonym;
682        }
683
684        public void setIgnoreUnusedSynonym(boolean ignoreUnusedSynonym) {
685                this.ignoreUnusedSynonym = ignoreUnusedSynonym;
686        }
687
688        public boolean isShowCandidateTable() {
689                return showCandidateTable;
690        }
691
692        public void setShowCandidateTable(boolean showCandidateTable) {
693                this.showCandidateTable = showCandidateTable;
694        }
695
696        public Set<String> getSimpleShowRelationTypes() {
697                return simpleShowRelationTypes;
698        }
699
700        public void setSimpleShowRelationTypes(String... types) {
701                if (types != null) {
702                        for (String type : types) {
703                                RelationshipType relationshipType = RelationshipType.of(type);
704                                if (relationshipType != null) {
705                                        simpleShowRelationTypes.add(relationshipType.name());
706                                }
707                        }
708                }
709        }
710        
711        public void setSimpleShowRelationTypes(RelationshipType... types) {
712                if (types != null) {
713                        for (RelationshipType relationshipType : types) {
714                                simpleShowRelationTypes.add(relationshipType.name());
715                        }
716                }
717        }
718
719        public AnalyzeMode getAnalyzeMode() {
720                return analyzeMode;
721        }
722
723        public void setAnalyzeMode(AnalyzeMode analyzeMode) {
724                this.analyzeMode = analyzeMode;
725        }
726
727        public boolean isIgnoreInsertIntoValues() {
728                return ignoreInsertIntoValues;
729        }
730
731        public void setIgnoreInsertIntoValues(boolean ignoreInsertIntoValues) {
732                this.ignoreInsertIntoValues = ignoreInsertIntoValues;
733        }
734
735    public boolean isNormalizeOutput() {
736        return normalizeOutput;
737    }
738
739    public void setNormalizeOutput(boolean normalizeOutput) {
740        this.normalizeOutput = normalizeOutput;
741    }
742
743        public boolean isTraceTablePosition() {
744                return traceTablePosition;
745        }
746
747        public void setTraceTablePosition(boolean traceTablePosition) {
748                this.traceTablePosition = traceTablePosition;
749        }
750
751        public boolean isEnablePipelinedStitching() {
752                return enablePipelinedStitching;
753        }
754
755        public boolean isIdentityFirstVariablePools() {
756                return identityFirstVariablePools;
757        }
758
759        public void setIdentityFirstVariablePools(boolean identityFirstVariablePools) {
760                this.identityFirstVariablePools = identityFirstVariablePools;
761        }
762
763        public void setEnablePipelinedStitching(boolean enablePipelinedStitching) {
764                this.enablePipelinedStitching = enablePipelinedStitching;
765        }
766
767        public int getMaxPipelinedExpansionDepth() {
768                return maxPipelinedExpansionDepth;
769        }
770
771        public void setMaxPipelinedExpansionDepth(int maxPipelinedExpansionDepth) {
772                this.maxPipelinedExpansionDepth = maxPipelinedExpansionDepth;
773        }
774
775        public int getMaxStitchedSourcesPerColumn() {
776        return maxStitchedSourcesPerColumn;
777    }
778
779    public void setMaxStitchedSourcesPerColumn(int maxStitchedSourcesPerColumn) {
780        this.maxStitchedSourcesPerColumn = maxStitchedSourcesPerColumn;
781    }
782
783    public Set<String> getExcludedProcedureNames() {
784        return excludedProcedureNames;
785    }
786
787    public void setExcludedProcedureNames(Set<String> excludedProcedureNames) {
788        this.excludedProcedureNames = excludedProcedureNames;
789    }
790
791    public void addExcludedProcedureName(String name) {
792        if (name != null && !name.isEmpty()) {
793            this.excludedProcedureNames.add(name);
794        }
795    }
796
797    public void addExcludedProcedureNames(String... names) {
798        if (names != null) {
799            for (String name : names) {
800                addExcludedProcedureName(name);
801            }
802        }
803    }
804
805    /**
806     * @see #excludedProcedurePatterns
807     */
808    public Set<String> getExcludedProcedurePatterns() {
809        return excludedProcedurePatterns;
810    }
811
812    /**
813     * @see #excludedProcedurePatterns
814     */
815    public void setExcludedProcedurePatterns(Set<String> excludedProcedurePatterns) {
816        this.excludedProcedurePatterns = excludedProcedurePatterns;
817    }
818
819    /**
820     * Adds a wildcard pattern to exclude procedures from analysis.
821     * @param pattern A wildcard pattern (e.g., "SCHEMA1.*", "*.SP_*", "*_TEST")
822     * @see #excludedProcedurePatterns
823     */
824    public void addExcludedProcedurePattern(String pattern) {
825        if (pattern != null && !pattern.isEmpty()) {
826            this.excludedProcedurePatterns.add(pattern);
827        }
828    }
829
830    /**
831     * Adds multiple wildcard patterns to exclude procedures from analysis.
832     * @param patterns Variable number of wildcard patterns
833     * @see #excludedProcedurePatterns
834     */
835    public void addExcludedProcedurePatterns(String... patterns) {
836        if (patterns != null) {
837            for (String pattern : patterns) {
838                addExcludedProcedurePattern(pattern);
839            }
840        }
841    }
842
843    /**
844     * Whether to automatically detect large files and delegate to
845     * {@link gudusoft.gsqlparser.dlineage.ParallelDataFlowAnalyzer} for parallel
846     * processing. When enabled, the {@link gudusoft.gsqlparser.dlineage.util.LargeFileDetector}
847     * checks the input scale against the configured thresholds and may split large
848     * files or route sharded manifests to the parallel analyzer. Default {@code false}.
849     */
850    private boolean autoDetectLargeFile = false;
851
852    public boolean isAutoDetectLargeFile() {
853        return autoDetectLargeFile;
854    }
855
856    public void setAutoDetectLargeFile(boolean autoDetectLargeFile) {
857        this.autoDetectLargeFile = autoDetectLargeFile;
858    }
859
860    /**
861     * Threshold for the number of {@link SqlInfo} entries that triggers large-file
862     * delegation. When the total SqlInfo count reaches this value, the analyzer
863     * delegates to parallel processing. Default {@code 1000}.
864     */
865    private int largeSqlInfoCountThreshold = 1000;
866
867    public int getLargeSqlInfoCountThreshold() {
868        return largeSqlInfoCountThreshold;
869    }
870
871    public void setLargeSqlInfoCountThreshold(int largeSqlInfoCountThreshold) {
872        this.largeSqlInfoCountThreshold = largeSqlInfoCountThreshold;
873    }
874
875    /**
876     * Threshold for the total SQL content size (in bytes) that triggers large-file
877     * delegation. When the sum of all SQL string lengths reaches this value, the
878     * analyzer delegates to parallel processing. Default {@code 25 * 1024 * 1024} (25 MB).
879     */
880    private long largeSqlTotalSizeThreshold = 25 * 1024 * 1024L;
881
882    public long getLargeSqlTotalSizeThreshold() {
883        return largeSqlTotalSizeThreshold;
884    }
885
886    public void setLargeSqlTotalSizeThreshold(long largeSqlTotalSizeThreshold) {
887        this.largeSqlTotalSizeThreshold = largeSqlTotalSizeThreshold;
888    }
889
890    /**
891     * Threshold for the number of queries in a sqlflow/grabit manifest that triggers
892     * large-file delegation. When the manifest contains this many queries, the analyzer
893     * delegates to parallel processing. Default {@code 1000}.
894     */
895    private int largeQueryCountThreshold = 1000;
896
897    public int getLargeQueryCountThreshold() {
898        return largeQueryCountThreshold;
899    }
900
901    public void setLargeQueryCountThreshold(int largeQueryCountThreshold) {
902        this.largeQueryCountThreshold = largeQueryCountThreshold;
903    }
904
905    /**
906     * Threshold for the number of shards in a sqlflow-sharded manifest that triggers
907     * large-file delegation. When the manifest references this many shard source files,
908     * the analyzer delegates to parallel processing. Default {@code 10}.
909     */
910    private int largeShardCountThreshold = 10;
911
912    public int getLargeShardCountThreshold() {
913        return largeShardCountThreshold;
914    }
915
916    public void setLargeShardCountThreshold(int largeShardCountThreshold) {
917        this.largeShardCountThreshold = largeShardCountThreshold;
918    }
919
920    /**
921     * Maximum size in MB for a single file before it is split into smaller chunks
922     * for parallel processing. Files larger than this threshold are split using
923     * {@link gudusoft.gsqlparser.util.FileSplitter}. Default {@code 5} (5 MB).
924     */
925    private int largeFileSplitSizeMB = 5;
926
927    public int getLargeFileSplitSizeMB() {
928        return largeFileSplitSizeMB;
929    }
930
931    public void setLargeFileSplitSizeMB(int largeFileSplitSizeMB) {
932        this.largeFileSplitSizeMB = largeFileSplitSizeMB;
933    }
934
935    /**
936     * Multiplier applied to {@link #largeFileSplitSizeMB} to calculate the
937     * byte threshold in {@link #isLargeSql(String)}. Default {@code 1.5}.
938     */
939    private double largeSqlThresholdMultiplier = 1.5;
940
941    public double getLargeSqlThresholdMultiplier() {
942        return largeSqlThresholdMultiplier;
943    }
944
945    public void setLargeSqlThresholdMultiplier(double largeSqlThresholdMultiplier) {
946        this.largeSqlThresholdMultiplier = largeSqlThresholdMultiplier;
947    }
948
949    public boolean isLargeSql(String sql) {
950        if (!autoDetectLargeFile || sql == null) {
951            return false;
952        }
953        return sql.length() > getLargeSqlThresholdBytes();
954    }
955
956    public long getLargeSqlThresholdBytes() {
957        return (long) (largeFileSplitSizeMB * largeSqlThresholdMultiplier * 1024 * 1024);
958    }
959
960    /**
961     * Estimated memory consumption per parallel task in MB, used to calculate the
962     * thread pool size in {@code ParallelDataFlowAnalyzer}. The pool size is capped
963     * so that {@code poolSize * estimatedMemoryPerTaskMB} does not exceed the JVM's
964     * available memory. Default {@code 2560} (2.5 GB).
965     */
966    private long estimatedMemoryPerTaskMB = 2560L;
967
968    public long getEstimatedMemoryPerTaskMB() {
969        return estimatedMemoryPerTaskMB;
970    }
971
972    public void setEstimatedMemoryPerTaskMB(long estimatedMemoryPerTaskMB) {
973        this.estimatedMemoryPerTaskMB = estimatedMemoryPerTaskMB;
974    }
975
976}