001package gudusoft.gsqlparser.dlineage;
002
003import gudusoft.gsqlparser.EDbVendor;
004import gudusoft.gsqlparser.TBaseType;
005import gudusoft.gsqlparser.TGSqlParser;
006import gudusoft.gsqlparser.dlineage.dataflow.listener.DataFlowHandleListener;
007import gudusoft.gsqlparser.dlineage.dataflow.metadata.MetadataReader;
008import gudusoft.gsqlparser.dlineage.dataflow.metadata.grabit.GrabitMetadataAnalyzer;
009import gudusoft.gsqlparser.dlineage.dataflow.metadata.sqlflow.SqlflowMetadataAnalyzer;
010import gudusoft.gsqlparser.dlineage.dataflow.metadata.sqlflow.sharded.SqlflowShardedMetadataAnalyzer;
011import gudusoft.gsqlparser.dlineage.dataflow.model.*;
012import gudusoft.gsqlparser.dlineage.dataflow.model.json.Coordinate;
013import gudusoft.gsqlparser.dlineage.dataflow.model.xml.*;
014import gudusoft.gsqlparser.dlineage.dataflow.sqlenv.SQLEnvParser;
015import gudusoft.gsqlparser.dlineage.util.*;
016import gudusoft.gsqlparser.sqlenv.ESQLDataObjectType;
017import gudusoft.gsqlparser.sqlenv.IdentifierProfile;
018import gudusoft.gsqlparser.sqlenv.IdentifierService;
019import gudusoft.gsqlparser.sqlenv.QualifiedCanonKey;
020import gudusoft.gsqlparser.sqlenv.TSQLEnv;
021
022import gudusoft.gsqlparser.util.IndexedLinkedHashMap;
023import gudusoft.gsqlparser.util.Logger;
024import gudusoft.gsqlparser.util.LoggerFactory;
025import gudusoft.gsqlparser.util.SQLUtil;
026import gudusoft.gsqlparser.util.json.JSON;
027
028import java.io.File;
029import java.io.FileInputStream;
030import java.io.IOException;
031import java.io.BufferedReader;
032import java.io.InputStreamReader;
033import java.util.zip.GZIPInputStream;
034import java.util.*;
035import java.util.concurrent.*;
036import java.util.concurrent.atomic.AtomicInteger;
037
038public class ParallelDataFlowAnalyzer implements IDataFlowAnalyzer {
039    private static final Logger logger = LoggerFactory.getLogger(ParallelDataFlowAnalyzer.class);
040    private SqlInfo[] sqlInfos;
041    private Option option = new Option();
042    private TSQLEnv sqlenv = null;
043    private dataflow dataflow;
044    private String dataflowString;
045    private List<ErrorInfo> errorInfos = new CopyOnWriteArrayList<ErrorInfo>();
046    private IndexedLinkedHashMap<String, List<SqlInfo>> sqlInfoMap = new IndexedLinkedHashMap<String, List<SqlInfo>>();
047    //autogenerate 并发任务在 run() 中并发 putAll,必须使用并发容器,否则 HashMap 并发写入会丢失/损坏条目
048    private final Map<String, String> hashSQLMap = new ConcurrentHashMap<>();
049    private dataflow metadataFlow;
050
051    public ParallelDataFlowAnalyzer(SqlInfo[] sqlInfos, EDbVendor dbVendor, boolean simpleOutput) {
052        this.sqlInfos = sqlInfos;
053        option.setVendor(dbVendor);
054        option.setSimpleOutput(simpleOutput);
055        ModelBindingManager.setGlobalOption(option);
056    }
057
058    public ParallelDataFlowAnalyzer(String[] sqlContents, EDbVendor dbVendor, boolean simpleOutput, String defaultServer, String defaultDatabase, String defaltSchema) {
059        SqlInfo[] sqlInfos = new SqlInfo[sqlContents.length];
060        for (int i = 0; i < sqlContents.length; i++) {
061            SqlInfo info = new SqlInfo();
062            info.setSql(sqlContents[i]);
063            info.setOriginIndex(0);
064            sqlInfos[i] = info;
065        }
066        option.setVendor(dbVendor);
067        option.setSimpleOutput(simpleOutput);
068                option.setDefaultServer(defaultServer);
069                option.setDefaultDatabase(defaultDatabase);
070                option.setDefaultSchema(defaltSchema);
071        this.sqlInfos = convertSQL(dbVendor, JSON.toJSONString(sqlInfos)).toArray(new SqlInfo[0]);
072    }
073    
074    public ParallelDataFlowAnalyzer(String[] sqlContents, EDbVendor dbVendor, boolean simpleOutput) {
075        SqlInfo[] sqlInfos = new SqlInfo[sqlContents.length];
076        for (int i = 0; i < sqlContents.length; i++) {
077            SqlInfo info = new SqlInfo();
078            info.setSql(sqlContents[i]);
079            info.setOriginIndex(0);
080            sqlInfos[i] = info;
081        }
082        option.setVendor(dbVendor);
083        option.setSimpleOutput(simpleOutput);
084        this.sqlInfos = convertSQL(dbVendor, JSON.toJSONString(sqlInfos)).toArray(new SqlInfo[0]);
085    }
086
087    public ParallelDataFlowAnalyzer(SqlInfo[] sqlInfos, Option option) {
088        this.sqlInfos = sqlInfos;
089        this.option = option;
090        ModelBindingManager.setGlobalOption(option);
091    }
092
093    public ParallelDataFlowAnalyzer(File[] sqlFiles, Option option) {
094        SqlInfo[] sqlInfos = new SqlInfo[sqlFiles.length];
095        for (int i = 0; i < sqlFiles.length; i++) {
096            SqlInfo info = new SqlInfo();
097            info.setSql(SQLUtil.getFileContent(sqlFiles[i]));
098            info.setFileName(sqlFiles[i].getName());
099            info.setFilePath(sqlFiles[i].getAbsolutePath());
100            info.setOriginIndex(0);
101            sqlInfos[i] = info;
102        }
103        this.sqlInfos = sqlInfos;
104        this.option = option;
105        ModelBindingManager.setGlobalOption(option);
106    }
107    
108    public ParallelDataFlowAnalyzer(File[] sqlFiles, Option option, int splitSizeMB, File splitDir) {
109        List<SqlInfo> sqlInfoList = new ArrayList<SqlInfo>();
110        for (int i = 0; i < sqlFiles.length; i++) {
111            try {
112                // Ensure split directory exists
113                if (!splitDir.exists() && !splitDir.mkdirs()) {
114                    throw new IOException("Failed to create split directory: " + splitDir.getAbsolutePath());
115                }
116                
117                // Split the file if it's large
118                List<File> splitFiles = gudusoft.gsqlparser.util.FileSplitter.splitFile(sqlFiles[i], splitDir, splitSizeMB, option.getVendor());
119                
120                if (splitFiles.isEmpty()) {
121                    // If no split files were created, process the original file
122                    SqlInfo info = new SqlInfo();
123                    info.setSql(SQLUtil.getFileContent(sqlFiles[i]));
124                    info.setFileName(sqlFiles[i].getName());
125                    info.setFilePath(sqlFiles[i].getAbsolutePath());
126                    info.setOriginIndex(0);
127                    sqlInfoList.add(info);
128                } else {
129                    // Process each split file
130                    for (File splitFile : splitFiles) {
131                        SqlInfo info = new SqlInfo();
132                        info.setSql(SQLUtil.getFileContent(splitFile));
133                        info.setSourceOriginKind("derived");
134                        info.setFileName(splitFile.getName());
135                        info.setFilePath(splitFile.getAbsolutePath());
136                        info.setOriginIndex(0);
137                        sqlInfoList.add(info);
138                    }
139                }
140            } catch (Exception e) {
141                sqlInfoList.clear();
142                logger.error("Error splitting file: " + sqlFiles[i].getAbsolutePath(), e);
143                // Fallback to original file if splitting fails
144                SqlInfo info = new SqlInfo();
145                info.setSql(SQLUtil.getFileContent(sqlFiles[i]));
146                info.setFileName(sqlFiles[i].getName());
147                info.setFilePath(sqlFiles[i].getAbsolutePath());
148                info.setOriginIndex(0);
149                sqlInfoList.add(info);
150            }
151        }
152        this.sqlInfos = sqlInfoList.toArray(new SqlInfo[0]);
153        this.option = option;
154        ModelBindingManager.setGlobalOption(option);
155    }
156    
157    public ParallelDataFlowAnalyzer(File[] sqlFiles, Option option, int splitSizeMB, String splitDirPath) {
158        this(sqlFiles, option, splitSizeMB, new File(splitDirPath));
159    }
160    
161    protected List<SqlInfo> convertSQL(EDbVendor vendor, String json) {
162                List<SqlInfo> sqlInfos = new ArrayList<SqlInfo>();
163                List sqlContents = (List) JSON.parseObject(json);
164                for (int j = 0; j < sqlContents.size(); j++) {
165                        Map sqlContent = (Map) sqlContents.get(j);
166                        String sql = (String) sqlContent.get("sql");
167                        String fileName = (String) sqlContent.get("fileName");
168                        String filePath = (String) sqlContent.get("filePath");
169                        if (sql != null && sql.trim().startsWith("{")) {
170                                if (sql.indexOf("createdBy") != -1) {
171                        // Exclude sqlflow-sharded manifests from the brand branch so a
172                        // SQLdep/grabit-branded sharded export routes to the sharded
173                        // reader below instead of the legacy queries[] path.
174                        if ((sql.toLowerCase(Locale.ROOT).indexOf("sqldep") != -1 || sql.toLowerCase(Locale.ROOT).indexOf("grabit") != -1)
175                                        && !gudusoft.gsqlparser.dlineage.dataflow.metadata.MetadataReader.isSqlflowSharded(sql)) {
176                                Map queryObject = (Map) JSON.parseObject(sql);
177                                        List querys = (List) queryObject.get("queries");
178                                        if (querys != null) {
179                                                for (int i = 0; i < querys.size(); i++) {
180                                                        Map object = (Map) querys.get(i);
181                                                        SqlInfo info = new SqlInfo();
182                                                        info.setSql(JSON.toJSONString(object));
183                                                        info.setFileName(fileName);
184                                                        info.setFilePath(filePath);
185                                                        info.setOriginIndex(i);
186                                                        sqlInfos.add(info);
187                                                }
188                                                queryObject.remove("queries");
189                                                SqlInfo info = new SqlInfo();
190                                                info.setSql(JSON.toJSONString(queryObject));
191                                                info.setFileName(fileName);
192                                                info.setFilePath(filePath);
193                                                info.setOriginIndex(querys.size());
194                                                sqlInfos.add(info);
195                                        } else {
196                                                SqlInfo info = new SqlInfo();
197                                                info.setSql(JSON.toJSONString(queryObject));
198                                                info.setFileName(fileName);
199                                                info.setFilePath(filePath);
200                                                info.setOriginIndex(0);
201                                                sqlInfos.add(info);
202                                        }
203                        }
204                        else if (sql.toLowerCase(Locale.ROOT).indexOf("sqlflow") != -1) {
205                                                Map sqlflow = (Map) JSON.parseObject(sql);
206                                                if ("sqlflow-sharded".equals(sqlflow.get("format"))
207                                                                && !gudusoft.gsqlparser.dlineage.dataflow.metadata.MetadataReader.isSupportedSqlflowSharded(sql)) {
208                                                        // Unknown future formatVersion: do not consume it as a
209                                                        // version we understand.
210                                                } else if ("sqlflow-sharded".equals(sqlflow.get("format"))) {
211                                                        String baseDir = null;
212                                                        if (filePath != null) {
213                                                                baseDir = new File(filePath).getParent();
214                                                        }
215                                                        String sourceCompression = (String) sqlflow.get("sourceCompression");
216                                                        List<Map> servers = (List<Map>) sqlflow.get("servers");
217                                                        if (servers != null) {
218                                                                for (Map serverObject : servers) {
219                                                                        String name = (String) serverObject.get("name");
220                                                                        String dbVendor = (String) serverObject.get("dbVendor");
221                                                                        List<Map> databases = (List<Map>) serverObject.get("databases");
222                                                                        // Source shards are under databases[] (catalog topology) or
223                                                                        // schemas[] (schema topology, the natural Oracle shape). Read
224                                                                        // whichever is present; databases-only dropped every source
225                                                                        // record of a schema-topology manifest -> empty lineage.
226                                                                        List<Map> schemaShards = (List<Map>) serverObject.get("schemas");
227                                                                        if (schemaShards != null && !schemaShards.isEmpty()) {
228                                                                                databases = schemaShards;
229                                                                        }
230                                                                        if (databases != null) {
231                                                                                for (Map database : databases) {
232                                                                                        Map source = (Map) database.get("source");
233                                                                                        if (source != null) {
234                                                                                                String sourcePath = (String) source.get("path");
235                                                                                                if (sourcePath != null && baseDir != null) {
236                                                    File sourceFile = new File(baseDir, sourcePath);
237                                                    String fullPath = sourceFile.getAbsolutePath();
238                                                    if ("block".equals(sourceCompression)) {
239                                                                                                                readGzipBlockSource(fullPath, fileName, filePath, name, dbVendor, sqlInfos);
240                                                                                                        } else {
241                                                                                                                String sourceContent = SQLUtil.getFileContent(fullPath);
242                                                                                                                if (sourceContent != null) {
243                                                                                                                        String[] lines = sourceContent.split("\\r?\\n");
244                                                                                                                        for (int i = 0; i < lines.length; i++) {
245                                                                                                                                String line = lines[i].trim();
246                                                                                                                                if (line.isEmpty()) {
247                                                                                                                                        continue;
248                                                                                                                                }
249                                                                                                                                try {
250                                                                                                                                        Map sourceObject = (Map) JSON.parseObject(line);
251                                                                                                                                        String sourceCode = (String) sourceObject.get("sourceCode");
252                                                                                                                                        if (sourceCode != null && !sourceCode.isEmpty() && !DataFlowAnalyzer.isSourceUnavailable(sourceObject)) {
253                                                                                                                                                SqlInfo info = new SqlInfo();
254                                                                                                                                                info.setSql(sourceCode);
255                                                                                                                                                info.setFileName(sourceFile.getName());
256                                                                                                                                                info.setFilePath(sourceFile.getAbsolutePath());
257                                                                                                                                                info.setOriginIndex(i);
258                                                                                                                                                info.setDbVendor(dbVendor);
259                                                                                                                                                info.setServer(name);
260                                                                                                                                                sqlInfos.add(info);
261                                                                                                                                        }
262                                                                                                                                } catch (Exception e) {
263                                                                                                                                        logger.warn("Parse source jsonl line failed.", e);
264                                                                                                                                }
265                                                                                                                        }
266                                                                                                                }
267                                                                                                        }
268                                                                                                }
269                                                                                        }
270                                                                                }
271                                                                        }
272                                                                        SqlInfo serverInfo = new SqlInfo();
273                                                                        serverInfo.setSql(JSON.toJSONString(serverObject));
274                                                                        serverInfo.setFileName(fileName);
275                                                                        serverInfo.setFilePath(filePath);
276                                                                        serverInfo.setDbVendor(dbVendor);
277                                                                        serverInfo.setServer(name);
278                                                                        sqlInfos.add(serverInfo);
279                                                                }
280                                                        }
281                                                } else {
282                                                        List<Map> servers = (List<Map>) sqlflow.get("servers");
283                                                        if (servers != null) {
284                                                                for (Map queryObject : servers) {
285                                                                        String name = (String) queryObject.get("name");
286                                                                        String dbVendor = (String) queryObject.get("dbVendor");
287                                                                        List querys = (List) queryObject.get("queries");
288                                                                        if (querys != null) {
289                                                                                for (int i = 0; i < querys.size(); i++) {
290                                                                                        Map object = (Map) querys.get(i);
291                                                                                        SqlInfo info = new SqlInfo();
292                                                                                        info.setSql(JSON.toJSONString(object));
293                                                                                        info.setFileName(fileName);
294                                                                                        info.setFilePath(filePath);
295                                                                                        info.setOriginIndex(i);
296                                                                                        info.setDbVendor(dbVendor);
297                                                                                        info.setServer(name);
298                                                                                        sqlInfos.add(info);
299                                                                                }
300                                                                                queryObject.remove("queries");
301                                                                                SqlInfo info = new SqlInfo();
302                                                                                info.setSql(JSON.toJSONString(queryObject));
303                                                                                info.setFileName(fileName);
304                                                                                info.setFilePath(filePath);
305                                                                                info.setOriginIndex(querys.size());
306                                                                                info.setDbVendor(dbVendor);
307                                                                                info.setServer(filePath);
308                                                                                sqlInfos.add(info);
309                                                                        } else {
310                                                                                SqlInfo info = new SqlInfo();
311                                                                                info.setSql(JSON.toJSONString(queryObject));
312                                                                                info.setFileName(fileName);
313                                                                                info.setFilePath(filePath);
314                                                                                info.setOriginIndex(0);
315                                                                                sqlInfos.add(info);
316                                                                        }
317                                                                }
318                                                        }
319                                                }
320                                        }
321                    }
322                        } else if (sql != null) {
323                                SqlInfo info = new SqlInfo();
324                                info.setSql(sql);
325                                info.setFileName(fileName);
326                                info.setFilePath(filePath);
327                                info.setOriginIndex(0);
328                                sqlInfos.add(info);
329                        }
330                }
331                return sqlInfos;
332        }
333
334        private void readGzipBlockSource(String fullPath, String fileName, String filePath, String serverName, String dbVendor, List<SqlInfo> sqlInfos) {
335                try (FileInputStream fis = new FileInputStream(fullPath);
336                         GZIPInputStream gzis = new GZIPInputStream(fis);
337                         BufferedReader reader = new BufferedReader(new InputStreamReader(gzis, "UTF-8"))) {
338                        String line;
339                        int j = 0;
340                        while ((line = reader.readLine()) != null) {
341                                line = line.trim();
342                                if (line.isEmpty()) {
343                                        continue;
344                                }
345                                try {
346                                        Map sourceObject = (Map) JSON.parseObject(line);
347                                        String sourceCode = (String) sourceObject.get("sourceCode");
348                                        if (sourceCode != null && !sourceCode.isEmpty() && !DataFlowAnalyzer.isSourceUnavailable(sourceObject)) {
349                                                SqlInfo info = new SqlInfo();
350                                                info.setSql(sourceCode);
351                                                info.setFileName(fileName);
352                                                info.setFilePath(filePath);
353                                                info.setOriginIndex(j);
354                                                info.setDbVendor(dbVendor);
355                                                info.setServer(serverName);
356                                                sqlInfos.add(info);
357                                        }
358                                } catch (Exception e) {
359                                        logger.warn("Parse gzip source jsonl line failed.", e);
360                                }
361                                j++;
362                        }
363                } catch (Exception e) {
364                        logger.warn("Read gzip source file failed: " + fullPath, e);
365                }
366        }
367
368    @Override
369    public boolean isIgnoreRecordSet() {
370        return option.isIgnoreRecordSet();
371    }
372
373    @Override
374    public void setIgnoreRecordSet(boolean ignoreRecordSet) {
375        option.setIgnoreRecordSet(ignoreRecordSet);
376    }
377
378    @Override
379    public boolean isSimpleShowTopSelectResultSet() {
380        return option.isSimpleShowTopSelectResultSet();
381    }
382
383    @Override
384    public void setSimpleShowTopSelectResultSet(boolean simpleShowTopSelectResultSet) {
385        option.setSimpleShowTopSelectResultSet(simpleShowTopSelectResultSet);
386    }
387
388    @Override
389    public boolean isSimpleShowFunction() {
390        return option.isSimpleShowFunction();
391    }
392
393    @Override
394    public void setSimpleShowFunction(boolean simpleShowFunction) {
395        option.setSimpleShowFunction(simpleShowFunction);
396    }
397
398    @Override
399    public boolean isShowJoin() {
400        return option.isShowJoin();
401    }
402
403    @Override
404    public void setShowJoin(boolean showJoin) {
405        option.setShowJoin(showJoin);
406    }
407
408    @Override
409    public boolean isShowImplicitSchema() {
410        return option.isShowImplicitSchema();
411    }
412
413    @Override
414    public void setShowImplicitSchema(boolean showImplicitSchema) {
415        option.setShowImplicitSchema(showImplicitSchema);
416    }
417
418    @Override
419    public boolean isShowConstantTable() {
420        return option.isShowConstantTable();
421    }
422
423    @Override
424    public void setShowConstantTable(boolean showConstantTable) {
425        option.setShowConstantTable(showConstantTable);
426    }
427
428    @Override
429    public boolean isShowCountTableColumn() {
430        return option.isShowCountTableColumn();
431    }
432
433    @Override
434    public void setShowCountTableColumn(boolean showCountTableColumn) {
435        option.setShowCountTableColumn(showCountTableColumn);
436    }
437
438    @Override
439    public boolean isTransform() {
440        return option.isTransform();
441    }
442
443    @Override
444    public void setTransform(boolean transform) {
445        option.setTransform(transform);
446        if (option.isTransformCoordinate()) {
447            option.setTransform(true);
448        }
449    }
450
451    @Override
452    public boolean isTransformCoordinate() {
453        return option.isTransformCoordinate();
454    }
455
456    @Override
457    public void setTransformCoordinate(boolean transformCoordinate) {
458        option.setTransformCoordinate(transformCoordinate);
459        if (transformCoordinate) {
460            option.setTransform(true);
461        }
462    }
463
464    @Override
465    public boolean isLinkOrphanColumnToFirstTable() {
466        return option.isLinkOrphanColumnToFirstTable();
467    }
468
469    @Override
470    public void setLinkOrphanColumnToFirstTable(boolean linkOrphanColumnToFirstTable) {
471        option.setLinkOrphanColumnToFirstTable(linkOrphanColumnToFirstTable);
472    }
473
474    @Override
475    public boolean isIgnoreCoordinate() {
476        return option.isIgnoreCoordinate();
477    }
478
479    @Override
480    public void setIgnoreCoordinate(boolean ignoreCoordinate) {
481        option.setIgnoreCoordinate(ignoreCoordinate);
482    }
483
484    @Override
485    public void setHandleListener(DataFlowHandleListener listener) {
486        option.setHandleListener(listener);
487    }
488
489    @Override
490    public void setSqlEnv(TSQLEnv sqlenv) {
491        this.sqlenv = sqlenv;
492    }
493
494    @Override
495    public void setOption(Option option) {
496        this.option = option;
497    }
498
499    @Override
500    public Option getOption() {
501        return option;
502    }
503
504    @Override
505    public List<ErrorInfo> getErrorMessages() {
506        return errorInfos;
507    }
508
509    @Override
510    public synchronized String generateSqlInfos() {
511        return JSON.toJSONString(sqlInfoMap);
512    }
513
514    @Override
515    public synchronized String generateDataFlow() {
516        return generateDataFlow(false);
517    }
518
519    @Override
520    public Map<String, List<SqlInfo>> getSqlInfos() {
521        return sqlInfoMap;
522    }
523
524    @Override
525    /**
526     * @deprecated please use SqlInfoHelper.getSelectedDbObjectInfo
527     */
528    public DbObjectPosition getSelectedDbObjectInfo(Coordinate start, Coordinate end) {
529        if (start == null || end == null) {
530            throw new IllegalArgumentException("Coordinate can't be null.");
531        }
532
533        String hashCode = start.getHashCode();
534
535        if (hashCode == null) {
536            throw new IllegalArgumentException("Coordinate hashcode can't be null.");
537        }
538
539        int dbObjectStartLine = (int) start.getX() - 1;
540        int dbObjectStarColumn = (int) start.getY() - 1;
541        int dbObjectEndLine = (int) end.getX() - 1;
542        int dbObjectEndColumn = (int) end.getY() - 1;
543        List<SqlInfo> sqlInfoList;
544        if (hashCode.matches("\\d+")) {
545            sqlInfoList = sqlInfoMap.getValueAtIndex(Integer.valueOf(hashCode));
546        } else {
547            sqlInfoList = sqlInfoMap.get(hashCode);
548        }
549        for (int j = 0; j < sqlInfoList.size(); j++) {
550            SqlInfo sqlInfo = sqlInfoList.get(j);
551            int startLine = sqlInfo.getLineStart();
552            int endLine = sqlInfo.getLineEnd();
553            if (dbObjectStartLine >= startLine && dbObjectStartLine <= endLine) {
554                DbObjectPosition position = new DbObjectPosition();
555                position.setFile(sqlInfo.getFileName());
556                position.setFilePath(sqlInfo.getFilePath());
557                position.setSql(sqlInfo.getSql());
558                position.setIndex(sqlInfo.getOriginIndex());
559                List<Pair<Integer, Integer>> positions = position.getPositions();
560                positions.add(new Pair<Integer, Integer>(
561                        dbObjectStartLine - startLine + sqlInfo.getOriginLineStart() + 1, dbObjectStarColumn + 1));
562                positions.add(new Pair<Integer, Integer>(dbObjectEndLine - startLine + sqlInfo.getOriginLineStart() + 1,
563                        dbObjectEndColumn + 1));
564                return position;
565            }
566        }
567        return null;
568    }
569
570    @Override
571    public synchronized String generateDataFlow(final boolean withExtraInfo) {
572        return generateDataFlow(withExtraInfo, true);
573    }
574    
575    public synchronized String generateDataFlow(final boolean withExtraInfo, boolean useSaveMemoryMode) {
576        sqlInfoMap.clear();
577        errorInfos.clear();
578        Map<String, Pair3<StringBuilder, AtomicInteger, String>> databaseMap = new LinkedHashMap<String, Pair3<StringBuilder, AtomicInteger, String>>();
579        for (int i = 0; i < sqlInfos.length; i++) {
580            SqlInfo sqlInfo = sqlInfos[i];
581                        if (sqlInfo != null && sqlInfo.getSql() == null && sqlInfo.getFilePath() != null) {
582                                sqlInfo.setSql(SQLUtil.getFileContent(sqlInfo.getFilePath()));
583                        }
584            if (sqlInfo != null && sqlInfo.getSql() == null && sqlInfo.getFileName() != null) {
585                sqlInfo.setSql(SQLUtil.getFileContent(sqlInfo.getFileName()));
586            }
587            if (sqlInfo == null || sqlInfo.getSql() == null) {
588                sqlInfoMap.put(String.valueOf(i), new ArrayList<SqlInfo>());
589                continue;
590            }
591            String sql = sqlInfo.getSql();
592            if (sql != null && sql.trim().startsWith("{")) {
593                if (MetadataReader.isGrabit(sql) || MetadataReader.isSqlflow(sql) || MetadataReader.isSqlflowSharded(sql)) {
594                    String hash = SHA256.getMd5(sql);
595                    String fileHash = SHA256.getMd5(hash);
596                    if (!sqlInfoMap.containsKey(fileHash)) {
597                        sqlInfoMap.put(fileHash, new ArrayList<SqlInfo>());
598                        sqlInfoMap.get(fileHash).add(sqlInfo);
599                    }
600                    if (this.metadataFlow == null) {
601                        this.metadataFlow = analyzeMetadata(sql, sqlInfo.getFilePath());
602                    }
603                } else {
604                    Map queryObject = (Map) JSON.parseObject(sql);
605                    appendSqlInfo(databaseMap, i, sqlInfo, queryObject);
606                }
607            } else {
608                String content = sql;
609                String hash = SHA256.getMd5(content);
610                String fileHash = SHA256.getMd5(hash);
611                if (!sqlInfoMap.containsKey(fileHash)) {
612                    sqlInfoMap.put(fileHash, new ArrayList<SqlInfo>());
613
614                    String database = TSQLEnv.DEFAULT_DB_NAME;
615                    String schema = TSQLEnv.DEFAULT_SCHEMA_NAME;
616                    if(sqlenv!=null) {
617                        // Prefer the per-analysis Option default database over the attached
618                        // env's default catalog (mirrors DataFlowAnalyzer), so a shared
619                        // multi-catalog TSQLEnv can be reused across analyses with different
620                        // default databases.
621                        if (!SQLUtil.isEmpty(option.getDefaultDatabase())
622                                && !TSQLEnv.DEFAULT_DB_NAME.equals(option.getDefaultDatabase())) {
623                            database = option.getDefaultDatabase();
624                        } else {
625                            database = sqlenv.getDefaultCatalogName();
626                        }
627                        if(database == null) {
628                            database = TSQLEnv.DEFAULT_DB_NAME;
629                        }
630                        schema = sqlenv.getDefaultSchemaName();
631                        if(schema == null) {
632                            schema = TSQLEnv.DEFAULT_SCHEMA_NAME;
633                        }
634                    }
635                    boolean supportCatalog = TSQLEnv.supportCatalog(option.getVendor());
636                    boolean supportSchema = TSQLEnv.supportSchema(option.getVendor());
637                    StringBuilder builder = new StringBuilder();
638                    if (supportCatalog) {
639                        builder.append(database);
640                    }
641                    if (supportSchema) {
642                        if (builder.length() > 0) {
643                            builder.append(".");
644                        }
645                        builder.append(schema);
646                    }
647                    String group = builder.toString();
648                    SqlInfo sqlInfoItem = new SqlInfo();
649                    sqlInfoItem.setFileName(sqlInfo.getFileName());
650                    sqlInfoItem.setFilePath(sqlInfo.getFilePath());
651                    sqlInfoItem.setSql(sqlInfo.getSql());
652                    sqlInfoItem.setOriginIndex(0);
653                    sqlInfoItem.setOriginLineStart(0);
654                    sqlInfoItem.setOriginLineEnd(sqlInfo.getSql().split("\n").length - 1);
655                    sqlInfoItem.setIndex(0);
656                    sqlInfoItem.setLineStart(0);
657                    sqlInfoItem.setLineEnd(sqlInfo.getSql().split("\n").length - 1);
658                    sqlInfoItem.setHash(fileHash);
659                    sqlInfoItem.setGroup(group);
660
661                    sqlInfoMap.get(fileHash).add(sqlInfoItem);
662                }
663            }
664        }
665
666        final TSQLEnv[] env = new TSQLEnv[]{sqlenv};
667        if (sqlenv == null) {
668                if (option.getHandleListener() != null) {
669                        option.getHandleListener().startParseSQLEnv();
670                }
671            TSQLEnv[] envs = new SQLEnvParser(option.getDefaultServer(), option.getDefaultDatabase(),
672                    option.getDefaultSchema())
673                    .withIdentifierProfile(option.getIdentifierProfile())
674                    .parseSQLEnv(option.getVendor(), sqlInfos);
675                        if (envs != null && envs.length > 0) {
676                                env[0] = envs[0];
677                        }
678                        if (option.getHandleListener() != null) {
679                                option.getHandleListener().endParseSQLEnv();
680                        }
681        }
682        // Guard against a zero-size pool: newFixedThreadPool(0) throws. sqlInfos can be
683        // empty when the only input was a manifest that produced no analyzable source
684        // (e.g. an unsupported sharded version that was refused).
685        int poolSize = calculateMemoryAwarePoolSize(option.getParallel(), sqlInfos.length, option.getEstimatedMemoryPerTaskMB());
686        ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(poolSize);
687        
688        final File[] tempFilesArray = new File[sqlInfos.length];
689        // XML does not carry a table's same-run identity; it is kept here, by spill file and table id,
690        // and put back when the merge reads the file (DataflowUtility.carriedIdentities).
691        final Map<File, DataflowUtility.SpilledIdentities> spilledIdentities =
692                new java.util.concurrent.ConcurrentHashMap<File, DataflowUtility.SpilledIdentities>();
693        final Map<SqlInfo, dataflow> dataflowMap = useSaveMemoryMode ? null : new ConcurrentHashMap<>();
694        
695        try {
696            final CountDownLatch latch = new CountDownLatch(sqlInfos.length);
697
698            logger.info("start parallel analyze " + sqlInfos.length + " sqlinfos");
699
700            for (int i = 0; i < sqlInfos.length; i++) {
701                final SqlInfo[] sqlInfoCopy = new SqlInfo[sqlInfos.length];
702                final SqlInfo item = sqlInfos[i];
703                sqlInfoCopy[i] = item;
704                final Option optionCopy = (Option) option.clone();
705                optionCopy.setStartId(5000000L * i);
706                optionCopy.setOutput(false);
707                optionCopy.setSimpleRetainIntermediate(true);
708                optionCopy.setAutoDetectLargeFile(false);
709                // Worker-local relationship IDs are remapped during merge. Until
710                // the evidence merge performs the same remap, publishing worker
711                // evidence would create invalid correlations.
712                optionCopy.setCollectAuthoritativeLineageEvidence(false);
713                final int index = i;
714                Runnable task = new Runnable() {
715                    @Override
716                    public void run() {
717                        try {
718                            logger.info("start analyze sqlinfo[" + index + "]" + (sqlInfos[index].getFilePath() != null ? ", file name = " + new File(sqlInfos[index].getFilePath()).getName() : ""));
719                            DataFlowAnalyzer analyzer = new DataFlowAnalyzer(sqlInfoCopy, optionCopy);
720                            analyzer.deferEndpointLabels();
721                            if (env[0] != null) {
722                                analyzer.setSqlEnv(env[0].copy());
723                            }
724                            analyzer.generateDataFlow(true);
725                            dataflow dataflow = analyzer.getDataFlow();
726                            if (dataflow == null) {
727                                logger.warn("analyze sqlinfo[" + index + "] done, but dataflow is null" + (sqlInfos[index].getFilePath() != null ? ", file name = " + new File(sqlInfos[index].getFilePath()).getName() : ""));
728                                errorInfos.addAll(analyzer.getErrorMessages());
729                                analyzer.dispose();
730                                return;
731                            }
732                            logger.info("analyze sqlinfo[" + index + "] done, relation count: " + dataflow.getRelationships().size()+", error count: "+ analyzer.getErrorMessages().size());
733                            //autogenerate analyzer 错误集合与 dataflow 错误集合不同源,各自按自身 size 安全截断,避免 subList 越界
734                            List<ErrorInfo> taskErrors = analyzer.getErrorMessages();
735                            if (taskErrors.size() > 10000) {
736                                errorInfos.addAll(taskErrors.subList(0, 10000));
737                                logger.warn("Too many errors ("+taskErrors.size()+"), truncating to first 10000 errors" + (sqlInfos[index].getFileName() != null ? ", file name = " + sqlInfos[index].getFileName() : ""));
738                            } else {
739                                errorInfos.addAll(taskErrors);
740                            }
741                            if (dataflow.getErrors() != null && dataflow.getErrors().size() > 10000) {
742                                dataflow.setErrors(new ArrayList<>(dataflow.getErrors().subList(0, 10000)));
743                            }
744                            
745                            if (useSaveMemoryMode) {
746                                File tempFile = File.createTempFile("dataflow_" + index + "_" + System.currentTimeMillis() + "_", ".xml.zip");
747                                //autogenerate saveXML 失败时删除已创建的临时文件,避免泄漏
748                                try {
749                                    XML2Model.saveXML(dataflow, tempFile);
750                                    spilledIdentities.put(tempFile, DataflowUtility.carriedIdentities(dataflow));
751                                    tempFilesArray[index] = tempFile;
752                                } catch (Exception ex) {
753                                    tempFile.delete();
754                                    throw ex;
755                                }
756                            } else {
757                                dataflowMap.put(item, dataflow);
758                            }
759                            
760                            hashSQLMap.putAll(analyzer.getHashSQLMap());
761                            analyzer.dispose();
762                        }
763                        catch (Exception e) {
764                            logger.error("analyze sqlinfo[" + index + "] failed.", e);
765                        }
766                        finally {
767                            latch.countDown();
768                        }
769                    }
770                };
771                executor.submit(task);
772            }
773            latch.await();
774        } catch (Exception e) {
775            logger.error("execute task failed.", e);
776        }
777        executor.shutdown();
778
779        ModelBindingManager modelManager = new ModelBindingManager();
780        modelManager.setGlobalVendor(option.getVendor());
781        modelManager.setGlobalOption(option);
782        ModelBindingManager.set(modelManager);
783        // The merge compares identities, so it runs under the SAME configuration the workers
784        // analyzed with. Merging on the vendor default while the workers were configured would
785        // dedup the merged tables by rules no worker applied.
786        IdentifierProfile mergeProfile = IdentifierProfile.forVendorOr(
787                option.getIdentifierProfile(), option.getVendor());
788        ModelBindingManager.setGlobalIdentifierService(IdentifierService.forProfile(mergeProfile));
789        
790        if (useSaveMemoryMode) {
791            List<File> tempFiles = new ArrayList<>(sqlInfos.length + 1);
792            for (File f : tempFilesArray) {
793                if (f != null) {
794                    tempFiles.add(f);
795                }
796            }
797            if (metadataFlow != null) {
798                File metadataFile = null;
799                //autogenerate metadata 临时 ZIP 在 saveXML 失败时必须删除,避免泄漏
800                try {
801                    metadataFile = File.createTempFile("gsp-metadata-", ".xml.zip");
802                    XML2Model.saveXML(metadataFlow, metadataFile);
803                    spilledIdentities.put(metadataFile, DataflowUtility.carriedIdentities(metadataFlow));
804                    tempFiles.add(metadataFile);
805                } catch (Exception e) {
806                    if (metadataFile != null) {
807                        metadataFile.delete();
808                    }
809                    logger.error("Failed to save metadata to temp file", e);
810                }
811            }
812            logger.info("start merge dataflow, dataflow count: " + tempFiles.size()+", useSaveMemoryMode: "+ useSaveMemoryMode+", gsp version: "+ TBaseType.versionid);
813            //autogenerate 合并与清理放入 try-finally,异常路径也必须删除临时 ZIP,避免泄漏
814            try {
815                this.dataflow = iterativeMergeDataFlows(tempFiles, 5000000L * sqlInfos.length, spilledIdentities);
816                if (this.dataflow != null && this.dataflow.getRelationships() != null) {
817                    logger.info("merge dataflow done, dataflow count: " + tempFiles.size() + ", relation count: " + this.dataflow.getRelationships().size());
818                }
819            } finally {
820                for (File tempFile : tempFiles) {
821                    tempFile.delete();
822                }
823            }
824        } else {
825            if (metadataFlow != null) {
826                dataflowMap.put(new SqlInfo(), metadataFlow);
827            }
828            logger.info("start merge dataflow, dataflow count: " + dataflowMap.size()+", useSaveMemoryMode: "+ useSaveMemoryMode+", gsp version: "+ TBaseType.versionid);
829            this.dataflow = mergeDataFlows(dataflowMap, 5000000L * sqlInfos.length);
830            if (this.dataflow != null && this.dataflow.getRelationships() != null) {
831                logger.info("merge dataflow done, dataflow count: " + dataflowMap.size() + ", relation count: " + this.dataflow.getRelationships().size());
832            }
833            dataflowMap.clear();
834        }
835        
836                if (this.dataflow != null) {
837                        logger.info("merge done, relation count: " + this.dataflow.getRelationships().size());
838                }
839
840        if(option.isSimpleOutput()){
841            List<String> showTypes = new ArrayList<>();
842            if(option.getSimpleShowRelationTypes()!=null) {
843                showTypes.addAll(option.getSimpleShowRelationTypes());
844            }
845            if(showTypes.isEmpty()) {
846                showTypes.add("fdd");
847            }
848            if(option.isShowCallRelation()) {
849                showTypes.add("call");
850            }
851            if(option.isShowERDiagram()) {
852                showTypes.add("er");
853            }
854            try {
855                this.dataflow = new DataFlowAnalyzer("", option).getSimpleDataflow(this.dataflow, option.isSimpleOutput(), showTypes);
856            } catch (Exception e) {
857                throw new RuntimeException(e);
858            }
859        }
860
861        if (dataflow != null && option.isOutput()) {
862            if (option.isTextFormat()) {
863                dataflowString = DataFlowAnalyzer.getTextOutput(dataflow);
864            } else {
865                try {
866                    dataflowString = XML2Model.saveXML(dataflow);
867                }catch (Exception e){
868                    logger.error("save dataflow as xml failed.", e);
869                    dataflowString = null;
870                }
871            }
872        }
873        ModelBindingManager.remove();
874        return dataflowString;
875    }
876    
877    /**
878     * Merge worker results that were spilled to temporary XML files.
879     *
880     * <p>The files carry no policy provenance — a round-tripped document cannot — so the caller
881     * states it by installing this analysis's service before the merge (see
882     * {@code setGlobalIdentifierService} above), rather than the merge recovering it.
883     */
884    private dataflow iterativeMergeDataFlows(List<File> tempFiles, long startId,
885            final Map<File, DataflowUtility.SpilledIdentities> spilledIdentities) {
886        dataflow merged = DataflowUtility.iterativeMergeDataflowsFromFilesByStartId(tempFiles, startId,
887                (file, loaded) -> DataflowUtility.restoreCarriedIdentities(loaded, spilledIdentities.get(file)));
888        if (merged != null) {
889            merged.setIdentifierPolicy(IdentifierProfile.forVendorOr(
890                    option.getIdentifierProfile(), option.getVendor()));
891        }
892        return merged;
893    }
894
895    private void appendSqlInfo(Map<String, Pair3<StringBuilder, AtomicInteger, String>> databaseMap, int index,
896                               SqlInfo sqlInfo, Map queryObject) {
897        EDbVendor vendor = option.getVendor();
898        if(!SQLUtil.isEmpty(sqlInfo.getDbVendor())){
899            vendor = EDbVendor.valueOf(sqlInfo.getDbVendor());
900        }
901
902        boolean supportCatalog = TSQLEnv.supportCatalog(vendor);
903        boolean supportSchema = TSQLEnv.supportSchema(vendor);
904
905        String groupName = (String) queryObject.get("groupName");
906        if (DlineageUtil.isProcedureExcluded(groupName)) {
907            return;
908        }
909
910        if (DataFlowAnalyzer.isSourceUnavailable(queryObject)) {
911            return; // DDL body not retrieved; its text is not the definition
912        }
913
914        String content = (String) queryObject.get("sourceCode");
915        if (SQLUtil.isEmpty(content)) {
916            return;
917        }
918
919        StringBuilder builder = new StringBuilder();
920        if (supportCatalog) {
921            String database = (String) queryObject.get("database");
922            if (database.indexOf(".") != -1) {
923                database = SQLUtil.quoteDottedName(vendor, ESQLDataObjectType.dotCatalog, database);
924            }
925            builder.append(database);
926        }
927        if (supportSchema) {
928            String schema = (String) queryObject.get("schema");
929            if (schema.indexOf(".") != -1) {
930                schema = SQLUtil.quoteDottedName(vendor, ESQLDataObjectType.dotSchema, schema);
931            }
932            if (builder.length() > 0) {
933                builder.append(".");
934            }
935            builder.append(schema);
936        }
937        String group = builder.toString();
938        String sqlHash = SHA256.getMd5(content);
939        String hash = SHA256.getMd5(sqlHash);
940        if (!databaseMap.containsKey(sqlHash)) {
941            databaseMap.put(sqlHash,
942                    new Pair3<StringBuilder, AtomicInteger, String>(new StringBuilder(), new AtomicInteger(), group));
943        }
944        String delimiterChar = String.valueOf(TGSqlParser.getDelimiterChar(option.getVendor()));
945        StringBuilder buffer = new StringBuilder(content);
946        if (content.trim().endsWith(delimiterChar) || content.trim().endsWith(";")) {
947            buffer.append("\n");
948        } else if(vendor == EDbVendor.dbvredshift
949                || vendor == EDbVendor.dbvgaussdb
950                || vendor == EDbVendor.dbvedb
951                || vendor == EDbVendor.dbvpostgresql
952                || vendor == EDbVendor.dbvmysql
953                || vendor == EDbVendor.dbvteradata){
954            buffer.append("\n\n-- " + TBaseType.sqlflow_stmt_delimiter_str + "\n\n");
955        } else{
956            SQLUtil.endTrim(buffer);
957            buffer.append(";").append("\n");
958        }
959
960        int lineStart = databaseMap.get(sqlHash).first.toString().split("\n", -1).length - 1;
961        if (databaseMap.get(sqlHash).first.toString().length() == 0) {
962            lineStart = 0;
963        }
964        databaseMap.get(sqlHash).first.append(buffer.toString());
965        SqlInfo sqlInfoItem = new SqlInfo();
966        sqlInfoItem.setFileName(sqlInfo.getFileName());
967        sqlInfoItem.setFilePath(sqlInfo.getFilePath());
968        sqlInfoItem.setSql(buffer.toString());
969        sqlInfoItem.setOriginIndex(index);
970        sqlInfoItem.setOriginLineStart(0);
971        sqlInfoItem.setOriginLineEnd(buffer.toString().split("\n", -1).length - 1);
972        sqlInfoItem.setIndex(databaseMap.get(sqlHash).second.getAndIncrement());
973        sqlInfoItem.setLineStart(lineStart);
974        sqlInfoItem.setLineEnd(databaseMap.get(sqlHash).first.toString().split("\n", -1).length - 1);
975        sqlInfoItem.setGroup(group);
976        if (gudusoft.gsqlparser.sqlenv.parser.sqlflow.SqlflowNameForm.declared(queryObject)
977                == gudusoft.gsqlparser.sqlenv.IdentifierInputForm.CATALOG_STORED) {
978            sqlInfoItem.setGroupNameForm(gudusoft.gsqlparser.sqlenv.IdentifierInputForm.CATALOG_STORED);
979        }
980        sqlInfoItem.setHash(hash);
981
982        if (!sqlInfoMap.containsKey(hash)) {
983            sqlInfoMap.put(hash, new ArrayList<SqlInfo>());
984        }
985        sqlInfoMap.get(hash).add(sqlInfoItem);
986    }
987
988    @Override
989    public void dispose() {
990        ModelBindingManager.remove();
991    }
992
993    private dataflow mergeDataFlows(Map<SqlInfo, dataflow> dataflowMap, long startId) {
994        IdentifierProfile mergeProfile = IdentifierProfile.forVendorOr(
995                option.getIdentifierProfile(), option.getVendor());
996        for (dataflow df : dataflowMap.values()) {
997            if (df != null && df.getIdentifierPolicy() != null
998                    && !df.getIdentifierPolicy().hasSameEffectivePolicyAs(mergeProfile)) {
999                throw new IllegalStateException("ParallelDataFlowAnalyzer: a worker produced its"
1000                        + " result under " + df.getIdentifierPolicy() + " but the merge runs under "
1001                        + mergeProfile + "; every worker of one analysis must share the profile.");
1002            }
1003        }
1004        dataflow merged = DataflowUtility.mergeDataflowsByStartId(dataflowMap.values(), startId);
1005        if (merged != null) {
1006            merged.setIdentifierPolicy(mergeProfile);
1007        }
1008        return merged;
1009    }
1010
1011    private dataflow analyzeMetadata(String manifestJson, String manifestPath) {
1012        try {
1013            if (MetadataReader.isSqlflowSharded(manifestJson)) {
1014                String baseDir = manifestPath != null ? new File(manifestPath).getParent() : null;
1015                SqlflowShardedMetadataAnalyzer analyzer = new SqlflowShardedMetadataAnalyzer(
1016                        sqlenv, baseDir);
1017                return analyzer.analyzeMetadata(option.getVendor(), manifestJson);
1018            } else if (MetadataReader.isSqlflow(manifestJson)) {
1019                SqlflowMetadataAnalyzer analyzer = new SqlflowMetadataAnalyzer(sqlenv);
1020                return analyzer.analyzeMetadata(option.getVendor(), manifestJson);
1021            } else if (MetadataReader.isGrabit(manifestJson)) {
1022                GrabitMetadataAnalyzer analyzer = new GrabitMetadataAnalyzer();
1023                return analyzer.analyzeMetadata(option.getVendor(), manifestJson);
1024            }
1025        } catch (Exception e) {
1026            logger.error("Failed to analyze metadata", e);
1027        }
1028        return null;
1029    }
1030
1031    @Override
1032    public synchronized dataflow getDataFlow() {
1033        if (dataflow != null) {
1034            return dataflow;
1035        } else if (dataflowString != null) {
1036            return XML2Model.loadXML(dataflow.class, dataflowString);
1037        }
1038        return null;
1039    }
1040
1041    @Override
1042    public Map<String, String> getHashSQLMap() {
1043        return hashSQLMap;
1044    }
1045
1046    static int calculateMemoryAwarePoolSize(int requestedParallel, int taskCount, long estimatedMemoryPerTaskMB) {
1047        int basePoolSize = Math.max(1, requestedParallel < taskCount ? requestedParallel : taskCount);
1048
1049        long maxMemory = Runtime.getRuntime().maxMemory();
1050        if (maxMemory == Long.MAX_VALUE) {
1051            return basePoolSize;
1052        }
1053
1054        long reservedMemory = (long) (maxMemory * 0.2);
1055        long usableMemory = maxMemory - reservedMemory;
1056
1057        long estimatedBytesPerTask = estimatedMemoryPerTaskMB * 1024L * 1024L;
1058        int memoryBasedLimit = (int) Math.max(1, usableMemory / estimatedBytesPerTask);
1059
1060        int poolSize = Math.min(basePoolSize, memoryBasedLimit);
1061
1062        if (poolSize < basePoolSize) {
1063            logger.warn("Thread pool size reduced from " + basePoolSize + " to " + poolSize
1064                    + " due to memory constraints. "
1065                    + "(maxMemory=" + (maxMemory / (1024 * 1024)) + "MB, "
1066                    + "usableMemory=" + (usableMemory / (1024 * 1024)) + "MB, "
1067                    + "estimatedMemoryPerTask=" + estimatedMemoryPerTaskMB + "MB)");
1068        }
1069
1070        return poolSize;
1071    }
1072
1073    public static void main(String[] args) throws Exception {
1074        Option option = new Option();
1075        option.setVendor(EDbVendor.dbvmssql);
1076        option.setOutput(false);
1077//        option.setSimpleOutput(true);
1078        File parentDir = new File("C:\\Users\\KK\\Desktop\\sql");
1079        ParallelDataFlowAnalyzer analyzer = new ParallelDataFlowAnalyzer(new File[]{new File("C:\\Users\\KK\\Desktop\\metadata_with_query\\客户原始sql.json")}, option, 5, new File("C:\\Users\\KK\\Desktop\\metadata_with_query"));
1080        analyzer.generateDataFlow(false, true);
1081        dataflow dataflow = analyzer.getDataFlow();
1082//        System.out.println(XML2Model.saveXML(dataflow));
1083//        dataflow dataflow = XML2Model.loadXML(gudusoft.gsqlparser.dlineage.dataflow.model.xml.dataflow.class,new File("D:\\dataflow.xml.zip"));
1084        XML2Model.saveXML(dataflow, new File("D:\\dataflow.xml.zip"));
1085//        dataflow = DataflowUtility.convertToTableLevelDataflow(dataflow);
1086//        dataflow = DataflowUtility.convertTableLevelToFunctionCallDataflow(dataflow, true, EDbVendor.dbvoracle);
1087//        System.out.println(XML2Model.saveXML(dataflow));
1088    }
1089
1090}