001package gudusoft.gsqlparser.analyzer.v2;
002
003import gudusoft.gsqlparser.EDbVendor;
004import gudusoft.gsqlparser.TGSqlParser;
005import gudusoft.gsqlparser.ir.builder.IBoundIRBuilder;
006import gudusoft.gsqlparser.ir.builder.IRTranslator;
007import gudusoft.gsqlparser.ir.builder.mssql.MssqlBoundIRBuilder;
008import gudusoft.gsqlparser.ir.builder.mssql.MssqlRoutineRefResolver;
009import gudusoft.gsqlparser.analyzer.v2.callgraph.CallGraph;
010import gudusoft.gsqlparser.ir.bound.BoundProgram;
011
012import java.util.List;
013
014/**
015 * Main entry point for the IR-based Analyzer v2.
016 * <p>
017 * Provides high-level APIs for analyzing SQL/PL/SQL text and producing
018 * structured analysis results (call graphs, impact analysis, lineage).
019 * <p>
020 * Phase A: Parses SQL and produces BoundProgram with routine/object/column refs.
021 * Phase B: Adds CallGraph and impact analysis.
022 * Phase C: Adds CFG/DFG and legacy diff.
023 */
024public class AnalyzerV2Facade {
025
026    private final IBoundIRBuilder boundBuilder;
027
028    public AnalyzerV2Facade(IBoundIRBuilder boundBuilder) {
029        this.boundBuilder = boundBuilder;
030    }
031
032    /**
033     * Analyzes the given SQL/PL/SQL text and returns the analysis result.
034     *
035     * @param sqlText SQL or PL/SQL text to analyze
036     * @param vendor  database vendor
037     * @param config  analyzer configuration
038     * @return analysis result
039     */
040    public AnalysisResult analyze(String sqlText, EDbVendor vendor, AnalyzerV2Config config) {
041        // Gate IR construction behind useIRPath flag
042        if (config != null && !config.useIRPath) {
043            return new AnalysisResult(new IRProgram(new BoundProgram()));
044        }
045
046        TGSqlParser parser = new TGSqlParser(vendor);
047        parser.sqltext = sqlText;
048        int result = parser.parse();
049        if (result != 0) {
050            // Return empty result on parse failure
051            return new AnalysisResult(new IRProgram(new BoundProgram()));
052        }
053
054        IRTranslator translator = new IRTranslator(config, boundBuilder);
055        IRProgram irProgram = translator.translate(parser.sqlstatements);
056
057        // For MSSQL, run resolver (not done inside build)
058        if (boundBuilder instanceof MssqlBoundIRBuilder) {
059            MssqlRoutineRefResolver.resolve(irProgram.getBoundProgram());
060        }
061
062        // Build CallGraph from BoundProgram
063        CallGraph callGraph = CallGraph.buildFrom(irProgram.getBoundProgram());
064        AnalysisResult analysisResult = new AnalysisResult(irProgram, callGraph);
065        analysisResult.setConfig(config);
066        return analysisResult;
067    }
068
069    /**
070     * Analyzes multiple SQL/PL/SQL files.
071     *
072     * @param sqlTexts list of SQL texts (one per file)
073     * @param vendor   database vendor
074     * @param config   analyzer configuration
075     * @return analysis result combining all files
076     */
077    public AnalysisResult analyzeMultiple(List<String> sqlTexts, EDbVendor vendor, AnalyzerV2Config config) {
078        // Phase A: analyze each text independently and merge bound programs
079        BoundProgram mergedProgram = new BoundProgram();
080
081        for (String sqlText : sqlTexts) {
082            TGSqlParser parser = new TGSqlParser(vendor);
083            parser.sqltext = sqlText;
084            int result = parser.parse();
085            if (result != 0) {
086                continue;
087            }
088
089            BoundProgram partial = boundBuilder.build(parser.sqlstatements, config);
090            mergeBoundPrograms(mergedProgram, partial);
091        }
092
093        // For MSSQL, run post-merge cross-file resolution
094        if (boundBuilder instanceof MssqlBoundIRBuilder) {
095            MssqlRoutineRefResolver.resolve(mergedProgram);
096        }
097
098        CallGraph callGraph = CallGraph.buildFrom(mergedProgram);
099        return new AnalysisResult(new IRProgram(mergedProgram), callGraph);
100    }
101
102    /**
103     * Analyzes multiple SQL scripts with file identifiers for cross-script traceability.
104     * <p>
105     * Each {@link SqlScriptInput} pairs a SQL text with a file ID. Source anchors in
106     * the resulting call graph carry the file ID, enabling cross-file navigation.
107     * <p>
108     * For MSSQL, this method uses a specialized flow:
109     * <ol>
110     *   <li>Parse each script independently</li>
111     *   <li>Build partial BoundProgram per file with fileId via {@link MssqlBoundIRBuilder}</li>
112     *   <li>Merge all partial programs (last-writer-wins for duplicate routineIds)</li>
113     *   <li>Run {@link MssqlRoutineRefResolver} post-merge for cross-file resolution</li>
114     *   <li>Build CallGraph from merged program</li>
115     * </ol>
116     *
117     * @param inputs list of SQL script inputs with file IDs
118     * @param vendor database vendor
119     * @param config analyzer configuration
120     * @return analysis result combining all files with fileId traceability
121     */
122    public AnalysisResult analyzeMultipleWithFileId(List<SqlScriptInput> inputs, EDbVendor vendor, AnalyzerV2Config config) {
123        BoundProgram mergedProgram = new BoundProgram();
124
125        for (SqlScriptInput input : inputs) {
126            TGSqlParser parser = new TGSqlParser(vendor);
127            parser.sqltext = input.sqlText;
128            int result = parser.parse();
129            if (result != 0) {
130                continue;
131            }
132
133            BoundProgram partial;
134            if (boundBuilder instanceof MssqlBoundIRBuilder) {
135                partial = ((MssqlBoundIRBuilder) boundBuilder).build(
136                        parser.sqlstatements, config, input.fileId);
137            } else {
138                partial = boundBuilder.build(parser.sqlstatements, config);
139            }
140            mergeBoundPrograms(mergedProgram, partial);
141        }
142
143        // Post-merge resolution for MSSQL
144        if (boundBuilder instanceof MssqlBoundIRBuilder) {
145            MssqlRoutineRefResolver.resolve(mergedProgram);
146        }
147
148        CallGraph callGraph = CallGraph.buildFrom(mergedProgram);
149        AnalysisResult analysisResult = new AnalysisResult(new IRProgram(mergedProgram), callGraph);
150        analysisResult.setConfig(config);
151        return analysisResult;
152    }
153
154    private void mergeBoundPrograms(BoundProgram target, BoundProgram source) {
155        for (gudusoft.gsqlparser.ir.bound.BoundScope scope : source.getScopes()) {
156            target.addScope(scope);
157        }
158        for (java.util.Map.Entry<String, gudusoft.gsqlparser.ir.bound.BoundRoutineSymbol> entry
159                : source.getRoutineIndex().entrySet()) {
160            target.registerRoutine(entry.getValue());
161        }
162        for (gudusoft.gsqlparser.ir.bound.BoundObjectRef ref : source.getAllObjectRefs()) {
163            target.addObjectRef(ref);
164        }
165        for (gudusoft.gsqlparser.ir.bound.BoundColumnRef ref : source.getAllColumnRefs()) {
166            target.addColumnRef(ref);
167        }
168        for (gudusoft.gsqlparser.ir.bound.BoundRoutineRef ref : source.getAllRoutineRefs()) {
169            target.addRoutineRef(ref);
170        }
171    }
172}