001package gudusoft.gsqlparser.dlineage.util;
002
003import gudusoft.gsqlparser.dlineage.dataflow.metadata.MetadataReader;
004import gudusoft.gsqlparser.dlineage.dataflow.metadata.sqlflow.SqlflowObjectAdapter;
005import gudusoft.gsqlparser.dlineage.dataflow.model.Option;
006import gudusoft.gsqlparser.dlineage.dataflow.model.SqlInfo;
007import gudusoft.gsqlparser.dlineage.metadata.Sqlflow;
008import gudusoft.gsqlparser.util.SQLUtil;
009import gudusoft.gsqlparser.util.json.JSON;
010
011import java.io.File;
012import java.io.FileWriter;
013import java.io.IOException;
014import java.util.List;
015import java.util.Map;
016
017import org.slf4j.Logger;
018import org.slf4j.LoggerFactory;
019
020public class LargeFileDetector {
021
022    private static final Logger logger = LoggerFactory.getLogger(LargeFileDetector.class);
023
024    public enum DetectionType {
025        SQL_INFO_COUNT,
026        SQL_TOTAL_SIZE,
027        SHARDED_SOURCE_COUNT,
028        SHARDED_SOURCE_SIZE,
029        MANIFEST_QUERY_COUNT,
030        SINGLE_FILE_SIZE,
031        NONE
032    }
033
034    public static class LargeFileDetectionResult {
035        public final boolean shouldDelegate;
036        public final String reason;
037        public final DetectionType type;
038
039        public LargeFileDetectionResult(boolean shouldDelegate, String reason, DetectionType type) {
040            this.shouldDelegate = shouldDelegate;
041            this.reason = reason;
042            this.type = type;
043        }
044    }
045
046    public static LargeFileDetectionResult detect(SqlInfo[] sqlInfos, File[] originalFiles, Option option) {
047        LargeFileDetectionResult r = checkManifestScale(sqlInfos, option);
048        if (r.shouldDelegate) return r;
049
050        r = checkSqlInfoCount(sqlInfos, option);
051        if (r.shouldDelegate) return r;
052
053        r = checkSqlTotalSize(sqlInfos, option);
054        if (r.shouldDelegate) return r;
055
056        r = checkSingleFileSize(originalFiles, option);
057        if (r.shouldDelegate) return r;
058
059        return checkTotalFileSize(originalFiles, option);
060    }
061
062    public static void persistSqlToTempFile(SqlInfo info, String sql) {
063        try {
064            File tempFile = File.createTempFile("gsp-dataflow-", ".sql");
065            try (FileWriter writer = new FileWriter(tempFile)) {
066                writer.write(sql);
067            }
068            info.setFilePath(tempFile.getAbsolutePath());
069            info.setFileName(tempFile.getName());
070            info.setSql(null);
071            logger.info("Large SQL persisted to temp file: " + tempFile.getAbsolutePath());
072        } catch (IOException e) {
073            logger.error("Failed to persist large SQL to temp file", e);
074        }
075    }
076
077    public static File serializeToTempFile(Map<String, Object> map) throws IOException {
078        File tempFile = File.createTempFile("gsp-dataflow-", ".sql");
079        try (FileWriter writer = new FileWriter(tempFile)) {
080            JSON.toJSONString(map, writer);
081        }
082        return tempFile;
083    }
084
085    public static File writeSqlflowToTempFile(Sqlflow sqlflow) {
086        try {
087            return serializeToTempFile(SqlflowObjectAdapter.toMap(sqlflow));
088        } catch (IOException e) {
089            throw new RuntimeException("Failed to serialize sqlflow to temp file", e);
090        }
091    }
092
093    private static LargeFileDetectionResult checkSqlInfoCount(SqlInfo[] sqlInfos, Option option) {
094        if (sqlInfos == null) return notDelegated();
095        int count = 0;
096        for (SqlInfo info : sqlInfos) {
097            if (info != null) count++;
098        }
099        if (count >= option.getLargeSqlInfoCountThreshold()) {
100            return delegated(DetectionType.SQL_INFO_COUNT,
101                    "SqlInfo count " + count + " >= " + option.getLargeSqlInfoCountThreshold());
102        }
103        return notDelegated();
104    }
105
106    private static LargeFileDetectionResult checkSqlTotalSize(SqlInfo[] sqlInfos, Option option) {
107        if (sqlInfos == null) return notDelegated();
108        long total = 0;
109        for (SqlInfo info : sqlInfos) {
110            if (info != null && info.getSql() != null) {
111                total += info.getSql().length();
112            }
113        }
114        if (total >= option.getLargeSqlTotalSizeThreshold()) {
115            return delegated(DetectionType.SQL_TOTAL_SIZE,
116                    "Total SQL size " + total + " >= " + option.getLargeSqlTotalSizeThreshold());
117        }
118        return notDelegated();
119    }
120
121    private static LargeFileDetectionResult checkSingleFileSize(File[] originalFiles, Option option) {
122        if (originalFiles == null) return notDelegated();
123        long thresholdBytes = option.getLargeFileSplitSizeMB() * 1024L * 1024L;
124        for (File f : originalFiles) {
125            if (f != null && f.isFile() && f.length() >= thresholdBytes) {
126                return delegated(DetectionType.SINGLE_FILE_SIZE,
127                        "File size " + f.length() + " >= " + thresholdBytes
128                                + " for " + f.getName());
129            }
130        }
131        return notDelegated();
132    }
133
134    private static LargeFileDetectionResult checkTotalFileSize(File[] originalFiles, Option option) {
135        if (originalFiles == null) return notDelegated();
136        long total = 0;
137        for (File f : originalFiles) {
138            if (f != null && f.isFile()) {
139                total += f.length();
140            }
141        }
142        if (total >= option.getLargeSqlTotalSizeThreshold()) {
143            return delegated(DetectionType.SQL_TOTAL_SIZE,
144                    "Total file size " + total + " >= " + option.getLargeSqlTotalSizeThreshold());
145        }
146        return notDelegated();
147    }
148
149    private static LargeFileDetectionResult checkManifestScale(SqlInfo[] sqlInfos, Option option) {
150        if (sqlInfos == null) return notDelegated();
151        for (SqlInfo info : sqlInfos) {
152            if (info == null) continue;
153            String sql = info.getSql();
154            if (sql == null && info.getFilePath() != null) {
155                File f = new File(info.getFilePath());
156                if (f.exists() && f.isFile()) {
157                    sql = SQLUtil.getFileContent(f);
158                }
159            }
160            if (sql == null) continue;
161            sql = sql.trim();
162            if (!sql.startsWith("{")) continue;
163
164            try {
165                if (MetadataReader.isSqlflowSharded(sql)) {
166                    return checkShardedManifest(sql, info.getFilePath(), option);
167                }
168                if (MetadataReader.isSqlflow(sql) || MetadataReader.isGrabit(sql)) {
169                    return checkManifestQueryCount(sql, option);
170                }
171            } catch (Exception e) {
172                // JSON 解析失败,跳过
173            }
174        }
175        return notDelegated();
176    }
177
178    static LargeFileDetectionResult checkShardedManifest(String manifestSql, String manifestPath, Option option) {
179        Map manifest = (Map) JSON.parseObject(manifestSql);
180        String baseDir = null;
181        if (manifestPath != null) {
182            File manifestFile = new File(manifestPath);
183            File parent = manifestFile.getParentFile();
184            if (parent != null) {
185                baseDir = parent.getAbsolutePath();
186            }
187        }
188        if (baseDir == null) return notDelegated();
189
190        List<Map> servers = (List<Map>) manifest.get("servers");
191        if (servers == null) return notDelegated();
192
193        int sourceFileCount = 0;
194        long sourceTotalSize = 0;
195
196        for (Map server : servers) {
197            List<Map> shards = (List<Map>) server.get("schemas");
198            if (shards == null || shards.isEmpty()) {
199                shards = (List<Map>) server.get("databases");
200            }
201            if (shards == null) continue;
202
203            for (Map shard : shards) {
204                Map source = (Map) shard.get("source");
205                if (source == null) continue;
206                String sourcePath = (String) source.get("path");
207                if (sourcePath == null) continue;
208
209                File sourceFile = new File(baseDir, sourcePath);
210                if (sourceFile.exists()) {
211                    sourceFileCount++;
212                    sourceTotalSize += sourceFile.length();
213                }
214            }
215        }
216
217        if (sourceFileCount >= option.getLargeShardCountThreshold()) {
218            return delegated(DetectionType.SHARDED_SOURCE_COUNT,
219                    "Sharded source count " + sourceFileCount + " >= " + option.getLargeShardCountThreshold());
220        }
221        long thresholdBytes = option.getLargeFileSplitSizeMB() * 1024L * 1024L;
222        if (sourceTotalSize >= thresholdBytes) {
223            return delegated(DetectionType.SHARDED_SOURCE_SIZE,
224                    "Sharded source total size " + sourceTotalSize + " >= " + thresholdBytes);
225        }
226        return notDelegated();
227    }
228
229    static LargeFileDetectionResult checkManifestQueryCount(String manifestSql, Option option) {
230        Map manifest = (Map) JSON.parseObject(manifestSql);
231        int queryCount = 0;
232
233        List queries = (List) manifest.get("queries");
234        if (queries != null) queryCount += queries.size();
235
236        List<Map> servers = (List<Map>) manifest.get("servers");
237        if (servers != null) {
238            for (Map server : servers) {
239                List serverQueries = (List) server.get("queries");
240                if (serverQueries != null) queryCount += serverQueries.size();
241            }
242        }
243
244        if (queryCount >= option.getLargeQueryCountThreshold()) {
245            return delegated(DetectionType.MANIFEST_QUERY_COUNT,
246                    "Manifest query count " + queryCount + " >= " + option.getLargeQueryCountThreshold());
247        }
248        return notDelegated();
249    }
250
251    private static LargeFileDetectionResult notDelegated() {
252        return new LargeFileDetectionResult(false, null, DetectionType.NONE);
253    }
254
255    private static LargeFileDetectionResult delegated(DetectionType type, String reason) {
256        return new LargeFileDetectionResult(true, reason, type);
257    }
258}