001package gudusoft.gsqlparser.util;
002
003import gudusoft.gsqlparser.EDbVendor;
004import gudusoft.gsqlparser.TGSqlParser;
005import gudusoft.gsqlparser.dlineage.dataflow.model.ModelBindingManager;
006import gudusoft.gsqlparser.dlineage.dataflow.model.SubType;
007import gudusoft.gsqlparser.dlineage.dataflow.model.xml.table;
008import gudusoft.gsqlparser.pp.para.GFmtOpt;
009import gudusoft.gsqlparser.pp.para.GFmtOptFactory;
010import gudusoft.gsqlparser.pp.para.styleenums.TCaseOption;
011import gudusoft.gsqlparser.pp.stmtformatter.FormatterFactory;
012import gudusoft.gsqlparser.sqlenv.ESQLDataObjectType;
013import gudusoft.gsqlparser.sqlenv.IdentifierService;
014import gudusoft.gsqlparser.sqlenv.TSQLEnv;
015
016import java.io.*;
017import java.nio.charset.Charset;
018import java.security.MessageDigest;
019import java.util.*;
020import java.util.stream.Collectors;
021
022public class SQLUtil {
023    private static final Logger logger = LoggerFactory.getLogger(SQLUtil.class);
024
025
026    public static String formatSql( EDbVendor dbVendor, String inputQuery )
027    {
028        String Result = inputQuery;
029        TGSqlParser sqlparser = new TGSqlParser( dbVendor );
030        sqlparser.sqltext = inputQuery;
031        int ret = sqlparser.parse( );
032        if ( ret == 0 )
033        {
034            GFmtOpt option = GFmtOptFactory.newInstance();
035            option.caseFuncname = TCaseOption.CoNoChange;
036            Result = FormatterFactory.pp(sqlparser, option);
037        }
038        return Result;
039    }
040
041    public static boolean isEmpty(String value) {
042        return value == null || value.trim().length() == 0;
043    }
044
045    public static String getFileContent(File file) {
046        String charset = null;
047        String sqlfilename = file.getAbsolutePath();
048        int read = 0;
049        try {
050            FileInputStream fr = new FileInputStream(sqlfilename);
051            byte[] bom = new byte[4];
052            fr.read(bom, 0, bom.length);
053
054            if ((bom[0] == (byte) 0x00) && (bom[1] == (byte) 0x00) && (bom[2] == (byte) 0xFE)
055                    && (bom[3] == (byte) 0xFF)) {
056                charset = "UTF-32BE";
057                read = 4;
058            } else if ((bom[0] == (byte) 0xFF) && (bom[1] == (byte) 0xFE) && (bom[2] == (byte) 0x00)
059                    && (bom[3] == (byte) 0x00)) {
060                charset = "UTF-32LE";
061                read = 4;
062            } else if ((bom[0] == (byte) 0xEF) && (bom[1] == (byte) 0xBB) && (bom[2] == (byte) 0xBF)) {
063                charset = "UTF-8";
064                read = 3;
065            } else if ((bom[0] == (byte) 0xFE) && (bom[1] == (byte) 0xFF)) {
066                charset = "UTF-16BE";
067                read = 2;
068            } else if ((bom[0] == (byte) 0xFF) && (bom[1] == (byte) 0xFE)) {
069                charset = "UTF-16LE";
070                read = 2;
071            } else {
072                charset = "UTF-8";
073                read = 0;
074            }
075
076            fr.close();
077        } catch (IOException e) {
078            logger.error("Check file encoding failed.", e);
079        }
080
081        Long filelength = file.length();
082        byte[] filecontent = new byte[filelength.intValue()];
083        try {
084            InputStream in = new BufferedInputStream(new FileInputStream(sqlfilename));
085            in.read(filecontent);
086            in.close();
087        } catch (IOException e) {
088            logger.error("read file content failed.", e);
089        }
090
091        byte[] content = new byte[filelength.intValue() - read];
092        System.arraycopy(filecontent, read, content, 0, content.length);
093
094        try {
095            String fileContent = new String(content, charset == null ? Charset.defaultCharset().name() : charset);
096            return fileContent.replace((char) 160, (char) 32);
097        } catch (UnsupportedEncodingException e) {
098            logger.error("The OS does not support " + charset == null ? Charset.defaultCharset().name() : charset, e);
099            return null;
100        }
101    }
102
103    public static String getInputStreamContent(InputStream is, boolean close) {
104        try {
105            ByteArrayOutputStream out = new ByteArrayOutputStream(4096);
106            byte[] tmp = new byte[4096];
107            while (true) {
108                int r = is.read(tmp);
109                if (r == -1)
110                    break;
111                out.write(tmp, 0, r);
112            }
113            byte[] bytes = out.toByteArray();
114            if (close) {
115                is.close();
116            }
117            out.close();
118            String content = new String(bytes);
119            return content;
120        } catch (IOException e) {
121            logger.error("read inputStream failed.", e);
122        }
123        return null;
124    }
125
126    public static String getFileContent(String filePath) {
127        if (filePath == null)
128            return "";
129        File file = new File(filePath);
130        if (!file.exists() || file.isDirectory())
131            return "";
132        return getFileContent(file);
133    }
134
135    public static String trimColumnStringQuote(String string) {
136        try {
137            if (string == null)
138                return string;
139
140            if (string.indexOf('.') != -1) {
141                List<String> splits = parseNames(string);
142                if (splits.size() > 1) {
143                    StringBuilder buffer = new StringBuilder();
144                    for (int i = 0; i < splits.size(); i++) {
145                        String segment = splits.get(i);
146                        if (parseNames(trimColumnStringQuote(segment)).size() > 1) {
147                            buffer.append(segment);
148                        } else {
149                            buffer.append(trimColumnStringQuote(segment));
150                        }
151                        if (i < splits.size() - 1) {
152                            buffer.append(".");
153                        }
154                    }
155                    string = buffer.toString();
156                    return string;
157                }
158            }
159            if (string.length() < 2) {
160                return string;
161            }
162            if (string.startsWith("'") && string.endsWith("'"))
163                return string.substring(1, string.length() - 1);
164            else if (string.startsWith("\"") && string.endsWith("\""))
165                return string.substring(1, string.length() - 1);
166            else if (string.startsWith("`") && string.endsWith("`"))
167                return string.substring(1, string.length() - 1);
168            else if (string.startsWith("[") && string.endsWith("]"))
169                return string.substring(1, string.length() - 1);
170            return string;
171        }catch (Exception e){
172            return string;
173        }
174    }
175
176//    public static List<String> parseNames(String nameString, EDbVendor vendor) {
177//        List<String> names = new ArrayList<String>();
178//        if (nameString.startsWith("`") && nameString.endsWith("`")){
179//            nameString = nameString.substring(1,nameString.length()-1);
180//        }
181//        String[] splits = nameString.trim().split("\\.");
182//
183//        for (int i = 0; i < splits.length; i++) {
184//            String split = splits[i].trim();
185//            if (TSQLEnv.isDelimitedIdentifier(vendor, split) && !TSQLEnv.endsWithDelimitedIdentifier(vendor, split)) {
186//                StringBuilder buffer = new StringBuilder();
187//                buffer.append(splits[i]);
188//                while (i < splits.length - 1
189//                        && !TSQLEnv.endsWithDelimitedIdentifier(vendor, split = splits[++i].trim())) {
190//                    buffer.append(".");
191//                    buffer.append(splits[i]);
192//                }
193//
194//                buffer.append(".");
195//                buffer.append(splits[i]);
196//
197//                names.add(buffer.toString());
198//                continue;
199//            }
200//            names.add(splits[i]);
201//        }
202//        return names;
203//    }
204
205    public static List<String> parseNames(String nameString) {
206        return parseNames(nameString, null);
207    }
208
209    // Pre-compiled patterns for better performance
210    private static final java.util.regex.Pattern DOT_PATTERN = java.util.regex.Pattern.compile("\\s*\\.\\s*");
211    
212//    // LRU Cache for parseNames results - thread-safe with limited size
213    private static final java.util.Map<String, List<String>> PARSE_NAMES_CACHE =
214        Collections.synchronizedMap(new java.util.LinkedHashMap<String, List<String>>(256, 0.75f, true) {
215            @Override
216            protected boolean removeEldestEntry(java.util.Map.Entry<String, List<String>> eldest) {
217                return size() > 10000; // Keep cache under 10000 entries
218            }
219        });
220
221    /**
222     * 解析以点号分隔的 SQL 标识符或表达式,并返回各层级片段。
223     * <p>
224     * 用途:
225     * <ul>
226     *   <li>将类似 catalog.schema.table.column 的全限定名拆分为有序片段;</li>
227     *   <li>在拆分时识别并保留引号/括号内的点号,不做误分割;</li>
228     *   <li>根据不同数据库厂商(如 BigQuery)的定界符规则进行处理。</li>
229     * </ul>
230     * 工作机制:
231     * <ol>
232     *   <li>按「可选空格 + '.' + 可选空格」初步切分;</li>
233     *   <li>对以下情况进行片段合并直至遇到闭合符号:
234     *     <ul>
235     *       <li>单引号字符串:'...'</li>
236     *       <li>双引号定界标识符:"..."</li>
237     *       <li>反引号定界标识符:`...`</li>
238     *       <li>方括号定界标识符:[...]</li>
239     *       <li>函数/表达式括号:(...)</li>
240     *     </ul>
241     *   </li>
242     *   <li>BigQuery(vendor == dbvbigquery)且包含反引号时,会先去除反引号再进行切分。</li>
243     *   <li>入参为 null 返回空列表;发生异常时返回仅包含原始字符串的列表。</li>
244     * </ol>
245     * 示例:
246     * <pre>
247     *  "dbo.Employee.Name"                    -> ["dbo", "Employee", "Name"]
248     *  "[Sales DB].[Employee].[Name]"        -> ["[Sales DB]", "[Employee]", "[Name]"]
249     *  "\"My.Schema\".\"My.Table\""       -> ["\"My.Schema\"", "\"My.Table\""]
250     *  "`project.dataset.table`" (BigQuery)  -> ["project", "dataset", "table"]
251     *  "OPENJSON(aptd.test.ActiviteTypeIDs)" -> ["OPENJSON(aptd.test.ActiviteTypeIDs)"]
252     * </pre>
253     *
254     * @param nameString 待解析的标识符或表达式字符串
255     * @param vendor 数据库厂商(用于处理厂商特定定界符,如 BigQuery 的反引号),可为 null
256     * @return 解析后的片段列表;顺序与层级一致
257     */
258    public static List<String> parseNames(String nameString, EDbVendor vendor) {
259        if(nameString == null){
260            return Collections.emptyList();
261        }
262        
263        // Cache lookup - use a simple cache key
264        String cacheKey = vendor == null ? nameString : (vendor.ordinal() + ":" + nameString);
265        List<String> cached = PARSE_NAMES_CACHE.get(cacheKey);
266        if (cached != null) {
267            return new ArrayList<String>(cached);
268        }
269        
270        String name = nameString.trim();
271        
272        // Fast path: simple names without special characters
273        // Check once for all special characters in a single pass
274        boolean hasSpecialChar = false;
275        boolean hasSingleQuote = false;
276        boolean hasDoubleQuote = false;
277        boolean hasBacktick = false;
278        boolean hasParenthesis = false;
279        boolean hasBracket = false;
280        
281        for (int i = 0, len = name.length(); i < len; i++) {
282            char c = name.charAt(i);
283            switch (c) {
284                case '\'':
285                    hasSingleQuote = true;
286                    hasSpecialChar = true;
287                    break;
288                case '"':
289                    hasDoubleQuote = true;
290                    hasSpecialChar = true;
291                    break;
292                case '`':
293                    hasBacktick = true;
294                    hasSpecialChar = true;
295                    break;
296                case '(':
297                case ')':
298                    hasParenthesis = true;
299                    hasSpecialChar = true;
300                    break;
301                case '[':
302                case ']':
303                    hasBracket = true;
304                    hasSpecialChar = true;
305                    break;
306            }
307        }
308        
309        List<String> names = new ArrayList<String>(4); // Pre-size for typical case
310        
311        // Handle BigQuery special case
312        if (vendor == EDbVendor.dbvbigquery && hasBacktick) {
313            String[] parts = DOT_PATTERN.split(name.replace("`", ""));
314            for (String part : parts) {
315                names.add(part);
316            }
317            putCache(cacheKey, names);
318            return names;
319        }
320        
321        try {
322            String[] splits = DOT_PATTERN.split(nameString);
323            
324            // Fast path: no special characters
325            if (!hasSpecialChar) {
326                for (String split : splits) {
327                    names.add(split);
328                }
329                putCache(cacheKey, names);
330                return names;
331            }
332            
333            // Handle special character cases
334            if (hasSingleQuote) {
335                if ("'.'".equals(name)) {
336                    names = Arrays.asList(".");
337                    putCache(cacheKey, names);
338                    return names;
339                }
340                parseWithDelimiter(splits, names, '\'', '\'');
341            } else if (hasParenthesis) {
342                parseWithParenthesis(splits, names);
343            } else if (hasBacktick) {
344                if ("`.`".equals(name)) {
345                    names = Arrays.asList(".");
346                    putCache(cacheKey, names);
347                    return names;
348                }
349                parseWithDelimiter(splits, names, '`', '`');
350            } else if (hasDoubleQuote) {
351                if ("\".\"".equals(name)) {
352                    names = Arrays.asList(".");
353                    putCache(cacheKey, names);
354                    return names;
355                }
356                parseWithDelimiter(splits, names, '"', '"');
357            } else if (hasBracket) {
358                if ("[.]".equals(name)) {
359                    names = Arrays.asList(".");
360                    putCache(cacheKey, names);
361                    return names;
362                }
363                parseWithDelimiter(splits, names, '[', ']');
364            } else {
365                for (String split : splits) {
366                    names.add(split);
367                }
368            }
369        } catch (Throwable e) {
370            names.clear();
371            names.add(nameString);
372        }
373        
374        putCache(cacheKey, names);
375        return names;
376    }
377
378        protected static List<String> putCache(String cacheKey, List<String> names) {
379                return PARSE_NAMES_CACHE.put(cacheKey, new ArrayList<String>(names));
380        }
381    
382    /**
383     * Helper method to parse name segments with paired delimiters (quotes, brackets, etc.)
384     */
385    private static void parseWithDelimiter(String[] splits, List<String> names, char startDelim, char endDelim) {
386        for (int i = 0; i < splits.length; i++) {
387            String split = splits[i].trim();
388            if (split.length() > 0 && split.charAt(0) == startDelim && 
389                (split.length() == 1 || split.charAt(split.length() - 1) != endDelim)) {
390                // Found start delimiter without matching end - need to merge segments
391                StringBuilder buffer = new StringBuilder(split.length() * 2);
392                buffer.append(splits[i]);
393                
394                while (i < splits.length - 1) {
395                    split = splits[++i].trim();
396                    buffer.append(".").append(splits[i]);
397                    if (split.length() > 0 && split.charAt(split.length() - 1) == endDelim) {
398                        break;
399                    }
400                }
401                names.add(buffer.toString());
402            } else if (startDelim == endDelim && countUnenclosedChar(split, startDelim) % 2 == 1) {
403                // The delimiter opens in the middle of the segment, e.g. the
404                // expression  SCHEMA_NAME(id) + '.' + st.name  splits into
405                // "SCHEMA_NAME(id) + '" / "' + st" / "name".  Merge segments
406                // until the delimiters are balanced, otherwise the dot inside
407                // the quoted literal produces unterminated fragments such as
408                // "' + st.name" (MantisBT issue 4496).
409                StringBuilder buffer = new StringBuilder(split.length() * 2);
410                buffer.append(splits[i]);
411                while (i < splits.length - 1 && countUnenclosedChar(buffer.toString(), startDelim) % 2 == 1) {
412                    buffer.append(".").append(splits[++i]);
413                }
414                names.add(buffer.toString());
415            } else {
416                names.add(splits[i]);
417            }
418        }
419    }
420
421    /**
422     * Counts occurrences of the given delimiter that are not enclosed in a
423     * different delimited-identifier region, so the apostrophe in
424     * [O'Reilly] or "O'Brien" is not mistaken for an opening string literal.
425     */
426    private static int countUnenclosedChar(String s, char delim) {
427        int count = 0;
428        char enclosing = 0;
429        for (int i = 0, len = s.length(); i < len; i++) {
430            char c = s.charAt(i);
431            if (enclosing != 0) {
432                if (c == (enclosing == '[' ? ']' : enclosing)) {
433                    enclosing = 0;
434                }
435            } else if (c == delim) {
436                count++;
437            } else if (c == '\'' || c == '"' || c == '`' || c == '[') {
438                enclosing = c;
439            }
440        }
441        return count;
442    }
443    
444    /**
445     * Helper method to parse name segments with parentheses
446     */
447    private static void parseWithParenthesis(String[] splits, List<String> names) {
448        for (int i = 0; i < splits.length; i++) {
449            String split = splits[i].trim();
450            int openParen = split.indexOf('(');
451            int closeParen = split.indexOf(')');
452            
453            if (openParen != -1 && closeParen == -1) {
454                // Found opening parenthesis without closing - need to merge segments
455                StringBuilder buffer = new StringBuilder(split.length() * 2);
456                buffer.append(splits[i]);
457                
458                while (i < splits.length - 1) {
459                    split = splits[++i].trim();
460                    buffer.append(".").append(splits[i]);
461                    if (split.indexOf(')') != -1) {
462                        break;
463                    }
464                }
465                names.add(buffer.toString());
466            } else {
467                names.add(splits[i]);
468            }
469        }
470    }
471        
472        public static void main(String[] args) {
473                SQLUtil.parseNames("OPENJSON(aptd.test.ActiviteTypeIDs)");
474        }
475
476    public static void writeToFile(File file, InputStream source, boolean close) {
477        BufferedInputStream bis = null;
478        BufferedOutputStream fouts = null;
479        try {
480            bis = new BufferedInputStream(source);
481            if (!file.exists()) {
482                if (!file.getParentFile().exists()) {
483                    file.getParentFile().mkdirs();
484                }
485                file.createNewFile();
486            }
487            fouts = new BufferedOutputStream(new FileOutputStream(file));
488            byte b[] = new byte[1024];
489            int i = 0;
490            while ((i = bis.read(b)) != -1) {
491                fouts.write(b, 0, i);
492            }
493            fouts.flush();
494            fouts.close();
495            if (close)
496                bis.close();
497        } catch (IOException e) {
498            logger.error("Write file failed.", e);
499            try {
500                if (fouts != null)
501                    fouts.close();
502            } catch (IOException f) {
503                logger.error("Close output stream failed.", f);
504            }
505            if (close) {
506                try {
507                    if (bis != null)
508                        bis.close();
509                } catch (IOException f) {
510                    logger.error("Close input stream failed.", f);
511                }
512            }
513        }
514    }
515
516    public static void writeToFile(File file, String string) throws IOException {
517
518        if (!file.exists()) {
519            if (!file.getParentFile().exists()) {
520                file.getParentFile().mkdirs();
521            }
522            file.createNewFile();
523        }
524        PrintWriter out = new PrintWriter(new OutputStreamWriter(new FileOutputStream(file)));
525        if (string != null)
526            out.print(string);
527        out.close();
528    }
529
530    public static void appendToFile(File file, String string) throws IOException {
531
532        if (!file.exists()) {
533            if (!file.getParentFile().exists()) {
534                file.getParentFile().mkdirs();
535            }
536            file.createNewFile();
537        }
538        PrintWriter out = new PrintWriter(new OutputStreamWriter(new FileOutputStream(file, true)));
539        if (string != null)
540            out.println(string);
541        out.close();
542    }
543
544    public static void deltree(File root) {
545        if (root == null || !root.exists()) {
546            return;
547        }
548
549        if (root.isFile()) {
550            root.delete();
551            return;
552        }
553
554        File[] children = root.listFiles();
555        if (children != null) {
556            for (int i = 0; i < children.length; i++) {
557                deltree(children[i]);
558            }
559        }
560
561        root.delete();
562    }
563
564    public static InputStream getInputStreamWithoutBom(String file) throws IOException {
565        UnicodeInputStream stream = null;
566        FileInputStream fis = new FileInputStream(file);
567        stream = new UnicodeInputStream(fis, null);
568        return stream;
569    }
570
571    public static class UnicodeInputStream extends InputStream {
572        PushbackInputStream internalIn;
573        boolean isInited = false;
574        String defaultEnc;
575        String encoding;
576
577        private static final int BOM_SIZE = 4;
578
579        public UnicodeInputStream(InputStream in, String defaultEnc) {
580            internalIn = new PushbackInputStream(in, BOM_SIZE);
581            this.defaultEnc = defaultEnc;
582        }
583
584        public String getDefaultEncoding() {
585            return defaultEnc;
586        }
587
588        public String getEncoding() {
589            if (!isInited) {
590                try {
591                    init();
592                } catch (IOException ex) {
593                    IllegalStateException ise = new IllegalStateException("Init method failed.");
594                    ise.initCause(ise);
595                    throw ise;
596                }
597            }
598            return encoding;
599        }
600
601        protected void init() throws IOException {
602            if (isInited)
603                return;
604
605            byte bom[] = new byte[BOM_SIZE];
606            int n, unread;
607            n = internalIn.read(bom, 0, bom.length);
608
609            if ((bom[0] == (byte) 0x00) && (bom[1] == (byte) 0x00) && (bom[2] == (byte) 0xFE)
610                    && (bom[3] == (byte) 0xFF)) {
611                encoding = "UTF-32BE";
612                unread = n - 4;
613            } else if ((bom[0] == (byte) 0xFF) && (bom[1] == (byte) 0xFE) && (bom[2] == (byte) 0x00)
614                    && (bom[3] == (byte) 0x00)) {
615                encoding = "UTF-32LE";
616                unread = n - 4;
617            } else if ((bom[0] == (byte) 0xEF) && (bom[1] == (byte) 0xBB) && (bom[2] == (byte) 0xBF)) {
618                encoding = "UTF-8";
619                unread = n - 3;
620            } else if ((bom[0] == (byte) 0xFE) && (bom[1] == (byte) 0xFF)) {
621                encoding = "UTF-16BE";
622                unread = n - 2;
623            } else if ((bom[0] == (byte) 0xFF) && (bom[1] == (byte) 0xFE)) {
624                encoding = "UTF-16LE";
625                unread = n - 2;
626            } else {
627                encoding = defaultEnc;
628                unread = n;
629            }
630
631            if (unread > 0)
632                internalIn.unread(bom, (n - unread), unread);
633
634            isInited = true;
635        }
636
637        public void close() throws IOException {
638            internalIn.close();
639        }
640
641        public int read() throws IOException {
642            return internalIn.read();
643        }
644    }
645
646    public static boolean compareIdentifier(EDbVendor dbVendor, ESQLDataObjectType sqlDataObjectType,
647                                            String identifier1, String identifier2) {
648        List<String> segments1 = parseNames(identifier1);
649        List<String> segments2 = parseNames(identifier2);
650
651        if (segments1.size() != segments2.size())
652            return false;
653
654        boolean supportCatalog = TSQLEnv.supportCatalog(dbVendor);
655        boolean supportSchema = TSQLEnv.supportSchema(dbVendor);
656
657        if(supportCatalog && supportSchema) {
658            if (sqlDataObjectType == ESQLDataObjectType.dotColumn) {
659                if (segments1.size() > 4) {
660                    return TSQLEnv.compareIdentifier(dbVendor, ESQLDataObjectType.dotCatalog, segments1.get(0),
661                            segments2.get(0))
662                            && TSQLEnv.compareIdentifier(dbVendor, ESQLDataObjectType.dotSchema, segments1.get(1),
663                            segments2.get(1))
664                            && TSQLEnv.compareIdentifier(dbVendor, ESQLDataObjectType.dotTable, segments1.get(2),
665                            segments2.get(2))
666                            && TSQLEnv.compareIdentifier(dbVendor, ESQLDataObjectType.dotColumn, mergeSegments(segments1, 3),
667                            mergeSegments(segments2, 3));
668                } else if (segments1.size() == 4) {
669                    return TSQLEnv.compareIdentifier(dbVendor, ESQLDataObjectType.dotCatalog, segments1.get(0),
670                            segments2.get(0))
671                            && TSQLEnv.compareIdentifier(dbVendor, ESQLDataObjectType.dotSchema, segments1.get(1),
672                            segments2.get(1))
673                            && TSQLEnv.compareIdentifier(dbVendor, ESQLDataObjectType.dotTable, segments1.get(2),
674                            segments2.get(2))
675                            && TSQLEnv.compareIdentifier(dbVendor, ESQLDataObjectType.dotColumn, segments1.get(3),
676                            segments2.get(3));
677                } else if (segments1.size() == 3) {
678                    return TSQLEnv.compareIdentifier(dbVendor, ESQLDataObjectType.dotSchema, segments1.get(0),
679                            segments2.get(0))
680                            && TSQLEnv.compareIdentifier(dbVendor, ESQLDataObjectType.dotTable, segments1.get(1),
681                            segments2.get(1))
682                            && TSQLEnv.compareIdentifier(dbVendor, ESQLDataObjectType.dotColumn, segments1.get(2),
683                            segments2.get(2));
684                } else if (segments1.size() == 2) {
685                    return TSQLEnv.compareIdentifier(dbVendor, ESQLDataObjectType.dotTable, segments1.get(0),
686                            segments2.get(0))
687                            && TSQLEnv.compareIdentifier(dbVendor, ESQLDataObjectType.dotColumn, segments1.get(1),
688                            segments2.get(1));
689                } else if (segments1.size() == 1) {
690                    return TSQLEnv.compareIdentifier(dbVendor, ESQLDataObjectType.dotColumn, segments1.get(0),
691                            segments2.get(0));
692                }
693            } else if (sqlDataObjectType == ESQLDataObjectType.dotTable
694                        || sqlDataObjectType == ESQLDataObjectType.dotSynonyms
695                    || sqlDataObjectType == ESQLDataObjectType.dotOraclePackage
696                    || sqlDataObjectType == ESQLDataObjectType.dotFunction
697                    || sqlDataObjectType == ESQLDataObjectType.dotProcedure
698                    || sqlDataObjectType == ESQLDataObjectType.dotTrigger) {
699                if (segments1.size() > 3) {
700                    return TSQLEnv.compareIdentifier(dbVendor, ESQLDataObjectType.dotCatalog, segments1.get(0),
701                            segments2.get(0))
702                            && TSQLEnv.compareIdentifier(dbVendor, ESQLDataObjectType.dotSchema, segments1.get(1),
703                            segments2.get(1))
704                            && TSQLEnv.compareIdentifier(dbVendor, sqlDataObjectType, mergeSegments(segments1, 2), mergeSegments(segments2, 2));
705                } else if (segments1.size() == 3) {
706                    return TSQLEnv.compareIdentifier(dbVendor, ESQLDataObjectType.dotCatalog, segments1.get(0),
707                            segments2.get(0))
708                            && TSQLEnv.compareIdentifier(dbVendor, ESQLDataObjectType.dotSchema, segments1.get(1),
709                            segments2.get(1))
710                            && TSQLEnv.compareIdentifier(dbVendor, sqlDataObjectType, segments1.get(2), segments2.get(2));
711                } else if (segments1.size() == 2) {
712                    return TSQLEnv.compareIdentifier(dbVendor, ESQLDataObjectType.dotSchema, segments1.get(0),
713                            segments2.get(0))
714                            && TSQLEnv.compareIdentifier(dbVendor, sqlDataObjectType, segments1.get(1), segments2.get(1));
715                } else if (segments1.size() == 1) {
716                    return TSQLEnv.compareIdentifier(dbVendor, sqlDataObjectType, segments1.get(0), segments2.get(0));
717                }
718            } else if (sqlDataObjectType == ESQLDataObjectType.dotSchema) {
719                if (segments1.size() > 2) {
720                    return TSQLEnv.compareIdentifier(dbVendor, ESQLDataObjectType.dotCatalog, segments1.get(0),
721                            segments2.get(0))
722                            && TSQLEnv.compareIdentifier(dbVendor, sqlDataObjectType, mergeSegments(segments1, 1), mergeSegments(segments2, 1));
723                } else if (segments1.size() == 2) {
724                    return TSQLEnv.compareIdentifier(dbVendor, ESQLDataObjectType.dotCatalog, segments1.get(0),
725                            segments2.get(0))
726                            && TSQLEnv.compareIdentifier(dbVendor, sqlDataObjectType, segments1.get(1), segments2.get(1));
727                } else if (segments1.size() == 1) {
728                    return TSQLEnv.compareIdentifier(dbVendor, sqlDataObjectType, segments1.get(0), segments2.get(0));
729                }
730            } else if (sqlDataObjectType == ESQLDataObjectType.dotCatalog) {
731                if (segments1.size() > 1) {
732                    return TSQLEnv.compareIdentifier(dbVendor, sqlDataObjectType, mergeSegments(segments1, 0), mergeSegments(segments2, 0));
733                } else if (segments1.size() == 1) {
734                    return TSQLEnv.compareIdentifier(dbVendor, sqlDataObjectType, segments1.get(0), segments2.get(0));
735                }
736            }
737        }
738        else if(supportCatalog){
739            if (sqlDataObjectType == ESQLDataObjectType.dotColumn) {
740                if (segments1.size() > 3) {
741                    return TSQLEnv.compareIdentifier(dbVendor, ESQLDataObjectType.dotCatalog, segments1.get(0),
742                            segments2.get(0))
743                            && TSQLEnv.compareIdentifier(dbVendor, ESQLDataObjectType.dotTable, segments1.get(1),
744                            segments2.get(1))
745                            && TSQLEnv.compareIdentifier(dbVendor, ESQLDataObjectType.dotColumn, mergeSegments(segments1, 2),
746                            mergeSegments(segments2, 2));
747                } else if (segments1.size() == 3) {
748                    return TSQLEnv.compareIdentifier(dbVendor, ESQLDataObjectType.dotCatalog, segments1.get(0),
749                            segments2.get(0))
750                            && TSQLEnv.compareIdentifier(dbVendor, ESQLDataObjectType.dotTable, segments1.get(1),
751                            segments2.get(1))
752                            && TSQLEnv.compareIdentifier(dbVendor, ESQLDataObjectType.dotColumn, segments1.get(2),
753                            segments2.get(2));
754                } else if (segments1.size() == 2) {
755                    return TSQLEnv.compareIdentifier(dbVendor, ESQLDataObjectType.dotTable, segments1.get(0),
756                            segments2.get(0))
757                            && TSQLEnv.compareIdentifier(dbVendor, ESQLDataObjectType.dotColumn, segments1.get(1),
758                            segments2.get(1));
759                } else if (segments1.size() == 1) {
760                    return TSQLEnv.compareIdentifier(dbVendor, ESQLDataObjectType.dotColumn, segments1.get(0),
761                            segments2.get(0));
762                }
763            } else if (sqlDataObjectType == ESQLDataObjectType.dotTable
764                        || sqlDataObjectType == ESQLDataObjectType.dotSynonyms
765                    || sqlDataObjectType == ESQLDataObjectType.dotOraclePackage
766                    || sqlDataObjectType == ESQLDataObjectType.dotFunction
767                    || sqlDataObjectType == ESQLDataObjectType.dotProcedure
768                    || sqlDataObjectType == ESQLDataObjectType.dotTrigger) {
769                if (segments1.size() > 2) {
770                    return TSQLEnv.compareIdentifier(dbVendor, ESQLDataObjectType.dotCatalog, segments1.get(0),
771                            segments2.get(0))
772                            && TSQLEnv.compareIdentifier(dbVendor, sqlDataObjectType, mergeSegments(segments1, 1), mergeSegments(segments2, 1));
773                } else if (segments1.size() == 2) {
774                    return TSQLEnv.compareIdentifier(dbVendor, ESQLDataObjectType.dotCatalog, segments1.get(0),
775                            segments2.get(0))
776                            && TSQLEnv.compareIdentifier(dbVendor, sqlDataObjectType, segments1.get(1), segments2.get(1));
777                } else if (segments1.size() == 1) {
778                    return TSQLEnv.compareIdentifier(dbVendor, sqlDataObjectType, segments1.get(0), segments2.get(0));
779                }
780            } else if (sqlDataObjectType == ESQLDataObjectType.dotCatalog || sqlDataObjectType == ESQLDataObjectType.dotSchema) {
781                if (segments1.size() > 1) {
782                    return TSQLEnv.compareIdentifier(dbVendor, ESQLDataObjectType.dotCatalog, mergeSegments(segments1, 0), mergeSegments(segments2, 0));
783                } else if (segments1.size() == 1) {
784                    return TSQLEnv.compareIdentifier(dbVendor, ESQLDataObjectType.dotCatalog, segments1.get(0), segments2.get(0));
785                }
786            }
787        }
788        else if(supportSchema){
789            if (sqlDataObjectType == ESQLDataObjectType.dotColumn) {
790                if (segments1.size() > 3) {
791                    return TSQLEnv.compareIdentifier(dbVendor, ESQLDataObjectType.dotSchema, segments1.get(0),
792                            segments2.get(0))
793                            && TSQLEnv.compareIdentifier(dbVendor, ESQLDataObjectType.dotTable, segments1.get(1),
794                            segments2.get(1))
795                            && TSQLEnv.compareIdentifier(dbVendor, ESQLDataObjectType.dotColumn, mergeSegments(segments1, 2),
796                            mergeSegments(segments2, 2));
797                } else if (segments1.size() == 3) {
798                    return TSQLEnv.compareIdentifier(dbVendor, ESQLDataObjectType.dotSchema, segments1.get(0),
799                            segments2.get(0))
800                            && TSQLEnv.compareIdentifier(dbVendor, ESQLDataObjectType.dotTable, segments1.get(1),
801                            segments2.get(1))
802                            && TSQLEnv.compareIdentifier(dbVendor, ESQLDataObjectType.dotColumn, segments1.get(2),
803                            segments2.get(2));
804                } else if (segments1.size() == 2) {
805                    return TSQLEnv.compareIdentifier(dbVendor, ESQLDataObjectType.dotTable, segments1.get(0),
806                            segments2.get(0))
807                            && TSQLEnv.compareIdentifier(dbVendor, ESQLDataObjectType.dotColumn, segments1.get(1),
808                            segments2.get(1));
809                } else if (segments1.size() == 1) {
810                    return TSQLEnv.compareIdentifier(dbVendor, ESQLDataObjectType.dotColumn, segments1.get(0),
811                            segments2.get(0));
812                }
813            } else if (sqlDataObjectType == ESQLDataObjectType.dotTable
814                        || sqlDataObjectType == ESQLDataObjectType.dotSynonyms
815                    || sqlDataObjectType == ESQLDataObjectType.dotOraclePackage
816                    || sqlDataObjectType == ESQLDataObjectType.dotFunction
817                    || sqlDataObjectType == ESQLDataObjectType.dotProcedure
818                    || sqlDataObjectType == ESQLDataObjectType.dotTrigger) {
819                if (segments1.size() > 2) {
820                    return TSQLEnv.compareIdentifier(dbVendor, ESQLDataObjectType.dotSchema, segments1.get(0),
821                            segments2.get(0))
822                            && TSQLEnv.compareIdentifier(dbVendor, sqlDataObjectType, mergeSegments(segments1, 1), mergeSegments(segments2, 1));
823                } else if (segments1.size() == 2) {
824                    return TSQLEnv.compareIdentifier(dbVendor, ESQLDataObjectType.dotSchema, segments1.get(0),
825                            segments2.get(0))
826                            && TSQLEnv.compareIdentifier(dbVendor, sqlDataObjectType, segments1.get(1), segments2.get(1));
827                } else if (segments1.size() == 1) {
828                    return TSQLEnv.compareIdentifier(dbVendor, sqlDataObjectType, segments1.get(0), segments2.get(0));
829                }
830            } else if (sqlDataObjectType == ESQLDataObjectType.dotCatalog || sqlDataObjectType == ESQLDataObjectType.dotSchema) {
831                if (segments1.size() > 1) {
832                    return TSQLEnv.compareIdentifier(dbVendor, ESQLDataObjectType.dotSchema, mergeSegments(segments1, 0), mergeSegments(segments2, 0));
833                } else if (segments1.size() == 1) {
834                    return TSQLEnv.compareIdentifier(dbVendor, ESQLDataObjectType.dotSchema, segments1.get(0), segments2.get(0));
835                }
836            }
837        }
838        return false;
839    }
840
841    public static String normalizeIdentifier(EDbVendor dbVendor, ESQLDataObjectType objectType, String identifier) {
842        if (identifier == null)
843            return null;
844        String originIdentifier = identifier;
845        identifier = IdentifierService.normalizeStatic(dbVendor, objectType, identifier);
846        boolean collationSensitive = false;
847        switch (objectType) {
848            case dotCatalog:
849            case dotSchema:
850                collationSensitive = TSQLEnv.catalogCollationCaseSensitive.get(dbVendor);
851                break;
852            case dotFunction:
853            case dotProcedure:
854            case dotTrigger:
855            case dotTable:
856            case dotSynonyms:   
857            case dotOraclePackage:
858                collationSensitive = TSQLEnv.tableCollationCaseSensitive.get(dbVendor);
859                break;
860            case dotColumn:
861                collationSensitive = TSQLEnv.columnCollationCaseSensitive.get(dbVendor);
862                break;
863            default:
864                collationSensitive = TSQLEnv.defaultCollationCaseSensitive.get(dbVendor);
865                break;
866        }
867        if(parseNames(identifier).size()>1) {
868            return collationSensitive ? originIdentifier : originIdentifier.toUpperCase();
869        }
870        else {
871            return collationSensitive ? identifier : identifier.toUpperCase();
872        }
873    }
874
875    public static String getIdentifierNormalColumnName(EDbVendor dbVendor, String name) {
876        return getIdentifierNormalName(dbVendor, name, ESQLDataObjectType.dotColumn);
877    }
878
879    public static String getIdentifierNormalTableName(String name) {
880        return getIdentifierNormalName(ModelBindingManager.getGlobalVendor(), name, ESQLDataObjectType.dotTable);
881    }
882    
883    public static String getIdentifierNormalTableName(EDbVendor dbVendor, String name) {
884        return getIdentifierNormalName(dbVendor, name, ESQLDataObjectType.dotTable);
885    }
886
887  /**
888   * 规范化多段限定名(Multi-Segment Qualified Name)并返回规范化后的完整限定名。
889   *
890   * <h3>核心功能</h3>
891   * <p>本方法是 SQL 标识符规范化的<b>多段名处理版本</b>,接受包含多段(catalog.schema.table.column)的限定名,
892   * 根据数据库厂商特性、层级支持能力和对象类型,智能解析和规范化每个段,最后返回用点号连接的完整规范化名称。</p>
893   *
894   * <h3>处理流程</h3>
895   * <ol>
896   *   <li><b>厂商特定语法展开</b>:
897   *       <ul>
898   *         <li>MSSQL/Azure SQL: 将 ".." 语法展开为 ".dbo." 或全局配置的 schema</li>
899   *         <li>示例:{@code "db..table"} → {@code "db.dbo.table"}</li>
900   *       </ul>
901   *   </li>
902   *   <li><b>解析多段名</b>:使用 {@link #parseNames(String)} 将限定名按点号分割成段列表</li>
903   *   <li><b>智能段类型推断</b>:根据以下因素决定每段的实际类型:
904   *       <ul>
905   *         <li>数据库厂商的层级支持能力(supportCatalog/supportSchema)</li>
906   *         <li>目标对象类型({@code sqlDataObjectType})</li>
907   *         <li>实际段数</li>
908   *       </ul>
909   *   </li>
910   *   <li><b>逐段规范化</b>:对每段调用 {@link #normalizeIdentifier(EDbVendor, ESQLDataObjectType, String)},
911   *       应用厂商特定的大小写规则、引号处理等</li>
912   *   <li><b>重组限定名</b>:用点号连接所有规范化后的段,返回完整的规范化限定名</li>
913   * </ol>
914   *
915   * <h3>智能段类型推断示例</h3>
916   * <p>假设数据库<b>同时支持 catalog 和 schema</b>,目标类型为 {@code dotTable}:</p>
917   * <ul>
918   *   <li>3段名 {@code "a.b.c"} → 解析为 {@code catalog.schema.table}</li>
919   *   <li>2段名 {@code "a.b"} → 解析为 {@code schema.table}</li>
920   *   <li>1段名 {@code "a"} → 解析为 {@code table}</li>
921   * </ul>
922   *
923   * <p>假设数据库<b>仅支持 catalog</b>(如 MySQL),目标类型为 {@code dotTable}:</p>
924   * <ul>
925   *   <li>2段名 {@code "a.b"} → 解析为 {@code catalog.table}</li>
926   *   <li>1段名 {@code "a"} → 解析为 {@code table}</li>
927   * </ul>
928   *
929   * <p>假设数据库<b>仅支持 schema</b>(如 PostgreSQL),目标类型为 {@code dotTable}:</p>
930   * <ul>
931   *   <li>2段名 {@code "a.b"} → 解析为 {@code schema.table}</li>
932   *   <li>1段名 {@code "a"} → 解析为 {@code table}</li>
933   * </ul>
934   *
935   * <h3>对于 dotColumn 类型的特殊处理</h3>
936   * <p>列名可能有4段(catalog.schema.table.column),方法会根据实际段数自动调整解析策略:</p>
937   * <ul>
938   *   <li>4段名 {@code "db.sch.tbl.col"} → {@code catalog.schema.table.column}</li>
939   *   <li>3段名 {@code "sch.tbl.col"} → {@code schema.table.column}</li>
940   *   <li>2段名 {@code "tbl.col"} → {@code table.column}</li>
941   *   <li>1段名 {@code "col"} → {@code column}</li>
942   * </ul>
943   *
944   * <h3>处理超长限定名</h3>
945   * <p>当段数超过标准层级时(如表名有4段或更多),方法会使用 {@link #mergeSegments(List, int)}
946   * 将多余的尾部段合并为单个段(保留点号),然后作为最后一段进行规范化。</p>
947   * <p>示例:5段列名 {@code "db.sch.tbl.nested.col"} → 合并后4段处理:
948   * {@code "db.sch.tbl.nested.col"} (最后两段合并为 {@code "nested.col"})</p>
949   *
950   * <h3>方法目的与使用场景</h3>
951   * <ul>
952   *   <li><b>目的</b>:为多段限定名提供统一的规范化接口,生成可用于比较、索引和查找的标准化名称</li>
953   *   <li><b>输出特性</b>:返回<b>多段名</b>(用点号连接),保留层级结构信息</li>
954   *   <li><b>使用场景</b>:
955   *       <ul>
956   *         <li>环境层(TSQLEnv)处理完整限定名</li>
957   *         <li>名称比较和匹配(需要保留层级信息)</li>
958   *         <li>快速索引查找(NameKey 构造)</li>
959   *       </ul>
960   *   </li>
961   * </ul>
962   *
963   * <h3>与相关方法的区别</h3>
964   * <ul>
965   *   <li><b>{@link #normalizeIdentifier(EDbVendor, ESQLDataObjectType, String)}</b>:
966   *       单段规范化,不解析限定名,直接处理引号和大小写,返回单段名</li>
967   *   <li><b>{@code IdentifierService.keyForMap(String, ESQLDataObjectType)}</b>:
968   *       仅接受单段名,用于生成 Map 的 key,会抛出异常如果输入是多段名</li>
969   *   <li><b>{@code IdentifierService.normalizeQualifiedName(String, ESQLDataObjectType)}</b>:
970   *       新架构中的等价方法,提供相同的多段名规范化功能</li>
971   * </ul>
972   *
973   * <h3>厂商特定行为</h3>
974   * <ul>
975   *   <li><b>MySQL</b>: 仅支持 catalog(即 database),反引号引用,大小写根据系统变量决定</li>
976   *   <li><b>PostgreSQL</b>: 仅支持 schema,双引号引用,未加引号的标识符转小写</li>
977   *   <li><b>SQL Server</b>: 支持 catalog+schema,方括号或双引号引用,".." 展开为 ".dbo."</li>
978   *   <li><b>Oracle</b>: 仅支持 schema,双引号引用,未加引号的标识符转大写</li>
979   *   <li><b>Snowflake</b>: 支持 catalog+schema,双引号引用,大小写保留但匹配不敏感</li>
980   *   <li><b>BigQuery</b>: 支持 catalog+schema(项目+数据集),反引号引用,大小写不敏感,可能内部转小写</li>
981   * </ul>
982   *
983   * @param dbVendor 数据库厂商类型(决定层级支持、引号风格、大小写规则)
984   * @param name 原始标识符或多段限定名(可能包含引号/反引号;可能包含 catalog/schema 前缀;
985   *             可能包含厂商特定语法如 MSSQL 的 "..")
986   * @param sqlDataObjectType 期望的对象类型(例如 dotTable, dotColumn, dotSchema, dotCatalog),
987   *                          用于指导段类型推断和规范化规则应用
988   * @return 规范化后的完整限定名,用点号连接各段;如果输入为 {@code null},返回 {@code null};
989   *         如果输入为空字符串或无法解析,返回空字符串
990   *
991   * @see #normalizeIdentifier(EDbVendor, ESQLDataObjectType, String) 单段规范化方法
992   * @see #parseNames(String) 多段名解析方法
993   * @see #mergeSegments(List, int) 段合并工具方法
994   * @see TSQLEnv#normalizeIdentifier(EDbVendor, ESQLDataObjectType, String)
995   * @see IdentifierService#normalizeQualifiedName(String, ESQLDataObjectType) Phase 0 新架构中的等价方法
996   *
997   * @since 3.1.0.8
998   */
999    public static String getIdentifierNormalName(EDbVendor dbVendor, String name,
1000                                                 ESQLDataObjectType sqlDataObjectType) {
1001        if (name == null) {
1002            return null;
1003        }
1004        if (dbVendor == EDbVendor.dbvmssql || dbVendor == EDbVendor.dbvazuresql) {
1005            if (name.indexOf("..") != -1) {
1006                if (ModelBindingManager.getGlobalSchema() != null) {
1007                    name = name.replace("..", "." + ModelBindingManager.getGlobalSchema() + ".");
1008                } else {
1009                    name = name.replace("..", ".dbo.");
1010                }
1011            }
1012        }
1013        List<String> segments = parseNames(name);
1014        StringBuilder builder = new StringBuilder();
1015        boolean supportCatalog = TSQLEnv.supportCatalog(dbVendor);
1016        boolean supportSchema = TSQLEnv.supportSchema(dbVendor);
1017
1018        if(supportCatalog && supportSchema) {
1019            if (sqlDataObjectType == ESQLDataObjectType.dotColumn) {
1020                if (segments.size() > 4) {
1021                    builder.append(normalizeIdentifier(dbVendor, ESQLDataObjectType.dotCatalog, segments.get(0)))
1022                            .append(".")
1023                            .append(normalizeIdentifier(dbVendor, ESQLDataObjectType.dotSchema, segments.get(1)))
1024                            .append(".")
1025                            .append(normalizeIdentifier(dbVendor, ESQLDataObjectType.dotTable, segments.get(2)))
1026                            .append(".")
1027                            .append(normalizeIdentifier(dbVendor, ESQLDataObjectType.dotColumn, mergeSegments(segments, 3)));
1028                } else if (segments.size() == 4) {
1029                    builder.append(normalizeIdentifier(dbVendor, ESQLDataObjectType.dotCatalog, segments.get(0)))
1030                            .append(".")
1031                            .append(normalizeIdentifier(dbVendor, ESQLDataObjectType.dotSchema, segments.get(1)))
1032                            .append(".")
1033                            .append(normalizeIdentifier(dbVendor, ESQLDataObjectType.dotTable, segments.get(2)))
1034                            .append(".")
1035                            .append(normalizeIdentifier(dbVendor, ESQLDataObjectType.dotColumn, segments.get(3)));
1036                } else if (segments.size() == 3) {
1037                    builder.append(normalizeIdentifier(dbVendor, ESQLDataObjectType.dotSchema, segments.get(0)))
1038                            .append(".")
1039                            .append(normalizeIdentifier(dbVendor, ESQLDataObjectType.dotTable, segments.get(1)))
1040                            .append(".")
1041                            .append(normalizeIdentifier(dbVendor, ESQLDataObjectType.dotColumn, segments.get(2)));
1042                } else if (segments.size() == 2) {
1043                    builder.append(normalizeIdentifier(dbVendor, ESQLDataObjectType.dotTable, segments.get(0)))
1044                            .append(".")
1045                            .append(normalizeIdentifier(dbVendor, ESQLDataObjectType.dotColumn, segments.get(1)));
1046                } else if (segments.size() == 1) {
1047                    builder.append(normalizeIdentifier(dbVendor, ESQLDataObjectType.dotColumn, segments.get(0)));
1048                }
1049            } else if (sqlDataObjectType == ESQLDataObjectType.dotTable
1050                        || sqlDataObjectType == ESQLDataObjectType.dotSynonyms
1051                    || sqlDataObjectType == ESQLDataObjectType.dotOraclePackage
1052                    || sqlDataObjectType == ESQLDataObjectType.dotFunction
1053                    || sqlDataObjectType == ESQLDataObjectType.dotProcedure
1054                    || sqlDataObjectType == ESQLDataObjectType.dotTrigger) {
1055                if (segments.size() > 3) {
1056                    builder.append(normalizeIdentifier(dbVendor, ESQLDataObjectType.dotCatalog, segments.get(0)))
1057                            .append(".")
1058                            .append(normalizeIdentifier(dbVendor, ESQLDataObjectType.dotSchema, segments.get(1)))
1059                            .append(".").append(normalizeIdentifier(dbVendor, sqlDataObjectType, mergeSegments(segments, 2)));
1060                } else if (segments.size() == 3) {
1061                    builder.append(normalizeIdentifier(dbVendor, ESQLDataObjectType.dotCatalog, segments.get(0)))
1062                            .append(".")
1063                            .append(normalizeIdentifier(dbVendor, ESQLDataObjectType.dotSchema, segments.get(1)))
1064                            .append(".").append(normalizeIdentifier(dbVendor, sqlDataObjectType, segments.get(2)));
1065                } else if (segments.size() == 2) {
1066                    builder.append(normalizeIdentifier(dbVendor, ESQLDataObjectType.dotSchema, segments.get(0)))
1067                            .append(".").append(normalizeIdentifier(dbVendor, sqlDataObjectType, segments.get(1)));
1068                } else if (segments.size() == 1) {
1069                    builder.append(normalizeIdentifier(dbVendor, sqlDataObjectType, segments.get(0)));
1070                }
1071            } else if (sqlDataObjectType == ESQLDataObjectType.dotSchema) {
1072                if (segments.size() > 2) {
1073                    builder.append(normalizeIdentifier(dbVendor, ESQLDataObjectType.dotCatalog, segments.get(0)))
1074                            .append(".").append(normalizeIdentifier(dbVendor, sqlDataObjectType, mergeSegments(segments, 1)));
1075                } else if (segments.size() == 2) {
1076                    builder.append(normalizeIdentifier(dbVendor, ESQLDataObjectType.dotCatalog, segments.get(0)))
1077                            .append(".").append(normalizeIdentifier(dbVendor, sqlDataObjectType, segments.get(1)));
1078                } else if (segments.size() == 1) {
1079                    builder.append(normalizeIdentifier(dbVendor, sqlDataObjectType, segments.get(0)));
1080                }
1081            } else if (sqlDataObjectType == ESQLDataObjectType.dotCatalog) {
1082                if (segments.size() > 1) {
1083                    builder.append(normalizeIdentifier(dbVendor, sqlDataObjectType, mergeSegments(segments, 0)));
1084                } else if (segments.size() == 1) {
1085                    builder.append(normalizeIdentifier(dbVendor, sqlDataObjectType, segments.get(0)));
1086                }
1087            }
1088        }
1089        else if(supportCatalog){
1090            if (sqlDataObjectType == ESQLDataObjectType.dotColumn) {
1091                if (segments.size() > 3) {
1092                    builder.append(normalizeIdentifier(dbVendor, ESQLDataObjectType.dotCatalog, segments.get(0)))
1093                            .append(".")
1094                            .append(normalizeIdentifier(dbVendor, ESQLDataObjectType.dotTable, segments.get(1)))
1095                            .append(".")
1096                            .append(normalizeIdentifier(dbVendor, ESQLDataObjectType.dotColumn, mergeSegments(segments, 2)));
1097                } else if (segments.size() == 3) {
1098                    builder.append(normalizeIdentifier(dbVendor, ESQLDataObjectType.dotCatalog, segments.get(0)))
1099                            .append(".")
1100                            .append(normalizeIdentifier(dbVendor, ESQLDataObjectType.dotTable, segments.get(1)))
1101                            .append(".")
1102                            .append(normalizeIdentifier(dbVendor, ESQLDataObjectType.dotColumn, segments.get(2)));
1103                } else if (segments.size() == 2) {
1104                    builder.append(normalizeIdentifier(dbVendor, ESQLDataObjectType.dotTable, segments.get(0)))
1105                            .append(".")
1106                            .append(normalizeIdentifier(dbVendor, ESQLDataObjectType.dotColumn, segments.get(1)));
1107                } else if (segments.size() == 1) {
1108                    builder.append(normalizeIdentifier(dbVendor, ESQLDataObjectType.dotColumn, segments.get(0)));
1109                }
1110            } else if (sqlDataObjectType == ESQLDataObjectType.dotTable
1111                        || sqlDataObjectType == ESQLDataObjectType.dotSynonyms
1112                    || sqlDataObjectType == ESQLDataObjectType.dotOraclePackage
1113                    || sqlDataObjectType == ESQLDataObjectType.dotFunction
1114                    || sqlDataObjectType == ESQLDataObjectType.dotProcedure
1115                    || sqlDataObjectType == ESQLDataObjectType.dotTrigger) {
1116                if (segments.size() > 2) {
1117                    builder.append(normalizeIdentifier(dbVendor, ESQLDataObjectType.dotCatalog, segments.get(0)))
1118                            .append(".").append(normalizeIdentifier(dbVendor, sqlDataObjectType, mergeSegments(segments, 1)));
1119                } else if (segments.size() == 2) {
1120                    builder.append(normalizeIdentifier(dbVendor, ESQLDataObjectType.dotCatalog, segments.get(0)))
1121                            .append(".").append(normalizeIdentifier(dbVendor, sqlDataObjectType, segments.get(1)));
1122                } else if (segments.size() == 1) {
1123                    builder.append(normalizeIdentifier(dbVendor, sqlDataObjectType, segments.get(0)));
1124                }
1125            } else if (sqlDataObjectType == ESQLDataObjectType.dotSchema || sqlDataObjectType == ESQLDataObjectType.dotCatalog) {
1126                if (segments.size() > 1) {
1127                    builder.append(normalizeIdentifier(dbVendor, ESQLDataObjectType.dotCatalog, mergeSegments(segments, 0)));
1128                } else if (segments.size() == 1) {
1129                    builder.append(normalizeIdentifier(dbVendor, ESQLDataObjectType.dotCatalog, segments.get(0)));
1130                }
1131            }
1132        }
1133        else if(supportSchema){
1134            if (sqlDataObjectType == ESQLDataObjectType.dotColumn) {
1135                if (segments.size() > 3) {
1136                    builder.append(normalizeIdentifier(dbVendor, ESQLDataObjectType.dotSchema, segments.get(0)))
1137                            .append(".")
1138                            .append(normalizeIdentifier(dbVendor, ESQLDataObjectType.dotTable, segments.get(1)))
1139                            .append(".")
1140                            .append(normalizeIdentifier(dbVendor, ESQLDataObjectType.dotColumn, mergeSegments(segments, 2)));
1141                } else if (segments.size() == 3) {
1142                    builder.append(normalizeIdentifier(dbVendor, ESQLDataObjectType.dotSchema, segments.get(0)))
1143                            .append(".")
1144                            .append(normalizeIdentifier(dbVendor, ESQLDataObjectType.dotTable, segments.get(1)))
1145                            .append(".")
1146                            .append(normalizeIdentifier(dbVendor, ESQLDataObjectType.dotColumn, segments.get(2)));
1147                } else if (segments.size() == 2) {
1148                    builder.append(normalizeIdentifier(dbVendor, ESQLDataObjectType.dotTable, segments.get(0)))
1149                            .append(".")
1150                            .append(normalizeIdentifier(dbVendor, ESQLDataObjectType.dotColumn, segments.get(1)));
1151                } else if (segments.size() == 1) {
1152                    builder.append(normalizeIdentifier(dbVendor, ESQLDataObjectType.dotColumn, segments.get(0)));
1153                }
1154            } else if (sqlDataObjectType == ESQLDataObjectType.dotTable
1155                        || sqlDataObjectType == ESQLDataObjectType.dotSynonyms
1156                    || sqlDataObjectType == ESQLDataObjectType.dotOraclePackage
1157                    || sqlDataObjectType == ESQLDataObjectType.dotFunction
1158                    || sqlDataObjectType == ESQLDataObjectType.dotProcedure
1159                    || sqlDataObjectType == ESQLDataObjectType.dotTrigger) {
1160                if (segments.size() > 2) {
1161                    builder.append(normalizeIdentifier(dbVendor, ESQLDataObjectType.dotSchema, segments.get(0)))
1162                            .append(".").append(normalizeIdentifier(dbVendor, sqlDataObjectType, mergeSegments(segments, 1)));
1163                } else if (segments.size() == 2) {
1164                    builder.append(normalizeIdentifier(dbVendor, ESQLDataObjectType.dotSchema, segments.get(0)))
1165                            .append(".").append(normalizeIdentifier(dbVendor, sqlDataObjectType, segments.get(1)));
1166                } else if (segments.size() == 1) {
1167                    builder.append(normalizeIdentifier(dbVendor, sqlDataObjectType, segments.get(0)));
1168                }
1169            } else if (sqlDataObjectType == ESQLDataObjectType.dotSchema || sqlDataObjectType == ESQLDataObjectType.dotCatalog) {
1170                if (segments.size() > 1) {
1171                    builder.append(normalizeIdentifier(dbVendor, ESQLDataObjectType.dotSchema, mergeSegments(segments, 0)));
1172                } else if (segments.size() == 1) {
1173                    builder.append(normalizeIdentifier(dbVendor, ESQLDataObjectType.dotSchema, segments.get(0)));
1174                }
1175            }
1176        }
1177        return builder.toString();
1178    }
1179
1180    public static String mergeSegments(List<String> segments, int index) {
1181        StringBuilder buffer = new StringBuilder();
1182                for (int i = index; i < segments.size(); i++) {
1183                        buffer.append(segments.get(i));
1184                        if(i<segments.size()-1) {
1185                                buffer.append(".");
1186                        }
1187                }
1188                return buffer.toString();
1189        }
1190
1191//      public static String getIdentifierNormalName(EDbVendor vendor, String name) {
1192//              if (isEmpty(name)) {
1193//                      return null;
1194//              }
1195//              name = name.replaceAll("(?i)null\\.", "");
1196//              switch (vendor) {
1197//              case dbvbigquery:
1198//                      return replaceIdentifierNormalName(name, "`.*?`", "`", true).replaceAll("\\.\\s+", ".");
1199//              case dbvcouchbase:
1200//              case dbvhive:
1201//              case dbvimpala:
1202//              case dbvmysql:
1203//                      return replaceIdentifierNormalName(name, "`.*?`", "`", true).replaceAll("\\.\\s+", ".");
1204//              case dbvdax:
1205//                      return replaceIdentifierNormalName(name, "'.*?'", "'", true).replaceAll("\\.\\s+", ".");
1206//              case dbvdb2:
1207//              case dbvhana:
1208//              case dbvinformix:
1209//              case dbvnetezza:
1210//              case dbvoracle:
1211//              case dbvsnowflake:
1212//              case dbvsybase:
1213//              case dbvteradata:
1214//              case dbvvertica:
1215//                      return replaceIdentifierNormalName(name, "\".*?\"", "\"", true).replaceAll("\\.\\s+", ".");
1216//              case dbvpostgresql:
1217//              case dbvgreenplum:
1218//              case dbvredshift:
1219//                      return replaceIdentifierNormalName(name, "\".*?\"", "\"", false).replaceAll("\\.\\s+", ".");
1220//              case dbvmssql:
1221//                      return replaceIdentifierNormalName(name, "([\"\\[']).*?([\"\\]'])", "[\"\\[\\]']", true)
1222//                                      .replaceAll("\\.\\s+", ".");
1223//              default:
1224//                      return replaceIdentifierNormalName(name, "\".*?\"", "\"", true).replaceAll("\\.\\s+", ".");
1225//              }
1226//      }
1227//
1228//      private static String replaceIdentifierNormalName(String content, String match, String replace, boolean toUpper) {
1229//              Pattern pattern = Pattern.compile(match, Pattern.CASE_INSENSITIVE | Pattern.DOTALL);
1230//              Matcher matcher = pattern.matcher(content);
1231//              StringBuilder buffer = new StringBuilder();
1232//              int start = 0;
1233//              while (matcher.find()) {
1234//                      int matchStart = matcher.start();
1235//                      int macthEnd = matcher.end();
1236//                      if (start < matchStart) {
1237//                              if (toUpper) {
1238//                                      buffer.append(content.substring(start, matchStart).toUpperCase());
1239//                              } else {
1240//                                      buffer.append(content.substring(start, matchStart).toLowerCase());
1241//                              }
1242//                      }
1243//                      buffer.append(matcher.group().replaceAll(replace, ""));
1244//                      start = macthEnd;
1245//              }
1246//              if (start < content.length()) {
1247//                      if (toUpper) {
1248//                              buffer.append(content.substring(start).toUpperCase());
1249//                      } else {
1250//                              buffer.append(content.substring(start).toLowerCase());
1251//                      }
1252//              }
1253//              return buffer.toString();
1254//      }
1255
1256    public static boolean isTempTable(table table) {
1257        if(SubType.temp_table.name().equals(table.getSubType())) {
1258                return true;
1259        }
1260        String tableName = table.getName();
1261        List<String> segments = parseNames(tableName);
1262        if (tableName.startsWith("@") || segments.get(segments.size() - 1).startsWith("@")) {
1263            return true;
1264        }
1265        if (tableName.startsWith("#") || segments.get(segments.size() - 1).startsWith("#")) {
1266            return true;
1267        }
1268        return false;
1269    }
1270    
1271//    public static boolean isTempTable(String tableName) {
1272//        List<String> segments = parseNames(tableName);
1273//        if (tableName.startsWith("@") || segments.get(segments.size() - 1).startsWith("@")) {
1274//            return true;
1275//        }
1276//        if (tableName.startsWith("#") || segments.get(segments.size() - 1).startsWith("#")) {
1277//            return true;
1278//        }
1279//        return false;
1280//    }
1281
1282//    public static boolean isTempTable(TTable table, EDbVendor vendor) {
1283//        switch (vendor) {
1284//            case dbvmssql:
1285//                return table.getName().startsWith("#");
1286//            default:
1287//                return false;
1288//        }
1289//    }
1290
1291    public static File[] listFiles(File sqlFiles) {
1292        List<File> children = new ArrayList<File>();
1293        if (sqlFiles != null)
1294            listFiles(sqlFiles, children);
1295        Collections.sort(children);
1296        return children.toArray(new File[0]);
1297    }
1298
1299    public static File[] listFiles(File sqlFiles, FileFilter filter) {
1300        List<File> children = new ArrayList<File>();
1301        if (sqlFiles != null)
1302            listFiles(sqlFiles, children, filter);
1303        Collections.sort(children);
1304        return children.toArray(new File[0]);
1305    }
1306
1307    private static Set<String> extensions = new HashSet<String>(Arrays.asList("java", "class", "php", "c", "cpp", "properties", "iml",
1308            "yml", "xml", "md", "jpg", "png", "gif", "bmp", "tiff", "tif", "svg", "pdf", "mp3", "mp4", "bak", "jar", "gz",
1309            "tar", "log", "conf", "dll", "exe", "so", "sh", "bat", "cdr", "docx", "doc", "xps", "xlsx", "xls", "ppt", "pptx",
1310            "rar", "7z", "ttf", "caj", "dwg", "dwf", "dxf", "ico", "epub", "webp", "heic", "html", "htm", "vsd", "vsdx", "rtf", "ico"));
1311
1312    public static void listFiles(File rootFile, List<File> children) {
1313        if (rootFile.isFile())
1314            children.add(rootFile);
1315        else {
1316            File[] files = rootFile.listFiles(t -> {
1317                int dotIndex = t.getName().lastIndexOf(".");
1318                if (dotIndex == -1) {
1319                    return true;
1320                }
1321                String extension = t.getName().toLowerCase().substring(dotIndex + 1);
1322                return !extensions.contains(extension);
1323            });
1324            if (files != null) {
1325                for (int i = 0; i < files.length; i++) {
1326                    listFiles(files[i], children);
1327                }
1328            }
1329        }
1330    }
1331
1332    public static void listFiles(File rootFile, List<File> children, FileFilter filter) {
1333        if (rootFile.isFile() && filter.accept(rootFile))
1334            children.add(rootFile);
1335        else {
1336            File[] files = rootFile.listFiles(filter);
1337            if (files != null) {
1338                for (int i = 0; i < files.length; i++) {
1339                    listFiles(files[i], children, filter);
1340                }
1341            }
1342        }
1343    }
1344
1345    public static String readFile(File file) {
1346        StringBuilder stringBuilder = new StringBuilder();
1347        BufferedReader reader = null;
1348        try {
1349            reader = new BufferedReader(new FileReader(file));
1350
1351            String line = null;
1352            String ls = System.getProperty("line.separator");
1353            while ((line = reader.readLine()) != null) {
1354                stringBuilder.append(line);
1355                stringBuilder.append(ls);
1356            }
1357            stringBuilder.deleteCharAt(stringBuilder.length() - 1);
1358        } catch (IOException e) {
1359            logger.error("read file failed.", e);
1360        } finally {
1361            if (reader != null) {
1362                try {
1363                    reader.close();
1364                } catch (IOException e) {
1365                    logger.error("close reader failed.", e);
1366                }
1367            }
1368        }
1369        return stringBuilder.toString();
1370    }
1371
1372        public static String stringToMD5(String plainText) {
1373                byte[] secretBytes = null;
1374                try {
1375                        secretBytes = MessageDigest.getInstance("md5").digest(plainText.getBytes("UTF-8"));
1376                        StringBuffer sb = new StringBuffer();
1377                        for (int i = 0; i < secretBytes.length; ++i) {
1378                                sb.append(Integer.toHexString((secretBytes[i] & 0xFF) | 0x100).substring(1, 3));
1379                        }
1380                        return sb.toString();
1381                } catch (Exception e) {
1382                        logger.error("get text md5 value failed.", e);
1383                }
1384                return null;
1385        }
1386
1387        public static String trimSingleQuote(String columnAlias) {
1388                if(columnAlias.startsWith("'") && columnAlias.endsWith("'")) {
1389                        return columnAlias.substring(1, columnAlias.length() - 1);
1390                }
1391                return columnAlias;
1392        }
1393        
1394    public static String endTrim(String input) {
1395        if (input == null) {
1396            return null;
1397        }
1398
1399        int end = input.length();
1400        while (end > 0 && Character.isWhitespace(input.charAt(end - 1))) {
1401            end--;
1402        }
1403
1404        return input.substring(0, end);
1405    }
1406        
1407    public static void endTrim(StringBuilder buffer) {
1408        int length = buffer.length();
1409        while (length > 0 && Character.isWhitespace(buffer.charAt(length - 1))) {
1410            length--; // 逐步减少长度
1411        }
1412        buffer.setLength(length); // 直接截断尾部空白字符
1413    }
1414
1415    public static String joinNonEmpty(String... parts) {
1416        return Arrays.stream(parts)
1417                .filter(s -> s != null && !s.trim().isEmpty())
1418                .collect(Collectors.joining("/"));
1419    }
1420}