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