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}