001
002package gudusoft.gsqlparser.dlineage.dataflow.model;
003
004import gudusoft.gsqlparser.EDbVendor;
005import gudusoft.gsqlparser.ETableKind;
006import gudusoft.gsqlparser.ETableSource;
007import gudusoft.gsqlparser.TCustomSqlStatement;
008import gudusoft.gsqlparser.TSourceToken;
009import gudusoft.gsqlparser.dlineage.util.DlineageUtil;
010import gudusoft.gsqlparser.dlineage.util.Pair;
011import gudusoft.gsqlparser.dlineage.util.Pair3;
012import gudusoft.gsqlparser.nodes.TFunctionCall;
013import gudusoft.gsqlparser.nodes.TObjectName;
014import gudusoft.gsqlparser.nodes.TTable;
015import gudusoft.gsqlparser.sqlenv.*;
016import gudusoft.gsqlparser.stmt.TCreateTableSqlStatement;
017import gudusoft.gsqlparser.stmt.TCreateViewSqlStatement;
018import gudusoft.gsqlparser.util.Logger;
019import gudusoft.gsqlparser.util.LoggerFactory;
020import gudusoft.gsqlparser.util.SQLUtil;
021
022import java.io.ByteArrayInputStream;
023import java.io.IOException;
024import java.util.*;
025
026public class Table {
027
028        private static final Logger logger = LoggerFactory.getLogger(Table.class);
029
030        private long id;
031        protected String server;
032        protected String database;
033        protected String schema;
034        protected String name;
035        protected String displayName;
036        protected String fullName;
037        protected Set<String> alias = new LinkedHashSet<String>();
038        protected Set<Pair<Pair3<Long, Long, String>, Pair3<Long, Long, String>>> positions = new LinkedHashSet<>();
039        protected String parent;
040        protected Pair3<Long, Long, String> startPosition;
041        protected Pair3<Long, Long, String> endPosition;
042        private List<TableColumn> columns = new ArrayList<TableColumn>();
043        private boolean subquery = false;
044
045        /**
046         * Set when an unqualified one-part name matches the same base table in two or
047         * more schemas of the (otherwise unambiguous) default catalog and the default
048         * schema holds no such object. In that case GSP must NOT fabricate a
049         * non-existent default-schema object; it leaves the reference unqualified and
050         * exposes the candidate set instead. See
051         * docs/tmp/unqualified-name-default-schema-masks-cross-schema-ambiguity.md.
052         */
053        private boolean ambiguousUnqualifiedTable = false;
054        private List<String> candidateTables;
055
056        private TTable tableObject;
057        private TObjectName viewName;
058        private boolean isCreateTable;
059        private boolean isView;
060        private boolean isPseudo;
061        private boolean isExternal;
062        private boolean isStage;
063        private boolean isSequence;
064        private boolean isPath;
065        private boolean isStream;
066        private boolean isVariable;
067        private boolean isConstant;
068        private boolean isTarget;
069        private String procedureId;
070        private boolean isDatabase;
071        private boolean isSchema;
072        private boolean isDataSource;
073        private String location;
074        private String fileType;
075        private String fileFormat;
076        private List<Process> processes;
077        private SubType subType;
078        private String starStmt;
079        private boolean hasSQLEnv = false;
080        private String currentAlias;
081    private boolean isDetermined;
082
083        private boolean fromDDL;
084
085        /**
086         * How this endpoint was introduced in the analyzed SQL. The create-effect values
087         * (CREATE_TABLE_DDL / SELECT_INTO / CTAS / CLONE_TABLE) are set as parse facts at
088         * build time, independent of {@link #isDetermined()} — unlike {@link #fromDDL}.
089         * Strictly additive; never back-fills {@link #subType}. See
090         * docs/tmp/dlineage-authoritative-endpoint-classification.md.
091         */
092        private EndpointIntroduction endpointIntroduction = EndpointIntroduction.UNKNOWN;
093
094        /**
095         * True when this endpoint is created by the analyzed SQL (CREATE TABLE / SELECT
096         * INTO / CTAS / clone target), set regardless of schema resolution.
097         */
098        private boolean createdInSql;
099
100        /**
101         * Pipe-joined, outermost-first list of the identifier segments of {@link #name} that
102         * overlap an unresolved dynamic-SQL hole ({@code "db"}, {@code "db|name"}, ...), or
103         * {@code null} when this endpoint is not runtime-templated. Written ONLY from inside a
104         * dynamic-fold context by the analyzer, so a static reference to a legally delimited
105         * {@code [weird@name]} is never marked. See
106         * docs/tmp/dynamic-template-endpoint-provenance.md.
107         */
108        private String templatedParts;
109
110        /**
111         * True when the hole covers the LEADING character of the object-name segment — i.e.
112         * the {@code #}/{@code ##}/{@code @} prefix that would otherwise classify this endpoint
113         * as a temp table / table variable is itself runtime text, so that classification is a
114         * fold artifact rather than a fact. Drives the {@link EndpointKind#DYNAMIC_TEMPLATE}
115         * precedence in {@link #getEndpointKind()}.
116         */
117        private boolean templatedIdentity;
118
119        private TableRelationRows relationRows = new TableRelationRows(this);
120
121        private TCustomSqlStatement stmt;
122
123        public Table(TCustomSqlStatement stmt, TObjectName viewName) {
124                this(viewName);
125                this.stmt = stmt;
126                if (stmt != null) {
127                        setPosition(stmt);
128                }
129        }
130
131        public TCustomSqlStatement getSqlStatement() {
132                return stmt;
133        }
134        
135        public Table(TTable table, String tableName) {
136                if (table == null) {
137                        throw new IllegalArgumentException("Table arguments can't be null.");
138                }
139
140                id = ++ModelBindingManager.get().TABLE_COLUMN_ID;
141
142                this.tableObject = table;
143
144                setPosition(table);
145
146                this.name = tableName;
147                this.fullName = tableName;
148                setAlias(table.getAliasName());
149                setCurrentAlias(table.getAliasName());
150                
151                if (name.startsWith("`") && name.endsWith("`") && name.indexOf(".") != -1
152                                && ModelBindingManager.getGlobalOption().getVendor() == EDbVendor.dbvbigquery) {
153                        this.name = SQLUtil.trimColumnStringQuote(name);
154                        this.fullName = SQLUtil.trimColumnStringQuote(fullName);
155                }
156
157                if (table.getSubquery() != null) {
158                        subquery = true;
159                }
160
161                EDbVendor vendor = ModelBindingManager.getGlobalOption().getVendor();
162                boolean supportCatalog = TSQLEnv.supportCatalog(vendor);
163                boolean supportSchema = TSQLEnv.supportSchema(vendor);
164
165                if (table.getTableName().getDblink() == null) {
166                        this.server = DlineageUtil.getTableServer(table.getTableName().toString());
167                        this.database = DlineageUtil.getTableDatabase(table.getTableName().toString());
168                        this.schema = DlineageUtil.getTableSchema(table.getTableName().toString());
169                }
170                else {
171                        if (this.server == null && !TSQLEnv.DEFAULT_SERVER_NAME.equals(getDefaultServer())) {
172                                this.server = getDefaultServer();
173                        }
174                        this.database = SQLUtil.normalizeIdentifier(vendor, ESQLDataObjectType.dotDblink, table.getTableName().getDblink().toString());
175                        if (!SQLUtil.isEmpty(table.getTableName().getSchemaString())) {
176                                this.schema = table.getTableName().getSchemaString();
177                        }
178                        this.subType = SubType.dblink;
179                        this.name = this.name.replace(table.getTableName().getDblink().toString(), this.database);
180                        ModelBindingManager.get().appendDblinkTable(this.name);
181                        return;
182                }
183
184                fillSchemaInfo();
185
186                if (SQLUtil.isEmpty(this.database)) {
187                        this.database = DlineageUtil.getTableDatabase(table.getTableName().toString());
188                        if (SQLUtil.isEmpty(this.database) && ModelBindingManager.getGlobalOption().isShowImplicitSchema()
189                                        && !SQLUtil.isEmpty(table.getTableName().getImplictDatabaseString())) {
190                                this.database = table.getTableName().getImplictDatabaseString();
191                        }
192                }
193
194                if (SQLUtil.isEmpty(this.schema)) {
195                        this.schema = DlineageUtil.getTableSchema(table.getTableName().toString());
196                        if (SQLUtil.isEmpty(this.schema) && ModelBindingManager.getGlobalOption().isShowImplicitSchema()
197                                        && !SQLUtil.isEmpty(implicitSchemaOf(table.getTableName()))) {
198                                this.schema = implicitSchemaOf(table.getTableName());
199                        }
200                }
201
202                if (!SQLUtil.isEmpty(this.database) && SQLUtil.isEmpty(this.schema)) {
203                        if (!SQLUtil.isEmpty(implicitSchemaOf(table.getTableName()))) {
204                                this.schema = implicitSchemaOf(table.getTableName());
205                        }
206                }
207
208                if (table.getTableType() == ETableSource.function) {
209                        subType = SubType.function;
210                }
211
212                if (!supportCatalog) {
213                        this.database = null;
214                } else if (this.database == null && !TSQLEnv.DEFAULT_DB_NAME.equals(getDefaultDatabase())) {
215                        this.database = getDefaultDatabase();
216                }
217
218                if (!supportSchema) {
219                        this.schema = null;
220                } else if (this.schema == null && !TSQLEnv.DEFAULT_SCHEMA_NAME.equals(getDefaultSchema())) {
221                        this.schema = getDefaultSchema();
222                }
223
224                updateTableName(supportCatalog, supportSchema);
225
226                if (this.server == null && !TSQLEnv.DEFAULT_SERVER_NAME.equals(getDefaultServer())) {
227                        this.server = getDefaultServer();
228                }
229        }
230
231        public Table(TTable table) {
232                if (table == null) {
233                        throw new IllegalArgumentException("Table arguments can't be null.");
234                }
235
236                id = ++ModelBindingManager.get().TABLE_COLUMN_ID;
237
238                this.tableObject = table;
239
240                setPosition(table);
241
242                if (table.getLinkTable() != null) {
243                        this.fullName = table.getLinkTable().getFullName();
244                        this.name = table.getLinkTable().getName();
245                        setAlias(table.getName());
246                        setCurrentAlias(table.getName());
247                } else if (table.getTableType() == ETableSource.function && table.getFuncCall() != null) {
248                        TFunctionCall function = table.getFuncCall();
249                        this.fullName = function.getFunctionName().toString();
250                        this.name = function.getFunctionName().toString();
251                        setAlias(table.getAliasName());
252                        setCurrentAlias(table.getAliasName());
253                } else {
254                        if (table.getSubquery() != null && table.getAliasClause() != null) {
255                                this.name = table.getAliasClause().getAliasName().toString();
256                                this.subType = SubType.alias_table;
257                        } else if (table.getTableName() != null) {
258                                this.name = table.getTableName().toString();
259                        } else {
260                                this.name = table.getName();
261                        }
262
263                        this.fullName = table.getFullName();
264                        if (this.fullName == null) {
265                                this.fullName = this.name;
266                        }
267
268                        setAlias(table.getAliasName());
269                        setCurrentAlias(table.getAliasName());
270                }
271
272                if (name.startsWith("`") && name.endsWith("`") && name.indexOf(".") != -1
273                                && ModelBindingManager.getGlobalOption().getVendor() == EDbVendor.dbvbigquery) {
274                        this.name = SQLUtil.trimColumnStringQuote(name);
275                        this.fullName = SQLUtil.trimColumnStringQuote(fullName);
276                }
277
278                if (table.getSubquery() != null) {
279                        subquery = true;
280                }
281
282                EDbVendor vendor = ModelBindingManager.getGlobalOption().getVendor();
283                boolean supportCatalog = TSQLEnv.supportCatalog(vendor);
284                boolean supportSchema = TSQLEnv.supportSchema(vendor);
285
286                if (table.getTableName().getDblink() == null) {
287                        this.server = DlineageUtil.getTableServer(table.getTableName().toString());
288                        this.database = DlineageUtil.getTableDatabase(table.getTableName().toString());
289                        this.schema = DlineageUtil.getTableSchema(table.getTableName().toString());
290                }
291                else {
292                        if (this.server == null && !TSQLEnv.DEFAULT_SERVER_NAME.equals(getDefaultServer())) {
293                                this.server = getDefaultServer();
294                        }
295                        this.database = SQLUtil.normalizeIdentifier(vendor, ESQLDataObjectType.dotDblink, table.getTableName().getDblink().toString());
296                        if (!SQLUtil.isEmpty(table.getTableName().getSchemaString())) {
297                                this.schema = table.getTableName().getSchemaString();
298                        }
299                        this.subType = SubType.dblink;
300                        this.name = this.name.replace(table.getTableName().getDblink().toString(), this.database);
301                        ModelBindingManager.get().appendDblinkTable(this.name);
302                        return;
303                }
304
305                fillSchemaInfo();
306
307                if (SQLUtil.isEmpty(this.database)) {
308                        this.database = DlineageUtil.getTableDatabase(table.getTableName().toString());
309                        if (SQLUtil.isEmpty(this.database) && ModelBindingManager.getGlobalOption().isShowImplicitSchema()
310                                        && !SQLUtil.isEmpty(table.getTableName().getImplictDatabaseString())) {
311                                this.database = table.getTableName().getImplictDatabaseString();
312                        }
313                }
314
315                if (SQLUtil.isEmpty(this.schema)) {
316                        this.schema = DlineageUtil.getTableSchema(table.getTableName().toString());
317                        if (SQLUtil.isEmpty(this.schema) && ModelBindingManager.getGlobalOption().isShowImplicitSchema()
318                                        && !SQLUtil.isEmpty(implicitSchemaOf(table.getTableName()))) {
319                                this.schema = implicitSchemaOf(table.getTableName());
320                        }
321                }
322
323                if (!SQLUtil.isEmpty(this.database) && SQLUtil.isEmpty(this.schema)) {
324                        if (!SQLUtil.isEmpty(implicitSchemaOf(table.getTableName()))) {
325                                this.schema = implicitSchemaOf(table.getTableName());
326                        }
327                }
328
329                if (table.getTableType() == ETableSource.function) {
330                        subType = SubType.function;
331                }
332
333                if (!supportCatalog) {
334                        this.database = null;
335                } else if (this.database == null && !TSQLEnv.DEFAULT_DB_NAME.equals(getDefaultDatabase())) {
336                        this.database = getDefaultDatabase();
337                }
338
339                if (!supportSchema) {
340                        this.schema = null;
341                } else if (this.schema == null && !TSQLEnv.DEFAULT_SCHEMA_NAME.equals(getDefaultSchema())) {
342                        this.schema = getDefaultSchema();
343                }
344
345                updateTableName(supportCatalog, supportSchema);
346
347                if (this.server == null && !TSQLEnv.DEFAULT_SERVER_NAME.equals(getDefaultServer())) {
348                        this.server = getDefaultServer();
349                }
350        }
351
352        public void addColumnsFromSQLEnv(){
353                TSQLEnv sqlEnv = ModelBindingManager.getGlobalSQLEnv();
354                if (sqlEnv == null) {
355                        return;
356                }
357                TSQLTable tsqlTable = sqlEnv.searchTable(ModelFactory.getQualifiedTableName(this));
358                if (tsqlTable != null && tsqlTable.getColumnList()!=null && !tsqlTable.getColumnList().isEmpty()) {
359                        for(TSQLColumn tsqlColumn: tsqlTable.getColumnList()){
360                                TObjectName columnName = new TObjectName();
361                                columnName.setString(tsqlColumn.getName());
362                                new TableColumn(this, columnName);
363                        }
364                        this.setCreateTable(true);
365                        this.setHasSQLEnv(true);
366                }
367        }
368
369        private void fillSchemaInfo() {
370                Stack<TCustomSqlStatement> stmtStack = ModelBindingManager.getGlobalStmtStack();
371                if (!stmtStack.isEmpty()) {
372                        TCustomSqlStatement currentStmt = stmtStack.peek();
373                        TCustomSqlStatement stmt = DlineageUtil.getTopStmt(stmtStack.peek());
374                        
375                        String sqlComment = null;
376                        try {
377                                sqlComment = stmt.getCommentBeforeNode();
378                        } catch (Exception e) {
379                        }
380                        if (!SQLUtil.isEmpty(sqlComment) && (sqlComment.indexOf("db") != -1 || sqlComment.indexOf("schema") != -1)) {
381                                Properties properties = new Properties();
382                                try {
383                                        properties.load(
384                                                        new ByteArrayInputStream(sqlComment.replace("--", "").trim().replace(",", "\n").getBytes()));
385                                        if (SQLUtil.isEmpty(this.server) && properties.containsKey("db-instance")) {
386                                                this.server = properties.getProperty("db-instance");
387                                        }
388                                        if (SQLUtil.isEmpty(this.database) && properties.containsKey("db")) {
389                                                this.database = properties.getProperty("db");
390                                                if(this.database.indexOf(".")!=-1) {
391                                                        this.database = SQLUtil.quoteDottedName(ModelBindingManager.getGlobalOption().getVendor(), ESQLDataObjectType.dotCatalog, this.database);
392                                                }
393                                        }
394                                        if (SQLUtil.isEmpty(this.schema) && properties.containsKey("schema")) {
395                                                this.schema = properties.getProperty("schema");
396                                                if(this.schema.indexOf(".")!=-1) {
397                                                        this.schema = SQLUtil.quoteDottedName(ModelBindingManager.getGlobalOption().getVendor(), ESQLDataObjectType.dotSchema, this.schema);
398                                                }
399                                        }
400                                } catch (IOException e) {
401                                        logger.error("load sql comment properties failed.", e);
402                                }
403                        }
404                        
405                        if(stmt instanceof TCreateTableSqlStatement) {
406                                TCreateTableSqlStatement createTableStmt = (TCreateTableSqlStatement)stmt;
407                                if (createTableStmt.getTableKinds() != null 
408                                                && (createTableStmt.getTableKinds().contains(ETableKind.etkTemporary) 
409                                                                || createTableStmt.getTableKinds().contains(ETableKind.etkTemp)
410                                                                || createTableStmt.getTableKinds().contains(ETableKind.etkLocalTemporary)
411                                                                || createTableStmt.getTableKinds().contains(ETableKind.etkLocalTemp)
412                                                                || createTableStmt.getTableKinds().contains(ETableKind.etkGlobalTemporary)
413                                                                || createTableStmt.getTableKinds().contains(ETableKind.etkGlobalTemp)
414                                                                || createTableStmt.getTableKinds().contains(ETableKind.etkScopedTemporary))) {
415                                        if (this.tableObject == createTableStmt.getTargetTable()) {
416                                                setSubType(SubType.temp_table);
417                                                return;
418                                        }
419                                }
420                        }
421                        
422                        if(currentStmt instanceof TCreateTableSqlStatement) {
423                                TCreateTableSqlStatement createTableStmt = (TCreateTableSqlStatement)currentStmt;
424                                if (createTableStmt.getTableKinds() != null 
425                                                && (createTableStmt.getTableKinds().contains(ETableKind.etkTemporary) 
426                                                                || createTableStmt.getTableKinds().contains(ETableKind.etkTemp)
427                                                                || createTableStmt.getTableKinds().contains(ETableKind.etkLocalTemporary)
428                                                                || createTableStmt.getTableKinds().contains(ETableKind.etkLocalTemp)
429                                                                || createTableStmt.getTableKinds().contains(ETableKind.etkGlobalTemporary)
430                                                                || createTableStmt.getTableKinds().contains(ETableKind.etkGlobalTemp)
431                                                                || createTableStmt.getTableKinds().contains(ETableKind.etkScopedTemporary))) {
432                                        if (this.tableObject == createTableStmt.getTargetTable()) {
433                                                setSubType(SubType.temp_table);
434                                                return;
435                                        }
436                                }
437                        }
438                        
439                        if(stmt instanceof TCreateViewSqlStatement) {
440                                TCreateViewSqlStatement createViewStmt = (TCreateViewSqlStatement)stmt;
441                                if (createViewStmt.getTableKind() != null 
442                                                && (createViewStmt.getTableKind() == (ETableKind.etkTemporary) 
443                                                                || createViewStmt.getTableKind() == (ETableKind.etkTemp)
444                                                                || createViewStmt.getTableKind() == (ETableKind.etkLocalTemporary)
445                                                                || createViewStmt.getTableKind() == (ETableKind.etkLocalTemp)
446                                                                || createViewStmt.getTableKind() == (ETableKind.etkGlobalTemporary)
447                                                                || createViewStmt.getTableKind() == (ETableKind.etkGlobalTemp))) {
448                                        if (this.viewName == createViewStmt.getViewName()) {
449                                                setSubType(SubType.temp_table);
450                                                return;
451                                        }
452                                }
453                        }
454                        
455                        if(currentStmt instanceof TCreateViewSqlStatement) {
456                                TCreateViewSqlStatement createViewStmt = (TCreateViewSqlStatement)currentStmt;
457                                if (createViewStmt.getTableKind() != null 
458                                                && (createViewStmt.getTableKind() == (ETableKind.etkTemporary) 
459                                                                || createViewStmt.getTableKind() == (ETableKind.etkTemp)
460                                                                || createViewStmt.getTableKind() == (ETableKind.etkLocalTemporary)
461                                                                || createViewStmt.getTableKind() == (ETableKind.etkLocalTemp)
462                                                                || createViewStmt.getTableKind() == (ETableKind.etkGlobalTemporary)
463                                                                || createViewStmt.getTableKind() == (ETableKind.etkGlobalTemp))) {
464                                        if (this.viewName == createViewStmt.getViewName()) {
465                                                setSubType(SubType.temp_table);
466                                                return;
467                                        }
468                                }
469                        }
470                }
471                
472
473                if (SQLUtil.isEmpty(this.database) || this.database.equals(ModelBindingManager.getGlobalDatabase())) {
474                        TSQLEnv sqlEnv = ModelBindingManager.getGlobalSQLEnv();
475                        if (sqlEnv == null) {
476                                return;
477                        }
478                        int occurrence = 0;
479                        int maxPriority = -1;
480                        String tableDatabase = null;
481                        String tableSchema = null;
482                        Map<Integer, List<String>> databaseSchemasMap = new HashMap<Integer, List<String>>();
483                        // Every cross-schema hit for the bare name, grouped by priority, so a name
484                        // that is ambiguous across schemas (and which the rules below cannot resolve
485                        // to a single winner) can be surfaced as a candidate set instead of being
486                        // stamped with a non-existent default schema.
487                        Map<Integer, List<String[]>> candidatesByPriority = new TreeMap<Integer, List<String[]>>();
488                        for (TSQLCatalog catalog : sqlEnv.getCatalogList()) {
489                                if (SQLUtil.isEmpty(schema) || this.schema.equals(ModelBindingManager.getGlobalSchema())) {
490                                        for (TSQLSchema schema : catalog.getSchemaList()) {
491                                                TSQLSchemaObject tsqlTable = schema.findTable(DlineageUtil.getSimpleTableName(this.name));
492                                                if(tsqlTable == null){
493                                                        tsqlTable = schema.findSynonyms(DlineageUtil.getSimpleTableName(this.name));
494                                                }
495                                                if (tsqlTable != null) {
496                                                        occurrence += 1;
497                                                        String candDb = quoteDottedSegment(catalog.getName());
498                                                        String candSchema = quoteDottedSegment(schema.getName());
499                                                        String candName = candDb + "." + candSchema + "."
500                                                                        + DlineageUtil.getSimpleTableName(this.name);
501                                                        int candPriority = tsqlTable.getPriority();
502                                                        if (!candidatesByPriority.containsKey(candPriority)) {
503                                                                candidatesByPriority.put(candPriority, new ArrayList<String[]>());
504                                                        }
505                                                        candidatesByPriority.get(candPriority).add(new String[] { candDb, candSchema, candName });
506                                                        if (tsqlTable.getPriority() > maxPriority
507                                                                        || (maxPriority > 0 && tsqlTable.getPriority() == maxPriority)) {
508                                                                tableDatabase = catalog.getName();
509                                                                if(tableDatabase.indexOf(".")!=-1) {
510                                                                        tableDatabase = SQLUtil.quoteDottedName(ModelBindingManager.getGlobalOption().getVendor(), ESQLDataObjectType.dotCatalog, tableDatabase);
511                                                                }
512                                                                tableSchema = schema.getName();
513                                                                if(tableSchema.indexOf(".")!=-1) {
514                                                                        tableSchema = SQLUtil.quoteDottedName(ModelBindingManager.getGlobalOption().getVendor(), ESQLDataObjectType.dotSchema, tableSchema);
515                                                                }
516                                                                maxPriority = tsqlTable.getPriority();
517                                                                if(!databaseSchemasMap.containsKey(maxPriority)) {
518                                                                        databaseSchemasMap.put(maxPriority, new ArrayList<String>());
519                                                                }
520                                                                databaseSchemasMap.get(maxPriority).add(tableDatabase + "___" + tableSchema);
521                                                        }
522                                                }
523                                        }
524//                                      if (occurrence > 1) {
525//                                              break;
526//                                      }
527                                } else {
528                                        TSQLSchema schema = catalog.getSchema(this.schema, false);
529                                        if (schema != null) {
530                                                TSQLSchemaObject tsqlTable = schema.findTable(DlineageUtil.getSimpleTableName(this.name));
531                                                if(tsqlTable == null){
532                                                        tsqlTable = schema.findSynonyms(DlineageUtil.getSimpleTableName(this.name));
533                                                }
534                                                if (tsqlTable != null) {
535                                                        occurrence += 1;
536                                                        if (tsqlTable.getPriority() > maxPriority
537                                                                        || (maxPriority > 0 && tsqlTable.getPriority() == maxPriority)) {
538                                                                tableDatabase = catalog.getName();
539                                                                if(tableDatabase.indexOf(".")!=-1) {
540                                                                        tableDatabase = SQLUtil.quoteDottedName(ModelBindingManager.getGlobalOption().getVendor(), ESQLDataObjectType.dotCatalog, tableDatabase);
541                                                                }
542                                                                tableSchema = schema.getName();
543                                                                if(tableSchema.indexOf(".")!=-1) {
544                                                                        tableSchema = SQLUtil.quoteDottedName(ModelBindingManager.getGlobalOption().getVendor(), ESQLDataObjectType.dotSchema, tableSchema);
545                                                                }
546                                                                maxPriority = tsqlTable.getPriority();
547                                                                if(!databaseSchemasMap.containsKey(maxPriority)) {
548                                                                        databaseSchemasMap.put(maxPriority, new ArrayList<String>());
549                                                                }
550                                                                databaseSchemasMap.get(maxPriority).add(tableDatabase + "___" + tableSchema);
551                                                        }
552                                                }
553                                        }
554                                }
555                        }
556                        if (occurrence == 1 || (maxPriority > 0 && databaseSchemasMap.get(maxPriority).size() == 1)) {
557                                if (this.database == null && tableDatabase != null && !TSQLEnv.DEFAULT_DB_NAME.equalsIgnoreCase(tableDatabase)) {
558                                        this.database = tableDatabase;
559                                }
560                                if (this.schema == null && tableSchema != null && !TSQLEnv.DEFAULT_SCHEMA_NAME.equalsIgnoreCase(tableSchema)) {
561                                        this.schema = tableSchema;
562                                }
563                        } else if ((this.schema == null || this.schema.equals(ModelBindingManager.getGlobalSchema()))
564                                        && !candidatesByPriority.isEmpty()) {
565                                // The existing rules could not pick a single winner and the schema is
566                                // still only a default (null or the global default). Look at the
567                                // top-priority group: when it holds two or more schemas AND none of
568                                // them is the default schema, the unqualified name is genuinely
569                                // ambiguous. Flag it and record the candidate set so the output layer
570                                // can surface the reference unqualified instead of fabricating a
571                                // non-existent default-schema object. A match in the default schema
572                                // (e.g. an existing dbo.orders) is left to win through the normal
573                                // stamping path.
574                                int maxCandPriority = Collections.max(candidatesByPriority.keySet());
575                                List<String[]> topCandidates = candidatesByPriority.get(maxCandPriority);
576                                String defaultSchema = getDefaultSchema();
577                                boolean defaultSchemaHasObject = false;
578                                for (String[] cand : topCandidates) {
579                                        if (cand[1] != null && DlineageUtil.sameName(ESQLDataObjectType.dotSchema, cand[1], defaultSchema)) {
580                                                defaultSchemaHasObject = true;
581                                                break;
582                                        }
583                                }
584                                if (!defaultSchemaHasObject && topCandidates.size() >= 2) {
585                                        List<String> candidates = new ArrayList<String>();
586                                        for (String[] cand : topCandidates) {
587                                                candidates.add(cand[2]);
588                                        }
589                                        this.candidateTables = candidates;
590                                        this.ambiguousUnqualifiedTable = true;
591                                }
592                        }
593                } else if (SQLUtil.isEmpty(this.schema) || this.schema.equals(ModelBindingManager.getGlobalSchema())) {
594                        TSQLEnv sqlEnv = ModelBindingManager.getGlobalSQLEnv();
595                        if (sqlEnv == null) {
596                                return;
597                        }
598                        int occurrence = 0;
599                        int maxPriority = -1;
600                        String tableDatabase = null;
601                        String tableSchema = null;
602                        Map<Integer, List<String>> databaseSchemasMap = new HashMap<Integer, List<String>>();
603                        TSQLCatalog catalog = sqlEnv.searchCatalog(this.database);
604                        if (catalog != null) {
605                                for (TSQLSchema schema : catalog.getSchemaList()) {
606                                        TSQLSchemaObject tsqlTable = schema.findTable(DlineageUtil.getSimpleTableName(this.name));
607                                        if(tsqlTable == null){
608                                                tsqlTable = schema.findSynonyms(DlineageUtil.getSimpleTableName(this.name));
609                                        }
610                                        if (tsqlTable != null) {
611                                                occurrence += 1;
612                                                if (tsqlTable.getPriority() > maxPriority
613                                                                || (maxPriority > 0 && tsqlTable.getPriority() == maxPriority)) {
614                                                        tableDatabase = catalog.getName();
615                                                        if (tableDatabase.indexOf(".") != -1) {
616                                                                tableDatabase = SQLUtil.quoteDottedName(ModelBindingManager.getGlobalOption().getVendor(), ESQLDataObjectType.dotCatalog, tableDatabase);
617                                                        }
618                                                        tableSchema = schema.getName();
619                                                        if (tableSchema.indexOf(".") != -1) {
620                                                                tableSchema = SQLUtil.quoteDottedName(ModelBindingManager.getGlobalOption().getVendor(), ESQLDataObjectType.dotSchema, tableSchema);
621                                                        }
622                                                        maxPriority = tsqlTable.getPriority();
623                                                        if(!databaseSchemasMap.containsKey(maxPriority)) {
624                                                                databaseSchemasMap.put(maxPriority, new ArrayList<String>());
625                                                        }
626                                                        databaseSchemasMap.get(maxPriority).add(tableDatabase + "___" + tableSchema);
627                                                }
628                                        }
629                                }
630                        }
631
632                        if (occurrence == 1 || (maxPriority > 0 && databaseSchemasMap.get(maxPriority).size() == 1)) {
633                                if (this.database == null && tableDatabase != null && !TSQLEnv.DEFAULT_DB_NAME.equalsIgnoreCase(tableDatabase)) {
634                                        this.database = tableDatabase;
635                                }
636                                if (this.schema == null && tableSchema != null && !TSQLEnv.DEFAULT_SCHEMA_NAME.equalsIgnoreCase(tableSchema)) {
637                                        this.schema = tableSchema;
638                                }
639                        }
640
641                        if (SQLUtil.isEmpty(this.schema) && !SQLUtil.isEmpty(this.database)) {
642                                this.schema = getDefaultSchema();
643                        }
644                }
645        }
646
647        public Table(TObjectName tableName) {
648                if (tableName == null) {
649                        throw new IllegalArgumentException("Table arguments can't be null.");
650                }
651
652                id = ++ModelBindingManager.get().TABLE_COLUMN_ID;
653                
654                this.viewName = tableName;
655
656                setPosition(tableName);
657
658                this.fullName = tableName.toString();
659                this.name = tableName.toString();
660                if (SQLUtil.isEmpty(name)) {
661                        name = tableName.toString();
662                }
663
664                if (name.startsWith("`") && name.endsWith("`") && name.indexOf(".") != -1
665                                && ModelBindingManager.getGlobalOption().getVendor() == EDbVendor.dbvbigquery) {
666                        this.name = SQLUtil.trimColumnStringQuote(name);
667                        this.fullName = SQLUtil.trimColumnStringQuote(fullName);
668                }
669
670                this.server = DlineageUtil.getTableServer(tableName.toString());
671                this.database = DlineageUtil.getTableDatabase(tableName.toString());
672                this.schema = DlineageUtil.getTableSchema(tableName.toString());
673
674                fillSchemaInfo();
675
676                EDbVendor vendor = ModelBindingManager.getGlobalOption().getVendor();
677                boolean supportCatalog = TSQLEnv.supportCatalog(vendor);
678                boolean supportSchema = TSQLEnv.supportSchema(vendor);
679
680                if (supportCatalog && SQLUtil.isEmpty(this.database)) {
681                        this.database = DlineageUtil.getTableDatabase(tableName.toString());
682                        if (SQLUtil.isEmpty(this.database) && ModelBindingManager.getGlobalOption().isShowImplicitSchema()
683                                        && !SQLUtil.isEmpty(tableName.getImplictDatabaseString())) {
684                                this.database = tableName.getImplictDatabaseString();
685                        }
686                }
687
688                if (supportSchema && SQLUtil.isEmpty(this.schema)) {
689                        this.schema = DlineageUtil.getTableSchema(tableName.toString());
690                        if (SQLUtil.isEmpty(this.schema) && ModelBindingManager.getGlobalOption().isShowImplicitSchema()
691                                        && !SQLUtil.isEmpty(implicitSchemaOf(tableName))) {
692                                this.schema = implicitSchemaOf(tableName);
693                        }
694                }
695
696                if (supportSchema && !SQLUtil.isEmpty(this.database) && SQLUtil.isEmpty(this.schema)) {
697                        if (!SQLUtil.isEmpty(implicitSchemaOf(tableName))) {
698                                this.schema = implicitSchemaOf(tableName);
699                        } else {
700                                this.schema = getDefaultSchema();
701                        }
702                }
703
704                if (!supportCatalog) {
705                        this.database = null;
706                } else if (this.database == null && !TSQLEnv.DEFAULT_DB_NAME.equals(getDefaultDatabase())) {
707                        this.database = getDefaultDatabase();
708                }
709
710                if (!supportSchema) {
711                        this.schema = null;
712                } else if (this.schema == null && !TSQLEnv.DEFAULT_SCHEMA_NAME.equals(getDefaultSchema())) {
713                        this.schema = getDefaultSchema();
714                }
715
716                updateTableName(supportCatalog, supportSchema);
717
718                if (this.server == null && !TSQLEnv.DEFAULT_SERVER_NAME.equals(getDefaultServer())) {
719                        this.server = getDefaultServer();
720                }
721        }
722
723        protected String getDefaultServer() {
724                String defaultServer = null;
725                if (ModelBindingManager.getGlobalSQLEnv() != null) {
726                        defaultServer = ModelBindingManager.getGlobalSQLEnv().getDefaultServerName();
727                }
728                if (!SQLUtil.isEmpty(defaultServer))
729                        return defaultServer;
730                return TSQLEnv.DEFAULT_SERVER_NAME;
731        }
732
733        /**
734         * The implicit schema stamped on {@code name}, or null when the statement
735         * being analyzed has no current schema because a Snowflake
736         * {@code USE <database>} unset it.
737         *
738         * <p>The stamp is applied by the resolvers from the TSQLEnv's default schema,
739         * which is batch-global: it holds the LAST {@code USE SCHEMA} of the whole
740         * script even for statements that run after a database switch invalidated it.
741         * Reading it unfiltered put the previous database's schema back on names the
742         * rest of this class had already left unqualified - a CREATE VIEW target
743         * reported {@code D2.S1.v} while its own source table reported
744         * {@code D2.DEFAULT.orders}. See GitHub #715 / MantisBT 4677.
745         */
746        private static String implicitSchemaOf(TObjectName name) {
747                if (name == null || ModelBindingManager.isGlobalSchemaUnset()) {
748                        return null;
749                }
750                return name.getImplictSchemaString();
751        }
752
753        protected String getDefaultSchema() {
754                return ModelBindingManager.getEffectiveDefaultSchema();
755        }
756
757        protected String getDefaultDatabase() {
758                return ModelBindingManager.getEffectiveDefaultDatabase();
759        }
760
761        /**
762         * @return true when this one-part reference is ambiguous across two or more
763         *         schemas and was intentionally left unqualified (no default schema
764         *         stamped). The candidates are available via {@link #getCandidateTables()}.
765         */
766        public boolean isAmbiguousUnqualifiedTable() {
767                return ambiguousUnqualifiedTable;
768        }
769
770        /**
771         * @return the qualified candidate table names (e.g. {@code testdb.s1.orders},
772         *         {@code testdb.s2.orders}) when this reference is ambiguous across
773         *         schemas, or null when the reference resolved normally.
774         */
775        public List<String> getCandidateTables() {
776                return candidateTables;
777        }
778
779        /**
780         * Quote a catalog/schema segment the same way the surrounding resolution code
781         * does (lines around the occurrence loop), so a name containing a dot is kept
782         * as a single delimited segment.
783         */
784        private static String quoteDottedSegment(String segment) {
785                if (segment != null && segment.indexOf('.') != -1) {
786                        // catalog and schema segments share NAME_GROUP quote rules
787                        return SQLUtil.quoteDottedName(ModelBindingManager.getGlobalOption().getVendor(),
788                                        ESQLDataObjectType.dotSchema, segment);
789                }
790                return segment;
791        }
792
793        public Table(String tableName) {
794                if (tableName == null) {
795                        throw new IllegalArgumentException("Table arguments can't be null.");
796                }
797
798                id = ++ModelBindingManager.get().TABLE_COLUMN_ID;
799
800                this.startPosition = new Pair3<Long, Long, String>(-1L, -1L, ModelBindingManager.getGlobalHash());
801                this.endPosition = new Pair3<Long, Long, String>(-1L, -1L, ModelBindingManager.getGlobalHash());
802
803                this.fullName = tableName;
804                this.name = tableName;
805                if (SQLUtil.isEmpty(name)) {
806                        name = tableName;
807                }
808
809                if (name.startsWith("`") && name.endsWith("`") && name.indexOf(".") != -1
810                                && ModelBindingManager.getGlobalOption().getVendor() == EDbVendor.dbvbigquery) {
811                        this.name = SQLUtil.trimColumnStringQuote(name);
812                        this.fullName = SQLUtil.trimColumnStringQuote(fullName);
813                }
814
815                EDbVendor vendor = ModelBindingManager.getGlobalOption().getVendor();
816                boolean supportCatalog = TSQLEnv.supportCatalog(vendor);
817                boolean supportSchema = TSQLEnv.supportSchema(vendor);
818
819                fillSchemaInfo();
820
821                this.server = DlineageUtil.getTableServer(tableName);
822
823                if (supportCatalog && SQLUtil.isEmpty(this.database)) {
824                        this.database = DlineageUtil.getTableDatabase(tableName);
825                }
826                if (supportSchema && SQLUtil.isEmpty(this.schema)) {
827                        this.schema = DlineageUtil.getTableSchema(tableName);
828                }
829                if (supportSchema && !SQLUtil.isEmpty(this.database) && SQLUtil.isEmpty(this.schema)) {
830                        this.schema = getDefaultSchema();
831                }
832
833                if (!supportCatalog) {
834                        this.database = null;
835                } else if (this.database == null && !TSQLEnv.DEFAULT_DB_NAME.equals(getDefaultDatabase())) {
836                        this.database = getDefaultDatabase();
837                }
838
839                if (!supportSchema) {
840                        this.schema = null;
841                } else if (this.schema == null && !TSQLEnv.DEFAULT_SCHEMA_NAME.equals(getDefaultSchema())) {
842                        this.schema = getDefaultSchema();
843                }
844
845                updateTableName(supportCatalog, supportSchema);
846
847                if (this.server == null && !TSQLEnv.DEFAULT_SERVER_NAME.equals(getDefaultServer())) {
848                        this.server = getDefaultServer();
849                }
850        }
851
852        protected void updateTableName(boolean supportCatalog, boolean supportSchema) {
853                List<String> segments = SQLUtil.parseNames(this.name);
854                this.name = segments.get(segments.size() - 1);
855                if (supportCatalog && supportSchema) {
856                        StringBuilder builder = new StringBuilder();
857                        if (segments.size() > 2) {
858                                builder.append(segments.get(segments.size() - 3)).append(".");
859                        } else {
860                                if (!SQLUtil.isEmpty(this.database) && !TSQLEnv.DEFAULT_DB_NAME.equalsIgnoreCase(this.database)) {
861                                        builder.append(this.database).append(".");
862                                }
863                        }
864                        if (segments.size() > 1) {
865                                builder.append(segments.get(segments.size() - 2)).append(".");
866                        } else {
867                                if (builder.length() > 0) {
868                                        if (this.schema == null) {
869                                                if (ModelBindingManager.getGlobalVendor() == EDbVendor.dbvmssql) {
870                                                        this.schema = "dbo";
871                                                } else {
872                                                        this.schema = getDefaultSchema();
873                                                }
874                                        }
875                                        else if (TSQLEnv.DEFAULT_SCHEMA_NAME.equalsIgnoreCase(this.schema)
876                                                        && ModelBindingManager.getGlobalVendor() == EDbVendor.dbvmssql) {
877                                                this.schema = "dbo";
878                                        }
879                                        builder.append(this.schema).append(".");
880                                } else {
881                                        if (!SQLUtil.isEmpty(this.schema) && !TSQLEnv.DEFAULT_SCHEMA_NAME.equalsIgnoreCase(this.schema)) {
882                                                builder.append(this.schema).append(".");
883                                        }
884                                }
885                        }
886                        builder.append(this.name);
887                        this.name = builder.toString();
888                } else if (supportCatalog) {
889                        if (segments.size() > 1) {
890                                this.name = segments.get(segments.size() - 2) + "." + this.name;
891                        }
892                        else {
893                                if (!SQLUtil.isEmpty(this.database) && !TSQLEnv.DEFAULT_DB_NAME.equalsIgnoreCase(this.database)) {
894                                        this.name = this.database + "." + this.name;
895                                }
896                        }
897                } else if (supportSchema) {
898                        if (segments.size() > 1) {
899                                this.name = segments.get(segments.size() - 2) + "." + this.name;
900                        } else {
901                                if (!SQLUtil.isEmpty(this.schema) && !TSQLEnv.DEFAULT_SCHEMA_NAME.equalsIgnoreCase(this.schema)) {
902                                        this.name = this.schema + "." + this.name;
903                                }
904                        }
905                }
906
907                if (this.server != null && !this.server.equals(getDefaultServer())) {
908                        this.name = this.server + "." + this.name;
909                }
910        }
911
912        public long getId() {
913                return id;
914        }
915
916        public String getName() {
917                return name;
918        }
919
920        public void setName(String name) {
921                EDbVendor vendor = ModelBindingManager.getGlobalOption().getVendor();
922                boolean supportCatalog = TSQLEnv.supportCatalog(vendor);
923                boolean supportSchema = TSQLEnv.supportSchema(vendor);
924                if (!supportCatalog) {
925                        this.database = null;
926                } else if (this.database == null && !TSQLEnv.DEFAULT_DB_NAME.equals(getDefaultDatabase())) {
927                        this.database = getDefaultDatabase();
928                }
929
930                if (!supportSchema) {
931                        this.schema = null;
932                } else if (this.schema == null && !TSQLEnv.DEFAULT_SCHEMA_NAME.equals(getDefaultSchema())) {
933                        this.schema = getDefaultSchema();
934                }
935                
936                this.name = name;
937                
938                updateTableName(supportCatalog, supportSchema);
939        }
940
941        public Pair3<Long, Long, String> getStartPosition() {
942                return startPosition;
943        }
944
945        public Pair3<Long, Long, String> getEndPosition() {
946                return endPosition;
947        }
948
949        public List<TableColumn> getColumns() {
950                return columns;
951        }
952
953        public void addColumn(TableColumn column) {
954                if (column != null && !this.columns.contains(column)) {
955                        this.columns.add(column);
956                }
957        }
958
959        public TTable getTableObject() {
960                return tableObject;
961        }
962
963        public String getAlias() {
964                String aliasString = Arrays.toString(this.alias.toArray(new String[0]));
965                return aliasString.substring(1, aliasString.length() - 1);
966        }
967
968        public void setAlias(String alias) {
969                if (!SQLUtil.isEmpty(alias)) {
970                        this.alias.add(alias);
971                }
972        }
973
974        public String getFullName() {
975                return fullName;
976        }
977
978
979        public boolean hasSubquery() {
980                return subquery;
981        }
982
983        public String getParent() {
984                return parent;
985        }
986
987        public void setParent(String parent) {
988                this.parent = parent;
989        }
990
991//      public TObjectName getTableName() {
992//              return tableName;
993//      }
994
995        public String getDatabase() {
996                return database;
997        }
998
999        public String getSchema() {
1000                return schema;
1001        }
1002
1003        public TableRelationRows getRelationRows() {
1004                return relationRows;
1005        }
1006
1007        public boolean isCreateTable() {
1008                return isCreateTable;
1009        }
1010
1011        public void setCreateTable(boolean isCreateTable, boolean fromDDL) {
1012                Stack<TCustomSqlStatement> stmtStack = ModelBindingManager.getGlobalStmtStack();
1013                TCustomSqlStatement stmt = null;
1014                if (stmtStack != null && !stmtStack.isEmpty()) {
1015                        stmt = stmtStack.peek();
1016                }
1017                if (stmt != null && stmt.getClass().getSimpleName().startsWith("TCreate")) {
1018                        Pair<Pair3<Long, Long, String>, Pair3<Long, Long, String>> position = setPosition(stmt);
1019                        if (position != null) {
1020                                this.startPosition = position.first;
1021                                this.endPosition = position.second;
1022                        }
1023                }
1024                this.isCreateTable = isCreateTable;
1025                if (isCreateTable) {
1026                        for (TableColumn column : columns) {
1027                                column.setShowStar(false);
1028                        }
1029                        setDetermined(true);
1030                        if (fromDDL) {
1031                                setFromDDL(true);
1032                        }
1033                }
1034        }
1035        
1036        public void setCreateTable(boolean isCreateTable) {
1037                setCreateTable(isCreateTable, false);
1038        }
1039
1040        public boolean isView() {
1041                return isView;
1042        }
1043
1044        public void setView(boolean isView) {
1045                this.isView = isView;
1046        }
1047
1048        public boolean isPseudo() {
1049                return isPseudo;
1050        }
1051
1052        public void setPseudo(boolean isPseudo) {
1053                this.isPseudo = isPseudo;
1054        }
1055
1056        public boolean isExternal() {
1057                return isExternal;
1058        }
1059
1060        public void setExternal(boolean isExternal) {
1061                this.isExternal = isExternal;
1062        }
1063
1064        public boolean isStage() {
1065                return isStage;
1066        }
1067
1068        public void setStage(boolean isStage) {
1069                this.isStage = isStage;
1070        }
1071
1072        public boolean isSequence() {
1073                return isSequence;
1074        }
1075
1076        public void setSequence(boolean sequence) {
1077                isSequence = sequence;
1078        }
1079
1080        public boolean isVariable() {
1081                return isVariable;
1082        }
1083
1084        public void setVariable(boolean isVariable) {
1085                this.isVariable = isVariable;
1086        }
1087
1088        public boolean isConstant() {
1089                return isConstant;
1090        }
1091
1092        public void setConstant(boolean isConstant) {
1093                this.isConstant = isConstant;
1094        }
1095
1096        public boolean isTarget() {
1097                return isTarget;
1098        }
1099
1100        public void setTarget(boolean isTarget) {
1101                this.isTarget = isTarget;
1102        }
1103
1104        public String getProcedureId() {
1105                return procedureId;
1106        }
1107
1108        public void setProcedureId(String procedureId) {
1109                this.procedureId = procedureId;
1110        }
1111
1112        public boolean isPath() {
1113                return isPath;
1114        }
1115
1116        public void setPath(boolean isPath) {
1117                this.isPath = isPath;
1118                if(isPath) {
1119                        String filePathDatabase = ModelBindingManager.getGlobalOption().getFilePathDatabase();
1120                        String filePathSchema = ModelBindingManager.getGlobalOption().getFilePathSchema();
1121                        if(SQLUtil.isEmpty(filePathDatabase)) {
1122                                this.database = null;
1123                        }
1124                        else {
1125                                this.database = filePathDatabase;
1126                        }
1127                        if(SQLUtil.isEmpty(filePathSchema)) {
1128                                this.schema = null;
1129                        }
1130                        else {
1131                                this.schema = filePathSchema;
1132                        }
1133
1134                        EDbVendor vendor = ModelBindingManager.getGlobalOption().getVendor();
1135                        boolean supportCatalog = TSQLEnv.supportCatalog(vendor);
1136                        boolean supportSchema = TSQLEnv.supportSchema(vendor);
1137                        if (!supportCatalog) {
1138                                this.database = null;
1139                        } else if (this.database == null && !TSQLEnv.DEFAULT_DB_NAME.equals(getDefaultDatabase())) {
1140                                this.database = getDefaultDatabase();
1141                        }
1142
1143                        if (!supportSchema) {
1144                                this.schema = null;
1145                        } else if (this.schema == null && !TSQLEnv.DEFAULT_SCHEMA_NAME.equals(getDefaultSchema())) {
1146                                this.schema = getDefaultSchema();
1147                        }
1148
1149                        updateTableName(supportCatalog, supportSchema);
1150                }
1151        }
1152
1153        public boolean isCursor() {
1154                return isVariable && getSubType() == SubType.cursor;
1155        }
1156
1157        public String getLocation() {
1158                return location;
1159        }
1160
1161        public void setLocation(String location) {
1162                this.location = location;
1163        }
1164
1165        public String getFileType() {
1166                return fileType;
1167        }
1168
1169        public void setFileType(String fileType) {
1170                this.fileType = fileType;
1171        }
1172
1173        public String getDisplayName() {
1174                return displayName;
1175        }
1176
1177        public void setDisplayName(String displayName) {
1178                this.displayName = displayName;
1179        }
1180
1181        public String getFileFormat() {
1182                return fileFormat;
1183        }
1184
1185        public void setFileFormat(String fileFormat) {
1186                this.fileFormat = fileFormat;
1187        }
1188
1189        public List<Process> getProcesses() {
1190                return processes;
1191        }
1192
1193        public void addProcess(Process process) {
1194                if (processes == null) {
1195                        processes = new ArrayList<Process>();
1196                }
1197                if (process != null && !processes.contains(process)) {
1198                        processes.add(process);
1199                }
1200        }
1201        
1202        public void removeProcess(Process process) {
1203                if (processes == null) {
1204                        processes = new ArrayList<Process>();
1205                }
1206                if (process != null && processes.contains(process)) {
1207                        processes.remove(process);
1208                }
1209        }
1210
1211        public void setSubType(SubType subType) {
1212                if (this.subType == null || subType == SubType.record_type) {
1213                        this.subType = subType;
1214                }
1215        }
1216
1217        public SubType getSubType() {
1218                return subType;
1219        }
1220
1221        public boolean isStream() {
1222                return isStream;
1223        }
1224
1225        public void setStream(boolean isStream) {
1226                this.isStream = isStream;
1227        }
1228
1229        public boolean isSchema() {
1230                return isSchema;
1231        }
1232
1233        public void setSchema(boolean isSchema) {
1234                this.isSchema = isSchema;
1235        }
1236
1237        public void setDatabase(String database) {
1238                this.database = database;
1239        }
1240
1241        public boolean isDatabase() {
1242                return isDatabase;
1243        }
1244
1245        public void setDatabase(boolean isDatabase) {
1246                this.isDatabase = isDatabase;
1247        }
1248
1249        public boolean isDataSource() {
1250                return isDataSource;
1251        }
1252
1253        public void setDataSource(boolean dataSource) {
1254                isDataSource = dataSource;
1255        }
1256
1257        public String getStarStmt() {
1258                return starStmt;
1259        }
1260
1261        public void setStarStmt(String starStmt) {
1262                this.starStmt = starStmt;
1263        }
1264
1265        public void setHasSQLEnv(boolean hasSQLEnv) {
1266                this.hasSQLEnv = hasSQLEnv;
1267        }
1268
1269        public boolean hasSQLEnv() {
1270                return hasSQLEnv;
1271        }
1272
1273        public String getCurrentAlias() {
1274                return currentAlias;
1275        }
1276
1277        public void setCurrentAlias(String currentAlias) {
1278                if (SQLUtil.isEmpty(currentAlias)) {
1279                        this.currentAlias = null;
1280                } else
1281                        this.currentAlias = currentAlias;
1282        }
1283
1284        public Set<String> getAliasList() {
1285                return alias;
1286        }
1287
1288        public String getServer() {
1289                return server;
1290        }
1291
1292        public boolean isDetermined() {
1293                return isDetermined;
1294        }
1295
1296        public void setDetermined(boolean isDetermined) {
1297                this.isDetermined = isDetermined;
1298        }
1299
1300        public boolean isFromDDL() {
1301                return fromDDL;
1302        }
1303
1304        public void setFromDDL(boolean fromDDL) {
1305                this.fromDDL = fromDDL;
1306        }
1307
1308        /**
1309         * Whether this endpoint is created by the analyzed SQL. A parse fact, NOT gated on
1310         * {@link #isDetermined()}; true for CREATE TABLE / SELECT INTO / CTAS / clone
1311         * targets even when the defining query's schema could not be resolved.
1312         */
1313        public boolean isCreatedInSql() {
1314                return createdInSql;
1315        }
1316
1317        public void setCreatedInSql(boolean createdInSql) {
1318                this.createdInSql = createdInSql;
1319        }
1320
1321        public EndpointIntroduction getEndpointIntroduction() {
1322                return endpointIntroduction;
1323        }
1324
1325        /**
1326         * Records how the endpoint was introduced. First non-UNKNOWN writer wins, so the
1327         * structural creation site (set early at build time) is not later clobbered.
1328         */
1329        public void setEndpointIntroduction(EndpointIntroduction endpointIntroduction) {
1330                if (endpointIntroduction == null) {
1331                        return;
1332                }
1333                if (this.endpointIntroduction == null || this.endpointIntroduction == EndpointIntroduction.UNKNOWN) {
1334                        this.endpointIntroduction = endpointIntroduction;
1335                }
1336        }
1337
1338        /**
1339         * Existing schema/model resolution state, exposed under the endpoint-classification
1340         * name. Mirrors {@link #isDetermined()} exactly; introduces no new resolution logic.
1341         */
1342        public boolean isResolved() {
1343                return isDetermined;
1344        }
1345
1346        /**
1347         * Delimiter-normalized, dialect-aware form of {@link #getName()} (brackets / quotes
1348         * stripped per identifier segment). Computed on access from the current name; does
1349         * NOT mutate {@link #getName()}, which stays raw. Returns the raw name when no
1350         * normalization is possible (e.g. no global vendor context).
1351         */
1352        public String getNormalizedName() {
1353                if (name == null) {
1354                        return null;
1355                }
1356                try {
1357                        String normalized = DlineageUtil.getIdentifierNormalTableName(name);
1358                        return normalized == null ? name : normalized;
1359                } catch (Exception e) {
1360                        return name;
1361                }
1362        }
1363
1364        /**
1365         * Records that this endpoint's identifier was assembled at runtime by a dynamic-SQL
1366         * fold. {@code parts} is the outermost-first, pipe-joined set of affected name
1367         * segments; {@code identityTemplated} says the hole reaches the leading character of
1368         * the object-name segment (so a {@code #}/{@code @} prefix there is fold text, not a
1369         * real temp / table-variable marker). Additive across repeated marks: parts union,
1370         * identity ORs — one folded statement can reach the same endpoint twice.
1371         *
1372         * <p>Callers MUST only invoke this from inside a dynamic-fold context; see
1373         * {@link EndpointKind#DYNAMIC_TEMPLATE}.
1374         */
1375        public void markDynamicTemplate(String parts, boolean identityTemplated) {
1376                if (parts != null && parts.length() > 0) {
1377                        if (this.templatedParts == null) {
1378                                this.templatedParts = parts;
1379                        } else if (!this.templatedParts.equals(parts)) {
1380                                java.util.LinkedHashSet<String> merged = new java.util.LinkedHashSet<String>();
1381                                for (String part : TEMPLATED_PART_ORDER) {
1382                                        if (containsPart(this.templatedParts, part) || containsPart(parts, part)) {
1383                                                merged.add(part);
1384                                        }
1385                                }
1386                                StringBuilder sb = new StringBuilder();
1387                                for (String part : merged) {
1388                                        if (sb.length() > 0) {
1389                                                sb.append('|');
1390                                        }
1391                                        sb.append(part);
1392                                }
1393                                this.templatedParts = sb.toString();
1394                        }
1395                }
1396                this.templatedIdentity = this.templatedIdentity || identityTemplated;
1397        }
1398
1399        /** Canonical outermost-first ordering of the segment labels used by {@link #getTemplatedParts()}. */
1400        private static final String[] TEMPLATED_PART_ORDER = { "server", "db", "schema", "name" };
1401
1402        private static boolean containsPart(String parts, String part) {
1403                if (parts == null) {
1404                        return false;
1405                }
1406                for (String candidate : parts.split("\\|")) {
1407                        // non-identifier-compare: fixed internal label vocabulary, not a DB object name
1408                        if (candidate.equals(part)) {
1409                                return true;
1410                        }
1411                }
1412                return false;
1413        }
1414
1415        /**
1416         * Which identifier segments of {@link #getName()} were assembled at runtime, pipe-joined
1417         * outermost-first ({@code "db"}, {@code "db|name"}, ...); {@code null} when this endpoint
1418         * is not runtime-templated. Lets a consumer match a templated skeleton against a
1419         * later-resolved concrete name without re-parsing the name text.
1420         */
1421        public String getTemplatedParts() {
1422                return templatedParts;
1423        }
1424
1425        /** True when this endpoint's identifier was assembled at runtime by a dynamic-SQL fold. */
1426        public boolean isDynamicTemplate() {
1427                return templatedParts != null;
1428        }
1429
1430        /**
1431         * Withdraws a dynamic-template mark. Used when the SAME node turns out to also be
1432         * referenced by a STATIC statement (by-name model reuse): the static reference provably
1433         * names a real object, so publishing the template claim on the shared node would make
1434         * that reference wrong — see the shared-node contract in
1435         * docs/tmp/dynamic-template-endpoint-provenance.md.
1436         */
1437        public void clearDynamicTemplate() {
1438                this.templatedParts = null;
1439                this.templatedIdentity = false;
1440        }
1441
1442        /**
1443         * Authoritative {@link EndpointKind}, computed on access from the current (raw)
1444         * name and the active dialect. Quote-robust: each identifier segment is delimiter-
1445         * stripped before the {@code #}/{@code ##}/{@code @}/{@code tempdb} tests. This
1446         * reads only the new classification; it never inspects or mutates {@link #subType}.
1447         *
1448         * <p>§5.6 minimum scope: distinguishes T-SQL temp / global-temp / table-variable /
1449         * tempdb objects from plain catalog objects. CTE / derived / TVF / constant kinds
1450         * are deferred and reported as {@link EndpointKind#UNKNOWN} here.
1451         */
1452        public static boolean isTsqlFamilyVendor(EDbVendor vendor) {
1453                return vendor == EDbVendor.dbvmssql || vendor == EDbVendor.dbvazuresql
1454                                || vendor == EDbVendor.dbvsybase || vendor == EDbVendor.dbvsybasease
1455                                || vendor == EDbVendor.dbvsqlanywhere || vendor == EDbVendor.dbvsybaseiq;
1456        }
1457
1458        public EndpointKind getEndpointKind() {
1459                if (SQLUtil.isEmpty(name)) {
1460                        return EndpointKind.UNKNOWN;
1461                }
1462
1463                EDbVendor vendor = null;
1464                if (ModelBindingManager.getGlobalOption() != null) {
1465                        vendor = ModelBindingManager.getGlobalOption().getVendor();
1466                }
1467
1468                if (isTsqlFamilyVendor(vendor)) {
1469                        List<String> segments = SQLUtil.parseNames(name);
1470                        if (!segments.isEmpty()) {
1471                                String last = SQLUtil.trimColumnStringQuote(segments.get(segments.size() - 1).trim());
1472                                // A '#'/'##'/'@' prefix that came out of a dynamic fold hole is runtime text,
1473                                // not evidence of a temp table / table variable: 'INSERT INTO ' + QUOTENAME(@tab)
1474                                // folds to a table literally named @tab, which is a runtime-built CATALOG name.
1475                                // Only then does DYNAMIC_TEMPLATE displace the temp family; a genuinely literal
1476                                // '#tmp_' prefix keeps its temp kind (the templated segments stay readable via
1477                                // getTemplatedParts()), because losing TEMP would send a consumer to the catalog.
1478                                if (last.startsWith("##")) {
1479                                        return templatedIdentity ? EndpointKind.DYNAMIC_TEMPLATE : EndpointKind.GLOBAL_TEMP_TABLE;
1480                                }
1481                                if (last.startsWith("#")) {
1482                                        return templatedIdentity ? EndpointKind.DYNAMIC_TEMPLATE : EndpointKind.LOCAL_TEMP_TABLE;
1483                                }
1484                                if (last.startsWith("@")) {
1485                                        return templatedIdentity ? EndpointKind.DYNAMIC_TEMPLATE : EndpointKind.TABLE_VARIABLE;
1486                                }
1487                                String first = SQLUtil.trimColumnStringQuote(segments.get(0).trim());
1488                                if (segments.size() > 1 && "tempdb".equalsIgnoreCase(first)) {
1489                                        // A literal leading "tempdb" proves the object lives in tempdb whatever the
1490                                        // later segments template to, so the kind stands and templatedParts carries
1491                                        // the rest of the story.
1492                                        return EndpointKind.TEMPDB_OBJECT;
1493                                }
1494                        }
1495                }
1496
1497                // A runtime-assembled identifier is never a catalog object name: report the
1498                // template, so a consumer does not bind it (or, worse, match an unrelated object
1499                // that happens to share the folded spelling). Marked only inside a fold context.
1500                if (templatedParts != null) {
1501                        return EndpointKind.DYNAMIC_TEMPLATE;
1502                }
1503
1504                // Only a plain real table is a catalog object here. Resultsets / derived /
1505                // views / variables / stages etc. are deferred kinds — leave them UNKNOWN
1506                // rather than mislabel them CATALOG_OBJECT.
1507                if (!subquery && !isView && !isPseudo && !isStage && !isSequence && !isStream
1508                                && !isVariable && !isConstant && !isDatabase && !isSchema && !isDataSource
1509                                && subType != SubType.alias_table && subType != SubType.values_table
1510                                && subType != SubType.synonym && subType != SubType.function
1511                                && subType != SubType.unnest) {
1512                        return EndpointKind.CATALOG_OBJECT;
1513                }
1514
1515                return EndpointKind.UNKNOWN;
1516        }
1517
1518        private Pair<Pair3<Long, Long, String>, Pair3<Long, Long, String>> setPositionInternal(TSourceToken startToken, TSourceToken endToken) {
1519                if (startToken == null || endToken == null) {
1520                        return null;
1521                }
1522
1523                Pair3<Long, Long, String> startPosition = new Pair3<Long, Long, String>(startToken.lineNo, startToken.columnNo,
1524                                ModelBindingManager.getGlobalHash());
1525                Pair3<Long, Long, String> endPosition = new Pair3<Long, Long, String>(endToken.lineNo, endToken.columnNo + SQLUtil.endTrim(endToken.getAstext()).length(),
1526                                ModelBindingManager.getGlobalHash());
1527
1528                if(this.startPosition == null && this.endPosition == null) {
1529                        this.startPosition = startPosition;
1530                        this.endPosition = endPosition;
1531                }
1532
1533                boolean traceTablePosition = ModelBindingManager.getGlobalOption().isTraceTablePosition();
1534
1535                
1536                Pair<Pair3<Long, Long, String>, Pair3<Long, Long, String>> position = Pair.create(startPosition, endPosition);
1537                
1538                if (traceTablePosition) {
1539                        positions.add(position);
1540                }
1541                
1542                return position;
1543        }
1544
1545        public Pair<Pair3<Long, Long, String>, Pair3<Long, Long, String>> setPosition(TTable table) {
1546                if (table == null) {
1547                        return null;
1548                }
1549                
1550                TSourceToken startToken = table.getStartToken();
1551                TSourceToken endToken = table.getEndToken();
1552                if (startToken == null && table.getSubquery() != null) {
1553                        startToken = table.getSubquery().getStartToken();
1554                        endToken = table.getSubquery().getEndToken();
1555                }
1556
1557                return setPositionInternal(startToken, endToken);
1558        }
1559
1560        public Pair<Pair3<Long, Long, String>, Pair3<Long, Long, String>> setPosition(TObjectName tableName) {
1561                if (tableName == null) {
1562                        return null;
1563                }
1564                
1565                TSourceToken startToken = tableName.getStartToken();
1566                TSourceToken endToken = tableName.getEndToken();
1567                
1568                return setPositionInternal(startToken, endToken);
1569        }
1570
1571        public Pair<Pair3<Long, Long, String>, Pair3<Long, Long, String>> setPosition(TCustomSqlStatement stmt) {
1572                if (stmt == null) {
1573                        return null;
1574                }
1575                
1576                TSourceToken startToken = stmt.getStartToken();
1577                TSourceToken endToken = stmt.getEndToken();
1578                
1579                return setPositionInternal(startToken, endToken);
1580        }
1581
1582        public Set<Pair<Pair3<Long, Long, String>, Pair3<Long, Long, String>>> getPositions() {
1583                return positions;
1584        }
1585        
1586        public TableColumn findColumn(String columnName) {
1587                if (columns == null) {
1588                        return null;
1589                }
1590                for (TableColumn column : columns) {
1591                        if (DlineageUtil.compareColumnIdentifier(column.getName(), columnName)) {
1592                                return column;
1593                        }
1594                }
1595                return null;
1596        }
1597
1598        public void removeColumn(TableColumn column) {
1599                if (columns != null && columns.contains(column)) {
1600                        columns.remove(column);
1601                }
1602        }
1603
1604        public void removeColumn(String columnName) {
1605                TableColumn column = findColumn(columnName);
1606                if (column != null) {
1607                        removeColumn(column);
1608                }
1609        }
1610}