/* * Copyright (c) 2005, PostgreSQL Global Development Group * See the LICENSE file in the project root for more information. */ package org.postgresql.jdbc; 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.util.GT; import org.postgresql.util.PGobject; import org.postgresql.util.PSQLException; import org.postgresql.util.PSQLState; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Types; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.Map; public class TypeInfoCache implements TypeInfo { // pgname (String) -> java.sql.Types (Integer) private Map<String, Integer> _pgNameToSQLType; // pgname (String) -> java class name (String) // ie "text" -> "java.lang.String" private Map<String, String> _pgNameToJavaClass; // oid (Integer) -> pgname (String) private Map<Integer, String> _oidToPgName; // pgname (String) -> oid (Integer) private Map<String, Integer> _pgNameToOid; // pgname (String) -> extension pgobject (Class) private Map<String, Class<? extends PGobject>> _pgNameToPgObject; // type array oid -> base type's oid private Map<Integer, Integer> _pgArrayToPgType; // array type oid -> base type array element delimiter private Map<Integer, Character> _arrayOidToDelimiter; private BaseConnection _conn; private final int _unknownLength; private PreparedStatement _getOidStatementSimple; private PreparedStatement _getOidStatementComplexNonArray; private PreparedStatement _getOidStatementComplexArray; private PreparedStatement _getNameStatement; private PreparedStatement _getArrayElementOidStatement; private PreparedStatement _getArrayDelimiterStatement; private PreparedStatement _getTypeInfoStatement; // basic pg types info: // 0 - type name // 1 - type oid // 2 - sql type // 3 - java class // 4 - array type oid private static final Object types[][] = { {"int2", Oid.INT2, Types.SMALLINT, "java.lang.Integer", Oid.INT2_ARRAY}, {"int4", Oid.INT4, Types.INTEGER, "java.lang.Integer", Oid.INT4_ARRAY}, {"oid", Oid.OID, Types.BIGINT, "java.lang.Long", Oid.OID_ARRAY}, {"int8", Oid.INT8, Types.BIGINT, "java.lang.Long", Oid.INT8_ARRAY}, {"money", Oid.MONEY, Types.DOUBLE, "java.lang.Double", Oid.MONEY_ARRAY}, {"numeric", Oid.NUMERIC, Types.NUMERIC, "java.math.BigDecimal", Oid.NUMERIC_ARRAY}, {"float4", Oid.FLOAT4, Types.REAL, "java.lang.Float", Oid.FLOAT4_ARRAY}, {"float8", Oid.FLOAT8, Types.DOUBLE, "java.lang.Double", Oid.FLOAT8_ARRAY}, {"char", Oid.CHAR, Types.CHAR, "java.lang.String", Oid.CHAR_ARRAY}, {"bpchar", Oid.BPCHAR, Types.CHAR, "java.lang.String", Oid.BPCHAR_ARRAY}, {"varchar", Oid.VARCHAR, Types.VARCHAR, "java.lang.String", Oid.VARCHAR_ARRAY}, {"text", Oid.TEXT, Types.VARCHAR, "java.lang.String", Oid.TEXT_ARRAY}, {"name", Oid.NAME, Types.VARCHAR, "java.lang.String", Oid.NAME_ARRAY}, {"bytea", Oid.BYTEA, Types.BINARY, "[B", Oid.BYTEA_ARRAY}, {"bool", Oid.BOOL, Types.BIT, "java.lang.Boolean", Oid.BOOL_ARRAY}, {"bit", Oid.BIT, Types.BIT, "java.lang.Boolean", Oid.BIT_ARRAY}, {"date", Oid.DATE, Types.DATE, "java.sql.Date", Oid.DATE_ARRAY}, {"time", Oid.TIME, Types.TIME, "java.sql.Time", Oid.TIME_ARRAY}, {"timetz", Oid.TIMETZ, Types.TIME, "java.sql.Time", Oid.TIMETZ_ARRAY}, {"timestamp", Oid.TIMESTAMP, Types.TIMESTAMP, "java.sql.Timestamp", Oid.TIMESTAMP_ARRAY}, {"timestamptz", Oid.TIMESTAMPTZ, Types.TIMESTAMP, "java.sql.Timestamp", Oid.TIMESTAMPTZ_ARRAY}, //#if mvn.project.property.postgresql.jdbc.spec >= "JDBC4.2" {"refcursor", Oid.REF_CURSOR, Types.REF_CURSOR, "java.sql.ResultSet", Oid.REF_CURSOR_ARRAY}, //#endif {"json", Oid.JSON, Types.OTHER, "org.postgresql.util.PGobject", Oid.JSON_ARRAY}, {"point", Oid.POINT, Types.OTHER, "org.postgresql.geometric.PGpoint", Oid.POINT_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 final static HashMap<String, String> typeAliases; static { typeAliases = new HashMap<String, String>(); 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<Integer, String>(); _pgNameToOid = new HashMap<String, Integer>(); _pgNameToJavaClass = new HashMap<String, String>(); _pgNameToPgObject = new HashMap<String, Class<? extends PGobject>>(); _pgArrayToPgType = new HashMap<Integer, Integer>(); _arrayOidToDelimiter = new HashMap<Integer, Character>(); // needs to be synchronized because the iterator is returned // from getPGTypeNamesWithSQLTypes() _pgNameToSQLType = Collections.synchronizedMap(new HashMap<String, Integer>()); 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); } _pgNameToJavaClass.put("hstore", Map.class.getName()); } 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); // 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 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); } } public synchronized void addDataType(String type, Class<? extends PGobject> klass) throws SQLException { _pgNameToPgObject.put(type, klass); _pgNameToJavaClass.put(type, klass.getName()); } public Iterator<String> 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; } 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; // in case of multiple records (in different schemas) choose the one from the current // schema, // otherwise take the last version of a type that is at least more deterministic then before // (keeping old behaviour of finding types, that should not be found without correct search // path) sql = "SELECT typinput='array_in'::regproc, typtype " + " FROM pg_catalog.pg_type " + " LEFT " + " JOIN (select ns.oid as nspoid, ns.nspname, r.r " + " from pg_namespace as ns " // -- go with older way of unnesting array to be compatible with 8.0 + " 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;"; _getTypeInfoStatement = _conn.prepareStatement(sql); } _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); } 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; } else if ("e".equals(typtype)) { type = Types.VARCHAR; } } if (type == null) { type = Types.OTHER; } rs.close(); _pgNameToSQLType.put(pgTypeName, type); return type; } private PreparedStatement getOidStatement(String pgTypeName) throws SQLException { boolean isArray = pgTypeName.endsWith("[]"); boolean hasQuote = pgTypeName.contains("\""); int dotIndex = pgTypeName.indexOf('.'); if (dotIndex == -1 && !hasQuote && !isArray) { if (_getOidStatementSimple == 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 " + " 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;"; _getOidStatementSimple = _conn.prepareStatement(sql); } // coerce to lower case to handle upper case type names String lcName = pgTypeName.toLowerCase(); // default arrays are represented with _ as prefix ... this dont even work for public schema // fully _getOidStatementSimple.setString(1, lcName); return _getOidStatementSimple; } PreparedStatement oidStatementComplex; if (isArray) { if (_getOidStatementComplexArray == null) { String sql; if (_conn.haveMinimumServerVersion(ServerVersion.v8_3)) { sql = "SELECT t.typarray, arr.typname " + " 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 ? IS NULL AND n.nspname = ANY (current_schemas(true)))" + " ORDER BY t.oid DESC LIMIT 1"; } else { sql = "SELECT t.oid, t.typname " + " 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 ? IS NULL AND n.nspname = ANY (current_schemas(true)))" + " ORDER BY t.typelem DESC LIMIT 1"; } _getOidStatementComplexArray = _conn.prepareStatement(sql); } oidStatementComplex = _getOidStatementComplexArray; } else { if (_getOidStatementComplexNonArray == null) { String sql = "SELECT t.oid, t.typname " + " FROM pg_catalog.pg_type t" + " JOIN pg_catalog.pg_namespace n ON t.typnamespace = n.oid" + " WHERE t.typname = ? AND (n.nspname = ? OR ? IS NULL AND n.nspname = ANY (current_schemas(true)))" + " ORDER BY t.oid DESC LIMIT 1"; _getOidStatementComplexNonArray = _conn.prepareStatement(sql); } oidStatementComplex = _getOidStatementComplexNonArray; } String fullName = isArray ? pgTypeName.substring(0, pgTypeName.length() - 2) : pgTypeName; String schema; String name; // simple use case if (dotIndex == -1) { schema = null; name = fullName; } else { if (fullName.startsWith("\"")) { if (fullName.endsWith("\"")) { String[] parts = fullName.split("\"\\.\""); schema = parts.length == 2 ? parts[0] + "\"" : null; name = parts.length == 2 ? "\"" + parts[1] : parts[0]; } else { int lastDotIndex = fullName.lastIndexOf('.'); name = fullName.substring(lastDotIndex + 1); schema = fullName.substring(0, lastDotIndex); } } else { schema = fullName.substring(0, dotIndex); name = fullName.substring(dotIndex + 1); } } if (schema != null && schema.startsWith("\"") && schema.endsWith("\"")) { schema = schema.substring(1, schema.length() - 1); } else if (schema != null) { schema = schema.toLowerCase(); } if (name.startsWith("\"") && name.endsWith("\"")) { name = name.substring(1, name.length() - 1); } else { name = name.toLowerCase(); } oidStatementComplex.setString(1, name); oidStatementComplex.setString(2, schema); oidStatementComplex.setString(3, schema); return oidStatementComplex; } 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); _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 (_getNameStatement == 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 = ?"; _getNameStatement = _conn.prepareStatement(sql); } _getNameStatement.setInt(1, oid); // Go through BaseStatement to avoid transaction start. if (!((BaseStatement) _getNameStatement).executeWithFlags(QueryExecutor.QUERY_SUPPRESS_BEGIN)) { throw new PSQLException(GT.tr("No results were returned by the query."), PSQLState.NO_DATA); } ResultSet rs = _getNameStatement.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); } } _pgNameToOid.put(pgTypeName, oid); _oidToPgName.put(oid, pgTypeName); } rs.close(); return pgTypeName; } public int getPGArrayType(String elementTypeName) throws SQLException { elementTypeName = getTypeForAlias(elementTypeName); return getPGType(elementTypeName + "[]"); } /** * Return the oid of the array's base element if it's an array, if not return the provided oid. * This doesn't do any database lookups, so it's only useful for the originally provided type * mappings. This is fine for it's intended uses where we only have intimate knowledge of types * that are already known to the driver. * * @param oid input oid * @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) { 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 " + "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); _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); } rs.close(); return pgType; } public synchronized Class<? extends PGobject> 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; } if (getSQLType(pgTypeName) == Types.ARRAY) { result = "java.sql.Array"; _pgNameToJavaClass.put(pgTypeName, result); } 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; } } return alias; } public int getPrecision(int oid, int typmod) { oid = convertArrayToBaseOid(oid); switch (oid) { case Oid.INT2: return 5; case Oid.OID: case Oid.INT4: return 10; case Oid.INT8: return 19; case Oid.FLOAT4: // 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. return 8; case Oid.FLOAT8: return 17; case Oid.NUMERIC: if (typmod == -1) { return 0; } return ((typmod - 4) & 0xFFFF0000) >> 16; case Oid.CHAR: case Oid.BOOL: return 1; case Oid.BPCHAR: case Oid.VARCHAR: if (typmod == -1) { return _unknownLength; } return typmod - 4; // datetime types get the // "length in characters of the String representation" case Oid.DATE: case Oid.TIME: case Oid.TIMETZ: case Oid.INTERVAL: case Oid.TIMESTAMP: case Oid.TIMESTAMPTZ: return getDisplaySize(oid, typmod); case Oid.BIT: return typmod; case Oid.VARBIT: if (typmod == -1) { return _unknownLength; } return typmod; case Oid.TEXT: case Oid.BYTEA: default: return _unknownLength; } } public int getScale(int oid, int typmod) { oid = convertArrayToBaseOid(oid); switch (oid) { case Oid.FLOAT4: return 8; case Oid.FLOAT8: return 17; case Oid.NUMERIC: if (typmod == -1) { return 0; } return (typmod - 4) & 0xFFFF; case Oid.TIME: case Oid.TIMETZ: case Oid.TIMESTAMP: case Oid.TIMESTAMPTZ: if (typmod == -1) { return 6; } return typmod; case Oid.INTERVAL: if (typmod == -1) { return 6; } return typmod & 0xFFFF; default: return 0; } } public boolean isCaseSensitive(int oid) { oid = convertArrayToBaseOid(oid); switch (oid) { case Oid.OID: case Oid.INT2: case Oid.INT4: case Oid.INT8: case Oid.FLOAT4: case Oid.FLOAT8: case Oid.NUMERIC: case Oid.BOOL: case Oid.BIT: case Oid.VARBIT: case Oid.DATE: case Oid.TIME: case Oid.TIMETZ: case Oid.TIMESTAMP: case Oid.TIMESTAMPTZ: case Oid.INTERVAL: return false; default: return true; } } public boolean isSigned(int oid) { oid = convertArrayToBaseOid(oid); switch (oid) { case Oid.INT2: case Oid.INT4: case Oid.INT8: case Oid.FLOAT4: case Oid.FLOAT8: case Oid.NUMERIC: return true; default: return false; } } public int getDisplaySize(int oid, int typmod) { oid = convertArrayToBaseOid(oid); switch (oid) { case Oid.INT2: return 6; // -32768 to +32767 case Oid.INT4: return 11; // -2147483648 to +2147483647 case Oid.OID: return 10; // 0 to 4294967295 case Oid.INT8: return 20; // -9223372036854775808 to +9223372036854775807 case Oid.FLOAT4: // varies based upon the extra_float_digits GUC. // These values are for the longest possible length. return 15; // sign + 9 digits + decimal point + e + sign + 2 digits case Oid.FLOAT8: return 25; // sign + 18 digits + decimal point + e + sign + 3 digits case Oid.CHAR: return 1; case Oid.BOOL: return 1; case Oid.DATE: return 13; // "4713-01-01 BC" to "01/01/4713 BC" - "31/12/32767" case Oid.TIME: case Oid.TIMETZ: case Oid.TIMESTAMP: case Oid.TIMESTAMPTZ: // Calculate the number of decimal digits + the decimal point. int secondSize; switch (typmod) { case -1: secondSize = 6 + 1; break; case 0: secondSize = 0; break; case 1: // Bizarrely SELECT '0:0:0.1'::time(1); returns 2 digits. secondSize = 2 + 1; break; default: secondSize = typmod + 1; break; } // We assume the worst case scenario for all of these. // time = '00:00:00' = 8 // date = '5874897-12-31' = 13 (although at large values second precision is lost) // date = '294276-11-20' = 12 --enable-integer-datetimes // zone = '+11:30' = 6; switch (oid) { case Oid.TIME: return 8 + secondSize; case Oid.TIMETZ: return 8 + secondSize + 6; case Oid.TIMESTAMP: return 13 + 1 + 8 + secondSize; case Oid.TIMESTAMPTZ: return 13 + 1 + 8 + secondSize + 6; } case Oid.INTERVAL: // SELECT LENGTH('-123456789 years 11 months 33 days 23 hours 10.123456 seconds'::interval); return 49; case Oid.VARCHAR: case Oid.BPCHAR: if (typmod == -1) { return _unknownLength; } return typmod - 4; case Oid.NUMERIC: if (typmod == -1) { return 131089; // SELECT LENGTH(pow(10::numeric,131071)); 131071 = 2^17-1 } int precision = (typmod - 4 >> 16) & 0xffff; int scale = (typmod - 4) & 0xffff; // sign + digits + decimal point (only if we have nonzero scale) return 1 + precision + (scale != 0 ? 1 : 0); case Oid.BIT: return typmod; case Oid.VARBIT: if (typmod == -1) { return _unknownLength; } return typmod; case Oid.TEXT: case Oid.BYTEA: return _unknownLength; default: return _unknownLength; } } public int getMaximumPrecision(int oid) { oid = convertArrayToBaseOid(oid); switch (oid) { case Oid.NUMERIC: return 1000; case Oid.TIME: case Oid.TIMETZ: // Technically this depends on the --enable-integer-datetimes // configure setting. It is 6 with integer and 10 with float. return 6; case Oid.TIMESTAMP: case Oid.TIMESTAMPTZ: case Oid.INTERVAL: return 6; case Oid.BPCHAR: case Oid.VARCHAR: return 10485760; case Oid.BIT: case Oid.VARBIT: return 83886080; default: return 0; } } public boolean requiresQuoting(int oid) throws SQLException { int sqlType = getSQLType(oid); return requiresQuotingSqlType(sqlType); } /** * Returns true if particular sqlType requires quoting. * This method is used internally by the driver, so it might disappear without notice. * * @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 { switch (sqlType) { case Types.BIGINT: case Types.DOUBLE: case Types.FLOAT: case Types.INTEGER: case Types.REAL: case Types.SMALLINT: case Types.TINYINT: case Types.NUMERIC: case Types.DECIMAL: return false; } return true; } }