001package gudusoft.gsqlparser.dlineage.util;
002
003import java.io.File;
004import java.util.ArrayList;
005import java.util.Arrays;
006import java.util.Comparator;
007import java.util.HashMap;
008import java.util.Iterator;
009import java.util.LinkedHashMap;
010import java.util.LinkedHashSet;
011import java.util.List;
012import java.util.Map;
013import java.util.Set;
014import java.util.TreeSet;
015
016import gudusoft.gsqlparser.EDbVendor;
017import gudusoft.gsqlparser.sqlenv.CollatorProvider;
018import gudusoft.gsqlparser.sqlenv.ESQLDataObjectType;
019import gudusoft.gsqlparser.sqlenv.IdentifierProfile;
020import gudusoft.gsqlparser.sqlenv.IdentifierService;
021import gudusoft.gsqlparser.dlineage.DataFlowAnalyzer;
022import gudusoft.gsqlparser.dlineage.dataflow.model.xml.dataflow;
023import gudusoft.gsqlparser.dlineage.dataflow.model.xml.process;
024import gudusoft.gsqlparser.dlineage.dataflow.model.xml.relationship;
025import gudusoft.gsqlparser.dlineage.dataflow.model.xml.sourceColumn;
026import gudusoft.gsqlparser.dlineage.dataflow.model.xml.table;
027import gudusoft.gsqlparser.dlineage.dataflow.model.xml.targetColumn;
028import gudusoft.gsqlparser.util.Logger;
029import gudusoft.gsqlparser.util.LoggerFactory;
030import gudusoft.gsqlparser.util.SQLUtil;
031
032public class ProcessUtility {
033
034        private static final Logger logger = LoggerFactory.getLogger(ProcessUtility.class);
035
036        public static dataflow generateTableLevelLineage(DataFlowAnalyzer analyzer, dataflow instance) {
037                return generateTableLevelLineage(analyzer, instance, true, false);
038        }
039
040        public static String generateTableLevelLineageCsv(DataFlowAnalyzer analyzer, dataflow instance) {
041                return generateTableLevelLineageCsv(analyzer, instance, true, false, ",");
042        }
043        
044        public static String generateTableLevelLineageCsv(DataFlowAnalyzer analyzer, dataflow instance, String delimiter) {
045                return generateTableLevelLineageCsv(analyzer, instance, true, false, delimiter);
046        }
047        
048        private static String generateTableLevelLineageCsv(DataFlowAnalyzer analyzer, dataflow instance, boolean ignoreSelfLineage, boolean isSimple, String delimiter) {
049                StringBuilder buffer = new StringBuilder();
050                buffer.append("source_db"+delimiter+"source_schema"+delimiter+"source_table"+delimiter+"source_column"+delimiter+"target_db"+delimiter+"target_schema"+delimiter+"target_table"+delimiter+"target_column"+delimiter+"process_type"+delimiter+"process_hashid"+delimiter+"coordinate"+delimiter+"procedure_names\n");
051                try {
052                        dataflow simple = instance;
053                        if (!isSimple) {
054                                simple = analyzer.getSimpleDataflow(instance, true);
055                        }
056                        if (simple.getTables() == null) {
057                                return null;
058                        }
059                        List<relationship> relations = instance.getRelationships();
060                        if (relations == null || relations.size() == 0) {
061                                return null;
062                        }
063                        long maxId = Long.parseLong(relations.get(relations.size() - 1).getId().split("\\-")[0].replace("_", ""))
064                                        * 100;
065
066                        Iterator<relationship> iter = simple.getRelationships().iterator();
067                        while (iter.hasNext()) {
068                                relationship relation = iter.next();
069                                if (!"fdd".equals(relation.getType())) {
070                                        iter.remove();
071                                }
072                        }
073                        
074                        Map<Pair3<String, String, String>, Set<relationship>> tableRelationMap = new LinkedHashMap<Pair3<String, String, String>, Set<relationship>>();
075                        List<relationship> tableRelations = new ArrayList<relationship>();
076                        iter = simple.getRelationships().iterator();
077                        while (iter.hasNext()) {
078                                relationship relation = iter.next();
079                                if (relation.getSources() != null && relation.getTarget() != null && relation.getProcessId() != null) {
080                                        String targetId = relation.getTarget().getParent_id();
081                                        if (SQLUtil.isEmpty(targetId))
082                                                continue;
083                                        for (sourceColumn sourceColumn : relation.getSources()) {
084                                                String sourceId = sourceColumn.getParent_id();
085                                                if (SQLUtil.isEmpty(sourceId))
086                                                        continue;
087
088                                                if (ignoreSelfLineage && sourceId.equals(targetId)) {
089                                                        continue;
090                                                }
091
092                                                Pair3<String, String, String> tableRelationPair = new Pair3<String, String, String>(sourceId,
093                                                                targetId, relation.getProcessId());
094                                                if (!tableRelationMap.containsKey(tableRelationPair)) {
095                                                        relationship tableRelation = new relationship();
096
097                                                        targetColumn targetTable = new targetColumn();
098                                                        targetTable.setId(String.valueOf(++maxId));
099                                                        targetTable.setTarget_id(targetId);
100                                                        targetTable.setTarget_name(relation.getTarget().getParent_name());
101                                                        tableRelation.setTarget(targetTable);
102
103                                                        sourceColumn sourceTale = new sourceColumn();
104                                                        sourceTale.setId(String.valueOf(++maxId));
105                                                        sourceTale.setSource_id(sourceId);
106                                                        sourceTale.setSource_name(sourceColumn.getParent_name());
107
108                                                        tableRelation.setSources(Arrays.asList(sourceTale));
109                                                        tableRelation.setType("fdd");
110                                                        tableRelation.setId(String.valueOf(++maxId));
111                                                        tableRelation.setProcessId(relation.getProcessId());
112                                                        tableRelations.add(tableRelation);
113                                                        tableRelationMap.put(tableRelationPair, new LinkedHashSet<relationship>());
114                                                }
115                                                tableRelationMap.get(tableRelationPair).add(relation);
116                                        }
117                                }
118                        }
119
120                        List<table> tables = new ArrayList<>();
121                        if (simple.getTables() != null) {
122                                tables.addAll(simple.getTables());
123                        }
124                        if (simple.getPaths() != null) {
125                                tables.addAll(simple.getPaths());
126                        }
127                        if (simple.getStages() != null) {
128                                tables.addAll(simple.getStages());
129                        }
130                        if (simple.getDatasources() != null) {
131                                tables.addAll(simple.getDatasources());
132                        }
133                        if (simple.getStreams() != null) {
134                                tables.addAll(simple.getStreams());
135                        }
136                        if (simple.getDatabases() != null) {
137                                tables.addAll(simple.getDatabases());
138                        }
139                        if (simple.getSchemas() != null) {
140                                tables.addAll(simple.getSchemas());
141                        }
142                        if (simple.getVariables() != null) {
143                                tables.addAll(simple.getVariables());
144                        }
145                        if (simple.getViews() != null) {
146                                tables.addAll(simple.getViews());
147                        }
148                        Map<String, table> tableMap = new HashMap<String, table>();
149                        Iterator<table> tableIter = tables.iterator();
150                        while (tableIter.hasNext()) {
151                                table table = tableIter.next();
152                                tableMap.put(table.getId(), table);
153                        }
154
155                        Map<String, process> processMap = new HashMap<String, process>();
156                        Iterator<process> processIter = simple.getProcesses().iterator();
157                        while (processIter.hasNext()) {
158                                process process = processIter.next();
159                                processMap.put(process.getId(), process);
160                        }
161                        
162                        Set<String> lineSet = new TreeSet<String>(new Comparator<String>() {
163                                @Override
164                                public int compare(String o1, String o2) {
165                                        return o1.toLowerCase().compareTo(o2.toLowerCase());
166                                }
167                        });
168                        for (Pair3<String, String, String> key : tableRelationMap.keySet()) {
169                                String sourceId = key.first;
170                                String targetId = key.second;
171                                String processId = key.third;
172                                table sourceTable = tableMap.get(sourceId);
173                                table targetTable = tableMap.get(targetId);
174                                process process = processMap.get(processId);
175                                
176                                String source_db = "default";
177                                if(!SQLUtil.isEmpty(sourceTable.getDatabase())){
178                                        source_db = sourceTable.getDatabase();
179                                }
180                                String source_schema = "default";
181                                if(!SQLUtil.isEmpty(sourceTable.getSchema())){
182                                        source_schema = sourceTable.getSchema();
183                                }
184                                String source_table = sourceTable.getTableNameOnly();
185                                Set<relationship> processRelations = tableRelationMap.get(key);
186                                Set<String> sourceColumns = new LinkedHashSet<String>();
187                                Set<String> targetColumns = new LinkedHashSet<String>();
188                                for (relationship relation : processRelations) {
189                                        for (sourceColumn column : relation.getSources()) {
190                                                sourceColumns.add(column.getColumn());
191                                        }
192                                        targetColumns.add(relation.getTarget().getColumn());
193                                }
194                                
195                                String target_db = "default";
196                                if(!SQLUtil.isEmpty(targetTable.getDatabase())){
197                                        target_db = targetTable.getDatabase();
198                                }
199                                String target_schema = "default";
200                                if(!SQLUtil.isEmpty(targetTable.getSchema())){
201                                        target_schema = targetTable.getSchema();
202                                }
203                                String target_table = targetTable.getTableNameOnly();
204                                String process_type = process.getType();
205                                String process_hashid = process.getQueryHashId();
206                                String process_coordinate = process.getCoordinate();
207                                String procedure_names = process.getProcedureName();
208                                if("batchQueries".equals(procedure_names)) {
209                                        procedure_names = "";
210                                }
211                                
212                                StringBuilder temp = new StringBuilder();
213                                temp.append(source_db).append(delimiter).append(source_schema).append(delimiter).append(source_table).append(delimiter)
214                                                .append(String.join(";", sourceColumns)).append(delimiter).append(target_db).append(delimiter)
215                                                .append(target_schema).append(delimiter).append(target_table).append(delimiter)
216                                                .append(String.join(";", targetColumns)).append(delimiter).append(process_type).append(delimiter)
217                                                .append(process_hashid).append(delimiter).append("\"").append(process_coordinate).append("\"").append(delimiter).append(procedure_names).append("\n");
218                                lineSet.add(temp.toString());
219                        }
220                        
221                        for(String lineKey: lineSet) {
222                                buffer.append(lineKey);
223                        }
224                        
225                } catch (Exception e) {
226                        logger.error("Generate table level csv failed.", e);
227                }
228                return buffer.toString();
229        }
230
231        public static dataflow generateTableLevelLineage(DataFlowAnalyzer analyzer, dataflow instance,
232                        boolean ignoreSelfLineage, boolean isSimple) {
233                try {
234                        dataflow simple = instance;
235                        if (!isSimple) {
236                                simple = analyzer.getSimpleDataflow(instance, true);
237                        }
238                        if (simple.getTables() == null) {
239                                return simple;
240                        }
241
242                        List<relationship> relations = instance.getRelationships();
243                        if (relations == null || relations.size() == 0) {
244                                return simple;
245                        }
246
247                        String id = relations.get(relations.size() - 1).getId().split("\\-")[0].replace("_", "");
248                        if(id.trim().length() == 0){
249                                System.out.println(relations.get(relations.size() - 1).getId());
250                        }
251                        long maxId = Long.parseLong(id)
252                                        * 100;
253
254                        Iterator<relationship> iter = simple.getRelationships().iterator();
255                        while (iter.hasNext()) {
256                                relationship relation = iter.next();
257                                if (!"fdd".equals(relation.getType())) {
258                                        iter.remove();
259                                }
260                        }
261
262                        Map<Pair3<String, String, String>, relationship> tableRelationMap = new LinkedHashMap<Pair3<String, String, String>, relationship>();
263                        List<relationship> tableRelations = new ArrayList<relationship>();
264                        iter = simple.getRelationships().iterator();
265                        while (iter.hasNext()) {
266                                relationship relation = iter.next();
267                                if (relation.getSources() != null && relation.getTarget() != null && relation.getProcessId() != null) {
268                                        String targetId = relation.getTarget().getParent_id();
269                                        if (SQLUtil.isEmpty(targetId))
270                                                continue;
271                                        for (sourceColumn sourceColumn : relation.getSources()) {
272                                                String sourceId = sourceColumn.getParent_id();
273                                                if (SQLUtil.isEmpty(sourceId))
274                                                        continue;
275
276                                                if (ignoreSelfLineage && sourceId.equals(targetId)) {
277                                                        continue;
278                                                }
279
280                                                Pair3<String, String, String> tableRelationPair = new Pair3<String, String, String>(sourceId,
281                                                                targetId, relation.getProcessId());
282                                                if (!tableRelationMap.containsKey(tableRelationPair)) {
283                                                        relationship tableRelation = new relationship();
284
285                                                        targetColumn targetTable = new targetColumn();
286                                                        targetTable.setId(String.valueOf(++maxId));
287                                                        targetTable.setTarget_id(targetId);
288                                                        targetTable.setTarget_name(relation.getTarget().getParent_name());
289                                                        tableRelation.setTarget(targetTable);
290
291                                                        sourceColumn sourceTale = new sourceColumn();
292                                                        sourceTale.setId(String.valueOf(++maxId));
293                                                        sourceTale.setSource_id(sourceId);
294                                                        sourceTale.setSource_name(sourceColumn.getParent_name());
295
296                                                        tableRelation.setSources(Arrays.asList(sourceTale));
297                                                        tableRelation.setType("fdd");
298                                                        tableRelation.setId(String.valueOf(++maxId));
299                                                        tableRelation.setProcessId(relation.getProcessId());
300                                                        tableRelations.add(tableRelation);
301                                                        tableRelationMap.put(tableRelationPair, tableRelation);
302                                                }
303                                        }
304                                }
305                        }
306
307                        simple.getRelationships().clear();
308                        simple.getRelationships().addAll(tableRelations);
309
310                        List<table> tables = new ArrayList<>();
311                        if (simple.getTables() != null) {
312                                tables.addAll(simple.getTables());
313                        }
314                        if (simple.getPaths() != null) {
315                                tables.addAll(simple.getPaths());
316                        }
317                        if (simple.getStages() != null) {
318                                tables.addAll(simple.getStages());
319                        }
320                        if (simple.getDatasources() != null) {
321                                tables.addAll(simple.getDatasources());
322                        }
323                        if (simple.getDatabases() != null) {
324                                tables.addAll(simple.getDatabases());
325                        }
326                        if (simple.getSchemas() != null) {
327                                tables.addAll(simple.getSchemas());
328                        }
329                        if (simple.getVariables() != null) {
330                                tables.addAll(simple.getVariables());
331                        }
332                        if (simple.getViews() != null) {
333                                tables.addAll(simple.getViews());
334                        }
335
336                        for (table table : tables) {
337                                table.getColumns().clear();
338                        }
339
340                        if (simple.getProcesses() == null) {
341                                return simple;
342                        }
343
344                        Map<String, table> tableMap = new HashMap<String, table>();
345                        Iterator<table> tableIter = tables.iterator();
346                        while (tableIter.hasNext()) {
347                                table table = tableIter.next();
348                                tableMap.put(table.getId(), table);
349                        }
350
351                        Map<String, process> processMap = new HashMap<String, process>();
352                        Iterator<process> processIter = simple.getProcesses().iterator();
353                        while (processIter.hasNext()) {
354                                process process = processIter.next();
355                                processMap.put(process.getId(), process);
356                        }
357
358                        Map<String, relationship> processRelations = new LinkedHashMap<String, relationship>();
359                        iter = simple.getRelationships().iterator();
360                        while (iter.hasNext()) {
361                                relationship relation = iter.next();
362                                relationship beforeRelation = new relationship();
363                                beforeRelation.setSources(relation.getSources());
364                                beforeRelation.setType("fdd");
365                                beforeRelation.setId(String.valueOf(++maxId));
366
367                                targetColumn targetProcess = new targetColumn();
368                                targetProcess.setId(String.valueOf(++maxId));
369                                targetProcess.setTarget_id(relation.getProcessId());
370                                targetProcess.setTarget_name(processMap.get(relation.getProcessId()).getName());
371                                beforeRelation.setTarget(targetProcess);
372
373                                String key = targetProcess.getTarget_id();
374                                if (beforeRelation.getSources() != null) {
375                                        for (sourceColumn sourceColumn : beforeRelation.getSources()) {
376                                                key += (":" + sourceColumn.getSource_id() + ":" + relation.getProcessId());
377                                        }
378                                }
379                                if (!processRelations.containsKey(key)) {
380                                        beforeRelation.getTarget().setId(beforeRelation.getTarget().getTarget_id());
381                                        if (beforeRelation.getSources() != null) {
382                                                for (sourceColumn sourceColumn : beforeRelation.getSources()) {
383                                                        sourceColumn.setId(sourceColumn.getSource_id());
384                                                }
385                                        }
386                                        processRelations.put(key, beforeRelation);
387                                }
388
389                                relationship afterRelation = new relationship();
390                                afterRelation.setTarget(relation.getTarget());
391                                afterRelation.setType("fdd");
392                                afterRelation.setId(String.valueOf(++maxId));
393
394                                sourceColumn sourceProcess = new sourceColumn();
395                                sourceProcess.setId(String.valueOf(++maxId));
396                                sourceProcess.setSource_id(relation.getProcessId());
397                                sourceProcess.setSource_name(processMap.get(relation.getProcessId()).getName());
398                                afterRelation.setSources(Arrays.asList(new sourceColumn[] { sourceProcess }));
399
400                                key = relation.getTarget().getTarget_id();
401                                key += (":" + sourceProcess.getSource_id() + ":" + relation.getProcessId());
402                                if (!processRelations.containsKey(key)) {
403                                        afterRelation.getTarget().setId(afterRelation.getTarget().getTarget_id());
404                                        sourceProcess.setId(sourceProcess.getSource_id());
405                                        processRelations.put(key, afterRelation);
406                                }
407
408                                iter.remove();
409                        }
410
411                        simple.getRelationships().addAll(processRelations.values());
412
413                        return simple;
414                } catch (Exception e) {
415                        logger.error("Generate table level process failed.", e);
416                }
417                return instance;
418        }
419
420        public static void main(String[] args) throws Exception {
421                dataflow dataflow = XML2Model.loadXML(dataflow.class, new File(
422                                "C:/Users/KK/Desktop/e091bfbe0f4178ffbcff4711751a119ec06087c9723be51a26c3a1b6b120acf8.dataflow.zip"));
423                DataFlowAnalyzer dataFlowAnalyzer = new DataFlowAnalyzer("", EDbVendor.dbvoracle, true);
424                dataflow dataflow1 = ProcessUtility.generateTableLevelLineage(dataFlowAnalyzer, dataflow);
425                XML2Model.saveXML(dataflow1, new File("D:\\1.zip"));
426        }
427        
428        public static String generateColumnLevelLineageCsv(DataFlowAnalyzer analyzer, dataflow instance) {
429                return generateColumnLevelLineageCsv(analyzer, instance, false, ",");
430        }
431        
432        public static String generateColumnLevelLineageCsv(DataFlowAnalyzer analyzer, dataflow instance, String delimiter) {
433                return generateColumnLevelLineageCsv(analyzer, instance, false, delimiter);
434        }
435        
436        public static String generateColumnLevelLineageCsv(DataFlowAnalyzer analyzer, dataflow instance, boolean isSimple, String delimiter) {
437                StringBuilder buffer = new StringBuilder();
438                buffer.append("SOURCE_DB"+delimiter+"SOURCE_SCHEMA"+delimiter+"SOURCE_TABLE_ID"+delimiter+"SOURCE_TABLE"+delimiter+"SOURCE_COLUMN_ID"+delimiter+"SOURCE_COLUMN"+delimiter+"TARGET_DB"+delimiter+"TARGET_SCHEMA"+delimiter+"TARGET_TABLE_ID"+delimiter+"TARGET_TABLE"+delimiter+"TARGET_COLUMN_ID"+delimiter+"TARGET_COLUMN"+delimiter+"RELATION_TYPE"+delimiter+"EFFECTTYPE\n");
439                try {
440                        dataflow simple = instance;
441//                      if (!isSimple) {
442//                              simple = analyzer.getSimpleDataflow(instance, true);
443//                      }
444                        List<relationship> relations = simple.getRelationships();
445                        if (relations == null || relations.size() == 0) 
446                                return buffer.toString();
447                        
448                        List<table> tables = new ArrayList<>();
449                        if (simple.getTables() != null) {
450                                tables.addAll(simple.getTables());
451                        }
452                        if (simple.getPaths() != null) {
453                                tables.addAll(simple.getPaths());
454                        }
455                        if (simple.getStages() != null) {
456                                tables.addAll(simple.getStages());
457                        }
458                        if (simple.getDatasources() != null) {
459                                tables.addAll(simple.getDatasources());
460                        }
461                        if (simple.getStreams() != null) {
462                                tables.addAll(simple.getStreams());
463                        }
464                        if (simple.getDatabases() != null) {
465                                tables.addAll(simple.getDatabases());
466                        }
467                        if (simple.getSchemas() != null) {
468                                tables.addAll(simple.getSchemas());
469                        }
470                        if (simple.getVariables() != null) {
471                                tables.addAll(simple.getVariables());
472                        }
473                        if (simple.getViews() != null) {
474                                tables.addAll(simple.getViews());
475                        }
476                        if (simple.getResultsets() != null) {
477                                tables.addAll(simple.getResultsets());
478                        }
479
480                        Map<String, table> tableMap = new HashMap<String, table>();
481                        Iterator<table> tableIter = tables.iterator();
482                        while (tableIter.hasNext()) {
483                                table table = tableIter.next();
484                                tableMap.put(table.getId(), table);
485                        }
486                        
487                        TreeSet<String> lines = new TreeSet<String>(new Comparator<String>() {
488                                @Override
489                                public int compare(String o1, String o2) {
490                                        return o1.toLowerCase().compareTo(o2.toLowerCase());
491                                }
492                        });
493                        for(relationship relation: relations) {
494                                if(relation.getSources()==null || relation.getTarget() == null)
495                                        continue;
496                                
497                                String relation_type = relation.getType();
498                                if("fdd".equals(relation_type)) {
499                                        relation_type = "direct";
500                                }
501                                else if("fdr".equals(relation_type) || "frd".equals(relation_type) || "fddi".equals(relation_type)) {
502                                        relation_type = "indirect";
503                                }
504                                
505                                String effect_type = relation.getEffectType();
506                                
507                                targetColumn targetColumn = relation.getTarget();
508                                table targetTable = tableMap.get(targetColumn.getParent_id());
509                                String target_db = targetTable.getDatabase();
510                                target_db = target_db == null ? "default" : target_db;
511                                String target_schema = targetTable.getSchema();
512                                target_schema = target_schema == null ? "default" : target_schema;
513                                String target_table = targetTable.getName();
514                                String target_table_id = targetTable.getId();
515                                String target_column = targetColumn.getColumn();
516                                String target_column_id = targetColumn.getId();
517                                
518                                for (sourceColumn sourceColumn : relation.getSources()) {
519                                        StringBuilder temp = new StringBuilder();
520                                        table sourceTable = tableMap.get(sourceColumn.getParent_id());
521                                        String source_db = sourceTable.getDatabase();
522                                        source_db = source_db == null ? "default" : source_db;
523                                        String source_schema = sourceTable.getSchema();
524                                        source_schema = source_schema == null ? "default" : source_schema;
525                                        String source_table = sourceTable.getName();
526                                        String source_table_id = sourceTable.getId();
527                                        String source_column = sourceColumn.getColumn();
528                                        String source_column_id = sourceColumn.getId();
529                                
530                                        temp.append(source_db).append(delimiter).append(source_schema).append(delimiter).append(source_table_id).append(delimiter).append(source_table)
531                                                        .append(delimiter).append(source_column_id).append(delimiter).append(source_column).append(delimiter).append(target_db)
532                                                        .append(delimiter).append(target_schema).append(delimiter).append(target_table_id).append(delimiter).append(target_table).append(delimiter)
533                                                        .append(target_column_id).append(delimiter).append(target_column).append(delimiter).append(relation_type).append(delimiter)
534                                                        .append(effect_type).append("\n");
535                                        lines.add(temp.toString());
536                                }
537                        }
538                        for(String line: lines) {
539                                buffer.append(line);
540                        }
541                } catch (Exception e) {
542                        logger.error("Generate column level csv failed.", e);
543                }
544                return buffer.toString();
545        }
546
547        /**
548         * Simplified column level lineage output. Each line has the form:
549         *
550         *   <source schema.table.column><delimiter><target schema.table.column><delimiter><relation type>
551         *
552         * e.g. {@code scott.emp.deptno, default.vsal.salary, fdr}
553         *
554         * The raw relation type (fdd/fdr) is emitted rather than direct/indirect, and
555         * any record whose source or target column is the synthetic {@code RelationRows}
556         * column is dropped.
557         */
558        public static String generateColumnLevelLineageCsvSimple(DataFlowAnalyzer analyzer, dataflow instance, String delimiter) {
559                StringBuilder buffer = new StringBuilder();
560                try {
561                        EDbVendor vendor = analyzer.getOption().getVendor();
562                        IdentifierService identifierService = identifierServiceFor(vendor);
563                        dataflow simple = instance;
564                        List<relationship> relations = simple.getRelationships();
565                        if (relations == null || relations.size() == 0)
566                                return buffer.toString();
567
568                        List<table> tables = new ArrayList<>();
569                        if (simple.getTables() != null) {
570                                tables.addAll(simple.getTables());
571                        }
572                        if (simple.getPaths() != null) {
573                                tables.addAll(simple.getPaths());
574                        }
575                        if (simple.getStages() != null) {
576                                tables.addAll(simple.getStages());
577                        }
578                        if (simple.getDatasources() != null) {
579                                tables.addAll(simple.getDatasources());
580                        }
581                        if (simple.getStreams() != null) {
582                                tables.addAll(simple.getStreams());
583                        }
584                        if (simple.getDatabases() != null) {
585                                tables.addAll(simple.getDatabases());
586                        }
587                        if (simple.getSchemas() != null) {
588                                tables.addAll(simple.getSchemas());
589                        }
590                        if (simple.getVariables() != null) {
591                                tables.addAll(simple.getVariables());
592                        }
593                        if (simple.getViews() != null) {
594                                tables.addAll(simple.getViews());
595                        }
596                        if (simple.getResultsets() != null) {
597                                tables.addAll(simple.getResultsets());
598                        }
599
600                        Map<String, table> tableMap = new HashMap<String, table>();
601                        Iterator<table> tableIter = tables.iterator();
602                        while (tableIter.hasNext()) {
603                                table table = tableIter.next();
604                                tableMap.put(table.getId(), table);
605                        }
606
607                        TreeSet<String> lines = new TreeSet<String>(new Comparator<String>() {
608                                @Override
609                                public int compare(String o1, String o2) {
610                                        return o1.toLowerCase().compareTo(o2.toLowerCase());
611                                }
612                        });
613                        for (relationship relation : relations) {
614                                if (relation.getSources() == null || relation.getTarget() == null)
615                                        continue;
616
617                                String relation_type = relation.getType();
618
619                                targetColumn targetColumn = relation.getTarget();
620                                String target_column = targetColumn.getColumn();
621                                if (isRelationRowsColumn(target_column))
622                                        continue;
623                                table targetTable = tableMap.get(targetColumn.getParent_id());
624                                String targetName = qualifiedColumnName(identifierService, targetTable, target_column);
625
626                                for (sourceColumn sourceColumn : relation.getSources()) {
627                                        String source_column = sourceColumn.getColumn();
628                                        if (isRelationRowsColumn(source_column))
629                                                continue;
630                                        table sourceTable = tableMap.get(sourceColumn.getParent_id());
631                                        String sourceName = qualifiedColumnName(identifierService, sourceTable, source_column);
632
633                                        lines.add(sourceName + delimiter + targetName + delimiter + relation_type + "\n");
634                                }
635                        }
636                        for (String line : lines) {
637                                buffer.append(line);
638                        }
639                } catch (Exception e) {
640                        logger.error("Generate simple column level csv failed.", e);
641                }
642                return buffer.toString();
643        }
644
645        private static boolean isRelationRowsColumn(String column) {
646                return "RelationRows".equalsIgnoreCase(column);
647        }
648
649        /**
650         * Build a {@code schema.table.column} qualified name, normalizing each real
651         * identifier segment according to the database vendor's case-folding and
652         * quoting rules via {@link IdentifierService#normalizeQualifiedName}. The
653         * synthetic {@code default} placeholder (used when a table has no schema) is
654         * left as-is rather than being treated as a vendor identifier.
655         */
656        private static String qualifiedColumnName(IdentifierService identifierService, table table, String column) {
657                if (table == null) {
658                        return identifierService.normalizeQualifiedName(column, ESQLDataObjectType.dotColumn);
659                }
660                String schema = table.getSchema();
661                String name = table.getName();
662
663                String qualified;
664                String schemaPlaceholder = null;
665                if (name != null && schema != null
666                                && name.toLowerCase().startsWith((schema + ".").toLowerCase())) {
667                        // table name already carries the schema prefix (e.g. "scott.emp")
668                        qualified = name + "." + column;
669                } else if (schema != null) {
670                        qualified = schema + "." + name + "." + column;
671                } else {
672                        // no real schema; keep the synthetic "default" placeholder out of
673                        // vendor normalization and prepend it afterwards
674                        schemaPlaceholder = "default";
675                        qualified = name + "." + column;
676                }
677
678                // normalize the whole dotted path; each segment is folded per its object
679                // type (schema/table/column) using the vendor's identifier rules
680                String normalized = identifierService.normalizeQualifiedName(qualified, ESQLDataObjectType.dotColumn);
681                return schemaPlaceholder == null ? normalized : schemaPlaceholder + "." + normalized;
682        }
683
684        /**
685         * Build a vendor-aware {@link IdentifierService}, mirroring the wiring used by
686         * {@link IdentifierService#normalizeStatic} (a collator is only attached for
687         * the collation-based SQL Server family).
688         */
689        private static IdentifierService identifierServiceFor(EDbVendor vendor) {
690                IdentifierProfile profile = IdentifierProfile.forVendor(vendor, IdentifierProfile.VendorFlags.defaults());
691                CollatorProvider collatorProvider = (vendor == EDbVendor.dbvmssql || vendor == EDbVendor.dbvazuresql)
692                                ? new CollatorProvider()
693                                : null;
694                return new IdentifierService(profile, collatorProvider);
695        }
696}