From 2c3b1c48b7b067ee5969ee56f97d672083ac7c8f Mon Sep 17 00:00:00 2001 From: zhangpeng Date: Fri, 1 Dec 2023 16:53:31 +0800 Subject: [PATCH 1/2] feat: support compatibility b unsigned int metadata --- .../java/org/postgresql/core/TypeInfo.java | 10 +- .../main/java/org/postgresql/jdbc/PgType.java | 178 +++ .../java/org/postgresql/jdbc/PgTypeAttr.java | 37 + .../org/postgresql/jdbc/PgTypeCategory.java | 44 + .../java/org/postgresql/jdbc/PgTypeType.java | 33 + .../org/postgresql/jdbc/TypeInfoCache.java | 1054 ++++++++--------- .../test/jdbc2/UintMetadataTest.java | 123 ++ 7 files changed, 882 insertions(+), 597 deletions(-) create mode 100644 pgjdbc/src/main/java/org/postgresql/jdbc/PgType.java create mode 100644 pgjdbc/src/main/java/org/postgresql/jdbc/PgTypeAttr.java create mode 100644 pgjdbc/src/main/java/org/postgresql/jdbc/PgTypeCategory.java create mode 100644 pgjdbc/src/main/java/org/postgresql/jdbc/PgTypeType.java create mode 100644 pgjdbc/src/test/java/org/postgresql/test/jdbc2/UintMetadataTest.java diff --git a/pgjdbc/src/main/java/org/postgresql/core/TypeInfo.java b/pgjdbc/src/main/java/org/postgresql/core/TypeInfo.java index cd4dbf4..24dd03c 100644 --- a/pgjdbc/src/main/java/org/postgresql/core/TypeInfo.java +++ b/pgjdbc/src/main/java/org/postgresql/core/TypeInfo.java @@ -84,13 +84,11 @@ public interface TypeInfo { Iterator getPGTypeNamesWithSQLTypes(); - Class getPGobject(String type); + Class getPGobject(String type) throws SQLException; String getJavaClass(int oid) throws SQLException; - String getTypeForAlias(String alias); - - int getPrecision(int oid, int typmod); + int getPrecision(int oid, int typmod) throws SQLException; int getScale(int oid, int typmod); @@ -98,7 +96,7 @@ public interface TypeInfo { boolean isSigned(int oid); - int getDisplaySize(int oid, int typmod); + int getDisplaySize(int oid, int typmod) throws SQLException; int getMaximumPrecision(int oid); @@ -112,7 +110,7 @@ public interface TypeInfo { * @return true if the type requires quoting * @throws SQLException if something goes wrong */ - boolean requiresQuotingSqlType(int sqlType) throws SQLException; + boolean requiresQuotingSqlType(int sqlType); /** * Returns the attributes sql type list of the object based on oid diff --git a/pgjdbc/src/main/java/org/postgresql/jdbc/PgType.java b/pgjdbc/src/main/java/org/postgresql/jdbc/PgType.java new file mode 100644 index 0000000..409fc30 --- /dev/null +++ b/pgjdbc/src/main/java/org/postgresql/jdbc/PgType.java @@ -0,0 +1,178 @@ +/* + * Copyright (c) 2023, openGauss Development Group + * See the LICENSE file in the project root for more information. + */ + +package org.postgresql.jdbc; + +import org.postgresql.util.PGobject; + +import java.util.List; + +/** + * PgType entity class, used for pg_type basic information. + */ +public class PgType { + // embedded base types + private boolean jdbcBaseType; + private Integer oid; // pg_type.oid + private Integer typElem; // pg_type.typelem + private Integer typArray; // pg_type.typarray + private String typeName; // pg_type.typname + private String schemaName; // pg_type schema name + private char typDelim; // pg_type.typdelim array type oid -> base type array element delimiter + private PgTypeType typType; // pg_type.typtype + private PgTypeCategory typeCategory; // pg_type.typcategory + private List typeAttrs; // composite type attrs + private String JavaClass; // java class name (String) + private int sqlType; // java.sql.Types (Integer) + private Class PgObject; // extension pgobject (Class) + private int precision; + private int displaySize; + + public PgType(Integer oid, String typeName) { + this.oid = oid; + this.typeName = typeName; + } + + public PgType(Integer oid, String typeName, String schemaName, Integer typElem, + Integer typArray, char typDelim, PgTypeType typType, PgTypeCategory typeCategory, + List typeAttrs) { + this.oid = oid; + this.typeName = typeName; + this.schemaName = schemaName; + this.typElem = typElem; + this.typArray = typArray; + this.typDelim = typDelim; + this.typType = typType; + this.typeCategory = typeCategory; + this.typeAttrs = typeAttrs; + } + + public Integer getOid() { + return oid; + } + + public void setOid(Integer oid) { + this.oid = oid; + } + + public String getTypeName() { + return typeName; + } + + public String getTypeFullName() { + if (this.schemaName != null) { + return this.schemaName + "." + this.typeName; + } + return getTypeName(); + } + + public void setTypeName(String typeName) { + this.typeName = typeName; + } + + public String getSchemaName() { + return schemaName; + } + + public void setSchemaName(String schemaName) { + this.schemaName = schemaName; + } + + public Integer getTypElem() { + return typElem; + } + + public void setTypElem(Integer typElem) { + this.typElem = typElem; + } + + public Integer getTypArray() { + return typArray; + } + + public void setTypArray(Integer typArray) { + this.typArray = typArray; + } + + public char getTypDelim() { + return typDelim; + } + + public void setTypDelim(char typDelim) { + this.typDelim = typDelim; + } + + public PgTypeType getTypType() { + return typType; + } + + public void setTypType(PgTypeType typType) { + this.typType = typType; + } + + public PgTypeCategory getTypeCategory() { + return typeCategory; + } + + public void setTypeCategory(PgTypeCategory typeCategory) { + this.typeCategory = typeCategory; + } + + public List getTypeAttrs() { + return typeAttrs; + } + + public void setTypeAttrs(List typeAttrs) { + this.typeAttrs = typeAttrs; + } + + public String getJavaClass() { + return JavaClass; + } + + public void setJavaClass(String javaClass) { + JavaClass = javaClass; + } + + public int getSqlType() { + return sqlType; + } + + public void setSqlType(int sqlType) { + this.sqlType = sqlType; + } + + public Class getPgObject() { + return PgObject; + } + + public void setPgObject(Class pgObject) { + PgObject = pgObject; + } + + public boolean isJdbcBaseType() { + return jdbcBaseType; + } + + public void setJdbcBaseType(boolean jdbcBaseType) { + this.jdbcBaseType = jdbcBaseType; + } + + public int getPrecision() { + return precision; + } + + public void setPrecision(int precision) { + this.precision = precision; + } + + public int getDisplaySize() { + return displaySize; + } + + public void setDisplaySize(int displaySize) { + this.displaySize = displaySize; + } +} diff --git a/pgjdbc/src/main/java/org/postgresql/jdbc/PgTypeAttr.java b/pgjdbc/src/main/java/org/postgresql/jdbc/PgTypeAttr.java new file mode 100644 index 0000000..49def16 --- /dev/null +++ b/pgjdbc/src/main/java/org/postgresql/jdbc/PgTypeAttr.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2023, openGauss Development Group + * See the LICENSE file in the project root for more information. + */ + +package org.postgresql.jdbc; + +/** + * PgTypeAttr entity class, used to store composite type attrs. + */ +public class PgTypeAttr { + // attribute name + private String attrName; + // attribute oid + private PgType pgType; + + public PgTypeAttr(String attrName, PgType pgType) { + this.attrName = attrName; + this.pgType = pgType; + } + + public String getAttrName() { + return attrName; + } + + public void setAttrName(String attrName) { + this.attrName = attrName; + } + + public PgType getPgType() { + return pgType; + } + + public void setPgType(PgType pgType) { + this.pgType = pgType; + } +} diff --git a/pgjdbc/src/main/java/org/postgresql/jdbc/PgTypeCategory.java b/pgjdbc/src/main/java/org/postgresql/jdbc/PgTypeCategory.java new file mode 100644 index 0000000..d98ef20 --- /dev/null +++ b/pgjdbc/src/main/java/org/postgresql/jdbc/PgTypeCategory.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2023, openGauss Development Group + * See the LICENSE file in the project root for more information. + */ + +package org.postgresql.jdbc; + +/** + * Enumerates system-defined values of pg_type.typcategory. + * cat./src/include/catalog/pg_type.h + */ +public enum PgTypeCategory { + A("Array"), + B("Boolean"), + C("Composite"), + D("Date/time"), + E("Enum"), + G("Geometric"), + I("Network address"), + J("JSON"), + N("Numeric"), + P("Pseudo"), + R("Range"), //$NON-NLS-1$ + S("String"), + T("Timespan"), + U("User-defined"), + V("Bit-string"), + X("Unknown"), + Z("Internal-use types"), //$NON-NLS-1$ + F("Table Of Type Integer"), /* table of type, index by integer */ + Q("Table Of Type Varchar"), /* table of type, index by varchar */ + O("Table Of Type"), /* table of type */ + H("Set Type"); /* for set type */ + + private final String desc; + + PgTypeCategory(String desc) { + this.desc = desc; + } + + public String getName() { + return desc; + } +} diff --git a/pgjdbc/src/main/java/org/postgresql/jdbc/PgTypeType.java b/pgjdbc/src/main/java/org/postgresql/jdbc/PgTypeType.java new file mode 100644 index 0000000..7f41aed --- /dev/null +++ b/pgjdbc/src/main/java/org/postgresql/jdbc/PgTypeType.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2023, openGauss Development Group + * See the LICENSE file in the project root for more information. + */ + +package org.postgresql.jdbc; + +/** + * Enumerates system-defined values of pg_type.typtype. + * cat./src/include/catalog/pg_type.h + */ +public enum PgTypeType { + a("Abstract"), // This is non-standard extension (PG Enterprise?) + b("Base"), + c("Composite"), + d("Domain"), + e("Enum type"), + m("Multirange"), // Starting with the 14 PG version + p("Pseudo-type"), + r("Range"), + o("Table of Type"), /* table of type */ + u("Subtype of type"), /* subtype of types */ + s("Set Type"); + private final String desc; + + PgTypeType(String desc) { + this.desc = desc; + } + + public String getName() { + return desc; + } +} diff --git a/pgjdbc/src/main/java/org/postgresql/jdbc/TypeInfoCache.java b/pgjdbc/src/main/java/org/postgresql/jdbc/TypeInfoCache.java index 7702ccd..5084869 100644 --- a/pgjdbc/src/main/java/org/postgresql/jdbc/TypeInfoCache.java +++ b/pgjdbc/src/main/java/org/postgresql/jdbc/TypeInfoCache.java @@ -9,8 +9,9 @@ import org.postgresql.core.BaseConnection; import org.postgresql.core.BaseStatement; import org.postgresql.core.Oid; import org.postgresql.core.QueryExecutor; -import org.postgresql.core.ServerVersion; import org.postgresql.core.TypeInfo; +import org.postgresql.log.Log; +import org.postgresql.log.Logger; import org.postgresql.util.GT; import org.postgresql.util.PGobject; import org.postgresql.util.PSQLException; @@ -23,48 +24,47 @@ import java.sql.Types; import java.util.*; import java.util.concurrent.ConcurrentHashMap; +/** + * TypeInfoCache pg_type cache + * 1. Query pg_type to build PgType through OID (getPyTypeByOid) or type name (getPyTypeByName) + * 2. Determine whether PgType is a combined type, and then query (getPyTypeAttr) attribute information + * 3. Construct Map based on OID as cache, and construct the mapping relationship between Map name and OID + * 4. For all subsequent operations, obtain PgType based on name or OID + */ public class TypeInfoCache implements TypeInfo { - - // pgname (String) -> java.sql.Types (Integer) - private Map _pgNameToSQLType; - - // pgname (String) -> java class name (String) - // ie "text" -> "java.lang.String" - private Map _pgNameToJavaClass; - - // oid (Integer) -> pgname (String) - private Map _oidToPgName; - // pgname (String) -> oid (Integer) - private Map _pgNameToOid; - - // pgname (String) -> extension pgobject (Class) - private Map> _pgNameToPgObject; - - // type array oid -> base type's oid - private Map _pgArrayToPgType; - - // table of type -> base type's oid - private Map _pgTableOfPgType; - - // array type oid -> base type array element delimiter - private Map _arrayOidToDelimiter; - - private BaseConnection _conn; - private final int _unknownLength; - private PreparedStatement _getOidStatementSimple; - private PreparedStatement getOidStatementComplexNonArray; - private PreparedStatement getOidStatementComplexArray; - private PreparedStatement _getNameStatement; - private PreparedStatement _getTypeNameByOIDStatement; - private PreparedStatement _getArrayElementOidStatement; - private PreparedStatement _getArrayDelimiterStatement; - private PreparedStatement _getTypeInfoStatement; - - private PreparedStatement _getStructElementStatement; - private Map> _pgStructToPgTypes; - - // cache the subscript of the current custom type in the statement and the struct of the custom type - private ConcurrentHashMap> compositeTypeStructMap = new ConcurrentHashMap<>(); + private static Log LOGGER = Logger.getLogger(TypeInfoCache.class.getName()); + private BaseConnection _conn; + private final int _unknownLength; + + // oid (Integer) -> PgType (bean) + private Map _oidToPgType; + + // pgname (String) -> oid (Integer) + private Map _pgNameToOid; + + // query pg_type by oid + private String queryPgTypeByOidSQL = "select t.oid,n.nspname,t.typname,t.typtype,t.typcategory,t.typdelim," + + "t.typelem ,t.typarray ,n.nspname = any(current_schemas(true)) as onpath " + + "from pg_catalog.pg_type t join pg_catalog.pg_namespace n on t.typnamespace = n.oid " + + "where t.oid = ?"; + private PreparedStatement _getPgTypeByOidStatement; + + // query pg_attribute by oid + private String queryPgTypeAttrByOidSQL = "select a.oid,n.nspname,a.typname,a.typtype ,a.typcategory ,a.typdelim ," + + "a.typelem ,a.typarray ,n.nspname = any(current_schemas(true)) as onpath,attr.attname " + + "from pg_type t join pg_class c on (c.reltype = t.oid) " + + "join pg_attribute attr on (attr.attrelid = c.oid and attnum > 0) join pg_type a on (attr.atttypid = a.oid) " + + "join pg_namespace n on (a.typnamespace = n.oid) " + + "where t.oid = ? order by attr.attnum"; + private PreparedStatement _getPgTypeAttrByOidSQLStatement; + + // query pg_type by oid + private String queryPgTypeByNameSQL = "SELECT t.oid,n.nspname,t.typname,t.typtype ,t.typcategory,t.typdelim ,t.typelem ," + + "t.typarray ,n.nspname = any(current_schemas(true)) as onpath " + + "FROM pg_catalog.pg_type t JOIN pg_catalog.pg_namespace n ON t.typnamespace = n.oid " + + "WHERE t.typname = ? AND (n.nspname = ? OR ? AND n.nspname = ANY (current_schemas(true))) " + + "ORDER BY t.oid DESC LIMIT 1"; + private PreparedStatement _getPgTypeByNameStatement; // basic pg types info: // 0 - type name @@ -72,6 +72,8 @@ public class TypeInfoCache implements TypeInfo { // 2 - sql type // 3 - java class // 4 - array type oid + // 5 - jdbc DisplaySize + // 6 - jdbc recision private static final Object[][] types = { {"int1", Oid.INT1, Types.TINYINT, "java.lang.Integer", Oid.INT1_ARRAY}, {"int2", Oid.INT2, Types.SMALLINT, "java.lang.Integer", Oid.INT2_ARRAY}, @@ -105,225 +107,324 @@ public class TypeInfoCache implements TypeInfo { {"refcursor", Oid.REF_CURSOR, Types.REF_CURSOR, "java.sql.ResultSet", Oid.REF_CURSOR_ARRAY} }; - /** - * PG maps several alias to real type names. When we do queries against pg_catalog, we must use - * the real type, not an alias, so use this mapping. - */ - private static final HashMap typeAliases; - - static { - typeAliases = new HashMap(); - typeAliases.put("tinyint", "int1"); - typeAliases.put("smallint", "int2"); - typeAliases.put("integer", "int4"); - typeAliases.put("int", "int4"); - typeAliases.put("bigint", "int8"); - typeAliases.put("float", "float8"); - typeAliases.put("boolean", "bool"); - typeAliases.put("decimal", "numeric"); - } - - public TypeInfoCache(BaseConnection conn, int unknownLength) { - _conn = conn; - _unknownLength = unknownLength; - _oidToPgName = new HashMap(); - _pgNameToOid = new HashMap(); - _pgNameToJavaClass = new HashMap(); - _pgNameToPgObject = new HashMap>(); - _pgArrayToPgType = new HashMap(); - _pgTableOfPgType = new HashMap(); - _arrayOidToDelimiter = new HashMap(); - _pgStructToPgTypes = new HashMap<>(); + // typeName,jdbcType,JdbcDisplaySize,jdbcPricision + private static final ConcurrentHashMap datCompatibilityUint = new ConcurrentHashMap(); + + static { + datCompatibilityUint.put("uint1", new Object[]{Types.TINYINT, 3, 3}); + datCompatibilityUint.put("uint2", new Object[]{Types.SMALLINT, 5, 5}); + datCompatibilityUint.put("uint4", new Object[]{Types.INTEGER, 10, 10}); + datCompatibilityUint.put("uint8", new Object[]{Types.BIGINT, 20, 20}); + } + + private static ConcurrentHashMap oidToDisplaySizeInteger = new ConcurrentHashMap(); + + static { + oidToDisplaySizeInteger.put(Oid.INT1, 3); + oidToDisplaySizeInteger.put(Oid.INT2, 6); // -32768 to +32767 + oidToDisplaySizeInteger.put(Oid.INT4, 11); // -2147483648 to +2147483647 + oidToDisplaySizeInteger.put(Oid.INT8, 20); // -9223372036854775808 to +9223372036854775807 + oidToDisplaySizeInteger.put(Oid.OID, 10); // 0 to 4294967295 + // varies based upon the extra_float_digits GUC. + // These values are for the longest possible length. + oidToDisplaySizeInteger.put(Oid.FLOAT4, 15); // sign + 9 digits + decimal point + e + sign + 2 digits + oidToDisplaySizeInteger.put(Oid.FLOAT8, 25); // sign + 18 digits + decimal point + e + sign + 3 digits + oidToDisplaySizeInteger.put(Oid.CHAR, 1); + oidToDisplaySizeInteger.put(Oid.BOOL, 1); + oidToDisplaySizeInteger.put(Oid.DATE, 13); // "4713-01-01 BC" to "01/01/4713 BC" - "31/12/32767" + oidToDisplaySizeInteger.put(Oid.INTERVAL, 49); + } + + private static ConcurrentHashMap precisionToInteger = new ConcurrentHashMap(); + + static { + precisionToInteger.put(Oid.INT1, 3); + precisionToInteger.put(Oid.INT2, 5); + precisionToInteger.put(Oid.INT4, 10); + precisionToInteger.put(Oid.INT8, 19); + precisionToInteger.put(Oid.OID, 10); + // For float4 and float8, we can normally only get 6 and 15 + // significant digits out, but extra_float_digits may raise + // that number by up to two digits. + precisionToInteger.put(Oid.FLOAT4, 8); + precisionToInteger.put(Oid.FLOAT8, 17); + precisionToInteger.put(Oid.CHAR, 1); + precisionToInteger.put(Oid.BOOL, 1); + } + + public TypeInfoCache(BaseConnection conn, int unknownLength) { + _conn = conn; + _unknownLength = unknownLength; + _oidToPgType = new HashMap<>(); + _pgNameToOid = new HashMap<>(); + + // register base type + for (Object[] type : types) { + String pgTypeName = (String) type[0]; + Integer oid = (Integer) type[1]; + Integer sqlType = (Integer) type[2]; + String javaClass = (String) type[3]; + Integer arrayOid = (Integer) type[4]; + addCoreType(pgTypeName, oid, sqlType, javaClass, arrayOid, true); + } - // needs to be synchronized because the iterator is returned - // from getPGTypeNamesWithSQLTypes() - _pgNameToSQLType = Collections.synchronizedMap(new HashMap()); + /* + PG maps several alias to real type names. When we do queries against pg_catalog, we must use + the real type, not an alias, so use this mapping. + */ + _pgNameToOid.put("tinyint", Oid.INT1); + _pgNameToOid.put("smallint", Oid.INT2); + _pgNameToOid.put("integer", Oid.INT4); + _pgNameToOid.put("int", Oid.INT4); + _pgNameToOid.put("bigint", Oid.INT8); + _pgNameToOid.put("float", Oid.FLOAT8); + _pgNameToOid.put("boolean", Oid.BOOL); + _pgNameToOid.put("decimal", Oid.NUMERIC); + } + + private PgType getPgTypeByOid(int oid) throws SQLException { + if (oid == Oid.UNSPECIFIED) { + return null; + } + PgType pgType = _oidToPgType.get(oid); + if (pgType != null) { + return pgType; + } + PreparedStatement statement = _getPgTypeByOidStatement; + if (statement == null) { + statement = _conn.prepareStatement(queryPgTypeByOidSQL); + _getPgTypeByOidStatement = statement; + } + statement.setInt(1, oid); + // Go through BaseStatement to avoid transaction start. + if (!((BaseStatement) statement).executeWithFlags(QueryExecutor.QUERY_SUPPRESS_BEGIN)) { + throw new PSQLException(GT.tr("No results were returned by the query."), PSQLState.NO_DATA); + } + ResultSet rs = statement.getResultSet(); - for (Object[] type : types) { - String pgTypeName = (String) type[0]; - Integer oid = (Integer) type[1]; - Integer sqlType = (Integer) type[2]; - String javaClass = (String) type[3]; - Integer arrayOid = (Integer) type[4]; + if (rs.next()) { + pgType = newPgTypeByResultSet(rs); + } + rs.close(); - addCoreType(pgTypeName, oid, sqlType, javaClass, arrayOid); + if (pgType != null) { + if (pgType.getTypType() == PgTypeType.c) { + pgType.setTypeAttrs(getPgTypeAttrByOid(pgType.getOid())); + } + } + return pgType; } - } - - public synchronized void addCoreType(String pgTypeName, Integer oid, Integer sqlType, - String javaClass, Integer arrayOid) { - _pgNameToJavaClass.put(pgTypeName, javaClass); - _pgNameToOid.put(pgTypeName, oid); - _oidToPgName.put(oid, pgTypeName); - _pgArrayToPgType.put(arrayOid, oid); - _pgNameToSQLType.put(pgTypeName, sqlType); + private PgType getPgTypeByName(String pgTypeName) throws SQLException { + Integer oid = _pgNameToOid.get(pgTypeName); + if (oid != null && oid != Oid.UNSPECIFIED) { + return getPgTypeByOid(oid); + } - // Currently we hardcode all core types array delimiter - // to a comma. In a stock install the only exception is - // the box datatype and it's not a JDBC core type. - // - Character delim = ','; - _arrayOidToDelimiter.put(oid, delim); + String[] names = getPgTypeSchemaName(pgTypeName, true); + String schema = names[0]; + String typeName = names[1]; - String pgArrayTypeName = pgTypeName + "[]"; - _pgNameToJavaClass.put(pgArrayTypeName, "java.sql.Array"); - _pgNameToSQLType.put(pgArrayTypeName, Types.ARRAY); - _pgNameToOid.put(pgArrayTypeName, arrayOid); - pgArrayTypeName = "_" + pgTypeName; - if (!_pgNameToJavaClass.containsKey(pgArrayTypeName)) { - _pgNameToJavaClass.put(pgArrayTypeName, "java.sql.Array"); - _pgNameToSQLType.put(pgArrayTypeName, Types.ARRAY); - _pgNameToOid.put(pgArrayTypeName, arrayOid); - _oidToPgName.put(arrayOid, pgArrayTypeName); + PreparedStatement statement = _getPgTypeByNameStatement; + if (statement == null) { + statement = _conn.prepareStatement(queryPgTypeByNameSQL); + _getPgTypeByNameStatement = statement; + } + statement.setString(1, typeName); + statement.setString(2, schema); + statement.setBoolean(3, schema == null); + // Go through BaseStatement to avoid transaction start. + if (!((BaseStatement) statement).executeWithFlags(QueryExecutor.QUERY_SUPPRESS_BEGIN)) { + throw new PSQLException(GT.tr("No results were returned by the query."), PSQLState.NO_DATA); + } + ResultSet rs = statement.getResultSet(); + PgType pgType = null; + if (rs.next()) { + pgType = newPgTypeByResultSet(rs); + } + rs.close(); + if (pgType != null) { + if (pgType.getTypType() == PgTypeType.c) { + pgType.setTypeAttrs(getPgTypeAttrByOid(pgType.getOid())); + } + } + return pgType; } - } - - - public synchronized void addDataType(String type, Class klass) - throws SQLException { - _pgNameToPgObject.put(type, klass); - _pgNameToJavaClass.put(type, klass.getName()); - } - public Iterator getPGTypeNamesWithSQLTypes() { - return _pgNameToSQLType.keySet().iterator(); - } - - public int getSQLType(int oid) throws SQLException { - return getSQLType(getPGType(oid)); - } - - public synchronized int getSQLType(String pgTypeName) throws SQLException { - if (pgTypeName.endsWith("[]")) { - return Types.ARRAY; - } - Integer i = _pgNameToSQLType.get(pgTypeName); - if (i != null) { - return i; + private PgType newPgTypeByResultSet(ResultSet rs) throws SQLException { + int oid = rs.getInt("oid"); + if (oid == Oid.UNSPECIFIED) { + return null; + } + PgType pgType = _oidToPgType.get(oid); + if (pgType != null) { + return pgType; + } + String schema = rs.getString("nspname"); + String typName = rs.getString("typname"); + String typtype = rs.getString("typtype"); + String typcategory = rs.getString("typcategory"); + String typdelim = rs.getString("typdelim"); + int typelem = rs.getInt("typelem"); + int typarray = rs.getInt("typarray"); + boolean onPath = rs.getBoolean("onpath"); + pgType = new PgType(oid, typName); + pgType.setSchemaName(schema); + pgType.setTypDelim(typdelim.charAt(0)); + pgType.setTypElem(typelem); + pgType.setTypArray(typarray); + // Prevents exceptions when new TypType/TypeCategory are not defined + try { + pgType.setTypType(PgTypeType.valueOf(typtype)); + pgType.setTypeCategory(PgTypeCategory.valueOf(typcategory)); + } catch (Exception e) { + LOGGER.trace("newPgTypeByResultSet" + e); + } + addPyTypeToCache(pgType, onPath); + return pgType; } - if (_getTypeInfoStatement == null) { - // There's no great way of telling what's an array type. - // People can name their own types starting with _. - // Other types use typelem that aren't actually arrays, like box. - // - String sql = "SELECT typinput='array_in'::regproc, typtype FROM "; - // -- go with older way of unnesting array to be compatible with 8.0 - - sql += "pg_catalog."; - - sql += "pg_type WHERE typname = ?"; + private void addPyTypeToCache(PgType pgType, boolean onPath) { + String typName = pgType.getTypeName(); + String schema = pgType.getSchemaName(); + // array + if (pgType.getTypeCategory() == PgTypeCategory.A) { + pgType.setSqlType(Types.ARRAY); + pgType.setJavaClass("java.sql.Array"); + } else if (pgType.getTypType() == PgTypeType.o) { + // table of type + pgType.setSqlType(Types.ARRAY); + pgType.setJavaClass("java.sql.Array"); + } else if (pgType.getTypType() == PgTypeType.c) { + // struct + pgType.setSqlType(Types.STRUCT); + } else if (pgType.getTypType() == PgTypeType.d) { + // distinct + pgType.setSqlType(Types.DISTINCT); + } else { + // other + pgType.setSqlType(Types.OTHER); + } + // database Compatibility b uint1/uint2/uint4/uint8 + if ("b".equalsIgnoreCase(_conn.getQueryExecutor().getCompatibilityMode()) && "pg_catalog".equalsIgnoreCase(schema)) { + Object[] o = datCompatibilityUint.get(typName); + if (o != null) { + pgType.setSqlType((Integer) o[0]); + pgType.setDisplaySize((Integer) o[1]); + pgType.setPrecision((Integer) o[2]); + } + } - _getTypeInfoStatement = _conn.prepareStatement(sql); + String pgTypeName = typName; + int oid = pgType.getOid(); + _oidToPgType.put(pgType.getOid(), pgType); + if (schema != null) { + if (onPath) { + _pgNameToOid.put(schema + "." + pgTypeName, oid); + } else { + // TODO: escaping !? + pgTypeName = "\"" + schema + "\".\"" + pgTypeName + "\""; + // if all is lowercase add special type info + // TODO: should probably check for all special chars + if (schema.equals(schema.toLowerCase()) && schema.indexOf('.') == -1 + && pgTypeName.equals(pgTypeName.toLowerCase()) && pgTypeName.indexOf('.') == -1) { + _pgNameToOid.put(schema + "." + pgTypeName, oid); + } + } + } + _pgNameToOid.put(pgTypeName, oid); } - _getTypeInfoStatement.setString(1, pgTypeName); - - // Go through BaseStatement to avoid transaction start. - if (!((BaseStatement) _getTypeInfoStatement) - .executeWithFlags(QueryExecutor.QUERY_SUPPRESS_BEGIN)) { - throw new PSQLException(GT.tr("No results were returned by the query."), PSQLState.NO_DATA); + private List getPgTypeAttrByOid(int oid) throws SQLException { + if (oid == Oid.UNSPECIFIED) { + return null; + } + PgType pgType = _oidToPgType.get(oid); + if (pgType != null && pgType.getTypeAttrs() != null) { + return pgType.getTypeAttrs(); + } + PreparedStatement statement = _getPgTypeAttrByOidSQLStatement; + if (statement == null) { + statement = _conn.prepareStatement(queryPgTypeAttrByOidSQL); + _getPgTypeAttrByOidSQLStatement = statement; + } + statement.setInt(1, oid); + // Go through BaseStatement to avoid transaction start. + if (!((BaseStatement) statement).executeWithFlags(QueryExecutor.QUERY_SUPPRESS_BEGIN)) { + throw new PSQLException(GT.tr("No results were returned by the query."), PSQLState.NO_DATA); + } + ResultSet rs = statement.getResultSet(); + List res = new ArrayList<>(); + while (rs.next()) { + String attrName = rs.getString("attname"); + PgType pgType1 = newPgTypeByResultSet(rs); + PgTypeAttr pgTypeAttr = new PgTypeAttr(attrName, pgType1); + res.add(pgTypeAttr); + } + rs.close(); + for (PgTypeAttr re : res) { + if (re.getPgType().getTypType() == PgTypeType.c) { + re.getPgType().setTypeAttrs(getPgTypeAttrByOid(re.getPgType().getOid())); + } + } + return res; } - ResultSet rs = _getTypeInfoStatement.getResultSet(); - - Integer type = null; - if (rs.next()) { - boolean isArray = rs.getBoolean(1); - String typtype = rs.getString(2); - if (isArray) { - type = Types.ARRAY; - } else if ("c".equals(typtype)) { - type = Types.STRUCT; - } else if ("d".equals(typtype)) { - type = Types.DISTINCT; - } + public synchronized void addCoreType(String pgTypeName, Integer oid, Integer sqlType, + String javaClass, Integer arrayOid) { + addCoreType(pgTypeName, oid, sqlType, javaClass, arrayOid, false); } - if (type == null) { - type = Types.OTHER; + public synchronized void addCoreType(String pgTypeName, Integer oid, Integer sqlType, + String javaClass, Integer arrayOid, boolean jdbcBaseType) { + PgType pgType = new PgType(oid, pgTypeName); + addPyTypeToCache(pgType, false); + pgType.setJavaClass(javaClass); + pgType.setTypArray(arrayOid); + pgType.setSqlType(sqlType); + pgType.setTypDelim(','); + pgType.setJdbcBaseType(jdbcBaseType); } - rs.close(); - _pgNameToSQLType.put(pgTypeName, type); - return type; - } + public synchronized void addDataType(String type, Class klass) + throws SQLException { + PgType pgType = getPgTypeByName(type); + if (pgType == null) { + return; + } + pgType.setPgObject(klass); + pgType.setJavaClass(klass.getName()); + } + + public Iterator getPGTypeNamesWithSQLTypes() { + List sqlTypeList = new ArrayList<>(); + for (Integer oid : _oidToPgType.keySet()) { + // basic types not returned + if (_oidToPgType.get(oid).isJdbcBaseType()) { + continue; + } + if (_oidToPgType.get(oid).getSqlType() > 0) { + sqlTypeList.add(_oidToPgType.get(oid).getTypeName()); + } + } + return sqlTypeList.listIterator(); + } - private PreparedStatement getOidStatement(String pgTypeName) throws SQLException { - boolean isArray = pgTypeName.endsWith("[]"); - String[] names = getPgTypeSchemaName(pgTypeName, true); - String schema = names[0]; - String typeName = names[1]; - boolean hasQuote = typeName.contains("\""); - int dotIndex = pgTypeName.indexOf('.'); - - if (schema == null && !hasQuote && !isArray) { - if (_getNameStatement == null) { - String sql; - // see comments in @getSQLType() - // -- go with older way of unnesting array to be compatible with 8.0 - sql = "SELECT pg_type.oid, typname, typtype, typelem " - + " FROM pg_catalog.pg_type " - + " LEFT " - + " JOIN (select ns.oid as nspoid, ns.nspname, r.r " - + " from pg_namespace as ns " - + " join ( select s.r, (current_schemas(false))[s.r] as nspname" - + " from generate_series(1, array_upper(current_schemas(false), 1)) as s(r) ) as r " - + " using ( nspname ) " - + " ) as sp " - + " ON sp.nspoid = typnamespace " - + " WHERE typname = ? " - + " ORDER BY sp.r, pg_type.oid DESC LIMIT 1;"; - _getNameStatement = _conn.prepareStatement(sql); - } - // coerce to lower case to handle upper case type names - String lcName = typeName.toLowerCase(Locale.ROOT); - // default arrays are represented with _ as prefix ... this dont even work for public schema - // fully - _getNameStatement.setString(1, lcName); - return _getNameStatement; + public int getSQLType(int oid) throws SQLException { + PgType pgType = getPgTypeByOid(oid); + if (pgType == null) { + return Types.OTHER; + } + return pgType.getSqlType(); } - PreparedStatement oidStatementComplex; - if (isArray) { - if (getOidStatementComplexArray == null) { - String sql; - if ( _conn.haveMinimumServerVersion(ServerVersion.v8_3)) { - sql = "SELECT t.typarray, arr.typname, arr.typtype, arr.typelem " - + " FROM pg_catalog.pg_type t" - + " JOIN pg_catalog.pg_namespace n ON t.typnamespace = n.oid" - + " JOIN pg_catalog.pg_type arr ON arr.oid = t.typarray" - + " WHERE t.typname = ? AND (n.nspname = ? OR ? AND n.nspname = ANY (current_schemas(true)))" - + " ORDER BY t.oid DESC LIMIT 1"; - } else { - sql = "SELECT t.oid, t.typname, typtype, typelem " - + " FROM pg_catalog.pg_type t" - + " JOIN pg_catalog.pg_namespace n ON t.typnamespace = n.oid" - + " WHERE t.typelem = (SELECT oid FROM pg_catalog.pg_type WHERE typname = ?)" - + " AND substring(t.typname, 1, 1) = '_' AND t.typlen = -1" - + " AND (n.nspname = ? OR ? AND n.nspname = ANY (current_schemas(true)))" - + " ORDER BY t.typelem DESC LIMIT 1"; + + public synchronized int getSQLType(String pgTypeName) throws SQLException { + PgType pgType = getPgTypeByName(pgTypeName); + if (pgType == null) { + return Types.OTHER; } - getOidStatementComplexArray = _conn.prepareStatement(sql); - } - oidStatementComplex = getOidStatementComplexArray; - } else { - if (getOidStatementComplexNonArray == null) { - String sql = "SELECT t.oid, t.typname, typtype, typelem " - + " FROM pg_catalog.pg_type t" - + " JOIN pg_catalog.pg_namespace n ON t.typnamespace = n.oid" - + " WHERE t.typname = ? AND (n.nspname = ? OR ? AND n.nspname = ANY (current_schemas(true)))" - + " ORDER BY t.oid DESC LIMIT 1"; - getOidStatementComplexNonArray = _conn.prepareStatement(sql); - } - oidStatementComplex = getOidStatementComplexNonArray; + return pgType.getSqlType(); } - // simple use case - oidStatementComplex.setString(1, typeName); - oidStatementComplex.setString(2, schema); - oidStatementComplex.setBoolean(3, schema == null); - return oidStatementComplex; - } /** * Processing type names returns the schema+typename array @@ -376,93 +477,21 @@ public class TypeInfoCache implements TypeInfo { names[1] = name; return names; } - public synchronized int getPGType(String pgTypeName) throws SQLException { - Integer oid = _pgNameToOid.get(pgTypeName); - if (oid != null) { - return oid; - } - - PreparedStatement oidStatement = getOidStatement(pgTypeName); - - // Go through BaseStatement to avoid transaction start. - if (!((BaseStatement) oidStatement).executeWithFlags(QueryExecutor.QUERY_SUPPRESS_BEGIN)) { - throw new PSQLException(GT.tr("No results were returned by the query."), PSQLState.NO_DATA); - } - - oid = Oid.UNSPECIFIED; - ResultSet rs = oidStatement.getResultSet(); - if (rs.next()) { - oid = (int) rs.getLong(1); - String internalName = rs.getString(2); - String tytType = rs.getString(3); - int typElem = (int) rs.getLong(4); - // the tytType value of pgarray is b - if (tytType != null && tytType.equals("b")) { - _pgArrayToPgType.put(oid, typElem); - } - // the tytType value of pg table of type is o - if (tytType != null && tytType.equals("o")) { - _pgTableOfPgType.put(oid, typElem); - } - _oidToPgName.put(oid, internalName); - _pgNameToOid.put(internalName, oid); - } - _pgNameToOid.put(pgTypeName, oid); - rs.close(); - - return oid; - } - - public synchronized String getPGType(int oid) throws SQLException { - if (oid == Oid.UNSPECIFIED) { - return null; - } - - String pgTypeName = _oidToPgName.get(oid); - if (pgTypeName != null) { - return pgTypeName; - } - if (_getTypeNameByOIDStatement == null) { - String sql; - sql = "SELECT n.nspname = ANY(current_schemas(true)), n.nspname, t.typname " - + "FROM pg_catalog.pg_type t " - + "JOIN pg_catalog.pg_namespace n ON t.typnamespace = n.oid WHERE t.oid = ?"; - _getTypeNameByOIDStatement = _conn.prepareStatement(sql); - } - - _getTypeNameByOIDStatement.setInt(1, oid); - - // Go through BaseStatement to avoid transaction start. - if (!((BaseStatement) _getTypeNameByOIDStatement).executeWithFlags(QueryExecutor.QUERY_SUPPRESS_BEGIN)) { - throw new PSQLException(GT.tr("No results were returned by the query."), PSQLState.NO_DATA); + PgType pgType = getPgTypeByName(pgTypeName); + if (pgType == null) { + return Oid.UNSPECIFIED; + } + return pgType.getOid(); } - ResultSet rs = _getTypeNameByOIDStatement.getResultSet(); - if (rs.next()) { - boolean onPath = rs.getBoolean(1); - String schema = rs.getString(2); - String name = rs.getString(3); - if (onPath) { - pgTypeName = name; - _pgNameToOid.put(schema + "." + name, oid); - } else { - // TODO: escaping !? - pgTypeName = "\"" + schema + "\".\"" + name + "\""; - // if all is lowercase add special type info - // TODO: should probably check for all special chars - if (schema.equals(schema.toLowerCase()) && schema.indexOf('.') == -1 - && name.equals(name.toLowerCase()) && name.indexOf('.') == -1) { - _pgNameToOid.put(schema + "." + name, oid); + public synchronized String getPGType(int oid) throws SQLException { + PgType pgType = getPgTypeByOid(oid); + if (pgType == null) { + return null; } - } - _pgNameToOid.put(pgTypeName, oid); - _oidToPgName.put(oid, pgTypeName); + return pgType.getTypeName(); } - rs.close(); - - return pgTypeName; - } /** * use type ty_test return _ty_test oid @@ -473,38 +502,20 @@ public class TypeInfoCache implements TypeInfo { * @throws SQLException if something goes wrong */ public int getPGArrayType(String elementTypeName) throws SQLException { - elementTypeName = getTypeForAlias(elementTypeName); - String[] names = getPgTypeSchemaName(elementTypeName, false); - String schema = null; - if (names[0] != null) { - schema = names[0]; - elementTypeName = names[1]; - } - int pgType = Oid.UNSPECIFIED; - // the _getOidStatementComplexArray is type[], the _getNameStatement is table of type. - String[] newTypeNameArray = new String[]{elementTypeName + "[]", elementTypeName}; - for (String newTypeName : newTypeNameArray) { - if (schema != null && !schema.equals("")) { - newTypeName = schema + "." + newTypeName; + PgType pgType = getPgTypeByName(elementTypeName); + if (pgType == null) { + return Oid.UNSPECIFIED; } - pgType = getPGType(newTypeName); - - // if pgType is table of type return pg_type.typelem - Integer elementId = _pgTableOfPgType.get(pgType); - if (elementId != null && elementId > Oid.UNSPECIFIED) { - return elementId; + if (pgType.getTypeCategory() == PgTypeCategory.A) { + return pgType.getOid(); } - - elementId = _pgArrayToPgType.get(pgType); - if (elementId == null) { - continue; + if (pgType.getTypType() == PgTypeType.o) { + return pgType.getTypElem(); } - - if (pgType != Oid.UNSPECIFIED) { - return pgType; + if (pgType.getTypArray() != null) { + return pgType.getTypArray(); } - } - return pgType; + return Oid.UNSPECIFIED; } /** @@ -517,164 +528,80 @@ public class TypeInfoCache implements TypeInfo { * @return oid of the array's base element or the provided oid (if not array) */ protected synchronized int convertArrayToBaseOid(int oid) { - Integer i = _pgArrayToPgType.get(oid); - if (i == null) { + PgType pgType = null; + try { + pgType = getPgTypeByOid(oid); + } catch (SQLException e) { + return oid; + } + if (pgType == null) { + return oid; + } + if (pgType.getTypeCategory() == PgTypeCategory.A) { + return pgType.getOid(); + } + if (pgType.getTypType() == PgTypeType.o) { + return pgType.getTypElem(); + } + if (pgType.getTypArray() != null) { + return pgType.getOid(); + } return oid; - } - return i; } - public synchronized char getArrayDelimiter(int oid) throws SQLException { - if (oid == Oid.UNSPECIFIED) { - return ','; - } - - Character delim = _arrayOidToDelimiter.get(oid); - if (delim != null) { - return delim; - } - - if (_getArrayDelimiterStatement == null) { - String sql; - sql = "SELECT e.typdelim FROM pg_catalog.pg_type t, pg_catalog.pg_type e " - + "WHERE t.oid = ? and t.typelem = e.oid"; - _getArrayDelimiterStatement = _conn.prepareStatement(sql); - } - - _getArrayDelimiterStatement.setInt(1, oid); - - // Go through BaseStatement to avoid transaction start. - if (!((BaseStatement) _getArrayDelimiterStatement) - .executeWithFlags(QueryExecutor.QUERY_SUPPRESS_BEGIN)) { - throw new PSQLException(GT.tr("No results were returned by the query."), PSQLState.NO_DATA); - } - - ResultSet rs = _getArrayDelimiterStatement.getResultSet(); - if (!rs.next()) { - throw new PSQLException(GT.tr("No results were returned by the query."), PSQLState.NO_DATA); - } - - String s = rs.getString(1); - delim = s.charAt(0); - - _arrayOidToDelimiter.put(oid, delim); - - rs.close(); - - return delim; - } - - public synchronized int getPGArrayElement(int oid) throws SQLException { - if (oid == Oid.UNSPECIFIED) { - return Oid.UNSPECIFIED; - } - - Integer pgType = _pgArrayToPgType.get(oid); - - if (pgType != null) { - return pgType; - } - - if (_getArrayElementOidStatement == null) { - String sql; - sql = "SELECT e.oid, n.nspname = ANY(current_schemas(true)), n.nspname, e.typname, t.typtype as Ptyptype, e.typtype as Ctyptype " - + "FROM pg_catalog.pg_type t JOIN pg_catalog.pg_type e ON t.typelem = e.oid " - + "JOIN pg_catalog.pg_namespace n ON t.typnamespace = n.oid WHERE t.oid = ?"; - _getArrayElementOidStatement = _conn.prepareStatement(sql); - } - - _getArrayElementOidStatement.setInt(1, oid); - - // Go through BaseStatement to avoid transaction start. - if (!((BaseStatement) _getArrayElementOidStatement) - .executeWithFlags(QueryExecutor.QUERY_SUPPRESS_BEGIN)) { - throw new PSQLException(GT.tr("No results were returned by the query."), PSQLState.NO_DATA); - } - - ResultSet rs = _getArrayElementOidStatement.getResultSet(); - if (!rs.next()) { - throw new PSQLException(GT.tr("No results were returned by the query."), PSQLState.NO_DATA); - } - - pgType = (int) rs.getLong(1); - boolean onPath = rs.getBoolean(2); - String schema = rs.getString(3); - String name = rs.getString(4); - String pTytType = rs.getString(5); - String cTytType = rs.getString(6); - // use typElem oid query, if pgType is array return oid tytType is o and the typElem oid tytType is b - if (pTytType.equals("o") && cTytType.equals("b")) { - return this.getPGArrayElement(pgType); - } - _pgArrayToPgType.put(oid, pgType); - _pgNameToOid.put(schema + "." + name, pgType); - String fullName = "\"" + schema + "\".\"" + name + "\""; - _pgNameToOid.put(fullName, pgType); - if (onPath && name.equals(name.toLowerCase())) { - _oidToPgName.put(pgType, name); - _pgNameToOid.put(name, pgType); - } else { - _oidToPgName.put(pgType, fullName); + public synchronized char getArrayDelimiter(int oid) throws SQLException { + if (oid == Oid.UNSPECIFIED) { + return ','; + } + PgType pgType = getPgTypeByOid(oid); + if (pgType == null) { + return ','; + } + return pgType.getTypDelim(); } - rs.close(); - - return pgType; - } - - public synchronized Class getPGobject(String type) { - return _pgNameToPgObject.get(type); - } - - public synchronized String getJavaClass(int oid) throws SQLException { - String pgTypeName = getPGType(oid); - - String result = _pgNameToJavaClass.get(pgTypeName); - if (result != null) { - return result; + public synchronized int getPGArrayElement(int oid) throws SQLException { + if (oid == Oid.UNSPECIFIED) { + return Oid.UNSPECIFIED; + } + // if return oid tytType is o and the typElem oid tytType is b,use typElem oid query + PgType pgType = getPgTypeByOid(oid); + if (pgType == null) { + return Oid.UNSPECIFIED; + } + if (pgType.getTypeCategory() == PgTypeCategory.A) { + return pgType.getTypElem(); + } + if (pgType.getTypType() == PgTypeType.o) { + return getPGArrayElement(pgType.getTypElem()); + } + return Oid.UNSPECIFIED; } - if (getSQLType(pgTypeName) == Types.ARRAY) { - result = "java.sql.Array"; - _pgNameToJavaClass.put(pgTypeName, result); + public synchronized Class getPGobject(String type) throws SQLException { + PgType pgType = getPgTypeByName(type); + if (pgType == null) { + return null; + } + return pgType.getPgObject(); } - return result; - } - - public String getTypeForAlias(String alias) { - String type = typeAliases.get(alias); - if (type != null) { - return type; - } - if (alias.indexOf('"') == -1) { - type = typeAliases.get(alias.toLowerCase()); - if (type != null) { - return type; - } + public synchronized String getJavaClass(int oid) throws SQLException { + PgType pgType = getPgTypeByOid(oid); + if (pgType == null) { + return null; + } + return pgType.getJavaClass(); } - return alias; - } - private static ConcurrentHashMap pricisionToInteger = new ConcurrentHashMap(); - static { - pricisionToInteger.put(Oid.INT1, 3); - pricisionToInteger.put(Oid.INT2, 5); - pricisionToInteger.put(Oid.OID, 10); - pricisionToInteger.put(Oid.INT4, 10); - pricisionToInteger.put(Oid.INT8, 19); - // For float4 and float8, we can normally only get 6 and 15 - // significant digits out, but extra_float_digits may raise - // that number by up to two digits. - pricisionToInteger.put(Oid.FLOAT4, 8); - pricisionToInteger.put(Oid.FLOAT8, 17); - pricisionToInteger.put(Oid.CHAR, 1); - pricisionToInteger.put(Oid.BOOL, 1); - } - public int getPrecision(int oid, int typmod) { + public int getPrecision(int oid, int typmod) throws SQLException { oid = convertArrayToBaseOid(oid); - if (pricisionToInteger.containsKey(oid)) { - return pricisionToInteger.get(oid); + if (precisionToInteger.containsKey(oid)) { + return precisionToInteger.get(oid); + } + PgType pyType = getPgTypeByOid(oid); + if (pyType != null && pyType.getPrecision() > 0) { + return pyType.getPrecision(); } switch (oid) { case Oid.NUMERIC: @@ -790,22 +717,6 @@ public class TypeInfoCache implements TypeInfo { } } - private static ConcurrentHashMap oidToInteger = new ConcurrentHashMap(); - static { - oidToInteger.put(Oid.INT1, 3); - oidToInteger.put(Oid.INT2, 6); // -32768 to +32767 - oidToInteger.put(Oid.INT4, 11); // -2147483648 to +2147483647 - oidToInteger.put(Oid.OID, 10); // 0 to 4294967295 - oidToInteger.put(Oid.INT8, 20); // -9223372036854775808 to +9223372036854775807 - // varies based upon the extra_float_digits GUC. - // These values are for the longest possible length. - oidToInteger.put(Oid.FLOAT4, 15); // sign + 9 digits + decimal point + e + sign + 2 digits - oidToInteger.put(Oid.FLOAT8, 25); // sign + 18 digits + decimal point + e + sign + 3 digits - oidToInteger.put(Oid.CHAR, 1); - oidToInteger.put(Oid.BOOL, 1); - oidToInteger.put(Oid.DATE, 13); // "4713-01-01 BC" to "01/01/4713 BC" - "31/12/32767" - oidToInteger.put(Oid.INTERVAL, 49); - } private int getValueOfTime(int oid, int typmod) { // Calculate the number of decimal digits + the decimal point. int secondSize; @@ -845,10 +756,14 @@ public class TypeInfoCache implements TypeInfo { return 13 + 1 + 8 + secondSize + 6; } } - public int getDisplaySize(int oid, int typmod) { + public int getDisplaySize(int oid, int typmod) throws SQLException { oid = convertArrayToBaseOid(oid); - if (oidToInteger.containsKey(oid)) { - return oidToInteger.get(oid); + if (oidToDisplaySizeInteger.containsKey(oid)) { + return oidToDisplaySizeInteger.get(oid); + } + PgType pyType = getPgTypeByOid(oid); + if (pyType != null && pyType.getDisplaySize() > 0) { + return pyType.getDisplaySize(); } switch (oid) { case Oid.TIME: @@ -923,9 +838,8 @@ public class TypeInfoCache implements TypeInfo { * * @param sqlType sql type as in java.sql.Types * @return true if the type requires quoting - * @throws SQLException if something goes wrong */ - public boolean requiresQuotingSqlType(int sqlType) throws SQLException { + public boolean requiresQuotingSqlType(int sqlType) { switch (sqlType) { case Types.BIGINT: case Types.DOUBLE: @@ -944,92 +858,50 @@ public class TypeInfoCache implements TypeInfo { /** * Returns the attributes sql type list of the object based on oid * - * @param oid the type's OID + * @param oid pg_type oid * @return the attributes sql type list * @throws SQLException if something goes wrong */ @Override public List getStructAttributesOid(int oid) throws SQLException { - List struct = this.getCompositeTypeStruct(oid); - if (struct == null) { - return null; - } - List res = new ArrayList<>(); - for (Object[] objects : struct) { - res.add((Integer) objects[1]); - } - return res; + List struct = this.getCompositeTypeStruct(oid); + if (struct == null) { + return null; + } + List res = new ArrayList<>(); + for (PgTypeAttr pgTypeAttr : struct) { + res.add(pgTypeAttr.getPgType().getOid()); + } + return res; } @Override public Object[] getStructAttributesName(int oid) throws SQLException { - List struct = this.getCompositeTypeStruct(oid); - if (struct == null) { - return null; - } - Object[] res = new Object[struct.size()]; - for (int i = 0; i < struct.size(); i++) { - res[i] = struct.get(i)[0]; - } - return res; - } - - /* - * query struct attributes type by oid sql - */ - private final String queryStructAttributesTypeByOidSql = "select " - + "a.oid, n.nspname = ANY(current_schemas(true)), n.nspname, a.typname, attname " - + "from pg_type t join pg_class on (reltype = t.oid) " - + "join pg_attribute on (attrelid = pg_class.oid and attnum > 0) " - + "join pg_type a on (atttypid = a.oid) " - + "join pg_namespace n on (a.typnamespace = n.oid) " - + "where t.oid = ? order by pg_attribute.attnum "; - - /** - * Returns the attributes sql type list of the object based on oid - * - * @param oid the type's OID - * @return get custom Type attr list - */ - private List getCompositeTypeStruct(int oid) throws SQLException { - if (oid == 0) { - return null; - } - List list = this.compositeTypeStructMap.get(oid); - if (list != null) { - return list; - } - if (this._getStructElementStatement == null) { - this._getStructElementStatement = this._conn.prepareStatement(queryStructAttributesTypeByOidSql); - } - - this._getStructElementStatement.setInt(1, oid); - // Go through BaseStatement to avoid transaction start. - if (!((BaseStatement) this._getStructElementStatement).executeWithFlags(QueryExecutor.QUERY_SUPPRESS_BEGIN)) { - throw new PSQLException(GT.tr("No results were returned by the query."), PSQLState.NO_DATA); - } - // get attribute list from result set - ResultSet rs = this._getStructElementStatement.getResultSet(); - List compositeType = new ArrayList<>(); - while (rs.next()) { - int attrOid = rs.getInt(1); - boolean onPath = rs.getBoolean(2); - String schema = rs.getString(3); - String typName = rs.getString(4); - String attrName = rs.getString(5); - this._pgNameToOid.put(schema + "." + typName, attrOid); - String fullName = "\"" + schema + "\".\"" + typName + "\""; - this._pgNameToOid.put(fullName, attrOid); - if (onPath && typName.equals(typName.toLowerCase())) { - this._oidToPgName.put(attrOid, typName); - this._pgNameToOid.put(typName, attrOid); - } else { - this._oidToPgName.put(attrOid, fullName); + List struct = this.getCompositeTypeStruct(oid); + if (struct == null) { + return null; + } + Object[] res = new Object[struct.size()]; + for (int i = 0; i < struct.size(); i++) { + res[i] = struct.get(i).getAttrName(); + } + return res; + } + + /** + * Returns the attributes sql type list of the object based on oid + * + * @param oid py_type.oid + * @return get custom Type attr list + */ + private List getCompositeTypeStruct(int oid) throws SQLException { + if (oid == Oid.UNSPECIFIED) { + return null; } - compositeType.add(new Object[]{attrName, attrOid}); + PgType pgType = getPgTypeByOid(oid); + if (pgType == null) { + return null; + } + return pgType.getTypeAttrs(); } - rs.close(); - compositeTypeStructMap.put(oid, compositeType); - return compositeType; - } } diff --git a/pgjdbc/src/test/java/org/postgresql/test/jdbc2/UintMetadataTest.java b/pgjdbc/src/test/java/org/postgresql/test/jdbc2/UintMetadataTest.java new file mode 100644 index 0000000..04a6208 --- /dev/null +++ b/pgjdbc/src/test/java/org/postgresql/test/jdbc2/UintMetadataTest.java @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2023, openGauss Global Development Group + * See the LICENSE file in the project root for more information. + */ +package org.postgresql.test.jdbc2; + +import org.junit.Assume; +import org.junit.Test; +import org.postgresql.test.TestUtil; +import org.postgresql.util.DataBaseCompatibility; + +import java.sql.*; + +import static org.junit.Assert.assertEquals; + +/** + * test uint metadata. + */ +public class UintMetadataTest extends BaseTest4 { + private boolean isValid = false; + + @Override + public void setUp() throws Exception { + super.setUp(); + PreparedStatement statement = con.prepareStatement( + "select 1 from pg_extension a where a.extname = 'dolphin' limit 1"); + ResultSet rs = statement.executeQuery(); + boolean hasDolphin = false; + while (rs.next()) { + hasDolphin = rs.getInt(1) == 1; + } + rs.close(); + statement.close(); + Assume.assumeTrue("unsigned int requires the dolphin plugin, but no dolphin。", hasDolphin); + + DataBaseCompatibility.CompatibilityEnum compatibility = DataBaseCompatibility.getCompatibility(con); + Assume.assumeTrue("unsigned int requires compatibility b。", + compatibility == DataBaseCompatibility.CompatibilityEnum.MYSQL); + + isValid = true; + TestUtil.createTable(con, "test_uint", "col_tinyint tinyint," + + "col_tinyint_un tinyint unsigned," + + "col_smallint smallint," + + "col_smallint_un smallint unsigned," + + "col_integer integer," + + "col_integer_un integer unsigned," + + "col_mediumint mediumint," + + "col_mediumint_un mediumint unsigned," + + "col_bigint bigint," + + "col_bigint_un bigint unsigned"); + } + + @Override + public void tearDown() throws SQLException { + if (isValid) { + TestUtil.dropTable(con, "test_uint"); + } + super.tearDown(); + } + + @Test + public void testUintMetadata() throws Exception { + Assume.assumeTrue("unsigned int requires the dolphin plugin", isValid); + PreparedStatement ps = null; + try { + String querySql = "select * from test_uint"; + ps = con.prepareStatement(querySql); + ResultSet rs = ps.executeQuery(); + ResultSetMetaData rsmd = rs.getMetaData(); + // int1 + assertEquals("int1", rsmd.getColumnTypeName(1)); + assertEquals(Types.TINYINT, rsmd.getColumnType(1)); + assertEquals(3, rsmd.getColumnDisplaySize(1)); + assertEquals(3, rsmd.getPrecision(1)); + // uint1 + assertEquals("uint1", rsmd.getColumnTypeName(2)); + assertEquals(Types.TINYINT, rsmd.getColumnType(2)); + assertEquals(3, rsmd.getColumnDisplaySize(2)); + assertEquals(3, rsmd.getPrecision(2)); + // int2 + assertEquals("int2", rsmd.getColumnTypeName(3)); + assertEquals(Types.SMALLINT, rsmd.getColumnType(3)); + assertEquals(6, rsmd.getColumnDisplaySize(3)); + assertEquals(5, rsmd.getPrecision(3)); + // uint2 + assertEquals("uint2", rsmd.getColumnTypeName(4)); + assertEquals(Types.SMALLINT, rsmd.getColumnType(4)); + assertEquals(5, rsmd.getColumnDisplaySize(4)); + assertEquals(5, rsmd.getPrecision(4)); + // int4 + assertEquals("int4", rsmd.getColumnTypeName(5)); + assertEquals(Types.INTEGER, rsmd.getColumnType(5)); + assertEquals(11, rsmd.getColumnDisplaySize(5)); + assertEquals(10, rsmd.getPrecision(6)); + // uint4 + assertEquals("uint4", rsmd.getColumnTypeName(6)); + assertEquals(Types.INTEGER, rsmd.getColumnType(6)); + assertEquals(10, rsmd.getColumnDisplaySize(6)); + assertEquals(10, rsmd.getPrecision(6)); + + assertEquals("int4", rsmd.getColumnTypeName(7)); + assertEquals(Types.INTEGER, rsmd.getColumnType(7)); + assertEquals(11, rsmd.getColumnDisplaySize(7)); + assertEquals(10, rsmd.getPrecision(7)); + assertEquals("uint4", rsmd.getColumnTypeName(8)); + assertEquals(Types.INTEGER, rsmd.getColumnType(8)); + assertEquals(10, rsmd.getColumnDisplaySize(8)); + assertEquals(10, rsmd.getPrecision(8)); + // int8 + assertEquals("int8", rsmd.getColumnTypeName(9)); + assertEquals(Types.BIGINT, rsmd.getColumnType(9)); + assertEquals(20, rsmd.getColumnDisplaySize(9)); + assertEquals(19, rsmd.getPrecision(9)); + // uint8 + assertEquals("uint8", rsmd.getColumnTypeName(10)); + assertEquals(Types.BIGINT, rsmd.getColumnType(10)); + assertEquals(20, rsmd.getColumnDisplaySize(10)); + assertEquals(20, rsmd.getPrecision(10)); + } finally { + TestUtil.closeQuietly(ps); + } + } +} \ No newline at end of file -- Gitee From cc000da4876ebde972357d814174e5f646345d0b Mon Sep 17 00:00:00 2001 From: zhangpeng Date: Fri, 29 Mar 2024 16:14:20 +0800 Subject: [PATCH 2/2] =?UTF-8?q?repair=20int2vector=EF=BC=8Coidvector?= =?UTF-8?q?=EF=BC=8Coidvector=5Fextend=EF=BC=8Cint2vector=5Fextend=20sqlty?= =?UTF-8?q?pe?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/org/postgresql/core/TypeInfo.java | 3 + .../java/org/postgresql/jdbc/PgResultSet.java | 13 ++-- .../main/java/org/postgresql/jdbc/PgType.java | 16 ++++- .../org/postgresql/jdbc/TypeInfoCache.java | 60 ++++++++++++++----- 4 files changed, 71 insertions(+), 21 deletions(-) diff --git a/pgjdbc/src/main/java/org/postgresql/core/TypeInfo.java b/pgjdbc/src/main/java/org/postgresql/core/TypeInfo.java index 310eb23..205c0c3 100644 --- a/pgjdbc/src/main/java/org/postgresql/core/TypeInfo.java +++ b/pgjdbc/src/main/java/org/postgresql/core/TypeInfo.java @@ -5,6 +5,7 @@ package org.postgresql.core; +import org.postgresql.jdbc.PgType; import org.postgresql.util.PGobject; import java.sql.SQLException; @@ -136,4 +137,6 @@ public interface TypeInfo { * @throws SQLException if something goes wrong */ Object[] getStructAttributesName(int oid) throws SQLException; + + PgType getPgTypeByOid(int oid) throws SQLException; } \ No newline at end of file diff --git a/pgjdbc/src/main/java/org/postgresql/jdbc/PgResultSet.java b/pgjdbc/src/main/java/org/postgresql/jdbc/PgResultSet.java index 76575ec..cd853a4 100644 --- a/pgjdbc/src/main/java/org/postgresql/jdbc/PgResultSet.java +++ b/pgjdbc/src/main/java/org/postgresql/jdbc/PgResultSet.java @@ -372,7 +372,9 @@ public class PgResultSet implements ResultSet, org.postgresql.PGRefCursorResultS int oid = fields[i - 1].getOID(); String attrsValue = getString(i); PGStruct pgStruct = new PGStruct(connection, oid, attrsValue); - pgStruct.setType(this.getPGType(i)); + TypeInfo typeInfo = connection.getTypeInfo(); + PgType pgType = typeInfo.getPgTypeByOid(oid); + pgStruct.setType(pgType.getTypeFullName()); // set struct attribute name array pgStruct.setStruct(this.connection.getTypeInfo().getStructAttributesName(oid)); return pgStruct; @@ -2959,10 +2961,11 @@ public class PgResultSet implements ResultSet, org.postgresql.PGRefCursorResultS } TypeInfo typeInfo = connection.getTypeInfo(); int oid = field.getOID(); - String pgType = typeInfo.getPGType(oid); - int sqlType = typeInfo.getSQLType(pgType); - field.setSQLType(sqlType); - field.setPGType(pgType); + PgType pgType = typeInfo.getPgTypeByOid(oid); + if (pgType != null) { + field.setSQLType(pgType.getSqlType()); + field.setPGType(pgType.getTypeName()); + } } private void checkUpdateable() throws SQLException { diff --git a/pgjdbc/src/main/java/org/postgresql/jdbc/PgType.java b/pgjdbc/src/main/java/org/postgresql/jdbc/PgType.java index 409fc30..174cb5e 100644 --- a/pgjdbc/src/main/java/org/postgresql/jdbc/PgType.java +++ b/pgjdbc/src/main/java/org/postgresql/jdbc/PgType.java @@ -20,6 +20,7 @@ public class PgType { private Integer typArray; // pg_type.typarray private String typeName; // pg_type.typname private String schemaName; // pg_type schema name + private String fullTypeName; private char typDelim; // pg_type.typdelim array type oid -> base type array element delimiter private PgTypeType typType; // pg_type.typtype private PgTypeCategory typeCategory; // pg_type.typcategory @@ -62,12 +63,16 @@ public class PgType { } public String getTypeFullName() { - if (this.schemaName != null) { - return this.schemaName + "." + this.typeName; + if (schemaName != null) { + return fullTypeName; } return getTypeName(); } + public void setFullTypeName(String fullTypeName) { + this.fullTypeName = fullTypeName; + } + public void setTypeName(String typeName) { this.typeName = typeName; } @@ -175,4 +180,11 @@ public class PgType { public void setDisplaySize(int displaySize) { this.displaySize = displaySize; } + + /** + * Determine whether the sql type is an array type based on the fact that typcategory is equal to A and typarray is 0 + */ + public boolean checkSqlTypeIsArray() { + return typeCategory == PgTypeCategory.A && typArray == 0; + } } diff --git a/pgjdbc/src/main/java/org/postgresql/jdbc/TypeInfoCache.java b/pgjdbc/src/main/java/org/postgresql/jdbc/TypeInfoCache.java index a1a5dc7..b35d4df 100644 --- a/pgjdbc/src/main/java/org/postgresql/jdbc/TypeInfoCache.java +++ b/pgjdbc/src/main/java/org/postgresql/jdbc/TypeInfoCache.java @@ -66,6 +66,21 @@ public class TypeInfoCache implements TypeInfo { + "ORDER BY t.oid DESC LIMIT 1"; private PreparedStatement _getPgTypeByNameStatement; + private String queryPgTypeByNameNoSchemaSQL = "SELECT t.oid,sp.nspname,t.typname,t.typtype ,t.typcategory,t.typdelim ,t.typelem ," + + "t.typarray ,sp.nspname = any(current_schemas(true)) as onpath " + + " FROM pg_catalog.pg_type t" + + " LEFT " + + " JOIN (select ns.oid as nspoid, ns.nspname, r.r " + + " from pg_namespace as ns " + + " join ( select s.r, (current_schemas(false))[s.r] as nspname" + + " from generate_series(1, array_upper(current_schemas(false), 1)) as s(r) ) as r " + + " using ( nspname ) " + + " ) as sp " + + " ON sp.nspoid = typnamespace " + + " WHERE t.typname = ? " + + " ORDER BY sp.r, t.oid DESC LIMIT 1"; + private PreparedStatement _getPgTypeByNameNoSchemaStatement; + private static ConcurrentHashMap pgTypes = new ConcurrentHashMap<>(); // basic pg types info: @@ -184,7 +199,8 @@ public class TypeInfoCache implements TypeInfo { _pgNameToOid.put("decimal", Oid.NUMERIC); } - private PgType getPgTypeByOid(int oid) throws SQLException { + @Override + public PgType getPgTypeByOid(int oid) throws SQLException { if (oid == Oid.UNSPECIFIED) { return null; } @@ -226,15 +242,24 @@ public class TypeInfoCache implements TypeInfo { String[] names = getPgTypeSchemaName(pgTypeName, true); String schema = names[0]; String typeName = names[1]; - - PreparedStatement statement = _getPgTypeByNameStatement; - if (statement == null) { - statement = _conn.prepareStatement(queryPgTypeByNameSQL); - _getPgTypeByNameStatement = statement; + PreparedStatement statement; + if (schema == null) { + statement = _getPgTypeByNameNoSchemaStatement; + if (statement == null) { + statement = _conn.prepareStatement(queryPgTypeByNameNoSchemaSQL); + _getPgTypeByNameNoSchemaStatement = statement; + } + statement.setString(1, typeName); + } else { + statement = _getPgTypeByNameStatement; + if (statement == null) { + statement = _conn.prepareStatement(queryPgTypeByNameSQL); + _getPgTypeByNameStatement = statement; + } + statement.setString(1, typeName); + statement.setString(2, schema); + statement.setBoolean(3, schema == null); } - statement.setString(1, typeName); - statement.setString(2, schema); - statement.setBoolean(3, schema == null); // Go through BaseStatement to avoid transaction start. if (!((BaseStatement) statement).executeWithFlags(QueryExecutor.QUERY_SUPPRESS_BEGIN)) { throw new PSQLException(GT.tr("No results were returned by the query."), PSQLState.NO_DATA); @@ -290,7 +315,7 @@ public class TypeInfoCache implements TypeInfo { String typName = pgType.getTypeName(); String schema = pgType.getSchemaName(); // array - if (pgType.getTypeCategory() == PgTypeCategory.A) { + if (pgType.checkSqlTypeIsArray()) { pgType.setSqlType(Types.ARRAY); pgType.setJavaClass("java.sql.Array"); } else if (pgType.getTypType() == PgTypeType.o) { @@ -333,6 +358,7 @@ public class TypeInfoCache implements TypeInfo { _pgNameToOid.put(schema + "." + pgTypeName, oid); } } + pgType.setFullTypeName(pgTypeName); } _pgNameToOid.put(pgTypeName, oid); } @@ -475,6 +501,9 @@ public class TypeInfoCache implements TypeInfo { name = name.toLowerCase(Locale.ROOT); } } + if (isArray && !name.startsWith("_")) { + name = "_" + name; + } names[0] = schema; names[1] = name; return names; @@ -515,6 +544,9 @@ public class TypeInfoCache implements TypeInfo { if (pgType == null) { return null; } + if (pgType.getSchemaName() != null) { + return pgType.getTypeFullName(); + } return pgType.getTypeName(); } @@ -531,7 +563,7 @@ public class TypeInfoCache implements TypeInfo { if (pgType == null) { return Oid.UNSPECIFIED; } - if (pgType.getTypeCategory() == PgTypeCategory.A) { + if (pgType.checkSqlTypeIsArray()) { return pgType.getOid(); } if (pgType.getTypType() == PgTypeType.o) { @@ -562,8 +594,8 @@ public class TypeInfoCache implements TypeInfo { if (pgType == null) { return oid; } - if (pgType.getTypeCategory() == PgTypeCategory.A) { - return pgType.getOid(); + if (pgType.checkSqlTypeIsArray()) { + return pgType.getTypElem(); } if (pgType.getTypType() == PgTypeType.o) { return pgType.getTypElem(); @@ -594,7 +626,7 @@ public class TypeInfoCache implements TypeInfo { if (pgType == null) { return Oid.UNSPECIFIED; } - if (pgType.getTypeCategory() == PgTypeCategory.A) { + if (pgType.checkSqlTypeIsArray()) { return pgType.getTypElem(); } if (pgType.getTypType() == PgTypeType.o) { -- Gitee