001package gudusoft.gsqlparser.dlineage.dataflow.model; 002 003import java.util.ArrayList; 004import java.util.List; 005import java.util.Map; 006import java.util.Set; 007 008import gudusoft.gsqlparser.EDbVendor; 009import gudusoft.gsqlparser.nodes.TObjectName; 010import gudusoft.gsqlparser.sqlenv.CanonKey; 011import gudusoft.gsqlparser.dlineage.util.DlineageUtil; 012import gudusoft.gsqlparser.sqlenv.ESQLDataObjectType; 013import gudusoft.gsqlparser.util.SQLUtil; 014 015public abstract class Column { 016 017 /** 018 * Whether star links are grouped by identity in this JVM: the default; {@code -Dgsp.dlineage.starLinks=legacy} 019 * groups them by display. 020 */ 021 public static boolean starLinksGroupedByIdentity() { 022 return StarLinkStore.IDENTITY; 023 } 024 private CanonKey identifierKey; 025 026 /** Null for expressions, wildcards and other non-identifier model names. */ 027 public CanonKey getIdentifierKey() { 028 return identifierKey; 029 } 030 031 protected void setIdentifierKey(CanonKey key) { 032 identifierKey = key; 033 } 034 035 public void inheritIdentifier(Column source) { 036 identifierKey = source.getIdentifierKey(); 037 renamedForCollision = source.isRenamedForCollision(); 038 } 039 040 private boolean renamedForCollision; 041 042 /** 043 * Whether this column publishes a name changed to tell it from another column of the same result set that has 044 * the same alias ({@code x(1)} beside {@code x}): both keep the alias's identifier key, so that key does not tell 045 * them apart. 046 */ 047 public boolean isRenamedForCollision() { 048 return renamedForCollision; 049 } 050 051 void setRenamedForCollision(boolean renamedForCollision) { 052 this.renamedForCollision = renamedForCollision; 053 } 054 055 private boolean starColumn; 056 057 /** 058 * Whether this column stands for a star: {@code *} or {@code t.*} in the SQL, or a whole-row pseudo column 059 * dlineage synthesized. Its producer decides it from the parse ({@link DlineageUtil#isStarName}), never from the 060 * name: a column NAMED {@code *} (SQL Server {@code [*]}, BigQuery {@code `*`}) displays as {@code *} and is not a 061 * star. 062 */ 063 public boolean isStarColumn() { 064 return starColumn; 065 } 066 067 protected void setStarColumn(boolean starColumn) { 068 this.starColumn = starColumn; 069 } 070 071 /** A column that a star expansion produces from {@code source} stands for a star when its source does. */ 072 public void inheritStarColumn(Column source) { 073 starColumn = source != null && source.isStarColumn(); 074 } 075 076 public boolean matchesIdentifier(TObjectName reference) { 077 return getIdentifierKey() != null && getIdentifierKey().equals( 078 DlineageUtil.columnKey(reference)); 079 } 080 081 private List<Column> starSourceColumns = new ArrayList<>(); 082 083 public List<Column> getStarSourceColumns() { 084 return starSourceColumns; 085 } 086 087 public void appendStarSourceColumn(Column starSourceColumn) { 088 if (starSourceColumn != null && !starSourceColumns.contains(starSourceColumn)) { 089 starSourceColumns.add(starSourceColumn); 090 List<TObjectName> columns = getStarLinkColumnList(); 091 if (columns != null) { 092 if (starSourceColumn instanceof ResultColumn) { 093 ResultSet parent = ((ResultColumn) starSourceColumn).getResultSet(); 094 boolean found = false; 095 for (TObjectName column : columns) { 096 StarLinkKey nameKey = StarLinkStore.IDENTITY ? StarLinkStore.keyOf(column) : null; 097 for (ResultColumn resultColumn : parent.getColumns()) { 098 Boolean same = StarLinkStore.identityAnswer(resultColumn, nameKey); 099 if (same != null ? same.booleanValue() 100 : sameColumnRef(resultColumn.getName(), column.toString()) 101 || sameColumnName(resultColumn.getName(), column.getColumnNameOnly())) { 102 found = true; 103 break; 104 } 105 } 106 if (!found) { 107 starSourceColumn.bindStarLinkColumn(column); 108 } 109 } 110 } else if (starSourceColumn instanceof TableColumn) { 111 Table parent = ((TableColumn) starSourceColumn).getTable(); 112 boolean found = false; 113 for (TObjectName column : columns) { 114 StarLinkKey nameKey = StarLinkStore.IDENTITY ? StarLinkStore.keyOf(column) : null; 115 for (TableColumn resultColumn : parent.getColumns()) { 116 Boolean same = StarLinkStore.identityAnswer(resultColumn, nameKey); 117 if (same != null ? same.booleanValue() 118 : sameColumnRef(resultColumn.getName(), column.toString()) 119 || sameColumnName(resultColumn.getName(), column.getColumnNameOnly())) { 120 found = true; 121 break; 122 } 123 } 124 if (!found) { 125 starSourceColumn.bindStarLinkColumn(column); 126 } 127 } 128 } 129 } 130 } 131 } 132 133 /** The column's name as the model publishes it (a display string, not an identity). */ 134 public abstract String getName(); 135 136 private final StarLinkStore starLinks = new StarLinkStore(); 137 138 StarLinkStore starLinks() { 139 return starLinks; 140 } 141 142 /** 143 * Links a name a star expansion produced to this column, and passes it on to the columns this column's star was 144 * expanded from. A wildcard is never linked. Returns whether the name started a new star link rather than joining 145 * the link of its display. 146 */ 147 public boolean bindStarLinkColumn(TObjectName objectName) { 148 if (objectName == null) { 149 return false; 150 } 151 int links = starLinks.size(); 152 if (starLinks.bind(objectName) == null) { 153 return false; 154 } 155 boolean added = starLinks.size() > links; 156 propagateStarLink(objectName); 157 return added; 158 } 159 160 /** Passes a name just linked to this column on to the columns its star was expanded from. */ 161 void propagateStarLink(TObjectName objectName) { 162 } 163 164 public boolean hasStarLinkColumn() { 165 return !starLinks.isEmpty(); 166 } 167 168 /** Every linked name, link by link in link order. */ 169 public List<TObjectName> getStarLinkColumnList() { 170 return starLinks.allNames(); 171 } 172 173 /** The published name of each star link, in link order. */ 174 public List<String> getStarLinkColumnNames() { 175 return starLinks.displays(); 176 } 177 178 /** The number of star links. */ 179 public int starLinkCount() { 180 return starLinks.size(); 181 } 182 183 /** The published name of the star link at {@code ordinal}. */ 184 public String starLinkName(int ordinal) { 185 return starLinks.published(ordinal); 186 } 187 188 /** The display of the star link at {@code ordinal}: its first name's legacy fold, for legacy comparisons. */ 189 String starLinkDisplay(int ordinal) { 190 return starLinks.display(ordinal); 191 } 192 193 /** 194 * The identifier key of the star link at {@code ordinal}, which the column published for it carries so that 195 * output merges keep two links of one display apart; null for a label, and while links are grouped by display. 196 */ 197 public CanonKey starLinkIdentifier(int ordinal) { 198 return starLinks.identifier(ordinal); 199 } 200 201 /** The first name linked by the star link at {@code ordinal}. */ 202 public TObjectName getStarLinkColumnName(int ordinal) { 203 return starLinks.entries().get(ordinal).representative(); 204 } 205 206 /** 207 * A key of the star link at {@code ordinal}, for remembering which links were seen: two links of any columns have 208 * equal keys when they link the same name. Nothing else about it is part of the contract. 209 */ 210 public Object starLinkKey(int ordinal) { 211 return starLinks.key(ordinal); 212 } 213 214 /** Whether the star link at {@code ordinal} is published under the name {@code model} publishes. */ 215 public boolean starLinkNamesColumn(int ordinal, Column model) { 216 return starLinks.displaysColumn(ordinal, model); 217 } 218 219 /** 220 * The matcher that pairs the star link at {@code ordinal}, as the target of an expansion, with source links and 221 * model columns; null when the link has no name to match by. 222 */ 223 public StarLinkMatcher starLinkMatcher(int ordinal) { 224 return starLinks.matcher(ordinal); 225 } 226 227 /** The spelling a catalog is given for the column the star link at {@code ordinal} links. */ 228 public String starLinkCatalogSpelling(int ordinal) { 229 return starLinks.catalogSpelling(ordinal); 230 } 231 232 /** 233 * The spelling as SQL of the column the star link at {@code ordinal} names, when links are grouped by identity and 234 * the link is an identity's; null otherwise. An output merge names two columns of one table apart from it. 235 */ 236 public String starLinkSqlSpelling(int ordinal) { 237 return starLinks.sqlSpelling(ordinal); 238 } 239 240 /** 241 * A name for the star link at {@code ordinal}, spelled as it is published, that stands for that link wherever it 242 * is bound. 243 */ 244 public TObjectName starLinkNameNode(int ordinal) { 245 StarLinkEntry entry = starLinks.entries().get(ordinal); 246 return DeclaredColumnName.ofLink(entry, entry.display()); 247 } 248 249 /** A name built from {@code text} that publishes the name of {@code model}. */ 250 public static TObjectName nameOfColumn(Column model, String text) { 251 return DeclaredColumnName.ofModel(model, text); 252 } 253 254 /** A name built from {@code text} that names no column: expression text, a literal. */ 255 public static TObjectName nameOfText(String text) { 256 return DeclaredColumnName.ofLabel(text); 257 } 258 259 /** A name spelled {@code text} for the column a catalog stores as {@code storedName}. */ 260 public static TObjectName nameOfCatalogColumn(String storedName, String text) { 261 return DeclaredColumnName.ofCatalog(storedName, text); 262 } 263 264 /** The ordinal of the star link {@code objectName} would join, or -1 when there is none. */ 265 public int starLinkIndexOf(TObjectName objectName) { 266 return starLinks.indexOf(objectName); 267 } 268 269 /** 270 * The ordinal of the star link that names the column {@code model} (null for an element that is no column), whose 271 * name folds to {@code display}; -1 when there is none. 272 */ 273 public int starLinkIndexOf(Column model, String display) { 274 return starLinks.indexOf(model, display); 275 } 276 277 /** Whether this column has a star link that {@code objectName} would join. */ 278 public boolean containsStarLinkColumn(TObjectName objectName) { 279 return starLinks.indexOf(objectName) >= 0; 280 } 281 282 /** 283 * The star links as a map from each link's display (its first name's legacy fold) to its linked names, in link 284 * order. It is a live view: writing to it writes to this column's links. While links are grouped by identity, a 285 * display stands for the first link that has it; {@link #getStarLinkColumnNames()} and {@link #starLinkName(int)} 286 * give each link's published name. 287 */ 288 public Map<String, Set<TObjectName>> getStarLinkColumns() { 289 return starLinks.legacyView(); 290 } 291 292 /** 293 * Takes over the star links of {@code source}: each of them replaces this column's link of the same published 294 * name, or is appended. The links are shared, not copied, so a name later linked to one of them on either column 295 * is seen by both. 296 */ 297 public void shareStarLinksFrom(Column source) { 298 if (source != null) { 299 starLinks.share(source.starLinks); 300 } 301 } 302 303 /** 304 * Adds the linked names of {@code source} to this column's link of the same published name, appending a link of 305 * this column's own when there is none. Nothing is shared. 306 */ 307 public void mergeStarLinksFrom(Column source) { 308 if (source != null) { 309 starLinks.merge(source.starLinks); 310 } 311 } 312 313 /** Removes the star link that holds {@code objectName} itself, and returns whether there was one. */ 314 public boolean removeStarLinkOf(TObjectName objectName) { 315 return objectName != null && starLinks.removeEntryOf(objectName); 316 } 317 318 /** 319 * Column-name equality routed through the unified façade; falls back to 320 * case-insensitive comparison when no analysis vendor is bound. 321 */ 322 private static boolean sameColumnName(String a, String b) { 323 if (a == null || b == null) { 324 return false; 325 } 326 if (DlineageUtil.configuresIdentifierRules()) { 327 return DlineageUtil.sameName(ESQLDataObjectType.dotColumn, a, b); 328 } 329 EDbVendor vendor = ModelBindingManager.getGlobalVendor(); 330 return vendor == null ? a.equalsIgnoreCase(b) 331 : SQLUtil.sameName(vendor, ESQLDataObjectType.dotColumn, a, b); 332 } 333 334 /** 335 * Like {@link #sameColumnName} but for possibly-qualified references 336 * (TObjectName.toString() can be {@code table.column}); routes through the 337 * segmenting compare instead of the single-segment façade. 338 */ 339 private static boolean sameColumnRef(String a, String b) { 340 if (a == null || b == null) { 341 return false; 342 } 343 EDbVendor vendor = ModelBindingManager.getGlobalVendor(); 344 return vendor == null ? a.equalsIgnoreCase(b) 345 : SQLUtil.compareIdentifier(vendor, ESQLDataObjectType.dotColumn, a, b); 346 } 347 348}