<commit_before>/*
 * DBeaver - Universal Database Manager
 * Copyright (C) 2010-2016 <NAME> (<EMAIL>)
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License (version 2)
 * as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */
package org.jkiss.dbeaver.ext.postgresql.model;

import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.ext.postgresql.PostgreUtils;
import org.jkiss.dbeaver.model.DBPDataKind;
import org.jkiss.dbeaver.model.DBUtils;
import org.jkiss.dbeaver.model.exec.DBCException;
import org.jkiss.dbeaver.model.exec.jdbc.JDBCPreparedStatement;
import org.jkiss.dbeaver.model.exec.jdbc.JDBCResultSet;
import org.jkiss.dbeaver.model.exec.jdbc.JDBCSession;
import org.jkiss.dbeaver.model.exec.jdbc.JDBCStatement;
import org.jkiss.dbeaver.model.impl.jdbc.JDBCUtils;
import org.jkiss.dbeaver.model.impl.jdbc.cache.JDBCObjectCache;
import org.jkiss.dbeaver.model.impl.jdbc.struct.JDBCDataType;
import org.jkiss.dbeaver.model.meta.Property;
import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
import org.jkiss.dbeaver.model.struct.DBSDataType;
import org.jkiss.dbeaver.model.struct.DBSEntityAssociation;
import org.jkiss.dbeaver.model.struct.DBSEntityConstraint;
import org.jkiss.dbeaver.model.struct.DBSEntityType;
import org.jkiss.utils.ArrayUtils;
import org.jkiss.utils.CommonUtils;

import java.sql.SQLException;
import java.sql.Types;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

/**
 * PostgreTypeType
 */
public class PostgreDataType extends JDBCDataType<PostgreSchema> implements PostgreClass
{
    static final Log log = Log.getLog(PostgreDataType.class);

    private static final String CAT_MAIN = "Main";
    private static final String CAT_MISC = "Miscellaneous";
    private static final String CAT_MODIFIERS = "Modifiers";
    private static final String CAT_FUNCTIONS = "Functions";
    private static final String CAT_ARRAY = "Array";

    private static String[] OID_TYPES = new String[] {
        "regproc",
        "regprocedure",
        "regoper",
        "regoperator",
        "regclass",
        "regtype",
        "regconfig",
        "regdictionary",
    };

    private int typeId;
    private PostgreTypeType typeType;
    private PostgreTypeCategory typeCategory;

    private final int ownerId;
    private boolean isByValue;
    private boolean isPreferred;
    private String arrayDelimiter;
    private int classId;
    private int elementTypeId;
    private int arrayItemTypeId;
    private String inputFunc;
    private String outputFunc;
    private String receiveFunc;
    private String sendFunc;
    private String modInFunc;
    private String modOutFunc;
    private String analyzeFunc;
    private PostgreTypeAlign align = PostgreTypeAlign.c;
    private PostgreTypeStorage storage = PostgreTypeStorage.p;
    private boolean isNotNull;
    private int baseTypeId;
    private int typeMod;
    private int arrayDim;
    private int collationId;
    private String defaultValue;

    private final AttributeCache attributeCache;
    private Object[] enumValues;

    public PostgreDataType(@NotNull JDBCSession monitor, @NotNull PostgreSchema owner, int typeId, int valueType, String name, int length, JDBCResultSet dbResult) throws DBException {
        super(owner, valueType, name, null, false, true, length, -1, -1);

        this.typeId = typeId;
        this.typeType = PostgreTypeType.b;
        try {
            this.typeType = PostgreTypeType.valueOf(JDBCUtils.safeGetString(dbResult, "typtype"));
        } catch (IllegalArgumentException e) {
            log.debug(e);
        }
        this.typeCategory = PostgreTypeCategory.X;
        try {
            this.typeCategory = PostgreTypeCategory.valueOf(JDBCUtils.safeGetString(dbResult, "typcategory"));
        } catch (IllegalArgumentException e) {
            log.debug(e);
        }

        this.ownerId = JDBCUtils.safeGetInt(dbResult, "typowner");
        this.isByValue = JDBCUtils.safeGetBoolean(dbResult, "typbyval");
        this.isPreferred = JDBCUtils.safeGetBoolean(dbResult, "typispreferred");
        this.arrayDelimiter = JDBCUtils.safeGetString(dbResult, "typdelim");
        this.classId = JDBCUtils.safeGetInt(dbResult, "typrelid");
        this.elementTypeId = JDBCUtils.safeGetInt(dbResult, "typelem");
        this.arrayItemTypeId = JDBCUtils.safeGetInt(dbResult, "typarray");
        this.inputFunc = JDBCUtils.safeGetString(dbResult, "typinput");
        this.outputFunc = JDBCUtils.safeGetString(dbResult, "typoutput");
        this.receiveFunc = JDBCUtils.safeGetString(dbResult, "typreceive");
        this.sendFunc = JDBCUtils.safeGetString(dbResult, "typsend");
        this.modInFunc = JDBCUtils.safeGetString(dbResult, "typmodin");
        this.modOutFunc = JDBCUtils.safeGetString(dbResult, "typmodout");
        this.analyzeFunc = JDBCUtils.safeGetString(dbResult, "typanalyze");
        this.align = PostgreTypeAlign.valueOf(JDBCUtils.safeGetString(dbResult, "typalign"));
        this.storage = PostgreTypeStorage.valueOf(JDBCUtils.safeGetString(dbResult, "typstorage"));
        this.isNotNull = JDBCUtils.safeGetBoolean(dbResult, "typnotnull");
        this.baseTypeId = JDBCUtils.safeGetInt(dbResult, "typbasetype");
        this.typeMod = JDBCUtils.safeGetInt(dbResult, "typtypmod");
        this.arrayDim = JDBCUtils.safeGetInt(dbResult, "typndims");
        this.collationId = JDBCUtils.safeGetInt(dbResult, "typcollation");
        this.defaultValue = JDBCUtils.safeGetString(dbResult, "typdefault");

        this.attributeCache = hasAttributes() ? new AttributeCache() : null;

        if (typeCategory == PostgreTypeCategory.E) {
            readEnumValues(monitor);
        }
    }

    private void readEnumValues(JDBCSession session) throws DBException {
        try (JDBCPreparedStatement dbStat = session.prepareStatement(
            "SELECT e.enumlabel \n" +
                "FROM pg_catalog.pg_enum e\n" +
                "WHERE e.enumtypid=?")) {
            dbStat.setInt(1, getObjectId());
            try (JDBCResultSet rs = dbStat.executeQuery()) {
                List<String> values = new ArrayList<>();
                while (rs.nextRow()) {
                    values.add(JDBCUtils.safeGetString(rs, 1));
                }
                enumValues = values.toArray();
            }
        } catch (SQLException e) {
            throw new DBException("Error reading enum values", e, getDataSource());
        }
    }

    @NotNull
    @Override
    public PostgreDataSource getDataSource() {
        return (PostgreDataSource) super.getDataSource();
    }

    @NotNull
    @Override
    public PostgreDatabase getDatabase() {
        return getParentObject().getDatabase();
    }

    @Override
    public DBPDataKind getDataKind()
    {
        switch (typeCategory) {
            case A: return DBPDataKind.ARRAY;
            case B: return DBPDataKind.BOOLEAN;
            case C: return DBPDataKind.STRUCT;
            case D: return DBPDataKind.DATETIME;
            case E: return DBPDataKind.OBJECT;
            case N: return DBPDataKind.NUMERIC;
            case S: return DBPDataKind.STRING;
        }
        return super.getDataKind();
    }

    @Nullable
    @Override
    public DBSDataType getComponentType(@NotNull DBRProgressMonitor monitor) throws DBCException {
        return getElementType();
    }

    @Nullable
    @Override
    public Object geTypeExtension() {
        return typeCategory;
    }

    @Override
    public int getObjectId() {
        return typeId;
    }

    @Property(category = CAT_MAIN, viewable = true, order = 10)
    public PostgreTypeType getTypeType() {
        return typeType;
    }

    @Property(category = CAT_MAIN, viewable = true, order = 11)
    public PostgreTypeCategory getTypeCategory() {
        return typeCategory;
    }

    @Property(category = CAT_MAIN, viewable = true, order = 12)
    public PostgreDataType getBaseType() {
        return resolveType(baseTypeId);
    }

    @Property(category = CAT_MAIN, viewable = true, order = 13)
    public PostgreDataType getElementType() {
        return elementTypeId == 0 ? null : resolveType(elementTypeId);
    }

    @Property(category = CAT_MAIN, order = 15)
    public PostgreAuthId getOwner(DBRProgressMonitor monitor) throws DBException {
        return PostgreUtils.getObjectById(monitor, getDatabase().authIdCache, getDatabase(), ownerId);
    }

    @Property(category = CAT_MISC)
    public boolean isByValue() {
        return isByValue;
    }

    @Property(category = CAT_MISC)
    public boolean isPreferred() {
        return isPreferred;
    }

    @Property(category = CAT_MISC)
    public String getDefaultValue() {
        return defaultValue;
    }

    @Property(category = CAT_FUNCTIONS)
    public String getInputFunc() {
        return inputFunc;
    }

    @Property(category = CAT_FUNCTIONS)
    public String getOutputFunc() {
        return outputFunc;
    }

    @Property(category = CAT_FUNCTIONS)
    public String getReceiveFunc() {
        return receiveFunc;
    }

    @Property(category = CAT_FUNCTIONS)
    public String getSendFunc() {
        return sendFunc;
    }

    @Property(category = CAT_FUNCTIONS)
    public String getModInFunc() {
        return modInFunc;
    }

    @Property(category = CAT_FUNCTIONS)
    public String getModOutFunc() {
        return modOutFunc;
    }

    @Property(category = CAT_FUNCTIONS)
    public String getAnalyzeFunc() {
        return analyzeFunc;
    }

    @Property(category = CAT_MODIFIERS)
    public PostgreTypeAlign getAlign() {
        return align;
    }

    @Property(category = CAT_MODIFIERS)
    public PostgreTypeStorage getStorage() {
        return storage;
    }

    @Property(category = CAT_MODIFIERS)
    public boolean isNotNull() {
        return isNotNull;
    }

    @Property(category = CAT_MODIFIERS)
    public int getTypeMod() {
        return typeMod;
    }

    @Property(category = CAT_MODIFIERS)
    public int getCollationId() {
        return collationId;
    }

    @Property(category = CAT_ARRAY)
    public String getArrayDelimiter() {
        return arrayDelimiter;
    }

    @Property(category = CAT_ARRAY)
    public PostgreDataType getArrayItemType() {
        return arrayItemTypeId == 0 ? null : resolveType(arrayItemTypeId);
    }

    // Plain type
    public boolean isPlainType() {
        return arrayItemTypeId != 0;
    }

    @Property(category = CAT_ARRAY)
    public int getArrayDim() {
        return arrayDim;
    }

    public boolean hasAttributes() {
        return typeType == PostgreTypeType.c && classId >= 0;
    }

    private PostgreDataType resolveType(int typeId) {
        if (typeId <= 0) {
            return null;
        }
        final PostgreDataType dataType = getDatabase().dataTypeCache.getDataType(typeId);
        if (dataType == null) {
            log.debug("Data type '" + typeId + "' not found");
        }
        return dataType;
    }

    public static PostgreDataType readDataType(@NotNull JDBCSession session, @NotNull PostgreDatabase owner, @NotNull JDBCResultSet dbResult) throws SQLException, DBException
    {
        int schemaId = JDBCUtils.safeGetInt(dbResult, "typnamespace");
        final PostgreSchema schema = owner.getSchema(dbResult.getSourceStatement().getConnection().getProgressMonitor(), schemaId);
        int typeId = JDBCUtils.safeGetInt(dbResult, "oid");
        String name = JDBCUtils.safeGetString(dbResult, "typname");
        if (CommonUtils.isEmpty(name)) {
            return null;
        }
        int typeLength = JDBCUtils.safeGetInt(dbResult, "typlen");
        PostgreTypeCategory typeCategory;
            final String catString = JDBCUtils.safeGetString(dbResult, "typcategory");
            if (catString == null) {
                typeCategory = null;
            } else {
                try {
                    typeCategory = PostgreTypeCategory.valueOf(catString);
                } catch (IllegalArgumentException e) {
                    log.debug(e);<commit_msg>PostgreSQL types meta info read fix<commit_after>/*
 * DBeaver - Universal Database Manager
 * Copyright (C) 2010-2016 <NAME> (<EMAIL>)
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License (version 2)
 * as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */
package org.jkiss.dbeaver.ext.postgresql.model;

import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.ext.postgresql.PostgreUtils;
import org.jkiss.dbeaver.model.DBPDataKind;
import org.jkiss.dbeaver.model.DBUtils;
import org.jkiss.dbeaver.model.exec.DBCException;
import org.jkiss.dbeaver.model.exec.jdbc.JDBCPreparedStatement;
import org.jkiss.dbeaver.model.exec.jdbc.JDBCResultSet;
import org.jkiss.dbeaver.model.exec.jdbc.JDBCSession;
import org.jkiss.dbeaver.model.exec.jdbc.JDBCStatement;
import org.jkiss.dbeaver.model.impl.jdbc.JDBCUtils;
import org.jkiss.dbeaver.model.impl.jdbc.cache.JDBCObjectCache;
import org.jkiss.dbeaver.model.impl.jdbc.struct.JDBCDataType;
import org.jkiss.dbeaver.model.meta.Property;
import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
import org.jkiss.dbeaver.model.struct.DBSDataType;
import org.jkiss.dbeaver.model.struct.DBSEntityAssociation;
import org.jkiss.dbeaver.model.struct.DBSEntityConstraint;
import org.jkiss.dbeaver.model.struct.DBSEntityType;
import org.jkiss.utils.ArrayUtils;
import org.jkiss.utils.CommonUtils;

import java.sql.SQLException;
import java.sql.Types;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Locale;

/**
 * PostgreTypeType
 */
public class PostgreDataType extends JDBCDataType<PostgreSchema> implements PostgreClass
{
    static final Log log = Log.getLog(PostgreDataType.class);

    private static final String CAT_MAIN = "Main";
    private static final String CAT_MISC = "Miscellaneous";
    private static final String CAT_MODIFIERS = "Modifiers";
    private static final String CAT_FUNCTIONS = "Functions";
    private static final String CAT_ARRAY = "Array";

    private static String[] OID_TYPES = new String[] {
        "regproc",
        "regprocedure",
        "regoper",
        "regoperator",
        "regclass",
        "regtype",
        "regconfig",
        "regdictionary",
    };

    private int typeId;
    private PostgreTypeType typeType;
    private PostgreTypeCategory typeCategory;

    private final int ownerId;
    private boolean isByValue;
    private boolean isPreferred;
    private String arrayDelimiter;
    private int classId;
    private int elementTypeId;
    private int arrayItemTypeId;
    private String inputFunc;
    private String outputFunc;
    private String receiveFunc;
    private String sendFunc;
    private String modInFunc;
    private String modOutFunc;
    private String analyzeFunc;
    private PostgreTypeAlign align = PostgreTypeAlign.c;
    private PostgreTypeStorage storage = PostgreTypeStorage.p;
    private boolean isNotNull;
    private int baseTypeId;
    private int typeMod;
    private int arrayDim;
    private int collationId;
    private String defaultValue;

    private final AttributeCache attributeCache;
    private Object[] enumValues;

    public PostgreDataType(@NotNull JDBCSession monitor, @NotNull PostgreSchema owner, int typeId, int valueType, String name, int length, JDBCResultSet dbResult) throws DBException {
        super(owner, valueType, name, null, false, true, length, -1, -1);

        this.typeId = typeId;
        this.typeType = PostgreTypeType.b;
        try {
            String typTypeStr = JDBCUtils.safeGetString(dbResult, "typtype");
            if (typTypeStr != null) {
                this.typeType = PostgreTypeType.valueOf(typTypeStr.toLowerCase(Locale.ENGLISH));
            }
        } catch (IllegalArgumentException e) {
            log.debug(e);
        }
        this.typeCategory = PostgreTypeCategory.X;
        try {
            String typCategoryStr = JDBCUtils.safeGetString(dbResult, "typcategory");
            if (typCategoryStr != null) {
                this.typeCategory = PostgreTypeCategory.valueOf(typCategoryStr.toUpperCase(Locale.ENGLISH));
            }
        } catch (IllegalArgumentException e) {
            log.debug(e);
        }

        this.ownerId = JDBCUtils.safeGetInt(dbResult, "typowner");
        this.isByValue = JDBCUtils.safeGetBoolean(dbResult, "typbyval");
        this.isPreferred = JDBCUtils.safeGetBoolean(dbResult, "typispreferred");
        this.arrayDelimiter = JDBCUtils.safeGetString(dbResult, "typdelim");
        this.classId = JDBCUtils.safeGetInt(dbResult, "typrelid");
        this.elementTypeId = JDBCUtils.safeGetInt(dbResult, "typelem");
        this.arrayItemTypeId = JDBCUtils.safeGetInt(dbResult, "typarray");
        this.inputFunc = JDBCUtils.safeGetString(dbResult, "typinput");
        this.outputFunc = JDBCUtils.safeGetString(dbResult, "typoutput");
        this.receiveFunc = JDBCUtils.safeGetString(dbResult, "typreceive");
        this.sendFunc = JDBCUtils.safeGetString(dbResult, "typsend");
        this.modInFunc = JDBCUtils.safeGetString(dbResult, "typmodin");
        this.modOutFunc = JDBCUtils.safeGetString(dbResult, "typmodout");
        this.analyzeFunc = JDBCUtils.safeGetString(dbResult, "typanalyze");
        try {
            this.align = PostgreTypeAlign.valueOf(JDBCUtils.safeGetString(dbResult, "typalign"));
        } catch (IllegalArgumentException e) {
            log.debug(e);
        }
        try {
            this.storage = PostgreTypeStorage.valueOf(JDBCUtils.safeGetString(dbResult, "typstorage"));
        } catch (IllegalArgumentException e) {
            log.debug(e);
        }
        this.isNotNull = JDBCUtils.safeGetBoolean(dbResult, "typnotnull");
        this.baseTypeId = JDBCUtils.safeGetInt(dbResult, "typbasetype");
        this.typeMod = JDBCUtils.safeGetInt(dbResult, "typtypmod");
        this.arrayDim = JDBCUtils.safeGetInt(dbResult, "typndims");
        this.collationId = JDBCUtils.safeGetInt(dbResult, "typcollation");
        this.defaultValue = JDBCUtils.safeGetString(dbResult, "typdefault");

        this.attributeCache = hasAttributes() ? new AttributeCache() : null;

        if (typeCategory == PostgreTypeCategory.E) {
            readEnumValues(monitor);
        }
    }

    private void readEnumValues(JDBCSession session) throws DBException {
        try (JDBCPreparedStatement dbStat = session.prepareStatement(
            "SELECT e.enumlabel \n" +
                "FROM pg_catalog.pg_enum e\n" +
                "WHERE e.enumtypid=?")) {
            dbStat.setInt(1, getObjectId());
            try (JDBCResultSet rs = dbStat.executeQuery()) {
                List<String> values = new ArrayList<>();
                while (rs.nextRow()) {
                    values.add(JDBCUtils.safeGetString(rs, 1));
                }
                enumValues = values.toArray();
            }
        } catch (SQLException e) {
            throw new DBException("Error reading enum values", e, getDataSource());
        }
    }

    @NotNull
    @Override
    public PostgreDataSource getDataSource() {
        return (PostgreDataSource) super.getDataSource();
    }

    @NotNull
    @Override
    public PostgreDatabase getDatabase() {
        return getParentObject().getDatabase();
    }

    @Override
    public DBPDataKind getDataKind()
    {
        switch (typeCategory) {
            case A: return DBPDataKind.ARRAY;
            case B: return DBPDataKind.BOOLEAN;
            case C: return DBPDataKind.STRUCT;
            case D: return DBPDataKind.DATETIME;
            case E: return DBPDataKind.OBJECT;
            case N: return DBPDataKind.NUMERIC;
            case S: return DBPDataKind.STRING;
        }
        return super.getDataKind();
    }

    @Nullable
    @Override
    public DBSDataType getComponentType(@NotNull DBRProgressMonitor monitor) throws DBCException {
        return getElementType();
    }

    @Nullable
    @Override
    public Object geTypeExtension() {
        return typeCategory;
    }

    @Override
    public int getObjectId() {
        return typeId;
    }

    @Property(category = CAT_MAIN, viewable = true, order = 10)
    public PostgreTypeType getTypeType() {
        return typeType;
    }

    @Property(category = CAT_MAIN, viewable = true, order = 11)
    public PostgreTypeCategory getTypeCategory() {
        return typeCategory;
    }

    @Property(category = CAT_MAIN, viewable = true, order = 12)
    public PostgreDataType getBaseType() {
        return resolveType(baseTypeId);
    }

    @Property(category = CAT_MAIN, viewable = true, order = 13)
    public PostgreDataType getElementType() {
        return elementTypeId == 0 ? null : resolveType(elementTypeId);
    }

    @Property(category = CAT_MAIN, order = 15)
    public PostgreAuthId getOwner(DBRProgressMonitor monitor) throws DBException {
        return PostgreUtils.getObjectById(monitor, getDatabase().authIdCache, getDatabase(), ownerId);
    }

    @Property(category = CAT_MISC)
    public boolean isByValue() {
        return isByValue;
    }

    @Property(category = CAT_MISC)
    public boolean isPreferred() {
        return isPreferred;
    }

    @Property(category = CAT_MISC)
    public String getDefaultValue() {
        return defaultValue;
    }

    @Property(category = CAT_FUNCTIONS)
    public String getInputFunc() {
        return inputFunc;
    }

    @Property(category = CAT_FUNCTIONS)
    public String getOutputFunc() {
        return outputFunc;
    }

    @Property(category = CAT_FUNCTIONS)
    public String getReceiveFunc() {
        return receiveFunc;
    }

    @Property(category = CAT_FUNCTIONS)
    public String getSendFunc() {
        return sendFunc;
    }

    @Property(category = CAT_FUNCTIONS)
    public String getModInFunc() {
        return modInFunc;
    }

    @Property(category = CAT_FUNCTIONS)
    public String getModOutFunc() {
        return modOutFunc;
    }

    @Property(category = CAT_FUNCTIONS)
    public String getAnalyzeFunc() {
        return analyzeFunc;
    }

    @Property(category = CAT_MODIFIERS)
    public PostgreTypeAlign getAlign() {
        return align;
    }

    @Property(category = CAT_MODIFIERS)
    public PostgreTypeStorage getStorage() {
        return storage;
    }

    @Property(category = CAT_MODIFIERS)
    public boolean isNotNull() {
        return isNotNull;
    }

    @Property(category = CAT_MODIFIERS)
    public int getTypeMod() {
        return typeMod;
    }

    @Property(category = CAT_MODIFIERS)
    public int getCollationId() {
        return collationId;
    }

    @Property(category = CAT_ARRAY)
    public String getArrayDelimiter() {
        return arrayDelimiter;
    }

    @Property(category = CAT_ARRAY)
    public PostgreDataType getArrayItemType() {
        return arrayItemTypeId == 0 ? null : resolveType(arrayItemTypeId);
    }

    // Plain type
    public boolean isPlainType() {
        return arrayItemTypeId != 0;
    }

    @Property(category = CAT_ARRAY)
    public int getArrayDim() {
        return arrayDim;
    }

    public boolean hasAttributes() {
        return typeType == PostgreTypeType.c && classId >= 0;
    }

    private PostgreDataType resolveType(int typeId) {
        if (typeId <= 0) {
            return null;
        }
        final PostgreDataType dataType = getDatabase().dataTypeCache.getDataType(typeId);
        if (dataType == null) {
            log.debug("Data type '" + typeId + "' not found");
        }
        return dataType;
    }

    public static PostgreDataType readDataType(@NotNull JDBCSession session, @NotNull PostgreDatabase owner, @NotNull JDBCResultSet dbResult) throws SQLException, DBException
    {
        int schemaId = JDBCUtils.safeGetInt(dbResult, "typnamespace");
        final PostgreSchema schema = owner.getSchema(dbResult.getSourceStatement().getConnection().getProgressMonitor(), schemaId);
        int typeId = JDBCUtils.safeGetInt(dbResult, "oid");
        String name = JDBCUtils.safeGetString(dbResult, "typname");
        if (CommonUtils.isEmpty(name)) {
            return null;
        }
        int typeLength = JDBCUtils.safeGetInt(dbResult, "typlen");
        PostgreTypeCategory typeCategory;
            final String catString = JDBCUtils.safeGetString(dbResult, "typcategory");
            if (catString == null) {
                typeCategory = null;
            } else {
                try {
                    typeCategory = PostgreTypeCategory.valueOf(catString.toUpperCase());
                } catch (IllegalArgumentException e) {
                    log.debug(e);
{'fraction_non_alphanumeric': 0.09303765625604705, 'fraction_numerical': 0.0030186926738650878, 'mean_word_length': 2.9396249428266503, 'pattern_counts': {'":': 0, '<': 15, '<?xml version=': 0, '>': 15, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1571687', 'n_tokens_mistral': 7672, 'n_tokens_neox': 7397, 'n_words': 1722}