001package gudusoft.gsqlparser.dlineage.routine; 002 003import gudusoft.gsqlparser.EDataType; 004import gudusoft.gsqlparser.EDataTypeAttribute; 005import gudusoft.gsqlparser.EDbVendor; 006import gudusoft.gsqlparser.TSourceToken; 007import gudusoft.gsqlparser.nodes.TDatatypeAttribute; 008import gudusoft.gsqlparser.nodes.TObjectName; 009import gudusoft.gsqlparser.nodes.TTypeName; 010import gudusoft.gsqlparser.sqlenv.CanonKey; 011import gudusoft.gsqlparser.sqlenv.ESQLDataObjectType; 012import gudusoft.gsqlparser.sqlenv.IdentifierInputForm; 013import gudusoft.gsqlparser.sqlenv.IdentifierService; 014 015import java.util.ArrayList; 016import java.util.Collections; 017import java.util.List; 018import java.util.Objects; 019 020/** 021 * The declared type of one routine parameter, as a definition discriminator: two declarations of one 022 * routine name whose parameter types differ are different routines (overloads). 023 * 024 * <p>A type the parser recognizes is its {@link EDataType}, whatever its spelling, with the standard synonyms 025 * folded ({@code INT}, {@code INTEGER} and {@code INT4} are one type), and without modifiers: the vendors that 026 * overload by type do not tell overloads apart by length, precision, format or character set (PostgreSQL 027 * ignores typmods in a function's identity; Teradata says such attributes do not make a function unique). 028 * Only attributes that change the type itself are kept: an anchoring {@code %TYPE} or {@code %ROWTYPE}, and 029 * {@code WITH TIME ZONE}. A named type (a user-defined type, or an anchored reference) is its name path, each 030 * component keyed by the analysis {@link IdentifierService}, so a quoted {@code "T"} and an unquoted {@code t} 031 * stay apart where the vendor keeps them apart. An array or list type carries its element type. A type with 032 * no structure the parser exposes is its text as written, compared exactly, as before.</p> 033 * 034 * <p>It never decides whether an actual argument is compatible with a formal parameter.</p> 035 */ 036public final class ArgumentTypeKey { 037 038 /** A parameter the parser gave no data type. */ 039 public static final ArgumentTypeKey EMPTY = new ArgumentTypeKey(null, Collections.<CanonKey>emptyList(), 040 Collections.<String>emptyList(), null, null); 041 042 private final EDataType dataType; 043 private final List<CanonKey> path; 044 private final List<String> attributes; 045 private final ArgumentTypeKey element; 046 private final String fallbackText; 047 048 private ArgumentTypeKey(EDataType dataType, List<CanonKey> path, List<String> attributes, 049 ArgumentTypeKey element, String fallbackText) { 050 this.dataType = dataType; 051 this.path = path; 052 this.attributes = attributes; 053 this.element = element; 054 this.fallbackText = fallbackText; 055 } 056 057 public static ArgumentTypeKey of(TTypeName type, EDbVendor vendor, IdentifierService service) { 058 if (type == null) { 059 return EMPTY; 060 } 061 EDataType dataType = synonymOf(type.getDataType()); 062 List<String> attributes = new ArrayList<String>(); 063 if (dataType == EDataType.timestamp_with_time_zone_t || dataType == EDataType.timetz_t) { 064 // TIMESTAMPTZ is TIMESTAMP WITH TIME ZONE: one spelling of the type the attribute also expresses. 065 dataType = dataType == EDataType.timetz_t ? EDataType.time_t : EDataType.timestamp_t; 066 attributes.add(EDataTypeAttribute.with_time_zone_t.name()); 067 } 068 if (type.getDatatypeAttributeList() != null) { 069 for (int i = 0; i < type.getDatatypeAttributeList().size(); i++) { 070 TDatatypeAttribute attribute = type.getDatatypeAttributeList().getElement(i); 071 if (attribute != null && TYPE_ATTRIBUTES.contains(attribute.getAttributeType()) 072 && !attributes.contains(attribute.getAttributeType().name())) { 073 attributes.add(attribute.getAttributeType().name()); 074 } 075 } 076 } 077 ArgumentTypeKey element = type.getTypeOfList() == null ? null : of(type.getTypeOfList(), vendor, service); 078 TObjectName name = type.getDataTypeObjectName(); 079 boolean named = dataType == EDataType.generic_t || dataType == EDataType.unknown_t || !attributes.isEmpty(); 080 if (named && name != null) { 081 List<CanonKey> path = pathOf(name, service); 082 if (path != null) { 083 return new ArgumentTypeKey(dataType, path, attributes, element, null); 084 } 085 } 086 if (named) { 087 return new ArgumentTypeKey(dataType, Collections.<CanonKey>emptyList(), attributes, element, 088 type.getDataTypeName()); 089 } 090 return new ArgumentTypeKey(dataType, Collections.<CanonKey>emptyList(), attributes, element, null); 091 } 092 093 /** Attributes that change a type rather than modify it. */ 094 private static final java.util.Set<EDataTypeAttribute> TYPE_ATTRIBUTES = java.util.EnumSet.of( 095 EDataTypeAttribute.plsql_type_t, EDataTypeAttribute.plsql_rowtype_t, EDataTypeAttribute.type_t, 096 EDataTypeAttribute.with_time_zone_t); 097 098 /** One name for the standard synonyms of a built-in type. */ 099 static EDataType synonymOf(EDataType type) { 100 if (type == null) { 101 return null; 102 } 103 switch (type) { 104 case int_t: 105 case int4_t: 106 return EDataType.integer_t; 107 case int8_t: 108 return EDataType.bigint_t; 109 case bool_t: 110 return EDataType.boolean_t; 111 case varchar2_t: 112 return EDataType.varchar_t; 113 case nvarchar2_t: 114 return EDataType.nvarchar_t; 115 case number_t: 116 return EDataType.numeric_t; 117 case timestamp_without_time_zone_t: 118 return EDataType.timestamp_t; 119 case int2_t: 120 return EDataType.smallint_t; 121 case dec_t: 122 case decimal_t: 123 return EDataType.numeric_t; 124 case float8_t: 125 case double_t: 126 case double_precision_t: 127 return EDataType.double_precision_t; 128 case float4_t: 129 return EDataType.real_t; 130 case character_t: 131 return EDataType.char_t; 132 case ncharacter_t: 133 return EDataType.nchar_t; 134 default: 135 return type; 136 } 137 } 138 139 private static List<CanonKey> pathOf(TObjectName name, IdentifierService service) { 140 List<CanonKey> path = new ArrayList<CanonKey>(); 141 for (TSourceToken token : new TSourceToken[] {name.getServerToken(), name.getDatabaseToken(), 142 name.getSchemaToken(), name.getPackageToken(), name.getObjectToken(), name.getPartToken()}) { 143 if (token == null || (!path.isEmpty() && token.toString().isEmpty())) { 144 continue; 145 } 146 try { 147 path.add(service.canonKeyOf(token.toString(), ESQLDataObjectType.dotDataType, 148 IdentifierInputForm.SQL_LEXICAL)); 149 } catch (IllegalArgumentException notKeyable) { 150 return null; 151 } 152 } 153 return path.isEmpty() ? null : path; 154 } 155 156 @Override 157 public boolean equals(Object o) { 158 if (this == o) { 159 return true; 160 } 161 if (!(o instanceof ArgumentTypeKey)) { 162 return false; 163 } 164 ArgumentTypeKey other = (ArgumentTypeKey) o; 165 return dataType == other.dataType && path.equals(other.path) && attributes.equals(other.attributes) 166 && Objects.equals(element, other.element) && Objects.equals(fallbackText, other.fallbackText); 167 } 168 169 @Override 170 public int hashCode() { 171 return Objects.hash(dataType, path, attributes, element, fallbackText); 172 } 173 174 @Override 175 public String toString() { 176 if (this == EMPTY) { 177 return "<none>"; 178 } 179 StringBuilder b = new StringBuilder(String.valueOf(dataType)); 180 if (!path.isEmpty()) { 181 b.append(path); 182 } 183 if (fallbackText != null) { 184 b.append('\'').append(fallbackText).append('\''); 185 } 186 if (!attributes.isEmpty()) { 187 b.append(attributes); 188 } 189 if (element != null) { 190 b.append('<').append(element).append('>'); 191 } 192 return b.toString(); 193 } 194}