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