001package gudusoft.gsqlparser.dlineage.util; 002 003import gudusoft.gsqlparser.EDbVendor; 004import gudusoft.gsqlparser.dlineage.dataflow.model.ModelBindingManager; 005import gudusoft.gsqlparser.nodes.TObjectName; 006import gudusoft.gsqlparser.sqlenv.ESQLDataObjectType; 007import gudusoft.gsqlparser.util.SQLUtil; 008 009/** 010 * The names dlineage publishes, version 1: the legacy normal form. 011 * 012 * <p>Each segment is {@code IdentifierService.normalizeStatic} of the name, upper-cased where the legacy collation 013 * classes call the role case-insensitive ({@link SQLUtil#legacyNormalSegment}). A name that shares that string with 014 * a different name of the same analysis gets a name of its own ({@code NormalNameCollisions}), except in the 015 * normalize-output relabel, which asks for the bare legacy fold ({@link Profile#NORMALIZED_OUTPUT}). 016 * 017 * <p>This is a compatibility renderer, not identity. Call it only where a name is PUBLISHED: 018 * <ul> 019 * <li>the normalize-output relabel ({@link Profile#NORMALIZED_OUTPUT});</li> 020 * <li>the JSON model's names in normalized mode;</li> 021 * <li>result-set display labels;</li> 022 * <li>a table's {@code normalizedName}.</li> 023 * </ul> 024 * Do not use it for equality, lookups, keys or merges: use the name service's keys ({@code CanonKey}, 025 * {@code QualifiedCanonKey}, {@link DlineageUtil#getTableIdentityKey}, {@link DlineageUtil#lexicalColumnKey}). The one 026 * exception is explicit: the compatibility bucket of a name that carries no identity ({@link #tableBucket}, 027 * {@link #columnBucket}, {@link #columnSegmentBucket}). 028 * 029 * <p>The strings it gives are the published contract of dlineage output version 1. The name service's stored form 030 * (the vendor's own bare name) is another output version, not a fix to this one. 031 */ 032public final class LegacyDlineageWireNamesV1 { 033 034 /** Which names of output version 1 to render. */ 035 public enum Profile { 036 /** 037 * The names the analysis publishes. Two different names of the analysis that share a legacy fold get names 038 * of their own ({@code NormalNameCollisions}). Given identifiers. 039 */ 040 ANALYSIS, 041 /** 042 * The normalize-output relabel of finished output ({@code Option.setNormalizeOutput(true)}): the bare legacy 043 * fold of every name field it is given, labels included, because output version 1 publishes them that way 044 * (PostgreSQL {@code 'abc'} becomes {@code 'ABC'}). Colliding names are already kept apart by id. 045 */ 046 NORMALIZED_OUTPUT 047 } 048 049 private LegacyDlineageWireNamesV1() { 050 } 051 052 /** The published form of a name of the given role, qualified or not, under the analysis's vendor. */ 053 public static String name(String name, ESQLDataObjectType objectType) { 054 return name(Profile.ANALYSIS, ModelBindingManager.getGlobalVendor(), name, objectType); 055 } 056 057 /** The published form of a name field of the given role under the analysis's vendor, in the given profile. */ 058 public static String name(Profile profile, String name, ESQLDataObjectType objectType) { 059 return name(profile, ModelBindingManager.getGlobalVendor(), name, objectType); 060 } 061 062 /** The published form of a name of the given role, qualified or not. */ 063 public static String name(EDbVendor vendor, String name, ESQLDataObjectType objectType) { 064 return name(Profile.ANALYSIS, vendor, name, objectType); 065 } 066 067 /** The published form of a name field of the given role, in the given profile. */ 068 public static String name(Profile profile, EDbVendor vendor, String name, ESQLDataObjectType objectType) { 069 ModelBindingManager manager = profile == Profile.NORMALIZED_OUTPUT ? ModelBindingManager.get() : null; 070 boolean previous = manager != null && manager.useLegacyNormalNames(true); 071 try { 072 return SQLUtil.getIdentifierNormalName(vendor, name, objectType); 073 } finally { 074 if (manager != null) { 075 manager.useLegacyNormalNames(previous); 076 } 077 } 078 } 079 080 /** The published form of a column name, qualified or not, under the analysis's vendor. */ 081 public static String columnName(String name) { 082 return name(name, ESQLDataObjectType.dotColumn); 083 } 084 085 /** The published form of a column name, qualified or not. */ 086 public static String columnName(EDbVendor vendor, String name) { 087 return name(vendor, name, ESQLDataObjectType.dotColumn); 088 } 089 090 /** 091 * The published form of a column reference's own segment: its column name, or the whole reference where it has 092 * none. 093 */ 094 public static String columnName(TObjectName column) { 095 if (column == null || column.toString() == null) { 096 return null; 097 } 098 String name = column.getColumnNameOnly(); 099 return columnName(name == null || "".equals(name.trim()) ? column.toString().trim() : name.trim()); 100 } 101 102 /** The published form of one column segment: the fold without the qualified-name layout. */ 103 public static String columnSegment(String name) { 104 return columnSegment(ModelBindingManager.getGlobalVendor(), name); 105 } 106 107 /** The published form of one column segment: the fold without the qualified-name layout. */ 108 public static String columnSegment(EDbVendor vendor, String name) { 109 return segment(vendor, ESQLDataObjectType.dotColumn, name); 110 } 111 112 /** The published form of one name segment of the given role (a database link, a column). */ 113 public static String segment(EDbVendor vendor, ESQLDataObjectType objectType, String name) { 114 return SQLUtil.normalizeIdentifier(vendor, objectType, name); 115 } 116 117 /** The published form of a routine name under the analysis's vendor. */ 118 public static String functionName(String name) { 119 return name(name, ESQLDataObjectType.dotFunction); 120 } 121 122 /** 123 * The compatibility bucket of a table's name that carries no identity: its published form in output version 1, 124 * the only evidence a merge has left. A table read back from a serialized document publishes either a SQL 125 * spelling or a bare stored name, and nothing says which, so the name service cannot read it; a result set's 126 * or a variable's name is a label. Merges group such tables by this bucket, as they always did, and key every 127 * table that carries an identity by the name service ({@link PhysicalTableMergeKeys}). Not an identity: two 128 * tables of one bucket stay apart wherever an identity tells them apart. 129 */ 130 public static String tableBucket(String qualifiedName) { 131 return tableName(qualifiedName); 132 } 133 134 /** 135 * The compatibility bucket of a column's name that carries no identity (a label, a column read back from a 136 * serialized document): its published form in output version 1. See {@link #tableBucket}. 137 */ 138 public static String columnBucket(String name) { 139 return columnName(name); 140 } 141 142 /** {@link #columnBucket} of one column segment, without the qualified-name layout. */ 143 public static String columnSegmentBucket(String name) { 144 return columnSegment(name); 145 } 146 147 /** The published form of a table name. */ 148 public static String tableName(EDbVendor vendor, String name) { 149 return name(vendor, name, ESQLDataObjectType.dotTable); 150 } 151 152 /** 153 * The published form of a table reference under the analysis's vendor. A database link and a call-shaped name 154 * keep their suffix as written ({@code t@link}, {@code f(1)}). 155 */ 156 public static String tableName(String name) { 157 if (name == null) { 158 return null; 159 } 160 EDbVendor vendor = ModelBindingManager.getGlobalVendor(); 161 if (ModelBindingManager.get() != null && ModelBindingManager.get().isDblinkTable(name)) { 162 int index = name.lastIndexOf("@"); 163 return index > 0 ? tableName(vendor, name.substring(0, index)) + name.substring(index) : name; 164 } 165 if (name.endsWith(")") && name.indexOf("(") != -1) { 166 return tableName(vendor, name.substring(0, name.lastIndexOf("("))) + name.substring(name.lastIndexOf("(")); 167 } 168 return tableName(vendor, name); 169 } 170}