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