001package gudusoft.gsqlparser.common.structured;
002
003import gudusoft.gsqlparser.EDataType;
004import gudusoft.gsqlparser.nodes.TColumnDefinition;
005import gudusoft.gsqlparser.nodes.TObjectName;
006import gudusoft.gsqlparser.nodes.TTypeName;
007
008import java.util.ArrayList;
009import java.util.List;
010
011/**
012 * Reads the type a column's DDL declares into a {@link DeclaredTypeNode} tree. The grammars keep a nested type in
013 * different members of {@link TTypeName}; this is the one place that knows them (plan section 4.2):
014 * <ul>
015 *   <li>STRUCT / ROW fields: {@link TTypeName#getColumnDefList()} (Hive, Impala, Spark SQL, Databricks
016 *       {@code STRUCT<..>}, BigQuery, Athena, Presto/Trino {@code ROW(..)});</li>
017 *   <li>ARRAY element: {@link TTypeName#getTypeOfList()} (Hive {@code listType_t}, BigQuery, the Trino family) or
018 *       {@link TTypeName#getElementType()} (Databricks, Redshift);</li>
019 *   <li>MAP key and value: {@link TTypeName#getKeyType()} / {@link TTypeName#getValueType()} (Databricks) or
020 *       {@link TTypeName#getPrimitiveTypeOfMap()} / {@link TTypeName#getTypeOfMap()} (Hive, Impala, Redshift).</li>
021 * </ul>
022 * A part a grammar drops (Spark {@code ARRAY<T>}'s T, a Presto MAP's key and value) reads as null: the container is
023 * known, its parts are not.
024 */
025public final class DeclaredTypeReader {
026
027    private DeclaredTypeReader() {
028    }
029
030    /** The declared type of {@code type}; null for a null type. */
031    public static DeclaredTypeNode read(TTypeName type) {
032        return read(type, null);
033    }
034
035    private static DeclaredTypeNode read(TTypeName type, TObjectName fieldName) {
036        if (type == null) {
037            return null;
038        }
039        if (type.getColumnDefList() != null) {
040            List<DeclaredTypeNode> fields = new ArrayList<DeclaredTypeNode>();
041            for (int i = 0; i < type.getColumnDefList().size(); i++) {
042                TColumnDefinition field = type.getColumnDefList().getColumn(i);
043                if (field.getColumnName() == null) {
044                    // an unnamed field (BigQuery STRUCT<INT64, STRING>) cannot be read by name: no node
045                    continue;
046                }
047                DeclaredTypeNode node = read(field.getDatatype(), field.getColumnName());
048                fields.add(node != null ? node : new DeclaredTypeNode(DeclaredTypeNode.Kind.SCALAR,
049                        field.getColumnName(), null, null, null, field.getDatatype()));
050            }
051            return new DeclaredTypeNode(DeclaredTypeNode.Kind.STRUCT, fieldName, fields, null, null, type);
052        }
053        EDataType dataType = type.getDataType();
054        if (dataType == EDataType.array_t || dataType == EDataType.listType_t) {
055            TTypeName element = type.getTypeOfList() != null ? type.getTypeOfList() : type.getElementType();
056            return new DeclaredTypeNode(DeclaredTypeNode.Kind.ARRAY, fieldName, null, read(element, null), null, type);
057        }
058        if (dataType == EDataType.map_t || dataType == EDataType.mapType_t) {
059            TTypeName key = type.getKeyType() != null ? type.getKeyType() : type.getPrimitiveTypeOfMap();
060            TTypeName value = type.getValueType() != null ? type.getValueType() : type.getTypeOfMap();
061            return new DeclaredTypeNode(DeclaredTypeNode.Kind.MAP, fieldName, null, read(value, null),
062                    read(key, null), type);
063        }
064        if (dataType == EDataType.struct_t || dataType == EDataType.structType_t || dataType == EDataType.row_t) {
065            // a STRUCT whose member list the grammar did not keep: a STRUCT of unknown fields
066            return new DeclaredTypeNode(DeclaredTypeNode.Kind.STRUCT, fieldName, null, null, null, type);
067        }
068        return new DeclaredTypeNode(DeclaredTypeNode.Kind.SCALAR, fieldName, null, null, null, type);
069    }
070}