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