001package gudusoft.gsqlparser.sqlenv.parser;
002
003import gudusoft.gsqlparser.EDbVendor;
004import gudusoft.gsqlparser.sqlenv.TSQLEnv;
005import gudusoft.gsqlparser.sqlenv.parser.grabit.GrabitSQLEnv;
006import gudusoft.gsqlparser.sqlenv.parser.grabit.MultipleGrabitSQLEnv;
007import gudusoft.gsqlparser.sqlenv.parser.sharded.SqlflowShardedSQLEnv;
008import gudusoft.gsqlparser.sqlenv.parser.sqldep.MultipleSQLDepSQLEnv;
009import gudusoft.gsqlparser.sqlenv.parser.sqldep.SQLDepSQLEnv;
010import gudusoft.gsqlparser.sqlenv.parser.sqlflow.SqlflowSQLEnv;
011import gudusoft.gsqlparser.dlineage.dataflow.metadata.MetadataReader;
012import gudusoft.gsqlparser.util.SQLUtil;
013import gudusoft.gsqlparser.util.json.JSON;
014
015import java.io.File;
016import java.io.IOException;
017import java.nio.charset.StandardCharsets;
018import java.nio.file.Files;
019import java.nio.file.Path;
020import java.nio.file.Paths;
021import java.util.ArrayList;
022import java.util.List;
023import java.util.Map;
024import java.util.logging.Level;
025import java.util.logging.Logger;
026
027@SuppressWarnings("rawtypes")
028public class TJSONSQLEnvParser implements TSQLEnvParser {
029        
030        private String defaultServer;
031        private String defaultDatabase;
032        private String defaultSchema;
033        
034        public TJSONSQLEnvParser(String defaultServer, String defaultDatabase, String defaultSchema) {
035                this.defaultServer = defaultServer;
036                this.defaultDatabase = defaultDatabase;
037                this.defaultSchema = defaultSchema;
038        }
039
040        public TSQLEnv[] parseSQLEnv(EDbVendor vendor, String sql) {
041                return parseSQLEnv(vendor, sql, null);
042        }
043
044        /**
045         * @param explicitBaseDir directory to resolve a sharded manifest's relative
046         *        catalog/source paths against, used when {@code sql} is inline manifest
047         *        CONTENT (which carries no location of its own). Ignored when {@code sql}
048         *        is a file path, since the path already yields the base directory.
049         */
050        public TSQLEnv[] parseSQLEnv(EDbVendor vendor, String sql, String explicitBaseDir) {
051                if (SQLUtil.isEmpty(sql))
052                        return null;
053
054                String trimSQL = sql.trim();
055
056                // Check if input is a file path (for sqlflow-sharded format). For inline
057                // content, fall back to the caller-supplied base directory so relative
058                // catalog paths still resolve.
059                String baseDir = explicitBaseDir;
060                String jsonContent = trimSQL;
061
062                if (!trimSQL.startsWith("{") && !trimSQL.startsWith("[")) {
063                        // Try to interpret as a file path
064                        File file = new File(trimSQL);
065                        if (file.exists() && file.isFile()) {
066                                Path filePath = file.toPath();
067                                Path parentDir = filePath.getParent();
068                                baseDir = (parentDir != null) ? parentDir.toString() : "";
069
070                                try {
071                                        jsonContent = new String(Files.readAllBytes(filePath), StandardCharsets.UTF_8).trim();
072                                } catch (IOException e) {
073                                        Logger.getLogger(TJSONSQLEnvParser.class.getName())
074                                                        .log(Level.WARNING, "Failed to read file: " + trimSQL, e);
075                                        return null;
076                                }
077                        }
078                }
079
080                if (jsonContent.startsWith("{") && jsonContent.endsWith("}")) {
081                        return getJSONSQLEnv(vendor, jsonContent, baseDir);
082                }
083
084                if (jsonContent.startsWith("[") && jsonContent.endsWith("]")) {
085                        return getMultipleJSONSQLEnv(vendor, jsonContent);
086                }
087
088                return null;
089        }
090
091        private TSQLEnv[] getMultipleJSONSQLEnv(EDbVendor vendor, String trimSQL) {
092                try {
093                        List json = (List) JSON.parseObject(trimSQL);
094                        if (trimSQL.indexOf("createdBy") != -1) {
095                                if (trimSQL.toLowerCase().indexOf("sqldep") != -1) {
096                                        return new TSQLEnv[] { new MultipleSQLDepSQLEnv(defaultServer, defaultDatabase, defaultSchema, vendor, json) };
097                                }
098                                if (trimSQL.toLowerCase().indexOf("grabit") != -1) {
099                                        return new TSQLEnv[] { new MultipleGrabitSQLEnv(defaultServer, defaultDatabase, defaultSchema, vendor, json) };
100                                }
101                        }
102                } catch (Exception e) {
103                        Logger.getLogger(TJSONSQLEnvParser.class.getName()).log(Level.WARNING, "Parse json failed.", e);
104                }
105                return null;
106        }
107
108        private TSQLEnv[] getJSONSQLEnv(EDbVendor vendor, String trimSQL, String baseDir) {
109                try {
110                        Map json = (Map) JSON.parseObject(trimSQL);
111                        // Route on the authoritative `format` field BEFORE the createdBy
112                        // product brand. A sqlflow-sharded manifest carries the exporter
113                        // brand (SQLdep/grabit/sqlflow) in createdBy, so a brand-first check
114                        // sent a SQLdep-branded sharded manifest to the legacy SQLDepSQLEnv
115                        // and silently lost the external catalog used for column resolution.
116                        if (MetadataReader.isSqlflowSharded(trimSQL)) {
117                                if (!MetadataReader.isSupportedSqlflowSharded(trimSQL)) {
118                                        // Unknown future formatVersion: do not load a catalog we may
119                                        // mis-read. No lineage catalog is better than a wrong one.
120                                        Logger.getLogger(TJSONSQLEnvParser.class.getName()).log(Level.WARNING,
121                                                        "Unsupported sqlflow-sharded formatVersion "
122                                                                        + MetadataReader.shardedFormatVersion(trimSQL)
123                                                                        + "; this build supports up to "
124                                                                        + MetadataReader.SUPPORTED_SHARDED_FORMAT_VERSION
125                                                                        + ". SQLEnv catalog not loaded.");
126                                        return null;
127                                }
128                                List servers = (List) json.get("servers");
129                                if (servers == null)
130                                        return null;
131                                List<TSQLEnv> sqlEnvs = new ArrayList<TSQLEnv>();
132                                for (int i = 0; i < servers.size(); i++) {
133                                        Map server = (Map) servers.get(i);
134                                        String dbVendor = (String) server.get("dbVendor");
135                                        if (SQLUtil.isEmpty(dbVendor)) {
136                                                sqlEnvs.add(new SqlflowShardedSQLEnv(defaultServer, defaultDatabase, defaultSchema, vendor, server, baseDir));
137                                        } else {
138                                                sqlEnvs.add(new SqlflowShardedSQLEnv(defaultServer, defaultDatabase, defaultSchema, EDbVendor.valueOf(dbVendor), server, baseDir));
139                                        }
140                                }
141                                return sqlEnvs.toArray(new TSQLEnv[0]);
142                        }
143                        if (json.containsKey("createdBy")) {
144                                String createdBy = (String) json.get("createdBy");
145                                if (createdBy.toLowerCase().indexOf("sqldep") != -1) {
146                                        return new TSQLEnv[] { new SQLDepSQLEnv(defaultServer, defaultDatabase, defaultSchema, vendor, json) };
147                                }
148                                if (createdBy.toLowerCase().indexOf("grabit") != -1) {
149                                        return new TSQLEnv[] { new GrabitSQLEnv(defaultServer, defaultDatabase, defaultSchema, vendor, json) };
150                                }
151                                if (createdBy.toLowerCase().indexOf("sqlflow") != -1) {
152                                        {
153                                                List servers = (List) json.get("servers");
154                                                if (servers == null)
155                                                        return null;
156                                                List<TSQLEnv> sqlEnvs = new ArrayList<TSQLEnv>();
157                                                for (int i = 0; i < servers.size(); i++) {
158                                                        Map server = (Map) servers.get(i);
159                                                        String dbVendor = (String) server.get("dbVendor");
160                                                        if (SQLUtil.isEmpty(dbVendor)) {
161                                                                sqlEnvs.add(new SqlflowSQLEnv(defaultServer, defaultDatabase, defaultSchema, vendor, server));
162                                                        } else {
163                                                                sqlEnvs.add(new SqlflowSQLEnv(defaultServer, defaultDatabase, defaultSchema, EDbVendor.valueOf(dbVendor), server));
164                                                        }
165                                                }
166                                                return sqlEnvs.toArray(new TSQLEnv[0]);
167                                        }
168                                }
169                        }
170                } catch (Exception e) {
171                        Logger.getLogger(TJSONSQLEnvParser.class.getName()).log(Level.WARNING, "Parse json failed.", e);
172                }
173                return null;
174        }
175}