From 851df6d625aad7181bea889a24aa28701f84244d Mon Sep 17 00:00:00 2001 From: WangYuxuan Date: Mon, 20 May 2024 11:33:41 +0800 Subject: [PATCH 1/2] first commit --- .../src/main/java/org/postgresql/jdbc/PgDatabaseMetaData.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pgjdbc/src/main/java/org/postgresql/jdbc/PgDatabaseMetaData.java b/pgjdbc/src/main/java/org/postgresql/jdbc/PgDatabaseMetaData.java index 2c25d0a..de97c77 100644 --- a/pgjdbc/src/main/java/org/postgresql/jdbc/PgDatabaseMetaData.java +++ b/pgjdbc/src/main/java/org/postgresql/jdbc/PgDatabaseMetaData.java @@ -2251,7 +2251,9 @@ public class PgDatabaseMetaData implements DatabaseMetaData { String sql; sql = "SELECT t.typname,t.oid FROM pg_catalog.pg_type t" + " JOIN pg_catalog.pg_namespace n ON (t.typnamespace = n.oid) " - + " WHERE n.nspname != 'pg_toast'"; + + " WHERE n.nspname != 'pg_toast'" + + " AND " + + " (t.typrelid = 0 OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid))"; Statement stmt = connection.createStatement(); ResultSet rs = stmt.executeQuery(sql); -- Gitee From 4ca16e0fe551cdd6794f77c66052b0f48e7da2d1 Mon Sep 17 00:00:00 2001 From: WangYuxuan Date: Mon, 20 May 2024 11:39:31 +0800 Subject: [PATCH 2/2] backport pgjdbc !1302 --- .../postgresql/jdbc/PgDatabaseMetaData.java | 6 ++ .../org/postgresql/jdbc/TypeInfoCache.java | 89 +++++++++++++++---- .../test/jdbc2/DatabaseMetaDataCacheTest.java | 84 +++++++++++++++++ .../org/postgresql/util/TestLogHandler.java | 39 ++++++++ 4 files changed, 201 insertions(+), 17 deletions(-) create mode 100644 pgjdbc/src/test/java/org/postgresql/test/jdbc2/DatabaseMetaDataCacheTest.java create mode 100644 pgjdbc/src/test/java/org/postgresql/util/TestLogHandler.java diff --git a/pgjdbc/src/main/java/org/postgresql/jdbc/PgDatabaseMetaData.java b/pgjdbc/src/main/java/org/postgresql/jdbc/PgDatabaseMetaData.java index de97c77..958c60d 100644 --- a/pgjdbc/src/main/java/org/postgresql/jdbc/PgDatabaseMetaData.java +++ b/pgjdbc/src/main/java/org/postgresql/jdbc/PgDatabaseMetaData.java @@ -9,6 +9,7 @@ import org.postgresql.core.BaseStatement; import org.postgresql.core.Field; import org.postgresql.core.Oid; import org.postgresql.core.ServerVersion; +import org.postgresql.core.TypeInfo; import org.postgresql.util.GT; import org.postgresql.util.JdbcBlackHole; import org.postgresql.util.PSQLException; @@ -2269,6 +2270,11 @@ public class PgDatabaseMetaData implements DatabaseMetaData { byte[] bSearchable = connection.encodeString(Integer.toString(java.sql.DatabaseMetaData.typeSearchable)); + TypeInfo ti = connection.getTypeInfo(); + if (ti instanceof TypeInfoCache) { + ((TypeInfoCache) ti).cacheSQLTypes(); + } + while (rs.next()) { byte[][] tuple = new byte[18][]; String typname = rs.getString(1); diff --git a/pgjdbc/src/main/java/org/postgresql/jdbc/TypeInfoCache.java b/pgjdbc/src/main/java/org/postgresql/jdbc/TypeInfoCache.java index 30e8d8f..263f7d1 100644 --- a/pgjdbc/src/main/java/org/postgresql/jdbc/TypeInfoCache.java +++ b/pgjdbc/src/main/java/org/postgresql/jdbc/TypeInfoCache.java @@ -22,9 +22,13 @@ import java.sql.SQLException; import java.sql.Types; import java.util.*; import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; public class TypeInfoCache implements TypeInfo { + private static final Logger LOGGER = Logger.getLogger(TypeInfoCache.class.getName()); + // pgname (String) -> java.sql.Types (Integer) private Map _pgNameToSQLType; @@ -59,6 +63,7 @@ public class TypeInfoCache implements TypeInfo { private PreparedStatement _getArrayElementOidStatement; private PreparedStatement _getArrayDelimiterStatement; private PreparedStatement _getTypeInfoStatement; + private PreparedStatement _getAllTypeInfoStatement; private PreparedStatement _getStructElementStatement; private Map> _pgStructToPgTypes; @@ -203,6 +208,69 @@ public class TypeInfoCache implements TypeInfo { return _pgNameToSQLType.keySet().iterator(); } + private String getSQLTypeQuery(boolean typnameParam) { + // 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. + // + // 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) + StringBuilder sql = new StringBuilder(); + sql.append("SELECT typinput='array_in'::regproc as is_array, typtype, typname "); + sql.append(" FROM pg_catalog.pg_type "); + sql.append(" LEFT JOIN (select ns.oid as nspoid, ns.nspname, r.r "); + sql.append(" from pg_namespace as ns "); + // -- go with older way of unnesting array to be compatible with 8.0 + sql.append(" join ( select s.r, (current_schemas(false))[s.r] as nspname "); + sql.append(" from generate_series(1, array_upper(current_schemas(false), 1)) as s(r) ) as r "); + sql.append(" using ( nspname ) "); + sql.append(" ) as sp "); + sql.append(" ON sp.nspoid = typnamespace "); + if (typnameParam) { + sql.append(" WHERE typname = ? "); + } + sql.append(" ORDER BY sp.r, pg_type.oid DESC;"); + return sql.toString(); + } + + private Integer getSQLTypeFromQueryResult(ResultSet rs) throws SQLException { + Integer type = null; + boolean isArray = rs.getBoolean("is_array"); + String typtype = rs.getString("typtype"); + if (isArray) { + type = Types.ARRAY; + } else if ("c".equals(typtype)) { + type = Types.STRUCT; + } else if ("d".equals(typtype)) { + type = Types.DISTINCT; + } + return type; + } + + public void cacheSQLTypes() throws SQLException { + LOGGER.log(Level.FINEST, "caching all SQL typecodes"); + if (_getAllTypeInfoStatement == null) { + _getAllTypeInfoStatement = _conn.prepareStatement(getSQLTypeQuery(false)); + } + // Go through BaseStatement to avoid transaction start. + if (!((BaseStatement) _getAllTypeInfoStatement) + .executeWithFlags(QueryExecutor.QUERY_SUPPRESS_BEGIN)) { + throw new PSQLException(GT.tr("No results were returned by the query."), PSQLState.NO_DATA); + } + ResultSet rs = _getAllTypeInfoStatement.getResultSet(); + while (rs.next()) { + String typeName = rs.getString("typname"); + Integer type = getSQLTypeFromQueryResult(rs); + if (!_pgNameToSQLType.containsKey(typeName)) { + _pgNameToSQLType.put(typeName, type); + } + } + rs.close(); + } + public int getSQLType(int oid) throws SQLException { return getSQLType(getPGType(oid)); } @@ -216,6 +284,8 @@ public class TypeInfoCache implements TypeInfo { return i; } + LOGGER.log(Level.FINEST, "querying SQL typecode for pg type '{0}'", pgTypeName); + Integer type = null; if ("binary".equals(pgTypeName)) { type = Types.BINARY; @@ -232,15 +302,8 @@ public class TypeInfoCache implements TypeInfo { // 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 = ?"; - _getTypeInfoStatement = _conn.prepareStatement(sql); + _getTypeInfoStatement = _conn.prepareStatement(getSQLTypeQuery(true)); } _getTypeInfoStatement.setString(1, pgTypeName); @@ -254,15 +317,7 @@ public class TypeInfoCache implements TypeInfo { ResultSet rs = _getTypeInfoStatement.getResultSet(); 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; - } + type = getSQLTypeFromQueryResult(rs); } if (type == null) { diff --git a/pgjdbc/src/test/java/org/postgresql/test/jdbc2/DatabaseMetaDataCacheTest.java b/pgjdbc/src/test/java/org/postgresql/test/jdbc2/DatabaseMetaDataCacheTest.java new file mode 100644 index 0000000..4af005e --- /dev/null +++ b/pgjdbc/src/test/java/org/postgresql/test/jdbc2/DatabaseMetaDataCacheTest.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2020, PostgreSQL Global Development Group + * See the LICENSE file in the project root for more information. + */ + +package org.postgresql.test.jdbc2; + +import static org.junit.Assert.assertEquals; + +import org.postgresql.core.TypeInfo; +import org.postgresql.jdbc.PgConnection; +import org.postgresql.test.TestUtil; +import org.postgresql.util.TestLogHandler; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.sql.SQLException; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.LogManager; +import java.util.logging.LogRecord; +import java.util.logging.Logger; +import java.util.regex.Pattern; + +/* + * Tests for caching of DatabaseMetadata + * + */ +public class DatabaseMetaDataCacheTest { + private PgConnection con; + private TestLogHandler log; + private Logger driverLogger; + private Level driverLogLevel; + + private static final Pattern SQL_TYPE_QUERY_LOG_FILTER = Pattern.compile("querying SQL typecode for pg type"); + private static final Pattern SQL_TYPE_CACHE_LOG_FILTER = Pattern.compile("caching all SQL typecodes"); + + @Before + public void setUp() throws Exception { + con = (PgConnection)TestUtil.openDB(); + log = new TestLogHandler(); + driverLogger = LogManager.getLogManager().getLogger("org.postgresql"); + driverLogger.addHandler(log); + driverLogLevel = driverLogger.getLevel(); + driverLogger.setLevel(Level.ALL); + } + + @After + public void tearDown() throws Exception { + TestUtil.closeDB(con); + driverLogger.removeHandler(log); + driverLogger.setLevel(driverLogLevel); + log = null; + } + + @Test + public void testGetSQLTypeQueryCache() throws SQLException { + TypeInfo ti = con.getTypeInfo(); + + List typeQueries = log.getRecordsMatching(SQL_TYPE_QUERY_LOG_FILTER); + assertEquals(0, typeQueries.size()); + + ti.getSQLType("box"); // this must be a type not in the hardcoded 'types' list + typeQueries = log.getRecordsMatching(SQL_TYPE_QUERY_LOG_FILTER); + assertEquals(1, typeQueries.size()); + + ti.getSQLType("box"); // this time it should be retrieved from the cache + typeQueries = log.getRecordsMatching(SQL_TYPE_QUERY_LOG_FILTER); + assertEquals(1, typeQueries.size()); + } + + @Test + public void testGetTypeInfoUsesCache() throws SQLException { + con.getMetaData().getTypeInfo(); + + List typeCacheQuery = log.getRecordsMatching(SQL_TYPE_CACHE_LOG_FILTER); + assertEquals("PgDatabaseMetadata.getTypeInfo() did not cache SQL typecodes", 1, typeCacheQuery.size()); + + List typeQueries = log.getRecordsMatching(SQL_TYPE_QUERY_LOG_FILTER); + assertEquals("PgDatabaseMetadata.getTypeInfo() resulted in individual queries for SQL typecodes", 74, typeQueries.size()); + } +} \ No newline at end of file diff --git a/pgjdbc/src/test/java/org/postgresql/util/TestLogHandler.java b/pgjdbc/src/test/java/org/postgresql/util/TestLogHandler.java new file mode 100644 index 0000000..1f46c60 --- /dev/null +++ b/pgjdbc/src/test/java/org/postgresql/util/TestLogHandler.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2020, PostgreSQL Global Development Group + * See the LICENSE file in the project root for more information. + */ + +package org.postgresql.util; + +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Handler; +import java.util.logging.LogRecord; +import java.util.regex.Pattern; + +public class TestLogHandler extends Handler { + public List records = new ArrayList(); + + @Override + public void publish(LogRecord record) { + records.add(record); + } + + @Override + public void flush() { + } + + @Override + public void close() throws SecurityException { + } + + public List getRecordsMatching(Pattern messagePattern) { + ArrayList matches = new ArrayList(); + for (LogRecord r: this.records) { + if (messagePattern.matcher(r.getMessage()).find()) { + matches.add(r); + } + } + return matches; + } +} \ No newline at end of file -- Gitee