001package gudusoft.gsqlparser.dlineage.util;
002
003import java.io.File;
004import java.util.ArrayList;
005import java.util.Arrays;
006import java.util.LinkedHashSet;
007import java.util.List;
008import java.util.Map;
009import java.util.Set;
010
011import gudusoft.gsqlparser.EDbVendor;
012import gudusoft.gsqlparser.EParameterMode;
013import gudusoft.gsqlparser.TCustomSqlStatement;
014import gudusoft.gsqlparser.dlineage.dataflow.model.AnalyzeMode;
015import gudusoft.gsqlparser.dlineage.dataflow.model.ModelBindingManager;
016import gudusoft.gsqlparser.dlineage.dataflow.model.Option;
017import gudusoft.gsqlparser.dlineage.dataflow.model.SqlInfo;
018import gudusoft.gsqlparser.dlineage.dataflow.model.SubType;
019import gudusoft.gsqlparser.dlineage.dataflow.model.Table;
020import gudusoft.gsqlparser.dlineage.dataflow.model.xml.*;
021import gudusoft.gsqlparser.nodes.TFunctionCall;
022import gudusoft.gsqlparser.nodes.TObjectName;
023import gudusoft.gsqlparser.nodes.TParameterDeclaration;
024import gudusoft.gsqlparser.nodes.TParameterDeclarationList;
025import gudusoft.gsqlparser.sqlenv.IdentifierService;
026import gudusoft.gsqlparser.sqlenv.ESQLDataObjectType;
027import gudusoft.gsqlparser.sqlenv.TSQLEnv;
028import gudusoft.gsqlparser.stmt.TCommonBlock;
029import gudusoft.gsqlparser.stmt.TSelectSqlStatement;
030import gudusoft.gsqlparser.stmt.TCreateFunctionStmt;
031import gudusoft.gsqlparser.stmt.TStoredProcedureSqlStatement;
032import gudusoft.gsqlparser.stmt.mssql.TMssqlBlock;
033import gudusoft.gsqlparser.stmt.mssql.TMssqlCreateFunction;
034import gudusoft.gsqlparser.stmt.oracle.TPlsqlCreatePackage;
035import gudusoft.gsqlparser.TBaseType;
036import gudusoft.gsqlparser.util.SQLUtil;
037import gudusoft.gsqlparser.stmt.teradata.TTeradataCreateProcedure;
038import gudusoft.gsqlparser.util.SQLUtil;
039import gudusoft.gsqlparser.util.functionChecker;
040import gudusoft.gsqlparser.util.keywordChecker;
041import gudusoft.gsqlparser.util.json.JSON;
042
043public class DlineageUtil {
044
045    /**
046     * Whether a serialized coordinate ("[line,col,offset],[line,col,offset]...")
047     * points at a real reference site. A position synthesized for a column that
048     * has no source token renders as "[1,1,0],[1,n,0]" (start-of-module, width =
049     * name length) or carries -1 markers; a genuine column reference can never
050     * start at line 1, column 1, because the statement head keyword occupies
051     * that position.
052     */
053    public static boolean hasUsableCoordinate(String coordinate) {
054        if (SQLUtil.isEmpty(coordinate)) {
055            return false;
056        }
057        String trimmed = coordinate.trim();
058        return !trimmed.startsWith("[1,1,") && !trimmed.startsWith("[-1,-1,");
059    }
060
061    public static boolean compareIdentifier(String source, String target, ESQLDataObjectType sqlDataObjectType) {
062        return SQLUtil.compareIdentifier(ModelBindingManager.getGlobalVendor(), sqlDataObjectType, source, target);
063    }
064
065    public static boolean compareColumnIdentifier(String source, String target) {
066        return SQLUtil.compareIdentifier(ModelBindingManager.getGlobalVendor(), ESQLDataObjectType.dotColumn, source,
067                target);
068    }
069
070    public static boolean compareTableIdentifier(String source, String target) {
071        return SQLUtil.compareIdentifier(ModelBindingManager.getGlobalVendor(), ESQLDataObjectType.dotTable, source,
072                target);
073    }
074
075    /**
076     * Single-segment identifier equality via the unified façade, using the bound
077     * global analysis vendor; falls back to case-insensitive comparison when no
078     * vendor is bound (model classes used outside an analysis run).
079     */
080    public static boolean sameName(ESQLDataObjectType objectType, String a, String b) {
081        if (a == null || b == null) {
082            return false;
083        }
084        EDbVendor vendor = ModelBindingManager.getGlobalVendor();
085        return vendor == null ? a.equalsIgnoreCase(b) : SQLUtil.sameName(vendor, objectType, a, b);
086    }
087
088    /**
089     * Like {@link #sameName} but for possibly-qualified names; routes through the
090     * segmenting {@link SQLUtil#compareIdentifier}.
091     */
092    public static boolean sameQualifiedName(ESQLDataObjectType objectType, String a, String b) {
093        if (a == null || b == null) {
094            return false;
095        }
096        // Reflexivity guard: degenerate model names (e.g. the literal ".") defeat
097        // compareIdentifier's segmentation, but identical strings are always the same name.
098        if (a.equals(b)) {
099            return true;
100        }
101        EDbVendor vendor = ModelBindingManager.getGlobalVendor();
102        return vendor == null ? a.equalsIgnoreCase(b) : SQLUtil.compareIdentifier(vendor, objectType, a, b);
103    }
104
105    /**
106     * Canonical equality of the column (last) segments of two references — the
107     * canonical counterpart of comparing {@link #getColumnName(String)} results:
108     * same naive last-dot split, same trim and empty-segment fallback, but the raw
109     * segments go to the canonical engine instead of normalize-then-equalsIgnoreCase.
110     */
111    public static boolean sameColumnSegment(String a, String b) {
112        return sameName(ESQLDataObjectType.dotColumn, columnSegmentOf(a), columnSegmentOf(b));
113    }
114
115    /** Canonical counterpart of comparing {@link #getColumnName(TObjectName)} results. */
116    public static boolean sameColumnName(TObjectName a, TObjectName b) {
117        return sameName(ESQLDataObjectType.dotColumn, rawColumnName(a), rawColumnName(b));
118    }
119
120    /** Mixed operand shapes: model name string vs AST object name. */
121    public static boolean sameColumnName(String a, TObjectName b) {
122        return sameName(ESQLDataObjectType.dotColumn, columnSegmentOf(a), rawColumnName(b));
123    }
124
125    /**
126     * The RAW column (last) segment of a reference — {@link #getColumnName(String)}'s
127     * extraction (naive last-dot split, trim, whole-string fallback) WITHOUT the
128     * normalization, for callers that canonically compare a segment against a
129     * differently-shaped operand.
130     */
131    public static String columnSegmentOf(String column) {
132        if (column == null) {
133            return null;
134        }
135        String name = column.substring(column.lastIndexOf('.') + 1);
136        if ("".equals(name.trim())) {
137            return column.trim();
138        }
139        return name.trim();
140    }
141
142    private static String rawColumnName(TObjectName column) {
143        if (column == null || column.toString() == null) {
144            return null;
145        }
146        String name = column.getColumnNameOnly();
147        if (name == null || "".equals(name.trim())) {
148            return column.toString().trim();
149        }
150        return name.trim();
151    }
152
153        /**
154         * Decode a delimited COLUMN identifier to the name a catalog stores, keeping
155         * the author's letter case: {@code [Volgorde]}, {@code "Volgorde"},
156         * {@code `Volgorde`} and the SQL Server legacy alias form {@code 'Volgorde'}
157         * all become {@code Volgorde}; escapes resolve ({@code [Esc]]aped]} →
158         * {@code Esc]aped}). Vendor- and role-aware, so a spelling that is not a
159         * delimiter for the analysis vendor is left alone.
160         *
161         * <p>Used for the column names dlineage PUBLISHES, so that view columns and
162         * relationship endpoints carry the same identifier a consumer finds in
163         * {@code sys.columns} instead of a syntactic spelling it has to strip
164         * itself (GitHub issue #704). Unrecognized or malformed input is returned
165         * unchanged.
166         *
167         * <p><b>Only call this on a string of identifier provenance</b> — a
168         * {@code TObjectName} column token or a {@code TAliasClause} alias name.
169         * A string CONSTANT reaches the model as quoted text too
170         * ({@code 'literal value'} becomes a result-set column name), and decoding
171         * one would corrupt the value it carries.
172         *
173         * <p><b>Nothing is decoded on a vendor whose delimiters carry identity.</b>
174         * Quoting is not always cosmetic: where a vendor case-folds UNQUOTED names,
175         * the delimiters are what mark a name as case-exact, so Oracle
176         * {@code "Purchase Frequency"} and {@code Purchase Frequency} (which folds
177         * to {@code PURCHASE FREQUENCY}) are two DIFFERENT columns, and decoding
178         * would rename the column rather than clean up its spelling. Such a vendor
179         * is skipped ENTIRELY, by {@link #delimitersAreCosmetic}, rather than
180         * name-by-name: {@code "DESCRIPTION"} happens to fold to itself and could be
181         * decoded safely, but decoding it while {@code "Mixed Case"} keeps its
182         * quotes would publish two spelling conventions in one Oracle document and
183         * leave consumers no single rule to apply. SQL Server — where {@code [x]},
184         * {@code "x"}, {@code 'x'} and {@code x} are all one case-insensitive column
185         * — is cosmetic throughout, which is why issue #704 is a SQL Server issue.
186         *
187         * <p><b>A name whose payload contains a dot keeps its delimiters</b>, even
188         * on a vendor that passes the test above. Model names double as keys in the
189         * dot-delimited qualified-name domain that {@link SQLUtil#parseNames}
190         * segments, where the delimiters are what stop {@code "name.dot"} from
191         * reading as {@code name} qualified by {@code dot} (the invariant
192         * {@link SQLUtil#quoteDottedName} exists to keep). Publishing the decoded
193         * form there cost a real CTAS lineage edge in the Snowflake corpus — a
194         * downstream re-split renamed the target column to {@code dot} and dropped
195         * its dependency.
196         *
197         * <p>In both held-back cases the name is returned exactly as written, so a
198         * consumer still has to decode it; that is strictly better than dropping an
199         * edge or renaming a column to improve a spelling.
200         *
201         * @see SQLUtil#decodeDelimitedName
202         */
203        public static String decodeColumnIdentifier(String name) {
204                EDbVendor vendor = ModelBindingManager.getGlobalVendor();
205                if (name == null || vendor == null) {
206                        // No bound analysis vendor (model classes used outside a run): which
207                        // spellings delimit is unknowable, so leave the name as written.
208                        return name;
209                }
210                if (!delimitersAreCosmetic(vendor)) {
211                        return name;
212                }
213                String decoded = SQLUtil.decodeDelimitedName(vendor, ESQLDataObjectType.dotColumn, name);
214                if (decoded == null || decoded.equals(name) || !publishable(vendor, decoded)) {
215                        return name;
216                }
217                return decoded;
218        }
219
220        /**
221         * Decode a delimited identifier that sits in ALIAS position.
222         *
223         * <p>Same policy as {@link #decodeColumnIdentifier}, plus the vendor-blind
224         * apostrophe form: several vendors accept a string literal as a column
225         * alias ({@code SELECT c AS 'myTime'} is legal MySQL), and in alias position
226         * there is no ambiguity with a value literal to protect against — a
227         * {@code TAliasClause} has already proved this token names a column. The
228         * column-reference codec cannot make that call, because for MySQL an
229         * apostrophe delimits a STRING, not an identifier.
230         *
231         * <p>The apostrophe branch also preserves what
232         * {@code SQLUtil.trimSingleQuote} did here before issue #704: without it,
233         * routing aliases through the column codec would REGRESS MySQL
234         * {@code AS 'myTime'} from {@code myTime} back to {@code 'myTime'}.
235         * The {@link #publishable} guards still apply, so this path can only ever
236         * publish a name that survives a downstream re-decode.
237         */
238        public static String decodeAliasIdentifier(String alias) {
239                EDbVendor vendor = ModelBindingManager.getGlobalVendor();
240                if (alias == null || vendor == null) {
241                        return alias;
242                }
243                String decoded = decodeColumnIdentifier(alias);
244                if (!decoded.equals(alias)) {
245                        return decoded;
246                }
247                String unquoted = SQLUtil.trimSingleQuote(alias);
248                if (unquoted.equals(alias) || !publishable(vendor, unquoted)) {
249                        return alias;
250                }
251                // publishable() only knows the COLUMN codec, which on MySQL does not
252                // treat an apostrophe as a delimiter. An alias like '''foo''' strips to
253                // ''foo'', which THIS method would strip again on the next layer — so a
254                // chain of SELECT * views would rename the column at every hop. Require
255                // the alias decode to be idempotent too.
256                if (!SQLUtil.trimSingleQuote(unquoted).equals(unquoted)) {
257                        return alias;
258                }
259                return unquoted;
260        }
261
262        /**
263         * Is {@code decoded} safe to PUBLISH as a model name, or would emitting it
264         * cost a lineage edge? Two ways a decoded payload becomes a liability:
265         *
266         * <ol>
267         * <li><b>It contains a dot.</b> Model names double as keys in the
268         * dot-delimited qualified-name domain that {@link SQLUtil#parseNames}
269         * segments, so {@code "name.dot"} decoded to {@code name.dot} reads as
270         * {@code name} qualified by {@code dot}. That cost a real Snowflake CTAS
271         * edge: a downstream re-split renamed the target column to {@code dot}.</li>
272         * <li><b>It is ITSELF a delimited spelling.</b> A legal SQL Server column
273         * can be written {@code [[foo]]]}, whose stored name is {@code [foo]} —
274         * delimiter-shaped. Publishing {@code [foo]} hands the next consumer of the
275         * name a string it decodes AGAIN, to {@code foo}, and source and target
276         * stop matching. Verified: that spelling loses its {@code fdd} edge
277         * outright.</li>
278         * </ol>
279         *
280         * <p>In both cases the caller keeps the ORIGINAL spelling. A consumer then
281         * still has to decode the name itself, which is worse than a clean name but
282         * far better than a dropped dependency — a missing edge is invisible in the
283         * output, so nobody can tell it is missing.
284         */
285        private static boolean publishable(EDbVendor vendor, String decoded) {
286                if (decoded.indexOf('.') != -1) {
287                        return false;
288                }
289                return decoded.equals(SQLUtil.decodeDelimitedName(vendor, ESQLDataObjectType.dotColumn, decoded));
290        }
291
292        /**
293         * Probe cache for {@link #delimitersAreCosmetic}: one answer per vendor.
294         * {@link java.util.EnumMap} rather than a static table so the answer comes
295         * from the identifier façade and follows it if the vendor rules change.
296         */
297        private static final java.util.Map<EDbVendor, Boolean> COSMETIC_DELIMITERS =
298                        java.util.Collections.synchronizedMap(new java.util.EnumMap<EDbVendor, Boolean>(EDbVendor.class));
299
300        /**
301         * Does quoting a COLUMN name change which column it names, for this vendor?
302         *
303         * <p>Asked by round-tripping a deliberately MIXED-CASE probe through the
304         * vendor's own canonical quoted spelling and then asking
305         * {@link IdentifierService#areEqualStatic} whether the quoted and bare forms
306         * are the same identifier. Mixed case is the discriminator: it is exactly
307         * what a case-folding vendor's delimiters protect. Equal ⇒ the delimiters
308         * are decoration and can be dropped from a published name; not equal ⇒ they
309         * are part of the identity and must stay.
310         *
311         * <p>Computed from the façade rather than listed per vendor, so it tracks
312         * the identifier rules instead of drifting from them.
313         */
314        private static boolean delimitersAreCosmetic(EDbVendor vendor) {
315                Boolean cached = COSMETIC_DELIMITERS.get(vendor);
316                if (cached != null) {
317                        return cached.booleanValue();
318                }
319                boolean cosmetic;
320                try {
321                        String probe = "GspDelimiterProbe";
322                        String quoted = SQLUtil.quoteDottedName(vendor, ESQLDataObjectType.dotColumn, probe);
323                        cosmetic = !quoted.equals(probe)
324                                        && IdentifierService.areEqualStatic(vendor, ESQLDataObjectType.dotColumn, quoted, probe);
325                } catch (RuntimeException e) {
326                        // A vendor the façade cannot answer for keeps its spellings as written.
327                        cosmetic = false;
328                }
329                COSMETIC_DELIMITERS.put(vendor, Boolean.valueOf(cosmetic));
330                return cosmetic;
331        }
332
333        /**
334         * Legacy display/normal-name form of a column reference (global analysis vendor).
335         * NOT an equality primitive: to test whether two names refer to the same column use
336         * {@link #sameName}, {@link #sameColumnSegment} or {@link #sameColumnName} —
337         * comparing normal names with {@code equals}/{@code equalsIgnoreCase} is the pre-P0d
338         * bug pattern. Model display-name bookkeeping is the one documented exception (see
339         * the identifier normalization guide §6).
340         */
341        public static String getIdentifierNormalColumnName(String name) {
342                EDbVendor dbVendor = ModelBindingManager.getGlobalVendor();
343                return SQLUtil.getIdentifierNormalName(dbVendor, name, ESQLDataObjectType.dotColumn);
344        }
345
346        /**
347         * Legacy display/normal-name form — for equality use {@link #sameName} /
348         * {@link #sameColumnSegment} / {@link #sameColumnName}, not string compares on
349         * this result.
350         */
351        public static String getIdentifierNormalColumnName(String name, EDbVendor dbVendor) {
352                return SQLUtil.getIdentifierNormalName(dbVendor, name, ESQLDataObjectType.dotColumn);
353        }
354
355    /**
356     * Legacy uppercase-collapsed normal form (global analysis vendor) — for equality use
357     * {@link #sameName} / {@link #sameColumnSegment}, not string compares on this result.
358     */
359    public static String normalizeColumnName(String name) {
360        return SQLUtil.normalizeIdentifier(ModelBindingManager.getGlobalVendor(),  ESQLDataObjectType.dotColumn, name);
361    }
362
363    /**
364     * Legacy uppercase-collapsed normal form — for equality use {@link #sameName} /
365     * {@link #sameColumnSegment}, not string compares on this result.
366     */
367    public static String normalizeColumnName(String name, EDbVendor dbVendor) {
368        return SQLUtil.normalizeIdentifier(dbVendor,  ESQLDataObjectType.dotColumn, name);
369    }
370
371    
372    /**
373     * Legacy display/normal-name form of a table reference (global analysis vendor;
374     * dblink and call-shaped names keep their suffix). NOT an equality primitive — for
375     * equality use {@link #sameName} / {@link #sameQualifiedName} /
376     * {@link #compareTableIdentifier}.
377     */
378    public static String getIdentifierNormalTableName(String name) {
379                if (name == null) {
380                        return null;
381                }
382                if (ModelBindingManager.get() != null && ModelBindingManager.get().isDblinkTable(name)) {
383                        int index = name.lastIndexOf("@");
384                        if (index > 0) {
385                                return SQLUtil.getIdentifierNormalName(ModelBindingManager.getGlobalVendor(), name.substring(0, index),
386                                                ESQLDataObjectType.dotTable) + name.substring(index);
387                        } else {
388                                return name;
389                        }
390                }
391                if (name.endsWith(")") && name.indexOf("(") != -1) {
392                        return SQLUtil.getIdentifierNormalName(ModelBindingManager.getGlobalVendor(),
393                                        name.substring(0, name.lastIndexOf("(")), ESQLDataObjectType.dotTable)
394                                        + name.substring(name.lastIndexOf("("));
395                }
396        return SQLUtil.getIdentifierNormalName(ModelBindingManager.getGlobalVendor(), name,
397                ESQLDataObjectType.dotTable);
398    }
399
400    /**
401     * Legacy display/normal-name form of a function reference — for equality use
402     * {@link #sameName} with {@code dotFunction}, not string compares on this result.
403     */
404    public static String getIdentifierNormalFunctionName(String name) {
405        return SQLUtil.getIdentifierNormalName(ModelBindingManager.getGlobalVendor(), name,
406                ESQLDataObjectType.dotFunction);
407    }
408
409    /**
410     * Legacy display/normal-name form (global analysis vendor) — for equality use
411     * {@link #sameName} / {@link #sameQualifiedName}, not string compares on this result.
412     */
413    public static String getIdentifierNormalName(String name, ESQLDataObjectType sqlDataObjectType) {
414        return SQLUtil.getIdentifierNormalName(ModelBindingManager.getGlobalVendor(), name, sqlDataObjectType);
415    }
416
417    /**
418     * NORMALIZED display form of the column (last) segment of an AST object name. For
419     * equality prefer {@link #sameColumnName(TObjectName, TObjectName)} — it feeds the
420     * RAW segment to the canonical engine instead of comparing normalized display text.
421     */
422    public static String getColumnName(TObjectName column) {
423        if (column == null || column.toString() == null) {
424            return null;
425        }
426        String name = column.getColumnNameOnly();
427        if (name == null || "".equals(name.trim())) {
428            return getIdentifierNormalColumnName(column.toString().trim());
429        } else
430            return getIdentifierNormalColumnName(name.trim());
431    }
432    
433    /**
434     * NORMALIZED display form of the column (last) segment of a model reference (naive
435     * last-dot split). For equality prefer {@link #sameColumnSegment(String, String)} —
436     * it feeds the RAW segment to the canonical engine instead of comparing normalized
437     * display text.
438     */
439    public static String getColumnName(String column) {
440                if (column == null) {
441                        return null;
442                }
443                String name = column.substring(column.lastIndexOf(".") + 1);
444                if (name == null || "".equals(name.trim())) {
445                        return DlineageUtil.getIdentifierNormalColumnName(column.toString().trim());
446                } else
447                        return DlineageUtil.getIdentifierNormalColumnName(name.trim());
448        }
449
450    public static boolean isTempTable(Table tableModel, EDbVendor vendor) {
451                if (SubType.temp_table == tableModel.getSubType()) {
452                        return true;
453                }
454        switch (vendor) {
455            case dbvmssql:
456            case dbvazuresql:
457                return tableModel.getName().startsWith("#") && !tableModel.getName().startsWith("##");
458            default:
459                return false;
460        }
461    }
462
463    public static String getTableFullName(String tableName) {
464                if (ModelBindingManager.get() != null && ModelBindingManager.get().isDblinkTable(tableName)) {
465                        return tableName;
466                }
467
468        if(tableName == null){
469            return null;
470        }
471
472        if (tableName.startsWith("`") && tableName.endsWith("`") && tableName.indexOf(".") != -1
473                                && ModelBindingManager.getGlobalOption().getVendor() == EDbVendor.dbvbigquery) {
474                        tableName = SQLUtil.trimColumnStringQuote(tableName);
475                }
476        List<String> segments = SQLUtil.parseNames(tableName);
477        if (segments.size() == 1) {
478            StringBuffer buffer = new StringBuffer();
479            if (ModelBindingManager.getGlobalDatabase() != null) {
480                buffer.append(ModelBindingManager.getGlobalDatabase()).append(".");
481            }
482            if (ModelBindingManager.getGlobalSchema() != null) {
483                buffer.append(ModelBindingManager.getGlobalSchema()).append(".");
484            }
485            buffer.append(tableName);
486            return getIdentifierNormalTableName(buffer.toString());
487        } else if (segments.size() == 2) {
488            if (ModelBindingManager.getGlobalDatabase() != null) {
489                return getIdentifierNormalTableName(ModelBindingManager.getGlobalDatabase() + "." + tableName);
490            } else {
491                return getIdentifierNormalTableName(tableName);
492            }
493        } else {
494            if ((ModelBindingManager.getGlobalVendor() == EDbVendor.dbvmssql
495                    || ModelBindingManager.getGlobalVendor() == EDbVendor.dbvazuresql) && tableName.indexOf("..") != -1) {
496                if (ModelBindingManager.getGlobalSchema() != null) {
497                    return getIdentifierNormalTableName(
498                            tableName.replace("..", "." + ModelBindingManager.getGlobalSchema() + "."));
499                } else if (ModelBindingManager.getGlobalVendor() == EDbVendor.dbvmssql
500                        || ModelBindingManager.getGlobalVendor() == EDbVendor.dbvazuresql) {
501                    return getIdentifierNormalTableName(tableName.replace("..", ".dbo."));
502                } else {
503                    return getIdentifierNormalTableName(tableName);
504                }
505            } else {
506                return getIdentifierNormalTableName(tableName);
507            }
508        }
509    }
510
511
512    
513    public static String getSimpleTableName(String tableName) {
514        if (tableName.startsWith("`") && tableName.endsWith("`") && tableName.indexOf(".") != -1
515                                && ModelBindingManager.getGlobalOption().getVendor() == EDbVendor.dbvbigquery) {
516                        tableName = SQLUtil.trimColumnStringQuote(tableName);
517                }
518        List<String> segments = SQLUtil.parseNames(tableName);
519        return segments.get(segments.size()-1);
520    }
521    
522    public static String getColumnNameOnly(String columnName) {
523        if (columnName.startsWith("`") && columnName.endsWith("`") && columnName.indexOf(".") != -1
524                                && ModelBindingManager.getGlobalOption().getVendor() == EDbVendor.dbvbigquery) {
525                columnName = SQLUtil.trimColumnStringQuote(columnName);
526                }
527        List<String> segments = SQLUtil.parseNames(columnName);
528        return segments.get(segments.size()-1);
529    }
530
531    public static String getTableSchema(String tableName) {
532        EDbVendor vendor = ModelBindingManager.getGlobalOption().getVendor();
533        boolean supportSchema = TSQLEnv.supportSchema(vendor);
534
535        if (tableName.startsWith("`") && tableName.endsWith("`") && tableName.indexOf(".") != -1
536                && ModelBindingManager.getGlobalOption().getVendor() == EDbVendor.dbvbigquery) {
537            tableName = SQLUtil.trimColumnStringQuote(tableName);
538        }
539        List<String> segments = SQLUtil.parseNames(tableName);
540        if (segments.size() == 1) {
541            if (supportSchema && ModelBindingManager.getGlobalSchema() != null) {
542                return ModelBindingManager.getGlobalSchema();
543            }
544        } else if (segments.size() == 2) {
545            if(supportSchema) {
546                return segments.get(0);
547            }
548        } else {
549            if (ModelBindingManager.getGlobalVendor() == EDbVendor.dbvmssql && tableName.indexOf("..") != -1) {
550                if (ModelBindingManager.getGlobalSchema() != null) {
551                    return ModelBindingManager.getGlobalSchema();
552                } else if (ModelBindingManager.getGlobalVendor() == EDbVendor.dbvmssql) {
553                    return "dbo";
554                }
555            } else {
556                if(supportSchema) {
557                    return segments.get(segments.size() - 2);
558                }
559            }
560        }
561        return null;
562    }
563
564    public static String getTableServer(String tableName) {
565        EDbVendor vendor = ModelBindingManager.getGlobalOption().getVendor();
566        boolean supportCatalog = TSQLEnv.supportCatalog(vendor);
567        boolean supportSchema = TSQLEnv.supportSchema(vendor);
568
569        if (tableName.startsWith("`") && tableName.endsWith("`") && tableName.indexOf(".") != -1
570                && ModelBindingManager.getGlobalOption().getVendor() == EDbVendor.dbvbigquery) {
571            tableName = SQLUtil.trimColumnStringQuote(tableName);
572        }
573        List<String> segments = SQLUtil.parseNames(tableName);
574        if (segments.size() <= 2) {
575            return ModelBindingManager.getGlobalServer();
576        } else if (segments.size() == 3) {
577            if(supportCatalog && supportSchema) {
578                return ModelBindingManager.getGlobalServer();
579            } else {
580                return segments.get(0);
581            }
582        } else {
583            if(supportCatalog && supportSchema) {
584                    return segments.get(segments.size() - 4);
585            } else {
586                return segments.get(segments.size() - 3);
587            }
588        }
589    }
590
591    public static String getTableDatabase(String tableName) {
592        EDbVendor vendor = ModelBindingManager.getGlobalOption().getVendor();
593        boolean supportCatalog = TSQLEnv.supportCatalog(vendor);
594        boolean supportSchema = TSQLEnv.supportSchema(vendor);
595
596        if (tableName.startsWith("`") && tableName.endsWith("`") && tableName.indexOf(".") != -1
597                                && ModelBindingManager.getGlobalOption().getVendor() == EDbVendor.dbvbigquery) {
598                        tableName = SQLUtil.trimColumnStringQuote(tableName);
599                }
600        List<String> segments = SQLUtil.parseNames(tableName);
601        if (segments.size() == 1) {
602            if (supportCatalog && ModelBindingManager.getGlobalDatabase() != null) {
603                return ModelBindingManager.getGlobalDatabase();
604            }
605        } else if (segments.size() == 2) {
606            if(supportCatalog && supportSchema) {
607                if (ModelBindingManager.getGlobalDatabase() != null) {
608                    return ModelBindingManager.getGlobalDatabase();
609                }
610            } else {
611                if (supportCatalog) {
612                    return segments.get(0);
613                }
614            }
615        } else {
616            if (ModelBindingManager.getGlobalVendor() == EDbVendor.dbvmssql && tableName.indexOf("..") != -1) {
617                String fullName = tableName;
618                if (ModelBindingManager.getGlobalSchema() != null) {
619                    fullName = getIdentifierNormalTableName(
620                            tableName.replace("..", "." + ModelBindingManager.getGlobalSchema() + "."));
621                } else if (ModelBindingManager.getGlobalVendor() == EDbVendor.dbvmssql) {
622                    fullName = getIdentifierNormalTableName(tableName.replace("..", ".dbo."));
623                } else {
624                    fullName = getIdentifierNormalTableName(tableName);
625                }
626                return getTableDatabase(fullName);
627            } else {
628                if(supportCatalog && supportSchema) {
629                    return segments.get(segments.size() - 3);
630                }
631                else if(supportCatalog){
632                    return segments.get(segments.size() - 2);
633                }
634            }
635        }
636        return null;
637    }
638
639    public static String getTableFullNameWithDefaultSchema(String tableName) {
640        EDbVendor vendor = ModelBindingManager.getGlobalOption().getVendor();
641        boolean supportCatalog = TSQLEnv.supportCatalog(vendor);
642        boolean supportSchema = TSQLEnv.supportSchema(vendor);
643
644        if (tableName.startsWith("`") && tableName.endsWith("`") && tableName.indexOf(".") != -1
645                                && ModelBindingManager.getGlobalOption().getVendor() == EDbVendor.dbvbigquery) {
646                        tableName = SQLUtil.trimColumnStringQuote(tableName);
647                }
648        List<String> segments = SQLUtil.parseNames(tableName);
649        if (segments.size() == 1) {
650            StringBuffer buffer = new StringBuffer();
651            if (supportCatalog && ModelBindingManager.getGlobalDatabase() != null) {
652                buffer.append(ModelBindingManager.getGlobalDatabase()).append(".");
653            }
654            if (supportSchema && ModelBindingManager.getGlobalSchema() != null) {
655                buffer.append(ModelBindingManager.getGlobalSchema()).append(".");
656            } else if (ModelBindingManager.getGlobalVendor() == EDbVendor.dbvmssql) {
657                buffer.append("dbo").append(".");
658            }
659            buffer.append(tableName);
660            return getIdentifierNormalTableName(buffer.toString());
661        } else if (segments.size() == 2) {
662            if (supportCatalog && supportSchema && ModelBindingManager.getGlobalDatabase() != null) {
663                return getIdentifierNormalTableName(ModelBindingManager.getGlobalDatabase() + "." + tableName);
664            } else {
665                return getIdentifierNormalTableName(tableName);
666            }
667        } else {
668            if (ModelBindingManager.getGlobalVendor() == EDbVendor.dbvmssql && tableName.indexOf("..") != -1) {
669                if (ModelBindingManager.getGlobalSchema() != null) {
670                    return getIdentifierNormalTableName(
671                            tableName.replace("..", "." + ModelBindingManager.getGlobalSchema() + "."));
672                } else if (ModelBindingManager.getGlobalVendor() == EDbVendor.dbvmssql) {
673                    return getIdentifierNormalTableName(tableName.replace("..", ".dbo."));
674                } else {
675                    return getIdentifierNormalTableName(tableName);
676                }
677            } else {
678                return getIdentifierNormalTableName(tableName);
679            }
680        }
681    }
682
683    @SuppressWarnings("rawtypes")
684    public static SqlInfo[] convertSQL(File file, String json) {
685        List<SqlInfo> sqlInfos = new ArrayList<SqlInfo>();
686        try {
687            List sqlContents = (List) JSON.parseObject(json);
688            for (int j = 0; j < sqlContents.size(); j++) {
689                Map sqlContent = (Map) sqlContents.get(j);
690                String sql = (String) sqlContent.get("sql");
691                String fileName = (String) sqlContent.get("fileName");
692                String filePath = (String) sqlContent.get("filePath");
693                if (sql != null && sql.trim().startsWith("{")) {
694                    Map queryObject = (Map) JSON.parseObject(sql);
695                    List querys = (List) queryObject.get("queries");
696                    if (querys != null) {
697                        for (int i = 0; i < querys.size(); i++) {
698                            Map object = (Map) querys.get(i);
699                            SqlInfo info = new SqlInfo();
700                            info.setSql(JSON.toJSONString(object));
701                            info.setFileName(fileName);
702                            info.setFilePath(filePath);
703                            info.setOriginIndex(i);
704                            sqlInfos.add(info);
705                        }
706                    } else {
707                        SqlInfo info = new SqlInfo();
708                        info.setSql(JSON.toJSONString(queryObject));
709                        info.setFileName(fileName);
710                        info.setFilePath(filePath);
711                        info.setOriginIndex(0);
712                        sqlInfos.add(info);
713                    }
714                } else if (sql != null) {
715                    SqlInfo info = new SqlInfo();
716                    info.setSql(sql);
717                    info.setFileName(fileName);
718                    info.setFilePath(filePath);
719                    info.setOriginIndex(0);
720                    sqlInfos.add(info);
721                }
722            }
723        } catch (Exception e) {
724            try {
725                Map queryObject = (Map) JSON.parseObject(json);
726                List querys = (List) queryObject.get("queries");
727                if (querys != null) {
728                    for (int i = 0; i < querys.size(); i++) {
729                        Map object = (Map) querys.get(i);
730                        SqlInfo info = new SqlInfo();
731                        info.setSql(JSON.toJSONString(object));
732                        if (file != null) {
733                            info.setFileName(file.getName());
734                            info.setFilePath(file.getAbsolutePath());
735                        }
736                        info.setOriginIndex(i);
737                        sqlInfos.add(info);
738                    }
739                } else {
740                    SqlInfo info = new SqlInfo();
741                    info.setSql(JSON.toJSONString(queryObject));
742                    if (file != null) {
743                        info.setFileName(file.getName());
744                        info.setFilePath(file.getAbsolutePath());
745                    }
746                    info.setOriginIndex(0);
747                    sqlInfos.add(info);
748                }
749            } catch (Exception e1) {
750                SqlInfo info = new SqlInfo();
751                info.setSql(json);
752                if (file != null) {
753                    info.setFileName(file.getName());
754                    info.setFilePath(file.getAbsolutePath());
755                }
756                info.setOriginIndex(0);
757                sqlInfos.add(info);
758            }
759        }
760        return sqlInfos.toArray(new SqlInfo[0]);
761    }
762
763    
764        public static String getQualifiedTableName(table table) {
765                boolean supportCatalog = TSQLEnv.supportCatalog(ModelBindingManager.getGlobalVendor());
766                boolean supportSchema = TSQLEnv.supportSchema(ModelBindingManager.getGlobalVendor());
767                StringBuilder buffer = new StringBuilder();
768                if (supportCatalog && supportSchema) {
769                        if (!SQLUtil.isEmpty(table.getDatabase())) {
770                                if (ModelBindingManager.getGlobalVendor() == EDbVendor.dbvoracle
771                                                && SubType.dblink.name().equals(table.getSubType())) {
772                                        //dblink doesn't set database
773                                } else {
774                                        buffer.append(table.getDatabase());
775                                }
776                        }
777                        if (!SQLUtil.isEmpty(table.getSchema())) {
778                                if (buffer.length() > 0) {
779                                        buffer.append(".");
780                                }
781                                buffer.append(table.getSchema());
782                        } else {
783                                if (buffer.length() > 0) {
784                                        buffer.append(".");
785                                        if (ModelBindingManager.getGlobalVendor() == EDbVendor.dbvmssql) {
786                                                buffer.append("dbo");
787                                        } else {
788                                                buffer.append(TSQLEnv.DEFAULT_SCHEMA_NAME);
789                                        }
790                                }
791                        }
792                        if (buffer.length() > 0) {
793                                buffer.append(".");
794                        }
795                        buffer.append(table.getTableNameOnly());
796                }
797                else if(supportCatalog) {
798                        if (!SQLUtil.isEmpty(table.getDatabase())) {
799                                buffer.append(table.getDatabase());
800                        }
801                        if (buffer.length() > 0) {
802                                buffer.append(".");
803                        }
804                        buffer.append(table.getTableNameOnly());
805                }
806                else if(supportSchema) {
807                        if (!SQLUtil.isEmpty(table.getSchema())) {
808                                buffer.append(table.getSchema());
809                        }
810                        if (buffer.length() > 0) {
811                                buffer.append(".");
812                        }
813                        buffer.append(table.getTableNameOnly());
814                }
815                if (SubType.dblink.name().equals(table.getSubType())) {
816                        buffer.append("@").append(table.getDatabase());
817                }
818                return buffer.toString();
819        }
820
821    public static TCustomSqlStatement getTopStmt(TCustomSqlStatement stmt) {
822        if (ModelBindingManager.getGlobalOption() != null && ModelBindingManager.getGlobalOption().getAnalyzeMode() == AnalyzeMode.crud) {
823            TCustomSqlStatement parent = stmt.getParentStmt();
824            if (parent == null || parent instanceof TPlsqlCreatePackage
825                    || parent instanceof TStoredProcedureSqlStatement || parent instanceof TCommonBlock || parent instanceof TMssqlBlock)
826                return stmt;
827            return getTopStmt(parent);
828        } else {
829            TCustomSqlStatement parent = stmt.getParentStmt();
830            if (parent == null || parent instanceof TPlsqlCreatePackage)
831                return stmt;
832            return getTopStmt(parent);
833        }
834    }
835    
836        public static TCustomSqlStatement getTopBasicStmt(TCustomSqlStatement stmt) {
837                TCustomSqlStatement parent = stmt.getParentStmt();
838                if (parent == null || parent instanceof TPlsqlCreatePackage || parent instanceof TStoredProcedureSqlStatement
839                                || parent instanceof TCommonBlock || parent instanceof TMssqlBlock)
840                        return stmt;
841                return getTopBasicStmt(parent);
842        }
843
844    public static boolean isQuote(String column) {
845        return (column.startsWith("\"") && column.endsWith("\"")) || (column.startsWith("'") && column.endsWith("'"))
846                || (column.startsWith("[") && column.endsWith("]")) || (column.startsWith("`") && column.endsWith("`"));
847    }
848
849    public static TObjectName getProcedureOrFunctionName(TStoredProcedureSqlStatement stmt) {
850                if (stmt instanceof TCreateFunctionStmt) {
851                        return ((TCreateFunctionStmt) stmt).getFunctionName();
852                }
853                if (stmt instanceof TMssqlCreateFunction) {
854                        return ((TMssqlCreateFunction) stmt).getFunctionName();
855                }
856                return stmt.getStoredProcedureName();
857        }
858
859    public static String getProcedureNameWithArgs(TStoredProcedureSqlStatement stmt) {
860                StringBuilder buffer = new StringBuilder();
861                TObjectName name = getProcedureOrFunctionName(stmt);
862                if (name == null) {
863                        return null;
864                }
865                buffer.append(name.toString());
866                if (stmt.getParameterDeclarations() != null && DlineageUtil.supportFunctionOverride(ModelBindingManager.getGlobalVendor())) {
867                        buffer.append("(");
868                        TParameterDeclarationList parameters = stmt.getParameterDeclarations();
869                        for (int i = 0; i < parameters.size(); ++i) {
870                                TParameterDeclaration parameter = parameters.getParameterDeclarationItem(i);
871                                if(parameter.getDataType()!=null) {
872                                        buffer.append(parameter.getDataType().getDataTypeName());
873                                }
874                                if(i<parameters.size()-1) {
875                                        buffer.append(",");
876                                }
877                        }
878                        buffer.append(")");
879                }
880                return buffer.toString();
881        }
882
883    public static String getIdentifierProcedureNameWithArgNum(procedure procedure) {
884        StringBuilder buffer = new StringBuilder();
885        buffer.append(SQLUtil.trimColumnStringQuote(procedure.getName()));
886        if (procedure.getArguments() != null && procedure.getArguments().size() > 0 && DlineageUtil.supportFunctionOverride(ModelBindingManager.getGlobalVendor())) {
887            buffer.append("(");
888            buffer.append(procedure.getArguments().size());
889            buffer.append(")");
890        }
891        return buffer.toString().toUpperCase();
892    }
893
894    public static String getIdentifierOraclePackageNameWithArgNum(oraclePackage pkg) {
895        StringBuilder buffer = new StringBuilder();
896        buffer.append(SQLUtil.trimColumnStringQuote(pkg.getName()));
897        if (pkg.getArguments() != null && pkg.getArguments().size() > 0 && DlineageUtil.supportFunctionOverride(ModelBindingManager.getGlobalVendor())) {
898            buffer.append("(");
899            buffer.append(pkg.getArguments().size());
900            buffer.append(")");
901        }
902        return buffer.toString().toUpperCase();
903    }
904
905    public static String getIdentifierFunctionName(table function) {
906        StringBuilder buffer = new StringBuilder();
907        buffer.append(SQLUtil.trimColumnStringQuote(function.getName()));
908        return buffer.toString().toUpperCase();
909    }
910
911    public static String getProcedureNameWithArgNum(TStoredProcedureSqlStatement stmt) {
912                StringBuilder buffer = new StringBuilder();
913                TObjectName name = getProcedureOrFunctionName(stmt);
914                if (name == null) {
915                        return null;
916                }
917                buffer.append(name.toString());
918                if (stmt.getParameterDeclarations() != null && DlineageUtil.supportFunctionOverride(ModelBindingManager.getGlobalVendor())) {
919                        buffer.append("(");
920                        buffer.append(stmt.getParameterDeclarations().size());
921                        buffer.append(")");
922                }
923                return buffer.toString();
924        }
925    
926        public static String getProcedureNameWithInputArgNum(TStoredProcedureSqlStatement stmt) {
927                StringBuilder buffer = new StringBuilder();
928                TObjectName name = getProcedureOrFunctionName(stmt);
929                if (name == null) {
930                        return null;
931                }
932                buffer.append(name.toString());
933                if (stmt.getParameterDeclarations() != null && DlineageUtil.supportFunctionOverride(ModelBindingManager.getGlobalVendor())) {
934                        buffer.append("(");
935                        int count = 0;
936                        for (int i = 0; i < stmt.getParameterDeclarations().size(); i++) {
937                                if (stmt.getParameterDeclarations().getParameterDeclarationItem(i)
938                                                .getParameterMode() == EParameterMode.in) {
939                                        count++;
940                                }
941                        }
942                        if (count == 0) {
943                                count = stmt.getParameterDeclarations().size();
944                        }
945                        buffer.append(count);
946                        buffer.append(")");
947                }
948                return buffer.toString();
949        }
950
951        public static String getFunctionNameWithArgNum(TFunctionCall function) {
952                StringBuilder buffer = new StringBuilder();
953                buffer.append(function.getFunctionName().toString());
954                if (function.getArgs() != null && DlineageUtil.supportFunctionOverride(ModelBindingManager.getGlobalVendor())) {
955                        buffer.append("(");
956                        buffer.append(function.getArgs().size());
957                        buffer.append(")");
958                }
959                return buffer.toString();
960        }
961
962        public static String getTableSchema(Table tableModel) {
963                StringBuilder buffer = new StringBuilder();
964                if(tableModel.getDatabase()!=null) {
965                        buffer.append(tableModel.getDatabase());
966                }
967                else {
968                        buffer.append(TSQLEnv.DEFAULT_DB_NAME);
969                }
970                buffer.append(".");
971                if(tableModel.getSchema()!=null) {
972                        buffer.append(tableModel.getSchema());
973                }
974                else {
975                        buffer.append(TSQLEnv.DEFAULT_SCHEMA_NAME);
976                }
977                return buffer.toString();
978        }
979        
980        public static String getTableSQLEnvKey(Table tableModel) {
981                StringBuilder buffer = new StringBuilder();
982                if(tableModel.getDatabase()!=null) {
983                        buffer.append(tableModel.getDatabase());
984                }
985                else {
986                        buffer.append(TSQLEnv.DEFAULT_DB_NAME);
987                }
988                buffer.append(".");
989                if(tableModel.getSchema()!=null) {
990                        buffer.append(tableModel.getSchema());
991                }
992                else {
993                        buffer.append(TSQLEnv.DEFAULT_SCHEMA_NAME);
994                }
995                buffer.append(".");
996                buffer.append(DlineageUtil.getSimpleTableName(tableModel.getName()));
997                return buffer.toString();
998        }
999        
1000        /**
1001         * B1 (routine-summary-scc-design §3): overload-discriminating scope key.
1002         * The legacy scope key deliberately collides same-named overloads
1003         * (long-shipped LEGACY behavior); appending a stable digest of the
1004         * canonical signature descriptor yields one distinct key per overload for
1005         * the identity-first lookups of SHADOW/ENHANCED (slice B3). Run-scoped
1006         * only — the digest is Java hashCode-based and must never be persisted.
1007         */
1008        public static String identityScopeKey(String legacyScopeKey,
1009                        gudusoft.gsqlparser.dlineage.dynamicsql.RoutineIdentity identity) {
1010                if (legacyScopeKey == null || identity == null) {
1011                        return null;
1012                }
1013                // Normalize the legacy part FIRST (the same getTableFullName pass the
1014                // legacy registration key goes through), then append the discriminator
1015                // outside normalization so '#' is never fed to identifier folding.
1016                // The discriminator is the INJECTIVE canonical signature — a hash
1017                // digest collided across valid Oracle overloads and merged their
1018                // pools (codex-B3-r3 finding 2b).
1019                // The \u0002 prefix marks the key (and every key derived from it by
1020                // suffixing ".var") as IDENTITY-SCOPED: lookups must never
1021                // re-interpret such a key against another active scope
1022                // (codex-B3-r4 finding 1). No legacy registration key starts with a
1023                // control character.
1024                return "\u0002" + getTableFullName(legacyScopeKey) + "#"
1025                                + identity.signatureKey();
1026        }
1027
1028        public static String getProcedureParentName(TCustomSqlStatement stmt) {
1029                if (stmt instanceof TStoredProcedureSqlStatement) {
1030                        if (((TStoredProcedureSqlStatement) stmt).getStoredProcedureName() != null) {
1031                                return getOraclePackageName() + ((TStoredProcedureSqlStatement) stmt).getStoredProcedureName().toString();
1032                        }
1033                }
1034                stmt = stmt.getParentStmt();
1035                if (stmt == null)
1036                        return null;
1037
1038        if (stmt instanceof TCommonBlock) {
1039                        if(((TCommonBlock) stmt).getBlockBody().getParentObjectName() instanceof TStoredProcedureSqlStatement) {
1040                                stmt = (TStoredProcedureSqlStatement)((TCommonBlock) stmt).getBlockBody().getParentObjectName();
1041                        }
1042                }
1043        
1044                if (stmt instanceof TStoredProcedureSqlStatement) {
1045                        if (((TStoredProcedureSqlStatement) stmt).getStoredProcedureName() != null) {
1046                                String procedureName = getOraclePackageName() + ((TStoredProcedureSqlStatement) stmt).getStoredProcedureName().toString();
1047                return getTableFullName(procedureName);
1048                        }
1049                }
1050                if (stmt instanceof TTeradataCreateProcedure) {
1051                        if (((TTeradataCreateProcedure) stmt).getProcedureName() != null) {
1052                                String procedureName =  ((TTeradataCreateProcedure) stmt).getProcedureName().toString();
1053                return getTableFullName(procedureName);
1054                        }
1055                }
1056
1057                return getProcedureParentName(stmt);
1058        }
1059
1060        private static String getOraclePackageName() {
1061                if (ModelBindingManager.getGlobalOraclePackage() != null) {
1062                        return ModelBindingManager.getGlobalOraclePackage().getName() + ".";
1063                }
1064                return "";
1065        }
1066
1067        public static sourceColumn copySourceColumn(sourceColumn oldSourceName) {
1068                sourceColumn newSourceColumn = new sourceColumn();
1069                newSourceColumn.setClauseType(oldSourceName.getClauseType());
1070                newSourceColumn.setColumn(oldSourceName.getColumn());
1071                newSourceColumn.setColumn_type(oldSourceName.getColumn_type());
1072                newSourceColumn.setCoordinate(oldSourceName.getCoordinate());
1073                newSourceColumn.setId(oldSourceName.getId());
1074                newSourceColumn.setName(oldSourceName.getName());
1075                newSourceColumn.setParent_alias(oldSourceName.getParent_alias());
1076                newSourceColumn.setParent_id(oldSourceName.getParent_id());
1077                newSourceColumn.setParent_name(oldSourceName.getParent_name());
1078                newSourceColumn.setSource(oldSourceName.getSource());
1079                newSourceColumn.setSource_id(oldSourceName.getSource_id());
1080                newSourceColumn.setSource_name(oldSourceName.getSource_name());
1081                newSourceColumn.setStruct(oldSourceName.isStruct());
1082                newSourceColumn.setType(oldSourceName.getType());
1083                newSourceColumn.setValue(oldSourceName.getValue());
1084                if(oldSourceName.getTransforms()!=null) {
1085                        newSourceColumn.setTransforms(new LinkedHashSet<transform>());
1086                        for(transform transform: oldSourceName.getTransforms()) {
1087                                newSourceColumn.getTransforms().add(transform);
1088                        }
1089                }
1090                if(oldSourceName.getCandidateParents()!=null) {
1091                        newSourceColumn.setCandidateParents(new LinkedHashSet<candidateTable>());
1092                        for(candidateTable candidateTable: oldSourceName.getCandidateParents()) {
1093                                newSourceColumn.getCandidateParents().add(candidateTable);
1094                        }
1095                }
1096                return newSourceColumn;
1097        }
1098
1099        public static boolean supportFunctionOverride(EDbVendor vendor) {
1100                switch (vendor) {
1101                case dbvpostgresql:
1102                case dbvoracle:
1103                case dbvdb2:
1104                case dbvmysql:
1105                case dbvsnowflake:
1106                case dbvhana:
1107                case dbvgreenplum:
1108                case dbvgaussdb:
1109                case dbvedb:
1110                case dbvdameng:
1111                        return true;
1112                default:
1113                        return false;
1114                }
1115        }
1116        
1117        private static final List<String> TERADATA_BUILTIN_FUNCTIONS = Arrays
1118                        .asList(new String[] { "ACCOUNT", "CURRENT_DATE", "CURRENT_ROLE", "CURRENT_TIME", "CURRENT_TIMESTAMP",
1119                                        "CURRENT_USER", "DATABASE", "DATE", "PROFILE", "ROLE", "SESSION", "TIME", "USER", "SYSDATE", });
1120        
1121        public static boolean isBuiltInFunctionName(String functionName) {
1122                if (functionName == null)
1123                        return false;
1124                try {
1125                        EDbVendor vendor = ModelBindingManager.getGlobalOption().getVendor();
1126                        if (vendor == EDbVendor.dbvteradata) {
1127                                boolean result = TERADATA_BUILTIN_FUNCTIONS.contains(functionName.toUpperCase());
1128                                if (result) {
1129                                        return true;
1130                                }
1131                        }
1132
1133                        List<String> versions = functionChecker.getAvailableDbVersions(vendor);
1134                        if (versions != null && versions.size() > 0) {
1135                                for (int i = 0; i < versions.size(); i++) {
1136                                        // no caller-side folding: isBuiltInFunction folds with
1137                                        // Locale.ROOT itself; a default-locale toUpperCase here
1138                                        // breaks the lookup on Turkish JVMs
1139                                        boolean result = functionChecker.isBuiltInFunction(functionName,
1140                                                        vendor, versions.get(i));
1141                                        if (result) {
1142                                                return result;
1143                                        }
1144                                }
1145
1146                                // boolean result =
1147                                // TERADATA_BUILTIN_FUNCTIONS.contains(object.toString());
1148                                // if (result) {
1149                                // return true;
1150                                // }
1151                        }
1152                } catch (Exception e) {
1153                }
1154
1155                return false;
1156        }
1157        
1158        public static boolean isKeyword(String objectName) {
1159                if (objectName == null)
1160                        return false;
1161                try {
1162                        EDbVendor vendor = ModelBindingManager.getGlobalOption().getVendor();
1163
1164                        List<String> versions = keywordChecker.getAvailableDbVersions(vendor);
1165                        if (versions != null && versions.size() > 0) {
1166                                for (int i = 0; i < versions.size(); i++) {
1167                                        List<String> segments = SQLUtil.parseNames(objectName);
1168                                        boolean result = keywordChecker.isKeyword(segments.get(segments.size() - 1),
1169                                                        vendor, versions.get(i), false);
1170                                        if (result) {
1171                                                return result;
1172                                        }
1173                                }
1174                        }
1175                } catch (Exception e) {
1176                }
1177
1178                return false;
1179        }
1180
1181        public static TSelectSqlStatement getLeftStmt(TSelectSqlStatement stmt) {
1182                TSelectSqlStatement current = stmt.getLeftStmt();
1183                if (current == null) return null;
1184                while (current.getLeftStmt() != null) {
1185                        current = current.getLeftStmt();
1186                }
1187                return current;
1188        }
1189
1190        public static String stripQuotesFromQualifiedName(String qualifiedName) {
1191                if (SQLUtil.isEmpty(qualifiedName)) {
1192                        return qualifiedName;
1193                }
1194
1195                List<String> segments = SQLUtil.parseNames(qualifiedName);
1196                StringBuilder sb = new StringBuilder();
1197                for (int i = 0; i < segments.size(); i++) {
1198                        if (i > 0) {
1199                                sb.append('.');
1200                        }
1201                        String seg = segments.get(i);
1202                        sb.append(TBaseType.getTextWithoutQuoted(seg));
1203                }
1204                return sb.toString();
1205        }
1206
1207        public static boolean isProcedureExcluded(String fullName) {
1208                if (SQLUtil.isEmpty(fullName)) {
1209                        return false;
1210                }
1211
1212                Option option = ModelBindingManager.getGlobalOption();
1213                if (option == null) {
1214                        return false;
1215                }
1216
1217                Set<String> excludedNames = option.getExcludedProcedureNames();
1218                Set<String> excludedPatterns = option.getExcludedProcedurePatterns();
1219
1220                if ((excludedNames == null || excludedNames.isEmpty()) &&
1221                        (excludedPatterns == null || excludedPatterns.isEmpty())) {
1222                        return false;
1223                }
1224
1225        if (excludedNames != null) {
1226            for (String excludedName : excludedNames) {
1227                if (DlineageUtil.compareIdentifier(fullName, excludedName, ESQLDataObjectType.dotFunction)) {
1228                    return true;
1229                }
1230            }
1231        }
1232
1233        if (excludedPatterns != null && !excludedPatterns.isEmpty()) {
1234            // Generate the qualified-name and all of its trailing segment-suffixes so a
1235            // schema-qualified pattern can match a more-qualified name. e.g. the pattern
1236            // "SCHEM.*" matches the Snowflake procedure "DB.SCHEM.PROC" through the suffix
1237            // "SCHEM.PROC", and "PUBLIC.*" matches "COVID19.PUBLIC.INSERT_CDC_DATA".
1238            // Segments are parsed with quote-awareness so dots inside a quoted identifier
1239            // (e.g. "SCHEMA"."PROC.WITH.DOTS") are not split.
1240            List<String> candidates = buildQualifiedNameSuffixes(fullName);
1241            for (String pattern : excludedPatterns) {
1242                if (SQLUtil.isEmpty(pattern)) {
1243                        continue;
1244                }
1245                String normalizedPattern = stripQuotesFromQualifiedName(pattern);
1246                StringBuilder regexBuilder = new StringBuilder();
1247                for (char c : normalizedPattern.toCharArray()) {
1248                    switch (c) {
1249                        case '.':
1250                            regexBuilder.append("\\.");
1251                            break;
1252                        case '*':
1253                            regexBuilder.append(".*");
1254                            break;
1255                        case '?':
1256                            regexBuilder.append(".");
1257                            break;
1258                        case '(': case ')': case '[': case ']': case '{': case '}':
1259                        case '|': case '^': case '$': case '+': case '\\':
1260                            regexBuilder.append("\\").append(c);
1261                            break;
1262                        default:
1263                            regexBuilder.append(c);
1264                    }
1265                }
1266                String regex = "(?i)^" + regexBuilder.toString() + "$";
1267                for (String candidate : candidates) {
1268                    if (candidate.matches(regex)) {
1269                        return true;
1270                    }
1271                }
1272            }
1273        }
1274
1275                return false;
1276        }
1277
1278        /**
1279         * Builds the quote-stripped qualified name and each of its trailing segment-suffixes,
1280         * longest first. For {@code "DB"."SCHEM"."PROC"} this yields
1281         * {@code [DB.SCHEM.PROC, SCHEM.PROC, PROC]}. Used by procedure-exclusion pattern
1282         * matching so a schema-level pattern matches a database-qualified name.
1283         */
1284        private static List<String> buildQualifiedNameSuffixes(String fullName) {
1285                List<String> suffixes = new ArrayList<String>();
1286                List<String> segments = SQLUtil.parseNames(fullName);
1287                if (segments == null || segments.isEmpty()) {
1288                        suffixes.add(stripQuotesFromQualifiedName(fullName));
1289                        return suffixes;
1290                }
1291                List<String> stripped = new ArrayList<String>(segments.size());
1292                for (String seg : segments) {
1293                        stripped.add(TBaseType.getTextWithoutQuoted(seg));
1294                }
1295                for (int i = 0; i < stripped.size(); i++) {
1296                        StringBuilder sb = new StringBuilder();
1297                        for (int j = i; j < stripped.size(); j++) {
1298                                if (j > i) {
1299                                        sb.append('.');
1300                                }
1301                                sb.append(stripped.get(j));
1302                        }
1303                        suffixes.add(sb.toString());
1304                }
1305                return suffixes;
1306        }
1307}