001package gudusoft.gsqlparser.dlineage.util;
002
003import gudusoft.gsqlparser.EDbVendor;
004import gudusoft.gsqlparser.util.Logger;
005import gudusoft.gsqlparser.util.LoggerFactory;
006import gudusoft.gsqlparser.util.SQLUtil;
007
008import java.io.InputStream;
009import java.util.HashMap;
010import java.util.HashSet;
011import java.util.Map;
012import java.util.Set;
013
014public final class FunctionUtility {
015    private static final Logger logger = LoggerFactory.getLogger(FunctionUtility.class);
016
017    static Map<EDbVendor, Map<String, Set<Integer>>> dbFunctionDirectRelationMap = new HashMap<>();
018    static Map<EDbVendor, Map<String, Set<Integer>>> dbFunctionIndirectRelationMap = new HashMap<>();
019
020    public static boolean isDirectRelation(EDbVendor vendor, String function, int argCount, int argIndex) {
021        if (!dbFunctionDirectRelationMap.containsKey(vendor)) {
022            initFunctionMap(vendor);
023        }
024        Map<String, Set<Integer>> functionMap = dbFunctionDirectRelationMap.get(vendor);
025        Set<Integer> relationsArgs = functionMap.get(function.toUpperCase() + "_" + argCount);
026        if (relationsArgs != null) {
027            return relationsArgs.contains(argIndex);
028        }
029        // Variadic fallback: if any entry for this function name exists with a different
030        // arity, use the largest arity entry's pattern, defaulting to direct for overflow args.
031        String prefix = function.toUpperCase() + "_";
032        Set<Integer> bestMatch = null;
033        int bestArity = 0;
034        for (Map.Entry<String, Set<Integer>> entry : functionMap.entrySet()) {
035            if (entry.getKey().startsWith(prefix)) {
036                int arity = Integer.parseInt(entry.getKey().substring(prefix.length()));
037                if (arity > bestArity) {
038                    bestArity = arity;
039                    bestMatch = entry.getValue();
040                }
041            }
042        }
043        if (bestMatch != null) {
044            if (argIndex < bestArity) {
045                return bestMatch.contains(argIndex);
046            }
047            return true; // overflow args default to direct
048        }
049        return true;
050    }
051    
052    public static boolean isIndirectRelation(EDbVendor vendor, String function, int argCount, int argIndex) {
053        if (!dbFunctionIndirectRelationMap.containsKey(vendor)) {
054            initFunctionMap(vendor);
055        }
056        Map<String, Set<Integer>> functionMap = dbFunctionIndirectRelationMap.get(vendor);
057        Set<Integer> relationsArgs = functionMap.get(function.toUpperCase() + "_" + argCount);
058        if (relationsArgs != null) {
059            return relationsArgs.contains(argIndex);
060        }
061        // Variadic fallback: if any entry for this function name exists with a different
062        // arity, use the largest arity entry's pattern, defaulting to not-indirect for overflow args.
063        String prefix = function.toUpperCase() + "_";
064        Set<Integer> bestMatch = null;
065        int bestArity = 0;
066        for (Map.Entry<String, Set<Integer>> entry : functionMap.entrySet()) {
067            if (entry.getKey().startsWith(prefix)) {
068                int arity = Integer.parseInt(entry.getKey().substring(prefix.length()));
069                if (arity > bestArity) {
070                    bestArity = arity;
071                    bestMatch = entry.getValue();
072                }
073            }
074        }
075        if (bestMatch != null) {
076            if (argIndex < bestArity) {
077                return bestMatch.contains(argIndex);
078            }
079            return false; // overflow args default to not-indirect
080        }
081        return false;
082    }
083
084    private static void initFunctionMap(EDbVendor vendor) {
085        Map<String, Set<Integer>> functionDirectRelationMap = new HashMap<>();
086        Map<String, Set<Integer>> functionIndirectRelationMap = new HashMap<>();
087        try {
088            InputStream is = FunctionUtility.class.getResourceAsStream("/gudusoft/gsqlparser/dataflow/"
089                    + vendor.name().replace("dbv", "") + "/function.properties");
090            if (is != null) {
091                String[] lines = SQLUtil.getInputStreamContent(is, false).split("\n");
092                for (String line : lines) {
093                    String[] function = line.trim().replace("(", ",").replace(")", "").split("\\s*,\\s*", -1);
094                    if (function.length <= 1) {
095                        continue;
096                    }
097                    String functionName = function[0].toUpperCase();
098                    Integer argCount = function.length - 1;
099                    Set<Integer> directRelationArgs = new HashSet<>();
100                    Set<Integer> indirectRelationArgs = new HashSet<>();
101                    for (int i = 1; i <= argCount; i++) {
102                        if ("fdd".equalsIgnoreCase(function[i])) {
103                            directRelationArgs.add(i - 1);
104                        }
105                        if ("fdr".equalsIgnoreCase(function[i])) {
106                            indirectRelationArgs.add(i - 1);
107                        }
108                    }
109                    functionDirectRelationMap.put(functionName + "_" + argCount, directRelationArgs);
110                    functionIndirectRelationMap.put(functionName + "_" + argCount, indirectRelationArgs);
111                }
112            }
113        } catch (Exception e) {
114            logger.error("init function map failed.", e);
115        }
116        dbFunctionDirectRelationMap.put(vendor, functionDirectRelationMap);
117        dbFunctionIndirectRelationMap.put(vendor, functionIndirectRelationMap);
118    }
119}