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}