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