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 {
403                names.add(splits[i]);
404            }
405        }
406    }
407    
408    /**
409     * Helper method to parse name segments with parentheses
410     */
411    private static void parseWithParenthesis(String[] splits, List<String> names) {
412        for (int i = 0; i < splits.length; i++) {
413            String split = splits[i].trim();
414            int openParen = split.indexOf('(');
415            int closeParen = split.indexOf(')');
416            
417            if (openParen != -1 && closeParen == -1) {
418                // Found opening parenthesis without closing - need to merge segments
419                StringBuilder buffer = new StringBuilder(split.length() * 2);
420                buffer.append(splits[i]);
421                
422                while (i < splits.length - 1) {
423                    split = splits[++i].trim();
424                    buffer.append(".").append(splits[i]);
425                    if (split.indexOf(')') != -1) {
426                        break;
427                    }
428                }
429                names.add(buffer.toString());
430            } else {
431                names.add(splits[i]);
432            }
433        }
434    }
435        
436        public static void main(String[] args) {
437                SQLUtil.parseNames("OPENJSON(aptd.test.ActiviteTypeIDs)");
438        }
439
440    public static void writeToFile(File file, InputStream source, boolean close) {
441        BufferedInputStream bis = null;
442        BufferedOutputStream fouts = null;
443        try {
444            bis = new BufferedInputStream(source);
445            if (!file.exists()) {
446                if (!file.getParentFile().exists()) {
447                    file.getParentFile().mkdirs();
448                }
449                file.createNewFile();
450            }
451            fouts = new BufferedOutputStream(new FileOutputStream(file));
452            byte b[] = new byte[1024];
453            int i = 0;
454            while ((i = bis.read(b)) != -1) {
455                fouts.write(b, 0, i);
456            }
457            fouts.flush();
458            fouts.close();
459            if (close)
460                bis.close();
461        } catch (IOException e) {
462            logger.error("Write file failed.", e);
463            try {
464                if (fouts != null)
465                    fouts.close();
466            } catch (IOException f) {
467                logger.error("Close output stream failed.", f);
468            }
469            if (close) {
470                try {
471                    if (bis != null)
472                        bis.close();
473                } catch (IOException f) {
474                    logger.error("Close input stream failed.", f);
475                }
476            }
477        }
478    }
479
480    public static void writeToFile(File file, String string) throws IOException {
481
482        if (!file.exists()) {
483            if (!file.getParentFile().exists()) {
484                file.getParentFile().mkdirs();
485            }
486            file.createNewFile();
487        }
488        PrintWriter out = new PrintWriter(new OutputStreamWriter(new FileOutputStream(file)));
489        if (string != null)
490            out.print(string);
491        out.close();
492    }
493
494    public static void appendToFile(File file, String string) throws IOException {
495
496        if (!file.exists()) {
497            if (!file.getParentFile().exists()) {
498                file.getParentFile().mkdirs();
499            }
500            file.createNewFile();
501        }
502        PrintWriter out = new PrintWriter(new OutputStreamWriter(new FileOutputStream(file, true)));
503        if (string != null)
504            out.println(string);
505        out.close();
506    }
507
508    public static void deltree(File root) {
509        if (root == null || !root.exists()) {
510            return;
511        }
512
513        if (root.isFile()) {
514            root.delete();
515            return;
516        }
517
518        File[] children = root.listFiles();
519        if (children != null) {
520            for (int i = 0; i < children.length; i++) {
521                deltree(children[i]);
522            }
523        }
524
525        root.delete();
526    }
527
528    public static InputStream getInputStreamWithoutBom(String file) throws IOException {
529        UnicodeInputStream stream = null;
530        FileInputStream fis = new FileInputStream(file);
531        stream = new UnicodeInputStream(fis, null);
532        return stream;
533    }
534
535    public static class UnicodeInputStream extends InputStream {
536        PushbackInputStream internalIn;
537        boolean isInited = false;
538        String defaultEnc;
539        String encoding;
540
541        private static final int BOM_SIZE = 4;
542
543        public UnicodeInputStream(InputStream in, String defaultEnc) {
544            internalIn = new PushbackInputStream(in, BOM_SIZE);
545            this.defaultEnc = defaultEnc;
546        }
547
548        public String getDefaultEncoding() {
549            return defaultEnc;
550        }
551
552        public String getEncoding() {
553            if (!isInited) {
554                try {
555                    init();
556                } catch (IOException ex) {
557                    IllegalStateException ise = new IllegalStateException("Init method failed.");
558                    ise.initCause(ise);
559                    throw ise;
560                }
561            }
562            return encoding;
563        }
564
565        protected void init() throws IOException {
566            if (isInited)
567                return;
568
569            byte bom[] = new byte[BOM_SIZE];
570            int n, unread;
571            n = internalIn.read(bom, 0, bom.length);
572
573            if ((bom[0] == (byte) 0x00) && (bom[1] == (byte) 0x00) && (bom[2] == (byte) 0xFE)
574                    && (bom[3] == (byte) 0xFF)) {
575                encoding = "UTF-32BE";
576                unread = n - 4;
577            } else if ((bom[0] == (byte) 0xFF) && (bom[1] == (byte) 0xFE) && (bom[2] == (byte) 0x00)
578                    && (bom[3] == (byte) 0x00)) {
579                encoding = "UTF-32LE";
580                unread = n - 4;
581            } else if ((bom[0] == (byte) 0xEF) && (bom[1] == (byte) 0xBB) && (bom[2] == (byte) 0xBF)) {
582                encoding = "UTF-8";
583                unread = n - 3;
584            } else if ((bom[0] == (byte) 0xFE) && (bom[1] == (byte) 0xFF)) {
585                encoding = "UTF-16BE";
586                unread = n - 2;
587            } else if ((bom[0] == (byte) 0xFF) && (bom[1] == (byte) 0xFE)) {
588                encoding = "UTF-16LE";
589                unread = n - 2;
590            } else {
591                encoding = defaultEnc;
592                unread = n;
593            }
594
595            if (unread > 0)
596                internalIn.unread(bom, (n - unread), unread);
597
598            isInited = true;
599        }
600
601        public void close() throws IOException {
602            internalIn.close();
603        }
604
605        public int read() throws IOException {
606            return internalIn.read();
607        }
608    }
609
610    public static boolean compareIdentifier(EDbVendor dbVendor, ESQLDataObjectType sqlDataObjectType,
611                                            String identifier1, String identifier2) {
612        List<String> segments1 = parseNames(identifier1);
613        List<String> segments2 = parseNames(identifier2);
614
615        if (segments1.size() != segments2.size())
616            return false;
617
618        boolean supportCatalog = TSQLEnv.supportCatalog(dbVendor);
619        boolean supportSchema = TSQLEnv.supportSchema(dbVendor);
620
621        if(supportCatalog && supportSchema) {
622            if (sqlDataObjectType == ESQLDataObjectType.dotColumn) {
623                if (segments1.size() > 4) {
624                    return TSQLEnv.compareIdentifier(dbVendor, ESQLDataObjectType.dotCatalog, segments1.get(0),
625                            segments2.get(0))
626                            && TSQLEnv.compareIdentifier(dbVendor, ESQLDataObjectType.dotSchema, segments1.get(1),
627                            segments2.get(1))
628                            && TSQLEnv.compareIdentifier(dbVendor, ESQLDataObjectType.dotTable, segments1.get(2),
629                            segments2.get(2))
630                            && TSQLEnv.compareIdentifier(dbVendor, ESQLDataObjectType.dotColumn, mergeSegments(segments1, 3),
631                            mergeSegments(segments2, 3));
632                } else if (segments1.size() == 4) {
633                    return TSQLEnv.compareIdentifier(dbVendor, ESQLDataObjectType.dotCatalog, segments1.get(0),
634                            segments2.get(0))
635                            && TSQLEnv.compareIdentifier(dbVendor, ESQLDataObjectType.dotSchema, segments1.get(1),
636                            segments2.get(1))
637                            && TSQLEnv.compareIdentifier(dbVendor, ESQLDataObjectType.dotTable, segments1.get(2),
638                            segments2.get(2))
639                            && TSQLEnv.compareIdentifier(dbVendor, ESQLDataObjectType.dotColumn, segments1.get(3),
640                            segments2.get(3));
641                } else if (segments1.size() == 3) {
642                    return TSQLEnv.compareIdentifier(dbVendor, ESQLDataObjectType.dotSchema, segments1.get(0),
643                            segments2.get(0))
644                            && TSQLEnv.compareIdentifier(dbVendor, ESQLDataObjectType.dotTable, segments1.get(1),
645                            segments2.get(1))
646                            && TSQLEnv.compareIdentifier(dbVendor, ESQLDataObjectType.dotColumn, segments1.get(2),
647                            segments2.get(2));
648                } else if (segments1.size() == 2) {
649                    return TSQLEnv.compareIdentifier(dbVendor, ESQLDataObjectType.dotTable, segments1.get(0),
650                            segments2.get(0))
651                            && TSQLEnv.compareIdentifier(dbVendor, ESQLDataObjectType.dotColumn, segments1.get(1),
652                            segments2.get(1));
653                } else if (segments1.size() == 1) {
654                    return TSQLEnv.compareIdentifier(dbVendor, ESQLDataObjectType.dotColumn, segments1.get(0),
655                            segments2.get(0));
656                }
657            } else if (sqlDataObjectType == ESQLDataObjectType.dotTable
658                    || sqlDataObjectType == ESQLDataObjectType.dotOraclePackage
659                    || sqlDataObjectType == ESQLDataObjectType.dotFunction
660                    || sqlDataObjectType == ESQLDataObjectType.dotProcedure
661                    || sqlDataObjectType == ESQLDataObjectType.dotTrigger) {
662                if (segments1.size() > 3) {
663                    return TSQLEnv.compareIdentifier(dbVendor, ESQLDataObjectType.dotCatalog, segments1.get(0),
664                            segments2.get(0))
665                            && TSQLEnv.compareIdentifier(dbVendor, ESQLDataObjectType.dotSchema, segments1.get(1),
666                            segments2.get(1))
667                            && TSQLEnv.compareIdentifier(dbVendor, sqlDataObjectType, mergeSegments(segments1, 2), mergeSegments(segments2, 2));
668                } else if (segments1.size() == 3) {
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, sqlDataObjectType, segments1.get(2), segments2.get(2));
674                } else if (segments1.size() == 2) {
675                    return TSQLEnv.compareIdentifier(dbVendor, ESQLDataObjectType.dotSchema, segments1.get(0),
676                            segments2.get(0))
677                            && TSQLEnv.compareIdentifier(dbVendor, sqlDataObjectType, segments1.get(1), segments2.get(1));
678                } else if (segments1.size() == 1) {
679                    return TSQLEnv.compareIdentifier(dbVendor, sqlDataObjectType, segments1.get(0), segments2.get(0));
680                }
681            } else if (sqlDataObjectType == ESQLDataObjectType.dotSchema) {
682                if (segments1.size() > 2) {
683                    return TSQLEnv.compareIdentifier(dbVendor, ESQLDataObjectType.dotCatalog, segments1.get(0),
684                            segments2.get(0))
685                            && TSQLEnv.compareIdentifier(dbVendor, sqlDataObjectType, mergeSegments(segments1, 1), mergeSegments(segments2, 1));
686                } else if (segments1.size() == 2) {
687                    return TSQLEnv.compareIdentifier(dbVendor, ESQLDataObjectType.dotCatalog, segments1.get(0),
688                            segments2.get(0))
689                            && TSQLEnv.compareIdentifier(dbVendor, sqlDataObjectType, segments1.get(1), segments2.get(1));
690                } else if (segments1.size() == 1) {
691                    return TSQLEnv.compareIdentifier(dbVendor, sqlDataObjectType, segments1.get(0), segments2.get(0));
692                }
693            } else if (sqlDataObjectType == ESQLDataObjectType.dotCatalog) {
694                if (segments1.size() > 1) {
695                    return TSQLEnv.compareIdentifier(dbVendor, sqlDataObjectType, mergeSegments(segments1, 0), mergeSegments(segments2, 0));
696                } else if (segments1.size() == 1) {
697                    return TSQLEnv.compareIdentifier(dbVendor, sqlDataObjectType, segments1.get(0), segments2.get(0));
698                }
699            }
700        }
701        else if(supportCatalog){
702            if (sqlDataObjectType == ESQLDataObjectType.dotColumn) {
703                if (segments1.size() > 3) {
704                    return TSQLEnv.compareIdentifier(dbVendor, ESQLDataObjectType.dotCatalog, segments1.get(0),
705                            segments2.get(0))
706                            && TSQLEnv.compareIdentifier(dbVendor, ESQLDataObjectType.dotTable, segments1.get(1),
707                            segments2.get(1))
708                            && TSQLEnv.compareIdentifier(dbVendor, ESQLDataObjectType.dotColumn, mergeSegments(segments1, 2),
709                            mergeSegments(segments2, 2));
710                } else if (segments1.size() == 3) {
711                    return TSQLEnv.compareIdentifier(dbVendor, ESQLDataObjectType.dotCatalog, segments1.get(0),
712                            segments2.get(0))
713                            && TSQLEnv.compareIdentifier(dbVendor, ESQLDataObjectType.dotTable, segments1.get(1),
714                            segments2.get(1))
715                            && TSQLEnv.compareIdentifier(dbVendor, ESQLDataObjectType.dotColumn, segments1.get(2),
716                            segments2.get(2));
717                } else if (segments1.size() == 2) {
718                    return TSQLEnv.compareIdentifier(dbVendor, ESQLDataObjectType.dotTable, segments1.get(0),
719                            segments2.get(0))
720                            && TSQLEnv.compareIdentifier(dbVendor, ESQLDataObjectType.dotColumn, segments1.get(1),
721                            segments2.get(1));
722                } else if (segments1.size() == 1) {
723                    return TSQLEnv.compareIdentifier(dbVendor, ESQLDataObjectType.dotColumn, segments1.get(0),
724                            segments2.get(0));
725                }
726            } else if (sqlDataObjectType == ESQLDataObjectType.dotTable
727                    || sqlDataObjectType == ESQLDataObjectType.dotOraclePackage
728                    || sqlDataObjectType == ESQLDataObjectType.dotFunction
729                    || sqlDataObjectType == ESQLDataObjectType.dotProcedure
730                    || sqlDataObjectType == ESQLDataObjectType.dotTrigger) {
731                if (segments1.size() > 2) {
732                    return TSQLEnv.compareIdentifier(dbVendor, ESQLDataObjectType.dotCatalog, segments1.get(0),
733                            segments2.get(0))
734                            && TSQLEnv.compareIdentifier(dbVendor, sqlDataObjectType, mergeSegments(segments1, 1), mergeSegments(segments2, 1));
735                } else if (segments1.size() == 2) {
736                    return TSQLEnv.compareIdentifier(dbVendor, ESQLDataObjectType.dotCatalog, segments1.get(0),
737                            segments2.get(0))
738                            && TSQLEnv.compareIdentifier(dbVendor, sqlDataObjectType, segments1.get(1), segments2.get(1));
739                } else if (segments1.size() == 1) {
740                    return TSQLEnv.compareIdentifier(dbVendor, sqlDataObjectType, segments1.get(0), segments2.get(0));
741                }
742            } else if (sqlDataObjectType == ESQLDataObjectType.dotCatalog || sqlDataObjectType == ESQLDataObjectType.dotSchema) {
743                if (segments1.size() > 1) {
744                    return TSQLEnv.compareIdentifier(dbVendor, ESQLDataObjectType.dotCatalog, mergeSegments(segments1, 0), mergeSegments(segments2, 0));
745                } else if (segments1.size() == 1) {
746                    return TSQLEnv.compareIdentifier(dbVendor, ESQLDataObjectType.dotCatalog, segments1.get(0), segments2.get(0));
747                }
748            }
749        }
750        else if(supportSchema){
751            if (sqlDataObjectType == ESQLDataObjectType.dotColumn) {
752                if (segments1.size() > 3) {
753                    return TSQLEnv.compareIdentifier(dbVendor, ESQLDataObjectType.dotSchema, segments1.get(0),
754                            segments2.get(0))
755                            && TSQLEnv.compareIdentifier(dbVendor, ESQLDataObjectType.dotTable, segments1.get(1),
756                            segments2.get(1))
757                            && TSQLEnv.compareIdentifier(dbVendor, ESQLDataObjectType.dotColumn, mergeSegments(segments1, 2),
758                            mergeSegments(segments2, 2));
759                } else if (segments1.size() == 3) {
760                    return TSQLEnv.compareIdentifier(dbVendor, ESQLDataObjectType.dotSchema, segments1.get(0),
761                            segments2.get(0))
762                            && TSQLEnv.compareIdentifier(dbVendor, ESQLDataObjectType.dotTable, segments1.get(1),
763                            segments2.get(1))
764                            && TSQLEnv.compareIdentifier(dbVendor, ESQLDataObjectType.dotColumn, segments1.get(2),
765                            segments2.get(2));
766                } else if (segments1.size() == 2) {
767                    return TSQLEnv.compareIdentifier(dbVendor, ESQLDataObjectType.dotTable, segments1.get(0),
768                            segments2.get(0))
769                            && TSQLEnv.compareIdentifier(dbVendor, ESQLDataObjectType.dotColumn, segments1.get(1),
770                            segments2.get(1));
771                } else if (segments1.size() == 1) {
772                    return TSQLEnv.compareIdentifier(dbVendor, ESQLDataObjectType.dotColumn, segments1.get(0),
773                            segments2.get(0));
774                }
775            } else if (sqlDataObjectType == ESQLDataObjectType.dotTable
776                    || sqlDataObjectType == ESQLDataObjectType.dotOraclePackage
777                    || sqlDataObjectType == ESQLDataObjectType.dotFunction
778                    || sqlDataObjectType == ESQLDataObjectType.dotProcedure
779                    || sqlDataObjectType == ESQLDataObjectType.dotTrigger) {
780                if (segments1.size() > 2) {
781                    return TSQLEnv.compareIdentifier(dbVendor, ESQLDataObjectType.dotSchema, segments1.get(0),
782                            segments2.get(0))
783                            && TSQLEnv.compareIdentifier(dbVendor, sqlDataObjectType, mergeSegments(segments1, 1), mergeSegments(segments2, 1));
784                } else if (segments1.size() == 2) {
785                    return TSQLEnv.compareIdentifier(dbVendor, ESQLDataObjectType.dotSchema, segments1.get(0),
786                            segments2.get(0))
787                            && TSQLEnv.compareIdentifier(dbVendor, sqlDataObjectType, segments1.get(1), segments2.get(1));
788                } else if (segments1.size() == 1) {
789                    return TSQLEnv.compareIdentifier(dbVendor, sqlDataObjectType, segments1.get(0), segments2.get(0));
790                }
791            } else if (sqlDataObjectType == ESQLDataObjectType.dotCatalog || sqlDataObjectType == ESQLDataObjectType.dotSchema) {
792                if (segments1.size() > 1) {
793                    return TSQLEnv.compareIdentifier(dbVendor, ESQLDataObjectType.dotSchema, mergeSegments(segments1, 0), mergeSegments(segments2, 0));
794                } else if (segments1.size() == 1) {
795                    return TSQLEnv.compareIdentifier(dbVendor, ESQLDataObjectType.dotSchema, segments1.get(0), segments2.get(0));
796                }
797            }
798        }
799        return false;
800    }
801
802    public static String normalizeIdentifier(EDbVendor dbVendor, ESQLDataObjectType objectType, String identifier) {
803        if (identifier == null)
804            return null;
805        String originIdentifier = identifier;
806        identifier = IdentifierService.normalizeStatic(dbVendor, objectType, identifier);
807        boolean collationSensitive = false;
808        switch (objectType) {
809            case dotCatalog:
810            case dotSchema:
811                collationSensitive = TSQLEnv.catalogCollationCaseSensitive.get(dbVendor);
812                break;
813            case dotFunction:
814            case dotProcedure:
815            case dotTrigger:
816            case dotTable:
817            case dotOraclePackage:
818                collationSensitive = TSQLEnv.tableCollationCaseSensitive.get(dbVendor);
819                break;
820            case dotColumn:
821                collationSensitive = TSQLEnv.columnCollationCaseSensitive.get(dbVendor);
822                break;
823            default:
824                collationSensitive = TSQLEnv.defaultCollationCaseSensitive.get(dbVendor);
825                break;
826        }
827        if(parseNames(identifier).size()>1) {
828            return collationSensitive ? originIdentifier : originIdentifier.toUpperCase();
829        }
830        else {
831            return collationSensitive ? identifier : identifier.toUpperCase();
832        }
833    }
834
835    public static String getIdentifierNormalColumnName(EDbVendor dbVendor, String name) {
836        return getIdentifierNormalName(dbVendor, name, ESQLDataObjectType.dotColumn);
837    }
838
839    public static String getIdentifierNormalTableName(String name) {
840        return getIdentifierNormalName(ModelBindingManager.getGlobalVendor(), name, ESQLDataObjectType.dotTable);
841    }
842    
843    public static String getIdentifierNormalTableName(EDbVendor dbVendor, String name) {
844        return getIdentifierNormalName(dbVendor, name, ESQLDataObjectType.dotTable);
845    }
846
847  /**
848   * 规范化多段限定名(Multi-Segment Qualified Name)并返回规范化后的完整限定名。
849   *
850   * <h3>核心功能</h3>
851   * <p>本方法是 SQL 标识符规范化的<b>多段名处理版本</b>,接受包含多段(catalog.schema.table.column)的限定名,
852   * 根据数据库厂商特性、层级支持能力和对象类型,智能解析和规范化每个段,最后返回用点号连接的完整规范化名称。</p>
853   *
854   * <h3>处理流程</h3>
855   * <ol>
856   *   <li><b>厂商特定语法展开</b>:
857   *       <ul>
858   *         <li>MSSQL/Azure SQL: 将 ".." 语法展开为 ".dbo." 或全局配置的 schema</li>
859   *         <li>示例:{@code "db..table"} → {@code "db.dbo.table"}</li>
860   *       </ul>
861   *   </li>
862   *   <li><b>解析多段名</b>:使用 {@link #parseNames(String)} 将限定名按点号分割成段列表</li>
863   *   <li><b>智能段类型推断</b>:根据以下因素决定每段的实际类型:
864   *       <ul>
865   *         <li>数据库厂商的层级支持能力(supportCatalog/supportSchema)</li>
866   *         <li>目标对象类型({@code sqlDataObjectType})</li>
867   *         <li>实际段数</li>
868   *       </ul>
869   *   </li>
870   *   <li><b>逐段规范化</b>:对每段调用 {@link #normalizeIdentifier(EDbVendor, ESQLDataObjectType, String)},
871   *       应用厂商特定的大小写规则、引号处理等</li>
872   *   <li><b>重组限定名</b>:用点号连接所有规范化后的段,返回完整的规范化限定名</li>
873   * </ol>
874   *
875   * <h3>智能段类型推断示例</h3>
876   * <p>假设数据库<b>同时支持 catalog 和 schema</b>,目标类型为 {@code dotTable}:</p>
877   * <ul>
878   *   <li>3段名 {@code "a.b.c"} → 解析为 {@code catalog.schema.table}</li>
879   *   <li>2段名 {@code "a.b"} → 解析为 {@code schema.table}</li>
880   *   <li>1段名 {@code "a"} → 解析为 {@code table}</li>
881   * </ul>
882   *
883   * <p>假设数据库<b>仅支持 catalog</b>(如 MySQL),目标类型为 {@code dotTable}:</p>
884   * <ul>
885   *   <li>2段名 {@code "a.b"} → 解析为 {@code catalog.table}</li>
886   *   <li>1段名 {@code "a"} → 解析为 {@code table}</li>
887   * </ul>
888   *
889   * <p>假设数据库<b>仅支持 schema</b>(如 PostgreSQL),目标类型为 {@code dotTable}:</p>
890   * <ul>
891   *   <li>2段名 {@code "a.b"} → 解析为 {@code schema.table}</li>
892   *   <li>1段名 {@code "a"} → 解析为 {@code table}</li>
893   * </ul>
894   *
895   * <h3>对于 dotColumn 类型的特殊处理</h3>
896   * <p>列名可能有4段(catalog.schema.table.column),方法会根据实际段数自动调整解析策略:</p>
897   * <ul>
898   *   <li>4段名 {@code "db.sch.tbl.col"} → {@code catalog.schema.table.column}</li>
899   *   <li>3段名 {@code "sch.tbl.col"} → {@code schema.table.column}</li>
900   *   <li>2段名 {@code "tbl.col"} → {@code table.column}</li>
901   *   <li>1段名 {@code "col"} → {@code column}</li>
902   * </ul>
903   *
904   * <h3>处理超长限定名</h3>
905   * <p>当段数超过标准层级时(如表名有4段或更多),方法会使用 {@link #mergeSegments(List, int)}
906   * 将多余的尾部段合并为单个段(保留点号),然后作为最后一段进行规范化。</p>
907   * <p>示例:5段列名 {@code "db.sch.tbl.nested.col"} → 合并后4段处理:
908   * {@code "db.sch.tbl.nested.col"} (最后两段合并为 {@code "nested.col"})</p>
909   *
910   * <h3>方法目的与使用场景</h3>
911   * <ul>
912   *   <li><b>目的</b>:为多段限定名提供统一的规范化接口,生成可用于比较、索引和查找的标准化名称</li>
913   *   <li><b>输出特性</b>:返回<b>多段名</b>(用点号连接),保留层级结构信息</li>
914   *   <li><b>使用场景</b>:
915   *       <ul>
916   *         <li>环境层(TSQLEnv)处理完整限定名</li>
917   *         <li>名称比较和匹配(需要保留层级信息)</li>
918   *         <li>快速索引查找(NameKey 构造)</li>
919   *       </ul>
920   *   </li>
921   * </ul>
922   *
923   * <h3>与相关方法的区别</h3>
924   * <ul>
925   *   <li><b>{@link #normalizeIdentifier(EDbVendor, ESQLDataObjectType, String)}</b>:
926   *       单段规范化,不解析限定名,直接处理引号和大小写,返回单段名</li>
927   *   <li><b>{@code IdentifierService.keyForMap(String, ESQLDataObjectType)}</b>:
928   *       仅接受单段名,用于生成 Map 的 key,会抛出异常如果输入是多段名</li>
929   *   <li><b>{@code IdentifierService.normalizeQualifiedName(String, ESQLDataObjectType)}</b>:
930   *       新架构中的等价方法,提供相同的多段名规范化功能</li>
931   * </ul>
932   *
933   * <h3>厂商特定行为</h3>
934   * <ul>
935   *   <li><b>MySQL</b>: 仅支持 catalog(即 database),反引号引用,大小写根据系统变量决定</li>
936   *   <li><b>PostgreSQL</b>: 仅支持 schema,双引号引用,未加引号的标识符转小写</li>
937   *   <li><b>SQL Server</b>: 支持 catalog+schema,方括号或双引号引用,".." 展开为 ".dbo."</li>
938   *   <li><b>Oracle</b>: 仅支持 schema,双引号引用,未加引号的标识符转大写</li>
939   *   <li><b>Snowflake</b>: 支持 catalog+schema,双引号引用,大小写保留但匹配不敏感</li>
940   *   <li><b>BigQuery</b>: 支持 catalog+schema(项目+数据集),反引号引用,大小写不敏感,可能内部转小写</li>
941   * </ul>
942   *
943   * @param dbVendor 数据库厂商类型(决定层级支持、引号风格、大小写规则)
944   * @param name 原始标识符或多段限定名(可能包含引号/反引号;可能包含 catalog/schema 前缀;
945   *             可能包含厂商特定语法如 MSSQL 的 "..")
946   * @param sqlDataObjectType 期望的对象类型(例如 dotTable, dotColumn, dotSchema, dotCatalog),
947   *                          用于指导段类型推断和规范化规则应用
948   * @return 规范化后的完整限定名,用点号连接各段;如果输入为 {@code null},返回 {@code null};
949   *         如果输入为空字符串或无法解析,返回空字符串
950   *
951   * @see #normalizeIdentifier(EDbVendor, ESQLDataObjectType, String) 单段规范化方法
952   * @see #parseNames(String) 多段名解析方法
953   * @see #mergeSegments(List, int) 段合并工具方法
954   * @see TSQLEnv#normalizeIdentifier(EDbVendor, ESQLDataObjectType, String)
955   * @see IdentifierService#normalizeQualifiedName(String, ESQLDataObjectType) Phase 0 新架构中的等价方法
956   *
957   * @since 3.1.0.8
958   */
959    public static String getIdentifierNormalName(EDbVendor dbVendor, String name,
960                                                 ESQLDataObjectType sqlDataObjectType) {
961        if (name == null) {
962            return null;
963        }
964        if (dbVendor == EDbVendor.dbvmssql || dbVendor == EDbVendor.dbvazuresql) {
965            if (name.indexOf("..") != -1) {
966                if (ModelBindingManager.getGlobalSchema() != null) {
967                    name = name.replace("..", "." + ModelBindingManager.getGlobalSchema() + ".");
968                } else {
969                    name = name.replace("..", ".dbo.");
970                }
971            }
972        }
973        List<String> segments = parseNames(name);
974        StringBuilder builder = new StringBuilder();
975        boolean supportCatalog = TSQLEnv.supportCatalog(dbVendor);
976        boolean supportSchema = TSQLEnv.supportSchema(dbVendor);
977
978        if(supportCatalog && supportSchema) {
979            if (sqlDataObjectType == ESQLDataObjectType.dotColumn) {
980                if (segments.size() > 4) {
981                    builder.append(normalizeIdentifier(dbVendor, ESQLDataObjectType.dotCatalog, segments.get(0)))
982                            .append(".")
983                            .append(normalizeIdentifier(dbVendor, ESQLDataObjectType.dotSchema, segments.get(1)))
984                            .append(".")
985                            .append(normalizeIdentifier(dbVendor, ESQLDataObjectType.dotTable, segments.get(2)))
986                            .append(".")
987                            .append(normalizeIdentifier(dbVendor, ESQLDataObjectType.dotColumn, mergeSegments(segments, 3)));
988                } else if (segments.size() == 4) {
989                    builder.append(normalizeIdentifier(dbVendor, ESQLDataObjectType.dotCatalog, segments.get(0)))
990                            .append(".")
991                            .append(normalizeIdentifier(dbVendor, ESQLDataObjectType.dotSchema, segments.get(1)))
992                            .append(".")
993                            .append(normalizeIdentifier(dbVendor, ESQLDataObjectType.dotTable, segments.get(2)))
994                            .append(".")
995                            .append(normalizeIdentifier(dbVendor, ESQLDataObjectType.dotColumn, segments.get(3)));
996                } else if (segments.size() == 3) {
997                    builder.append(normalizeIdentifier(dbVendor, ESQLDataObjectType.dotSchema, segments.get(0)))
998                            .append(".")
999                            .append(normalizeIdentifier(dbVendor, ESQLDataObjectType.dotTable, segments.get(1)))
1000                            .append(".")
1001                            .append(normalizeIdentifier(dbVendor, ESQLDataObjectType.dotColumn, segments.get(2)));
1002                } else if (segments.size() == 2) {
1003                    builder.append(normalizeIdentifier(dbVendor, ESQLDataObjectType.dotTable, segments.get(0)))
1004                            .append(".")
1005                            .append(normalizeIdentifier(dbVendor, ESQLDataObjectType.dotColumn, segments.get(1)));
1006                } else if (segments.size() == 1) {
1007                    builder.append(normalizeIdentifier(dbVendor, ESQLDataObjectType.dotColumn, segments.get(0)));
1008                }
1009            } else if (sqlDataObjectType == ESQLDataObjectType.dotTable
1010                    || sqlDataObjectType == ESQLDataObjectType.dotOraclePackage
1011                    || sqlDataObjectType == ESQLDataObjectType.dotFunction
1012                    || sqlDataObjectType == ESQLDataObjectType.dotProcedure
1013                    || sqlDataObjectType == ESQLDataObjectType.dotTrigger) {
1014                if (segments.size() > 3) {
1015                    builder.append(normalizeIdentifier(dbVendor, ESQLDataObjectType.dotCatalog, segments.get(0)))
1016                            .append(".")
1017                            .append(normalizeIdentifier(dbVendor, ESQLDataObjectType.dotSchema, segments.get(1)))
1018                            .append(".").append(normalizeIdentifier(dbVendor, sqlDataObjectType, mergeSegments(segments, 2)));
1019                } else if (segments.size() == 3) {
1020                    builder.append(normalizeIdentifier(dbVendor, ESQLDataObjectType.dotCatalog, segments.get(0)))
1021                            .append(".")
1022                            .append(normalizeIdentifier(dbVendor, ESQLDataObjectType.dotSchema, segments.get(1)))
1023                            .append(".").append(normalizeIdentifier(dbVendor, sqlDataObjectType, segments.get(2)));
1024                } else if (segments.size() == 2) {
1025                    builder.append(normalizeIdentifier(dbVendor, ESQLDataObjectType.dotSchema, segments.get(0)))
1026                            .append(".").append(normalizeIdentifier(dbVendor, sqlDataObjectType, segments.get(1)));
1027                } else if (segments.size() == 1) {
1028                    builder.append(normalizeIdentifier(dbVendor, sqlDataObjectType, segments.get(0)));
1029                }
1030            } else if (sqlDataObjectType == ESQLDataObjectType.dotSchema) {
1031                if (segments.size() > 2) {
1032                    builder.append(normalizeIdentifier(dbVendor, ESQLDataObjectType.dotCatalog, segments.get(0)))
1033                            .append(".").append(normalizeIdentifier(dbVendor, sqlDataObjectType, mergeSegments(segments, 1)));
1034                } else if (segments.size() == 2) {
1035                    builder.append(normalizeIdentifier(dbVendor, ESQLDataObjectType.dotCatalog, segments.get(0)))
1036                            .append(".").append(normalizeIdentifier(dbVendor, sqlDataObjectType, segments.get(1)));
1037                } else if (segments.size() == 1) {
1038                    builder.append(normalizeIdentifier(dbVendor, sqlDataObjectType, segments.get(0)));
1039                }
1040            } else if (sqlDataObjectType == ESQLDataObjectType.dotCatalog) {
1041                if (segments.size() > 1) {
1042                    builder.append(normalizeIdentifier(dbVendor, sqlDataObjectType, mergeSegments(segments, 0)));
1043                } else if (segments.size() == 1) {
1044                    builder.append(normalizeIdentifier(dbVendor, sqlDataObjectType, segments.get(0)));
1045                }
1046            }
1047        }
1048        else if(supportCatalog){
1049            if (sqlDataObjectType == ESQLDataObjectType.dotColumn) {
1050                if (segments.size() > 3) {
1051                    builder.append(normalizeIdentifier(dbVendor, ESQLDataObjectType.dotCatalog, segments.get(0)))
1052                            .append(".")
1053                            .append(normalizeIdentifier(dbVendor, ESQLDataObjectType.dotTable, segments.get(1)))
1054                            .append(".")
1055                            .append(normalizeIdentifier(dbVendor, ESQLDataObjectType.dotColumn, mergeSegments(segments, 2)));
1056                } else if (segments.size() == 3) {
1057                    builder.append(normalizeIdentifier(dbVendor, ESQLDataObjectType.dotCatalog, segments.get(0)))
1058                            .append(".")
1059                            .append(normalizeIdentifier(dbVendor, ESQLDataObjectType.dotTable, segments.get(1)))
1060                            .append(".")
1061                            .append(normalizeIdentifier(dbVendor, ESQLDataObjectType.dotColumn, segments.get(2)));
1062                } else if (segments.size() == 2) {
1063                    builder.append(normalizeIdentifier(dbVendor, ESQLDataObjectType.dotTable, segments.get(0)))
1064                            .append(".")
1065                            .append(normalizeIdentifier(dbVendor, ESQLDataObjectType.dotColumn, segments.get(1)));
1066                } else if (segments.size() == 1) {
1067                    builder.append(normalizeIdentifier(dbVendor, ESQLDataObjectType.dotColumn, segments.get(0)));
1068                }
1069            } else if (sqlDataObjectType == ESQLDataObjectType.dotTable
1070                    || sqlDataObjectType == ESQLDataObjectType.dotOraclePackage
1071                    || sqlDataObjectType == ESQLDataObjectType.dotFunction
1072                    || sqlDataObjectType == ESQLDataObjectType.dotProcedure
1073                    || sqlDataObjectType == ESQLDataObjectType.dotTrigger) {
1074                if (segments.size() > 2) {
1075                    builder.append(normalizeIdentifier(dbVendor, ESQLDataObjectType.dotCatalog, segments.get(0)))
1076                            .append(".").append(normalizeIdentifier(dbVendor, sqlDataObjectType, mergeSegments(segments, 1)));
1077                } else if (segments.size() == 2) {
1078                    builder.append(normalizeIdentifier(dbVendor, ESQLDataObjectType.dotCatalog, segments.get(0)))
1079                            .append(".").append(normalizeIdentifier(dbVendor, sqlDataObjectType, segments.get(1)));
1080                } else if (segments.size() == 1) {
1081                    builder.append(normalizeIdentifier(dbVendor, sqlDataObjectType, segments.get(0)));
1082                }
1083            } else if (sqlDataObjectType == ESQLDataObjectType.dotSchema || sqlDataObjectType == ESQLDataObjectType.dotCatalog) {
1084                if (segments.size() > 1) {
1085                    builder.append(normalizeIdentifier(dbVendor, ESQLDataObjectType.dotCatalog, mergeSegments(segments, 0)));
1086                } else if (segments.size() == 1) {
1087                    builder.append(normalizeIdentifier(dbVendor, ESQLDataObjectType.dotCatalog, segments.get(0)));
1088                }
1089            }
1090        }
1091        else if(supportSchema){
1092            if (sqlDataObjectType == ESQLDataObjectType.dotColumn) {
1093                if (segments.size() > 3) {
1094                    builder.append(normalizeIdentifier(dbVendor, ESQLDataObjectType.dotSchema, segments.get(0)))
1095                            .append(".")
1096                            .append(normalizeIdentifier(dbVendor, ESQLDataObjectType.dotTable, segments.get(1)))
1097                            .append(".")
1098                            .append(normalizeIdentifier(dbVendor, ESQLDataObjectType.dotColumn, mergeSegments(segments, 2)));
1099                } else if (segments.size() == 3) {
1100                    builder.append(normalizeIdentifier(dbVendor, ESQLDataObjectType.dotSchema, segments.get(0)))
1101                            .append(".")
1102                            .append(normalizeIdentifier(dbVendor, ESQLDataObjectType.dotTable, segments.get(1)))
1103                            .append(".")
1104                            .append(normalizeIdentifier(dbVendor, ESQLDataObjectType.dotColumn, segments.get(2)));
1105                } else if (segments.size() == 2) {
1106                    builder.append(normalizeIdentifier(dbVendor, ESQLDataObjectType.dotTable, segments.get(0)))
1107                            .append(".")
1108                            .append(normalizeIdentifier(dbVendor, ESQLDataObjectType.dotColumn, segments.get(1)));
1109                } else if (segments.size() == 1) {
1110                    builder.append(normalizeIdentifier(dbVendor, ESQLDataObjectType.dotColumn, segments.get(0)));
1111                }
1112            } else if (sqlDataObjectType == ESQLDataObjectType.dotTable
1113                    || sqlDataObjectType == ESQLDataObjectType.dotOraclePackage
1114                    || sqlDataObjectType == ESQLDataObjectType.dotFunction
1115                    || sqlDataObjectType == ESQLDataObjectType.dotProcedure
1116                    || sqlDataObjectType == ESQLDataObjectType.dotTrigger) {
1117                if (segments.size() > 2) {
1118                    builder.append(normalizeIdentifier(dbVendor, ESQLDataObjectType.dotSchema, segments.get(0)))
1119                            .append(".").append(normalizeIdentifier(dbVendor, sqlDataObjectType, mergeSegments(segments, 1)));
1120                } else if (segments.size() == 2) {
1121                    builder.append(normalizeIdentifier(dbVendor, ESQLDataObjectType.dotSchema, segments.get(0)))
1122                            .append(".").append(normalizeIdentifier(dbVendor, sqlDataObjectType, segments.get(1)));
1123                } else if (segments.size() == 1) {
1124                    builder.append(normalizeIdentifier(dbVendor, sqlDataObjectType, segments.get(0)));
1125                }
1126            } else if (sqlDataObjectType == ESQLDataObjectType.dotSchema || sqlDataObjectType == ESQLDataObjectType.dotCatalog) {
1127                if (segments.size() > 1) {
1128                    builder.append(normalizeIdentifier(dbVendor, ESQLDataObjectType.dotSchema, mergeSegments(segments, 0)));
1129                } else if (segments.size() == 1) {
1130                    builder.append(normalizeIdentifier(dbVendor, ESQLDataObjectType.dotSchema, segments.get(0)));
1131                }
1132            }
1133        }
1134        return builder.toString();
1135    }
1136
1137    public static String mergeSegments(List<String> segments, int index) {
1138        StringBuilder buffer = new StringBuilder();
1139                for (int i = index; i < segments.size(); i++) {
1140                        buffer.append(segments.get(i));
1141                        if(i<segments.size()-1) {
1142                                buffer.append(".");
1143                        }
1144                }
1145                return buffer.toString();
1146        }
1147
1148//      public static String getIdentifierNormalName(EDbVendor vendor, String name) {
1149//              if (isEmpty(name)) {
1150//                      return null;
1151//              }
1152//              name = name.replaceAll("(?i)null\\.", "");
1153//              switch (vendor) {
1154//              case dbvbigquery:
1155//                      return replaceIdentifierNormalName(name, "`.*?`", "`", true).replaceAll("\\.\\s+", ".");
1156//              case dbvcouchbase:
1157//              case dbvhive:
1158//              case dbvimpala:
1159//              case dbvmysql:
1160//                      return replaceIdentifierNormalName(name, "`.*?`", "`", true).replaceAll("\\.\\s+", ".");
1161//              case dbvdax:
1162//                      return replaceIdentifierNormalName(name, "'.*?'", "'", true).replaceAll("\\.\\s+", ".");
1163//              case dbvdb2:
1164//              case dbvhana:
1165//              case dbvinformix:
1166//              case dbvnetezza:
1167//              case dbvoracle:
1168//              case dbvsnowflake:
1169//              case dbvsybase:
1170//              case dbvteradata:
1171//              case dbvvertica:
1172//                      return replaceIdentifierNormalName(name, "\".*?\"", "\"", true).replaceAll("\\.\\s+", ".");
1173//              case dbvpostgresql:
1174//              case dbvgreenplum:
1175//              case dbvredshift:
1176//                      return replaceIdentifierNormalName(name, "\".*?\"", "\"", false).replaceAll("\\.\\s+", ".");
1177//              case dbvmssql:
1178//                      return replaceIdentifierNormalName(name, "([\"\\[']).*?([\"\\]'])", "[\"\\[\\]']", true)
1179//                                      .replaceAll("\\.\\s+", ".");
1180//              default:
1181//                      return replaceIdentifierNormalName(name, "\".*?\"", "\"", true).replaceAll("\\.\\s+", ".");
1182//              }
1183//      }
1184//
1185//      private static String replaceIdentifierNormalName(String content, String match, String replace, boolean toUpper) {
1186//              Pattern pattern = Pattern.compile(match, Pattern.CASE_INSENSITIVE | Pattern.DOTALL);
1187//              Matcher matcher = pattern.matcher(content);
1188//              StringBuilder buffer = new StringBuilder();
1189//              int start = 0;
1190//              while (matcher.find()) {
1191//                      int matchStart = matcher.start();
1192//                      int macthEnd = matcher.end();
1193//                      if (start < matchStart) {
1194//                              if (toUpper) {
1195//                                      buffer.append(content.substring(start, matchStart).toUpperCase());
1196//                              } else {
1197//                                      buffer.append(content.substring(start, matchStart).toLowerCase());
1198//                              }
1199//                      }
1200//                      buffer.append(matcher.group().replaceAll(replace, ""));
1201//                      start = macthEnd;
1202//              }
1203//              if (start < content.length()) {
1204//                      if (toUpper) {
1205//                              buffer.append(content.substring(start).toUpperCase());
1206//                      } else {
1207//                              buffer.append(content.substring(start).toLowerCase());
1208//                      }
1209//              }
1210//              return buffer.toString();
1211//      }
1212
1213    public static boolean isTempTable(table table) {
1214        if(SubType.temp_table.name().equals(table.getSubType())) {
1215                return true;
1216        }
1217        String tableName = table.getName();
1218        List<String> segments = parseNames(tableName);
1219        if (tableName.startsWith("@") || segments.get(segments.size() - 1).startsWith("@")) {
1220            return true;
1221        }
1222        if (tableName.startsWith("#") || segments.get(segments.size() - 1).startsWith("#")) {
1223            return true;
1224        }
1225        return false;
1226    }
1227    
1228//    public static boolean isTempTable(String tableName) {
1229//        List<String> segments = parseNames(tableName);
1230//        if (tableName.startsWith("@") || segments.get(segments.size() - 1).startsWith("@")) {
1231//            return true;
1232//        }
1233//        if (tableName.startsWith("#") || segments.get(segments.size() - 1).startsWith("#")) {
1234//            return true;
1235//        }
1236//        return false;
1237//    }
1238
1239//    public static boolean isTempTable(TTable table, EDbVendor vendor) {
1240//        switch (vendor) {
1241//            case dbvmssql:
1242//                return table.getName().startsWith("#");
1243//            default:
1244//                return false;
1245//        }
1246//    }
1247
1248    public static File[] listFiles(File sqlFiles) {
1249        List<File> children = new ArrayList<File>();
1250        if (sqlFiles != null)
1251            listFiles(sqlFiles, children);
1252        Collections.sort(children);
1253        return children.toArray(new File[0]);
1254    }
1255
1256    public static File[] listFiles(File sqlFiles, FileFilter filter) {
1257        List<File> children = new ArrayList<File>();
1258        if (sqlFiles != null)
1259            listFiles(sqlFiles, children, filter);
1260        Collections.sort(children);
1261        return children.toArray(new File[0]);
1262    }
1263
1264    private static Set<String> extensions = new HashSet<String>(Arrays.asList("java", "class", "php", "c", "cpp", "properties", "iml",
1265            "yml", "xml", "md", "jpg", "png", "gif", "bmp", "tiff", "tif", "svg", "pdf", "mp3", "mp4", "bak", "jar", "gz",
1266            "tar", "log", "conf", "dll", "exe", "so", "sh", "bat", "cdr", "docx", "doc", "xps", "xlsx", "xls", "ppt", "pptx",
1267            "rar", "7z", "ttf", "caj", "dwg", "dwf", "dxf", "ico", "epub", "webp", "heic", "html", "htm", "vsd", "vsdx", "rtf", "ico"));
1268
1269    public static void listFiles(File rootFile, List<File> children) {
1270        if (rootFile.isFile())
1271            children.add(rootFile);
1272        else {
1273            File[] files = rootFile.listFiles(t -> {
1274                int dotIndex = t.getName().lastIndexOf(".");
1275                if (dotIndex == -1) {
1276                    return true;
1277                }
1278                String extension = t.getName().toLowerCase().substring(dotIndex + 1);
1279                return !extensions.contains(extension);
1280            });
1281            if (files != null) {
1282                for (int i = 0; i < files.length; i++) {
1283                    listFiles(files[i], children);
1284                }
1285            }
1286        }
1287    }
1288
1289    public static void listFiles(File rootFile, List<File> children, FileFilter filter) {
1290        if (rootFile.isFile() && filter.accept(rootFile))
1291            children.add(rootFile);
1292        else {
1293            File[] files = rootFile.listFiles(filter);
1294            if (files != null) {
1295                for (int i = 0; i < files.length; i++) {
1296                    listFiles(files[i], children, filter);
1297                }
1298            }
1299        }
1300    }
1301
1302    public static String readFile(File file) {
1303        StringBuilder stringBuilder = new StringBuilder();
1304        BufferedReader reader = null;
1305        try {
1306            reader = new BufferedReader(new FileReader(file));
1307
1308            String line = null;
1309            String ls = System.getProperty("line.separator");
1310            while ((line = reader.readLine()) != null) {
1311                stringBuilder.append(line);
1312                stringBuilder.append(ls);
1313            }
1314            stringBuilder.deleteCharAt(stringBuilder.length() - 1);
1315        } catch (IOException e) {
1316            logger.error("read file failed.", e);
1317        } finally {
1318            if (reader != null) {
1319                try {
1320                    reader.close();
1321                } catch (IOException e) {
1322                    logger.error("close reader failed.", e);
1323                }
1324            }
1325        }
1326        return stringBuilder.toString();
1327    }
1328
1329        public static String stringToMD5(String plainText) {
1330                byte[] secretBytes = null;
1331                try {
1332                        secretBytes = MessageDigest.getInstance("md5").digest(plainText.getBytes("UTF-8"));
1333                        StringBuffer sb = new StringBuffer();
1334                        for (int i = 0; i < secretBytes.length; ++i) {
1335                                sb.append(Integer.toHexString((secretBytes[i] & 0xFF) | 0x100).substring(1, 3));
1336                        }
1337                        return sb.toString();
1338                } catch (Exception e) {
1339                        logger.error("get text md5 value failed.", e);
1340                }
1341                return null;
1342        }
1343
1344        public static String trimSingleQuote(String columnAlias) {
1345                if(columnAlias.startsWith("'") && columnAlias.endsWith("'")) {
1346                        return columnAlias.substring(1, columnAlias.length() - 1);
1347                }
1348                return columnAlias;
1349        }
1350        
1351    public static String endTrim(String input) {
1352        if (input == null) {
1353            return null;
1354        }
1355
1356        int end = input.length();
1357        while (end > 0 && Character.isWhitespace(input.charAt(end - 1))) {
1358            end--;
1359        }
1360
1361        return input.substring(0, end);
1362    }
1363        
1364    public static void endTrim(StringBuilder buffer) {
1365        int length = buffer.length();
1366        while (length > 0 && Character.isWhitespace(buffer.charAt(length - 1))) {
1367            length--; // 逐步减少长度
1368        }
1369        buffer.setLength(length); // 直接截断尾部空白字符
1370    }
1371
1372    public static String joinNonEmpty(String... parts) {
1373        return Arrays.stream(parts)
1374                .filter(s -> s != null && !s.trim().isEmpty())
1375                .collect(Collectors.joining("/"));
1376    }
1377}