001package gudusoft.gsqlparser.dlineage.dataflow.metadata.sqlflow.sharded;
002
003import gudusoft.gsqlparser.dlineage.util.DlineageUtil;
004import gudusoft.gsqlparser.EDbVendor;
005import gudusoft.gsqlparser.sqlenv.ESQLDataObjectType;
006import gudusoft.gsqlparser.dlineage.dataflow.metadata.MetadataAnalyzer;
007import gudusoft.gsqlparser.dlineage.dataflow.metadata.MetadataReader;
008import gudusoft.gsqlparser.dlineage.dataflow.model.*;
009import gudusoft.gsqlparser.dlineage.dataflow.model.xml.*;
010import gudusoft.gsqlparser.dlineage.util.Pair3;
011import gudusoft.gsqlparser.sqlenv.IdentifierInputForm;
012import gudusoft.gsqlparser.sqlenv.TSQLEnv;
013import gudusoft.gsqlparser.sqlenv.parser.sqlflow.SqlflowNameForm;
014import gudusoft.gsqlparser.util.SQLUtil;
015import gudusoft.gsqlparser.util.json.JSON;
016
017import java.io.File;
018import java.io.IOException;
019import java.nio.charset.StandardCharsets;
020import java.nio.file.Files;
021import java.nio.file.Path;
022import java.nio.file.Paths;
023import java.util.*;
024
025/**
026 * Extracts metadata information (tables, views, procedures, relationships) from a
027 * sqlflow-sharded manifest and its catalog shards.
028 *
029 * <p>This class does <b>NOT</b> analyze SQL source code in queries, nor does it produce
030 * lineage for query objects. The lineage analysis of SQL queries is performed in
031 * {@link gudusoft.gsqlparser.dlineage.DataFlowAnalyzer DataFlowAnalyzer}, which reads
032 * the source JSONL files through the {@code databaseMap} / {@code appendSqlInfo}
033 * mechanism and invokes the SQL parser to generate dataflow relationships.
034 */
035public class SqlflowShardedMetadataAnalyzer implements MetadataAnalyzer<String> {
036
037    private Map<Object, procedure> procedureMap = new LinkedHashMap<Object, procedure>();
038    private Map<Object, oraclePackage> oraclePackageMap = new LinkedHashMap<Object, oraclePackage>();
039    private Map<Object, table> tableMap = new LinkedHashMap<Object, table>();
040    private Map<Object, column> columnMap = new LinkedHashMap<Object, column>();
041    private EDbVendor vendor;
042    private TSQLEnv sqlenv;
043    private String baseDir;
044
045    public SqlflowShardedMetadataAnalyzer(String baseDir) {
046        if (baseDir == null || baseDir.trim().isEmpty()) {
047            throw new IllegalArgumentException("baseDir is required");
048        }
049        this.baseDir = baseDir;
050    }
051
052    public SqlflowShardedMetadataAnalyzer(TSQLEnv sqlenv, String baseDir) {
053        if (baseDir == null || baseDir.trim().isEmpty()) {
054            throw new IllegalArgumentException("baseDir is required");
055        }
056        this.sqlenv = sqlenv;
057        this.baseDir = baseDir;
058    }
059
060    @Override
061    public synchronized dataflow analyzeMetadata(EDbVendor metadataVendor, String metadata) {
062        init(metadataVendor);
063        dataflow dataflow = new dataflow();
064
065        String manifestContent;
066        try {
067            if (metadata.trim().startsWith("{") && metadata.trim().endsWith("}")) {
068                manifestContent = metadata.trim();
069            } else {
070                manifestContent = new String(Files.readAllBytes(Paths.get(metadata)), StandardCharsets.UTF_8);
071            }
072        } catch (IOException e) {
073            throw new RuntimeException("Failed to read manifest: " + metadata, e);
074        }
075
076        Map manifest = (Map) JSON.parseObject(manifestContent);
077        if (manifest == null) {
078            return dataflow;
079        }
080
081        if (!"sqlflow-sharded".equals(manifest.get("format"))) {
082            return dataflow;
083        }
084
085        // Version gate: the `format` field is authoritative, so consume any
086        // `sqlflow-sharded` manifest whose `formatVersion` we understand,
087        // regardless of the `createdBy` product brand (SQLdep, sqlflow, grabit
088        // all emit this format). Anything outside the supported range — a newer
089        // major version, a bogus 0/negative, or a non-numeric value — is refused
090        // with a visible error rather than mis-parsed. ONE authoritative check
091        // (MetadataReader) is used here and at every other consumer entry point.
092        if (!MetadataReader.isSupportedSqlflowSharded(manifestContent)) {
093            dataflow.getErrors().add(
094                    unsupportedVersionError(MetadataReader.shardedFormatVersion(manifestContent)));
095            return dataflow;
096        }
097
098        // Declared once at the manifest root, for the manifest and every shard it points to.
099        IdentifierInputForm nameForm = SqlflowNameForm.toLexicalInPlace(metadataVendor, manifest);
100
101        {
102            {
103                List servers = (List) manifest.get("servers");
104                if (servers == null) {
105                    return dataflow;
106                }
107
108                for (int x = 0; x < servers.size(); x++) {
109                    Map server = (Map) servers.get(x);
110                    String serverName = (String) server.get("name");
111                    String dbVendor = (String) server.get("dbVendor");
112
113                    EDbVendor vendor = metadataVendor;
114                    if (dbVendor != null) {
115                        vendor = EDbVendor.valueOf(dbVendor);
116                    }
117
118                    boolean supportsCatalogs = TSQLEnv.supportCatalog(vendor);
119                    boolean supportsSchemas = TSQLEnv.supportSchema(vendor);
120
121                    if (server.containsKey("supportsCatalogs")) {
122                        supportsCatalogs = (Boolean) server.get("supportsCatalogs");
123                    }
124                    if (server.containsKey("supportsSchemas")) {
125                        supportsSchemas = (Boolean) server.get("supportsSchemas");
126                    }
127
128                    List databases = (List) server.get("databases");
129                    List schemaShards = (List) server.get("schemas");
130
131                    // Route on the PHYSICAL shard container. Schema-topology
132                    // exports (e.g. Oracle) list shards under servers[].schemas[],
133                    // each pointing at a FLAT single-schema catalog file
134                    // ({name, tables, views, procedures}) rather than a catalog
135                    // file that nests a "schemas" array. Prefer schema shards
136                    // when present; treat an empty databases[] as absent so a
137                    // manifest with "databases":[] alongside schemas[] still
138                    // loads. Handling only `databases` left the dataflow with no
139                    // catalog table/view nodes for schema-topology exports.
140                    boolean hasSchemaShards = schemaShards != null && !schemaShards.isEmpty();
141                    boolean hasDatabaseShards = databases != null && !databases.isEmpty();
142
143                    if (hasSchemaShards) {
144                        for (int i = 0; i < schemaShards.size(); i++) {
145                            Map jsonSchemaShard = (Map) schemaShards.get(i);
146                            String schemaName = (String) jsonSchemaShard.get("name");
147                            if (schemaName != null && SQLUtil.parseNames(schemaName).size() > 1) {
148                                schemaName = "\"" + schemaName + "\"";
149                            }
150
151                            Map catalogInfo = (Map) jsonSchemaShard.get("catalog");
152                            if (catalogInfo == null) {
153                                continue;
154                            }
155                            String catalogPath = (String) catalogInfo.get("path");
156                            if (SQLUtil.isEmpty(catalogPath)) {
157                                continue;
158                            }
159
160                            String catalogContent = loadCatalogFile(catalogPath);
161                            if (catalogContent == null) {
162                                continue;
163                            }
164
165                            Map catalog = (Map) JSON.parseObject(catalogContent);
166                            if (catalog == null) {
167                                continue;
168                            }
169                            SqlflowNameForm.shardToLexicalInPlace(vendor, catalog, null, nameForm);
170
171                            // Flat catalog IS the schema: read tables/views/
172                            // procedures directly off it, keyed by the shard's
173                            // schema name under the synthetic default catalog.
174                            processTablesAndViews(vendor, supportsCatalogs, supportsSchemas, dataflow,
175                                    serverName, TSQLEnv.DEFAULT_DB_NAME, schemaName, catalog);
176                            processProcedures(vendor, supportsCatalogs, supportsSchemas, dataflow,
177                                    serverName, TSQLEnv.DEFAULT_DB_NAME, schemaName, catalog);
178                        }
179                        continue;
180                    }
181
182                    if (!hasDatabaseShards) {
183                        continue;
184                    }
185
186                    for (int i = 0; i < databases.size(); i++) {
187                        Map jsonDatabase = (Map) databases.get(i);
188                        String databaseName = (String) jsonDatabase.get("name");
189                        if (SQLUtil.parseNames(databaseName).size() > 1) {
190                            databaseName = "\"" + databaseName + "\"";
191                        }
192
193                        Map catalogInfo = (Map) jsonDatabase.get("catalog");
194                        if (catalogInfo == null) {
195                            continue;
196                        }
197                        String catalogPath = (String) catalogInfo.get("path");
198
199                        String catalogContent = loadCatalogFile(catalogPath);
200                        if (catalogContent == null) {
201                            continue;
202                        }
203
204                        Map catalog = (Map) JSON.parseObject(catalogContent);
205                        if (catalog == null) {
206                            continue;
207                        }
208                        SqlflowNameForm.shardToLexicalInPlace(vendor, catalog, null, nameForm);
209
210                        if (supportsCatalogs && supportsSchemas) {
211                            processCatalogWithSchemas(vendor, supportsCatalogs, supportsSchemas, dataflow, serverName, databaseName, catalog);
212                        } else if (supportsCatalogs) {
213                            processCatalogCatalogOnly(vendor, supportsCatalogs, supportsSchemas, dataflow, serverName, databaseName, catalog);
214                        } else if (supportsSchemas) {
215                            processCatalogSchemaOnly(vendor, supportsCatalogs, supportsSchemas, dataflow, serverName, catalog);
216                        }
217                    }
218                }
219            }
220        }
221
222        sortTableColumns(dataflow);
223        return dataflow;
224    }
225
226    /** A visible, machine-readable marker that a manifest was refused on version. */
227    private static error unsupportedVersionError(int formatVersion) {
228        error e = new error();
229        e.setErrorType(ErrorInfo.METADATA_ERROR);
230        e.setErrorMessage("Unsupported sqlflow-sharded formatVersion " + formatVersion
231                + "; this build supports up to " + MetadataReader.SUPPORTED_SHARDED_FORMAT_VERSION
232                + ". Metadata not loaded.");
233        return e;
234    }
235
236    private String loadCatalogFile(String catalogPath) {
237        try {
238            if (catalogPath.startsWith("/") || catalogPath.matches("^[A-Za-z]:.*")) {
239                return new String(Files.readAllBytes(Paths.get(catalogPath)), StandardCharsets.UTF_8);
240            }
241            String normalizedPath = catalogPath.replace("/", File.separator);
242            Path fullPath = Paths.get(baseDir, normalizedPath);
243            return new String(Files.readAllBytes(fullPath), StandardCharsets.UTF_8);
244        } catch (IOException e) {
245            return null;
246        }
247    }
248
249    private void processCatalogWithSchemas(EDbVendor vendor, boolean supportsCatalogs, boolean supportsSchemas,
250                                           dataflow dataflow, String serverName, String databaseName, Map catalog) {
251        List schemas = (List) catalog.get("schemas");
252        if (schemas == null) {
253            return;
254        }
255
256        for (int j = 0; j < schemas.size(); j++) {
257            Map jsonSchema = (Map) schemas.get(j);
258            String schemaName = (String) jsonSchema.get("name");
259            if (SQLUtil.parseNames(schemaName).size() > 1) {
260                schemaName = "\"" + schemaName + "\"";
261            }
262
263            processTablesAndViews(vendor, supportsCatalogs, supportsSchemas, dataflow, serverName, databaseName, schemaName, jsonSchema);
264            processProcedures(vendor, supportsCatalogs, supportsSchemas, dataflow, serverName, databaseName, schemaName, jsonSchema);
265        }
266    }
267
268    private void processCatalogCatalogOnly(EDbVendor vendor, boolean supportsCatalogs, boolean supportsSchemas,
269                                           dataflow dataflow, String serverName, String databaseName, Map catalog) {
270        processTablesAndViews(vendor, supportsCatalogs, supportsSchemas, dataflow, serverName, databaseName,
271                TSQLEnv.DEFAULT_SCHEMA_NAME, catalog);
272        processProcedures(vendor, supportsCatalogs, supportsSchemas, dataflow, serverName, databaseName,
273                TSQLEnv.DEFAULT_SCHEMA_NAME, catalog);
274    }
275
276    private void processCatalogSchemaOnly(EDbVendor vendor, boolean supportsCatalogs, boolean supportsSchemas,
277                                          dataflow dataflow, String serverName, Map catalog) {
278        String databaseName = TSQLEnv.DEFAULT_DB_NAME;
279        if (SQLUtil.parseNames(databaseName).size() > 1) {
280            databaseName = "\"" + databaseName + "\"";
281        }
282
283        List schemas = (List) catalog.get("schemas");
284        if (schemas == null) {
285            return;
286        }
287
288        for (int j = 0; j < schemas.size(); j++) {
289            Map jsonSchema = (Map) schemas.get(j);
290            String schemaName = (String) jsonSchema.get("name");
291            if (SQLUtil.parseNames(schemaName).size() > 1) {
292                schemaName = "\"" + schemaName + "\"";
293            }
294
295            processTablesAndViews(vendor, supportsCatalogs, supportsSchemas, dataflow, serverName, databaseName, schemaName, jsonSchema);
296            processProcedures(vendor, supportsCatalogs, supportsSchemas, dataflow, serverName, databaseName, schemaName, jsonSchema);
297        }
298    }
299
300    private void processTablesAndViews(EDbVendor vendor, boolean supportsCatalogs, boolean supportsSchemas,
301                                      dataflow dataflow, String serverName, String databaseName, String schemaName,
302                                      Map jsonSchema) {
303        List tables = (List) jsonSchema.get("tables");
304        List views = (List) jsonSchema.get("views");
305
306        List dbObjs = new ArrayList();
307        if (tables != null) {
308            dbObjs.addAll(tables);
309        }
310        if (views != null) {
311            dbObjs.addAll(views);
312        }
313
314        for (int k = 0; k < dbObjs.size(); k++) {
315            Map jsonTable = (Map) dbObjs.get(k);
316            String tableName = (String) jsonTable.get("name");
317            String type = (String) jsonTable.get("type");
318            String fromDDL = (String) jsonTable.get("fromDDL");
319            boolean isView = false;
320            if (type != null && type.toLowerCase(Locale.ROOT).indexOf("view") != -1) {
321                isView = true;
322            }
323
324            List columns = (List) jsonTable.get("columns");
325            if (columns == null) {
326                continue;
327            }
328
329            for (int l = 0; l < columns.size(); l++) {
330                Map jsonColumn = (Map) columns.get(l);
331                String columnName = (String) jsonColumn.get("name");
332                String dataType = null;
333                if (jsonColumn.containsKey("dataType")) {
334                    dataType = (String) jsonColumn.get("dataType");
335                }
336                Boolean primaryKey = null;
337                if (jsonColumn.containsKey("primaryKey")) {
338                    primaryKey = (Boolean) jsonColumn.get("primaryKey");
339                }
340                Boolean unqiueKey = null;
341                if (jsonColumn.containsKey("unqiueKey")) {
342                    unqiueKey = (Boolean) jsonColumn.get("unqiueKey");
343                }
344                Boolean indexKey = null;
345                if (jsonColumn.containsKey("indexKey")) {
346                    indexKey = (Boolean) jsonColumn.get("indexKey");
347                }
348                Boolean foreignKey = null;
349                if (jsonColumn.containsKey("foreignKey")) {
350                    foreignKey = (Boolean) jsonColumn.get("foreignKey");
351                }
352
353                appendTable(vendor, supportsCatalogs, supportsSchemas, dataflow, serverName, databaseName,
354                        schemaName, tableName, isView, false, columnName, dataType, primaryKey, unqiueKey, indexKey, foreignKey, fromDDL);
355            }
356        }
357
358        List synonyms = (List) jsonSchema.get("synonyms");
359        if (synonyms != null) {
360            for (int k = 0; k < synonyms.size(); k++) {
361                Map jsonSynonym = (Map) synonyms.get(k);
362                String synonymName = (String) jsonSynonym.get("name");
363                String synonymSourceName = (String) jsonSynonym.get("sourceName");
364                String synonymSourceDb = (String) jsonSynonym.get("sourceDatabase");
365                String synonymSourceSchema = (String) jsonSynonym.get("sourceSchema");
366
367                if (SQLUtil.isEmpty(synonymSourceDb)) {
368                    synonymSourceDb = databaseName;
369                }
370                if (SQLUtil.isEmpty(synonymSourceSchema)) {
371                    synonymSourceSchema = schemaName;
372                }
373
374                Object tableKey = getFullTableName(vendor, serverName, synonymSourceDb, synonymSourceSchema, synonymSourceName);
375                table sourceTable = tableMap.get(tableKey);
376                if (sourceTable == null) {
377                    // the synonym of a synonym
378                    sourceTable = tableMap.get(getFullName(vendor, ESQLDataObjectType.dotSynonyms, serverName, synonymSourceDb, synonymSourceSchema, synonymSourceName));
379                }
380                if (sourceTable != null) {
381                    List<column> columns = sourceTable.getColumns();
382                    String fromDDL = sourceTable.getFromDDL();
383                    for (int l = 0; l < columns.size(); l++) {
384                        column jsonColumn = columns.get(l);
385                        String columnName = jsonColumn.getName();
386                        String dataType = jsonColumn.getDataType();
387                        appendTable(vendor, supportsCatalogs, supportsSchemas, dataflow,
388                                serverName, databaseName, schemaName, synonymName, false, true,
389                                columnName, dataType, false, false, false, false, fromDDL);
390                    }
391
392                    Object synonymTableKey = getFullName(vendor, ESQLDataObjectType.dotSynonyms, serverName, databaseName, schemaName, synonymName);
393                    table synonymTable = tableMap.get(synonymTableKey);
394                    if (synonymTable != null) {
395                        List<column> synonymColumns = synonymTable.getColumns();
396
397                        for (int l = 0; l < synonymColumns.size(); l++) {
398                            relationship relationElement = new relationship();
399                            relationElement.setType(RelationshipType.fdd.name());
400                            relationElement.setEffectType(EffectType.synonym.name());
401
402                            long id = ++ModelBindingManager.get().RELATION_ID;
403                            relationElement.setId(String.valueOf(id));
404
405                            column targetColumn = synonymColumns.get(l);
406                            targetColumn target = new targetColumn();
407                            target.setId(String.valueOf(targetColumn.getId()));
408                            target.setColumn(targetColumn.getName());
409                            target.setParent_id(String.valueOf(synonymTable.getId()));
410                            target.setParent_name(synonymTable.getName());
411                            relationElement.setTarget(target);
412
413                            column sourceColumn = columns.get(l);
414                            sourceColumn source = new sourceColumn();
415                            source.setId(String.valueOf(sourceColumn.getId()));
416                            source.setColumn(sourceColumn.getName());
417                            source.setParent_id(String.valueOf(sourceTable.getId()));
418                            source.setParent_name(sourceTable.getName());
419                            relationElement.addSource(source);
420
421                            dataflow.getRelationships().add(relationElement);
422                        }
423                    }
424                }
425            }
426        }
427    }
428
429    private void processProcedures(EDbVendor vendor, boolean supportsCatalogs, boolean supportsSchemas,
430                                   dataflow dataflow, String serverName, String databaseName, String schemaName,
431                                   Map jsonSchema) {
432        List<Map> procedures = (List<Map>) jsonSchema.get("procedures");
433        if (procedures != null) {
434            for (Map procedure : procedures) {
435                appendProcedure(vendor, supportsCatalogs, supportsSchemas, dataflow, serverName, databaseName, schemaName, procedure, null, ESQLDataObjectType.dotProcedure);
436            }
437        }
438
439        List<Map> functions = (List<Map>) jsonSchema.get("functions");
440        if (functions != null) {
441            for (Map function : functions) {
442                appendProcedure(vendor, supportsCatalogs, supportsSchemas, dataflow, serverName, databaseName, schemaName, function, null, ESQLDataObjectType.dotFunction);
443            }
444        }
445
446        List<Map> triggers = (List<Map>) jsonSchema.get("triggers");
447        if (triggers != null) {
448            for (Map trigger : triggers) {
449                appendProcedure(vendor, supportsCatalogs, supportsSchemas, dataflow, serverName, databaseName, schemaName, trigger, null, ESQLDataObjectType.dotTrigger);
450            }
451        }
452
453        List<Map> oraclePackages = (List<Map>) jsonSchema.get("packages");
454        if (oraclePackages != null) {
455            for (Map oraclePackageItem : oraclePackages) {
456                String oraclePackageName = (String) oraclePackageItem.get("name");
457                oraclePackage oraclePackage = appendOraclePackage(vendor, supportsCatalogs, supportsSchemas, dataflow, serverName, databaseName, schemaName, oraclePackageName);
458
459                List<Map> pkgProcedures = (List<Map>) oraclePackageItem.get("procedures");
460                if (pkgProcedures != null) {
461                    for (Map procedure : pkgProcedures) {
462                        appendProcedure(vendor, supportsCatalogs, supportsSchemas, dataflow, serverName, databaseName, schemaName, procedure, oraclePackage, ESQLDataObjectType.dotProcedure);
463                    }
464                }
465
466                List<Map> pkgFunctions = (List<Map>) oraclePackageItem.get("functions");
467                if (pkgFunctions != null) {
468                    for (Map function : pkgFunctions) {
469                        appendProcedure(vendor, supportsCatalogs, supportsSchemas, dataflow, serverName, databaseName, schemaName, function, oraclePackage, ESQLDataObjectType.dotFunction);
470                    }
471                }
472
473                List<Map> pkgTriggers = (List<Map>) oraclePackageItem.get("triggers");
474                if (pkgTriggers != null) {
475                    for (Map trigger : pkgTriggers) {
476                        appendProcedure(vendor, supportsCatalogs, supportsSchemas, dataflow, serverName, databaseName, schemaName, trigger, oraclePackage, ESQLDataObjectType.dotTrigger);
477                    }
478                }
479            }
480        }
481    }
482
483    private void init(EDbVendor vendor) {
484        this.vendor = vendor;
485        procedureMap.clear();
486        oraclePackageMap.clear();
487        tableMap.clear();
488        columnMap.clear();
489        if (ModelBindingManager.get() == null) {
490            ModelBindingManager.set(new ModelBindingManager());
491        }
492    }
493
494    private void sortTableColumns(dataflow dataflow) {
495        if (dataflow.getTables() != null) {
496            for (table table : dataflow.getTables()) {
497                Collections.sort(table.getColumns(), new Comparator<column>() {
498                    public int compare(column t1, column t2) {
499                        if (t1.getName().equalsIgnoreCase("RelationRows"))
500                            return 1;
501                        if (t2.getName().equalsIgnoreCase("RelationRows"))
502                            return -1;
503                        return 0;
504                    }
505                });
506            }
507        }
508
509        if (dataflow.getResultsets() != null) {
510            for (table table : dataflow.getResultsets()) {
511                Collections.sort(table.getColumns(), new Comparator<column>() {
512                    public int compare(column t1, column t2) {
513                        if (t1.getName().equalsIgnoreCase("RelationRows"))
514                            return 1;
515                        if (t2.getName().equalsIgnoreCase("RelationRows"))
516                            return -1;
517                        return 0;
518                    }
519                });
520            }
521        }
522    }
523
524    private oraclePackage appendOraclePackage(EDbVendor vendor, boolean supportsCatalogs, boolean supportsSchemas,
525                                              dataflow dataflow, String serverName, String databaseName, String schemaName,
526                                              String oraclePackageName) {
527        Object oraclePackageKey = getFullName(vendor, ESQLDataObjectType.dotOraclePackage, serverName, databaseName, schemaName, oraclePackageName);
528        if (!oraclePackageMap.containsKey(oraclePackageKey)) {
529            oraclePackage oraclePackage = new oraclePackage();
530            if (serverName != null) {
531                oraclePackage.setServer(serverName);
532            } else if (sqlenv != null && sqlenv.getDefaultServerName() != null
533                    && !TSQLEnv.DEFAULT_SERVER_NAME.equalsIgnoreCase(sqlenv.getDefaultServerName())) {
534                oraclePackage.setServer(sqlenv.getDefaultServerName());
535            }
536            if (supportsCatalogs && databaseName != null) {
537                oraclePackage.setDatabase(databaseName);
538            } else if (supportsCatalogs && sqlenv != null && sqlenv.getDefaultCatalogName() != null
539                    && !TSQLEnv.DEFAULT_DB_NAME.equalsIgnoreCase(sqlenv.getDefaultCatalogName())) {
540                oraclePackage.setDatabase(sqlenv.getDefaultCatalogName());
541            }
542
543            if (supportsSchemas && schemaName != null) {
544                oraclePackage.setSchema(schemaName);
545            } else if (supportsSchemas && sqlenv != null && sqlenv.getDefaultSchemaName() != null
546                    && !TSQLEnv.DEFAULT_SCHEMA_NAME.equalsIgnoreCase(sqlenv.getDefaultSchemaName())) {
547                oraclePackage.setSchema(sqlenv.getDefaultSchemaName());
548            }
549
550            oraclePackage.setName(oraclePackageName);
551            if (supportsSchemas && !SQLUtil.isEmpty(oraclePackage.getSchema())) {
552                oraclePackage.setName(oraclePackage.getSchema() + "." + oraclePackage.getName());
553            }
554            if (supportsCatalogs && !SQLUtil.isEmpty(oraclePackage.getDatabase())) {
555                oraclePackage.setName(oraclePackage.getDatabase() + "." + oraclePackage.getName());
556            }
557            oraclePackage.setId(String.valueOf(++ModelBindingManager.get().TABLE_COLUMN_ID));
558            if (ModelBindingManager.getGlobalSqlInfo() != null) {
559                oraclePackage.setCoordinate(new Pair3<Long, Long, Integer>(-1L, -1L,
560                        ModelBindingManager.getGlobalSqlInfo().getIndexOf(ModelBindingManager.getGlobalHash())) + ","
561                        + new Pair3<Long, Long, Integer>(-1L, -1L,
562                        ModelBindingManager.getGlobalSqlInfo().getIndexOf(ModelBindingManager.getGlobalHash())));
563            }
564            dataflow.getPackages().add(oraclePackage);
565            oraclePackageMap.put(oraclePackageKey, oraclePackage);
566        }
567        return oraclePackageMap.get(oraclePackageKey);
568    }
569
570    private void appendProcedure(EDbVendor vendor, boolean supportsCatalogs, boolean supportsSchemas,
571                                 dataflow dataflow, String serverName, String databaseName, String schemaName,
572                                 Map procedureObj, oraclePackage oraclePackage, ESQLDataObjectType role) {
573        String procedureName = (String) procedureObj.get("name");
574        if (procedureName == null) {
575            return;
576        }
577
578        Object procedureKey = getFullName(vendor, role, serverName, databaseName, schemaName, procedureName);
579        if (!procedureMap.containsKey(procedureKey)) {
580            procedure procedure = new procedure();
581            if (serverName != null) {
582                procedure.setServer(serverName);
583            } else if (sqlenv != null && sqlenv.getDefaultServerName() != null
584                    && !TSQLEnv.DEFAULT_SERVER_NAME.equalsIgnoreCase(sqlenv.getDefaultServerName())) {
585                procedure.setServer(sqlenv.getDefaultServerName());
586            }
587            if (supportsCatalogs && databaseName != null) {
588                procedure.setDatabase(databaseName);
589            } else if (supportsCatalogs && sqlenv != null && sqlenv.getDefaultCatalogName() != null
590                    && !TSQLEnv.DEFAULT_DB_NAME.equalsIgnoreCase(sqlenv.getDefaultCatalogName())) {
591                procedure.setDatabase(sqlenv.getDefaultCatalogName());
592            }
593
594            if (supportsSchemas && schemaName != null) {
595                procedure.setSchema(schemaName);
596            } else if (supportsSchemas && sqlenv != null && sqlenv.getDefaultSchemaName() != null
597                    && !TSQLEnv.DEFAULT_SCHEMA_NAME.equalsIgnoreCase(sqlenv.getDefaultSchemaName())) {
598                procedure.setSchema(sqlenv.getDefaultSchemaName());
599            }
600
601            procedure.setName(procedureName);
602            if (supportsSchemas && !SQLUtil.isEmpty(procedure.getSchema())) {
603                procedure.setName(procedure.getSchema() + "." + procedure.getName());
604            }
605            if (supportsCatalogs && !SQLUtil.isEmpty(procedure.getDatabase())) {
606                procedure.setName(procedure.getDatabase() + "." + procedure.getName());
607            }
608            procedure.setId(String.valueOf(++ModelBindingManager.get().TABLE_COLUMN_ID));
609            if (ModelBindingManager.getGlobalSqlInfo() != null) {
610                procedure.setCoordinate(new Pair3<Long, Long, Integer>(-1L, -1L,
611                        ModelBindingManager.getGlobalSqlInfo().getIndexOf(ModelBindingManager.getGlobalHash())) + ","
612                        + new Pair3<Long, Long, Integer>(-1L, -1L,
613                        ModelBindingManager.getGlobalSqlInfo().getIndexOf(ModelBindingManager.getGlobalHash())));
614            }
615            procedure.setType((String) procedureObj.get("type"));
616            if (oraclePackage != null) {
617                oraclePackage.getProcedures().add(procedure);
618            } else {
619                dataflow.getProcedures().add(procedure);
620            }
621            procedureMap.put(procedureKey, procedure);
622        }
623    }
624
625    private void appendTable(EDbVendor vendor, boolean supportsCatalogs, boolean supportsSchemas,
626                            dataflow dataflow, String serverName, String databaseName, String schemaName,
627                            String tableName, boolean isView, boolean isSynonym, String columnName,
628                            String dataType, Boolean primaryKey, Boolean unqiueKey, Boolean indexKey,
629                            Boolean foreignKey, String fromDDL) {
630        // A synonym is keyed in its own role: a profile may give synonyms rules of their own.
631        Object tableKey = getFullName(vendor, isSynonym ? ESQLDataObjectType.dotSynonyms : ESQLDataObjectType.dotTable,
632                serverName, databaseName, schemaName, tableName);
633        if (!tableMap.containsKey(tableKey)) {
634            table table = new table();
635            // This reader publishes SQL spellings (its input names, converted by their declared form).
636            table.setPublishedNameSqlSpelling(true);
637            if (serverName != null) {
638                table.setServer(serverName);
639            } else if (sqlenv != null && sqlenv.getDefaultServerName() != null
640                    && !TSQLEnv.DEFAULT_SERVER_NAME.equalsIgnoreCase(sqlenv.getDefaultServerName())) {
641                table.setServer(sqlenv.getDefaultServerName());
642            }
643            if (supportsCatalogs && databaseName != null) {
644                table.setDatabase(databaseName);
645            } else if (supportsCatalogs && sqlenv != null && sqlenv.getDefaultCatalogName() != null
646                    && !TSQLEnv.DEFAULT_DB_NAME.equalsIgnoreCase(sqlenv.getDefaultCatalogName())) {
647                table.setDatabase(sqlenv.getDefaultCatalogName());
648            }
649
650            if (supportsSchemas && schemaName != null) {
651                table.setSchema(schemaName);
652            } else if (supportsSchemas && sqlenv != null && sqlenv.getDefaultSchemaName() != null
653                    && !TSQLEnv.DEFAULT_SCHEMA_NAME.equalsIgnoreCase(sqlenv.getDefaultSchemaName())) {
654                table.setSchema(sqlenv.getDefaultSchemaName());
655            }
656
657            table.setName(tableName);
658            if (supportsSchemas && !SQLUtil.isEmpty(table.getSchema())) {
659                table.setName(table.getSchema() + "." + table.getName());
660            }
661            if (supportsCatalogs && !SQLUtil.isEmpty(table.getDatabase())) {
662                table.setName(table.getDatabase() + "." + table.getName());
663            }
664            table.setId(String.valueOf(++ModelBindingManager.get().TABLE_COLUMN_ID));
665            if (ModelBindingManager.getGlobalSqlInfo() != null) {
666                table.setCoordinate(new Pair3<Long, Long, Integer>(-1L, -1L,
667                        ModelBindingManager.getGlobalSqlInfo().getIndexOf(ModelBindingManager.getGlobalHash())) + ","
668                        + new Pair3<Long, Long, Integer>(-1L, -1L,
669                        ModelBindingManager.getGlobalSqlInfo().getIndexOf(ModelBindingManager.getGlobalHash())));
670            }
671
672            if (!SQLUtil.isEmpty(fromDDL)) {
673                table.setFromDDL(fromDDL);
674            }
675
676            if (isView) {
677                table.setType("view");
678                dataflow.getViews().add(table);
679            } else {
680                table.setType("table");
681                dataflow.getTables().add(table);
682            }
683            if (isSynonym) {
684                table.setSubType(SubType.synonym.name());
685            }
686
687            tableMap.put(tableKey, table);
688        }
689
690        table table = tableMap.get(tableKey);
691        // A column belongs to its table or synonym under that object's own key, and is named under column rules.
692        Object sourceColumnKey = java.util.Arrays.asList(tableKey,
693                DlineageUtil.lexicalNameKey(vendor, columnName, ESQLDataObjectType.dotColumn));
694        if (!columnMap.containsKey(sourceColumnKey)) {
695            column column = new column();
696            column.setId(String.valueOf(++ModelBindingManager.get().TABLE_COLUMN_ID));
697            column.setName(columnName);
698            if ("*".equals(columnName)) {
699                // a metadata column named *, not a star
700                column.setStar(Boolean.FALSE);
701            }
702            // Input names have been converted to SQL lexical form at the reader boundary.
703            column.setIdentifierKey(DlineageUtil.lexicalColumnKey(vendor, columnName));
704            column.setDataType(dataType);
705            column.setPrimaryKey(primaryKey);
706            column.setForeignKey(foreignKey);
707            column.setIndexKey(indexKey);
708            column.setUnqiueKey(unqiueKey);
709            if ("RelationRows".equalsIgnoreCase(columnName)) {
710                column.setSource("system");
711            }
712            if (ModelBindingManager.getGlobalSqlInfo() != null) {
713                column.setCoordinate(new Pair3<Long, Long, Integer>(-1L, -1L,
714                        ModelBindingManager.getGlobalSqlInfo().getIndexOf(ModelBindingManager.getGlobalHash())) + ","
715                        + new Pair3<Long, Long, Integer>(-1L, -1L,
716                        ModelBindingManager.getGlobalSqlInfo().getIndexOf(ModelBindingManager.getGlobalHash())));
717            }
718            table.getColumns().add(column);
719            columnMap.put(sourceColumnKey, column);
720        }
721    }
722
723    private Object getFullTableName(EDbVendor vendor, String... segments) {
724        return getFullName(vendor, ESQLDataObjectType.dotTable, segments);
725    }
726
727    /**
728     * The reader's key of an object: its server as given, and its name joined from the other segments under the
729     * analysis's identifier rules for {@code objectType} (the names are SQL spellings at this boundary).
730     */
731    private Object getFullName(EDbVendor vendor, ESQLDataObjectType objectType, String... segments) {
732        StringBuilder builder = new StringBuilder();
733        for (int i = 1; i < segments.length; i++) {
734                        if (segments[i] == null) {
735                                continue;
736                        }
737            builder.append(segments[i]);
738            if (i < segments.length - 1) {
739                builder.append(".");
740            }
741        }
742
743        return java.util.Arrays.asList(segments[0], DlineageUtil.lexicalNameKey(vendor, builder.toString(), objectType));
744    }
745
746    public static void main(String[] args) throws Exception {
747        String sampleManifestPath = "sqlflow-sharded/sample/manifest.json";
748        String baseDir = new File(sampleManifestPath).getParent();
749        dataflow dataflow = new SqlflowShardedMetadataAnalyzer(baseDir)
750                .analyzeMetadata(EDbVendor.dbvmssql, SQLUtil.getFileContent(new File(sampleManifestPath)));
751        System.out.println("Tables: " + dataflow.getTables().size());
752        System.out.println("Views: " + dataflow.getViews().size());
753        System.out.println("Procedures: " + dataflow.getProcedures().size());
754    }
755}