pgjdbc/pgjdbc GitHub issues and pull requests (mirror)  
help / color / mirror / Atom feed
[pgjdbc/pgjdbc] issue #3049: TypeInfoCache should be clearable/resetable
10+ messages / 4 participants
[nested] [flat]

* [pgjdbc/pgjdbc] issue #3049: TypeInfoCache should be clearable/resetable
@ 2023-11-30 18:41 "beikov (@beikov)" <[email protected]>
  0 siblings, 0 replies; 10+ messages in thread

From: beikov (@beikov) @ 2023-11-30 18:41 UTC (permalink / raw)
  To: pgjdbc/pgjdbc <[email protected]>

**Describe the issue**
Imagine TX1 binding a `PGObject` with type name `mystruct` via `PreparedStatement#setObject()`. This will put the oid of that type into `TypeInfoCache`. When the type gets dropped and a new type with the same name is created, one can never ever bind an object with this type name with this connection, because the oid does not exist anymore. The error I'm seeing is `ERROR: cache lookup failed for type 1234`.

**Driver Version?** 
42.6.0
**Java Version?**
11
**OS Version?**
Linux/Windows
**PostgreSQL Version?**
15
**To Reproduce**
See example.

**Expected behaviour**
Either the driver should handle this situation automatically by retrying the statement after looking up the oid again, or at least the `TypeInfo` or `TypeInfoCache` types should provide a way to clear the cache.

Using the following template code make sure the bug can be replicated in the driver alone.
```
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.Properties;

public class TestNullsFirst {
    public static void main(String []args) throws Exception {


        String url = "jdbc:postgresql://localhost:5432/test";

        Properties props = new Properties();
        props.setProperty("user", "test");
        props.setProperty("password", "test");
        try ( Connection conn = DriverManager.getConnection(url, props) ){
            try ( Statement statement = conn.createStatement() ) {
                statement.execute("create type structType as (v text)");
                statement.execute("create table tbl as (s structType)");
            }
            try ( PreparedStatement statement = conn.prepareStatement( "insert into tbl values (?)" ) ) {
                PGObject o = new PGObject();
                o.setType("structType")
                o.setValue("(\"abc\")")
                statement.setObject( 1, o );
                statement.executeUpdate();
            }
            try ( Statement statement = conn.createStatement() ) {
                statement.execute("drop table tbl");
                statement.execute("drop type structType");
                statement.execute("create type structType as (v text)");
                statement.execute("create table tbl as (s structType)");
            }
            try ( PreparedStatement statement = conn.prepareStatement( "insert into tbl values (?)" ) ) {
                PGObject o = new PGObject();
                o.setType("structType")
                o.setValue("(\"abc\")")
                statement.setObject( 1, o );
                statement.executeUpdate();
            }
        }
    }
}
```


^ permalink  raw  reply  [nested|flat] 10+ messages in thread

* Re: [pgjdbc/pgjdbc] issue #3049: TypeInfoCache should be clearable/resetable
@ 2023-11-30 19:19 ` "beikov (@beikov)" <[email protected]>
  8 siblings, 0 replies; 10+ messages in thread

From: beikov (@beikov) @ 2023-11-30 19:19 UTC (permalink / raw)
  To: pgjdbc/pgjdbc <[email protected]>

In case anyone else runs into the same issue, here is the code I'm using to clear the cache:

```java
final Class<?> pgConnection = Class.forName( "org.postgresql.jdbc.PgConnection" );
final Object connection = c.unwrap( pgConnection );
final Object typeInfo = pgConnection.getMethod( "getTypeInfo" ).invoke( connection );
final Class<?> typeInfoCacheClass = Class.forName( "org.postgresql.jdbc.TypeInfoCache" );
final Field oidToPgNameField = typeInfoCacheClass.getDeclaredField( "oidToPgName" );
final Field pgNameToOidField = typeInfoCacheClass.getDeclaredField( "pgNameToOid" );
final Field pgNameToSQLTypeField = typeInfoCacheClass.getDeclaredField( "pgNameToSQLType" );
final Field oidToSQLTypeField = typeInfoCacheClass.getDeclaredField( "oidToSQLType" );
oidToPgNameField.setAccessible( true );
pgNameToOidField.setAccessible( true );
pgNameToSQLTypeField.setAccessible( true );
oidToSQLTypeField.setAccessible( true );
//noinspection unchecked
final Map<Integer, String> oidToPgName = (Map<Integer, String>) oidToPgNameField.get( typeInfo );
//noinspection unchecked
final Map<String, Integer> pgNameToOid = (Map<String, Integer>) pgNameToOidField.get( typeInfo );
//noinspection unchecked
final Map<String, Integer> pgNameToSQLType = (Map<String, Integer>) pgNameToSQLTypeField.get( typeInfo );
//noinspection unchecked
final Map<Integer, Integer> oidToSQLType = (Map<Integer, Integer>) oidToSQLTypeField.get( typeInfo );
for ( Iterator<Map.Entry<String, Integer>> iter = pgNameToOid.entrySet().iterator(); iter.hasNext(); ) {
	Map.Entry<String, Integer> entry = iter.next();
	final String typeName = entry.getKey();
	if ( !PGJDBC_STANDARD_TYPE_NAMES.contains( typeName ) ) {
		final Integer oid = entry.getValue();
		oidToPgName.remove( oid );
		oidToSQLType.remove( oid );
		pgNameToSQLType.remove( typeName );
		iter.remove();
	}
}
```

with this utility:

```java
private static final Set<String> PGJDBC_STANDARD_TYPE_NAMES = buildTypeNames( Set.of(
			"int2",
			"int4",
			"oid",
			"int8",
			"money",
			"numeric",
			"float4",
			"float8",
			"char",
			"bpchar",
			"varchar",
			"text",
			"name",
			"bytea",
			"bool",
			"bit",
			"date",
			"time",
			"timetz",
			"timestamp",
			"timestamptz",
			"refcursor",
			"json",
			"jsonb",
			"box",
			"point",
			"uuid",
			"xml"
	) );

	private static Set<String> buildTypeNames(Set<String> baseTypeNames) {
		final HashSet<String> typeNames = new HashSet<>( baseTypeNames.size() * 3 );
		for ( String baseTypeName : baseTypeNames ) {
			typeNames.add( baseTypeName );
			typeNames.add( "_" + baseTypeName );
			typeNames.add( baseTypeName + "[]" );
		}
		return typeNames;
	}
```

^ permalink  raw  reply  [nested|flat] 10+ messages in thread

* Re: [pgjdbc/pgjdbc] issue #3049: TypeInfoCache should be clearable/resetable
@ 2023-12-01 11:57 ` "davecramer (@davecramer)" <[email protected]>
  8 siblings, 0 replies; 10+ messages in thread

From: davecramer (@davecramer) @ 2023-12-01 11:57 UTC (permalink / raw)
  To: pgjdbc/pgjdbc <[email protected]>

Care to provide a PR ?

^ permalink  raw  reply  [nested|flat] 10+ messages in thread

* Re: [pgjdbc/pgjdbc] issue #3049: TypeInfoCache should be clearable/resetable
@ 2023-12-01 12:03 ` "beikov (@beikov)" <[email protected]>
  8 siblings, 0 replies; 10+ messages in thread

From: beikov (@beikov) @ 2023-12-01 12:03 UTC (permalink / raw)
  To: pgjdbc/pgjdbc <[email protected]>

First of all I'd like to understand what you think about this problem. Providing a method to clear the type cache is easy enough, but I have no idea how re-attempting statement execution on such an error could work.

^ permalink  raw  reply  [nested|flat] 10+ messages in thread

* Re: [pgjdbc/pgjdbc] issue #3049: TypeInfoCache should be clearable/resetable
@ 2023-12-01 12:08 ` "davecramer (@davecramer)" <[email protected]>
  8 siblings, 0 replies; 10+ messages in thread

From: davecramer (@davecramer) @ 2023-12-01 12:08 UTC (permalink / raw)
  To: pgjdbc/pgjdbc <[email protected]>

Fair enough. My first instinct here is to have a way to *NOT* cache an object if we know we are going to change it soon. That said I haven't really given it enough thought.

^ permalink  raw  reply  [nested|flat] 10+ messages in thread

* Re: [pgjdbc/pgjdbc] issue #3049: TypeInfoCache should be clearable/resetable
@ 2023-12-01 12:13 ` "vlsi (@vlsi)" <[email protected]>
  8 siblings, 0 replies; 10+ messages in thread

From: vlsi (@vlsi) @ 2023-12-01 12:13 UTC (permalink / raw)
  To: pgjdbc/pgjdbc <[email protected]>

I have not looked into this exact case, however, here's a way re-executing might be implemented: https://github.com/pgjdbc/pgjdbc/pull/451/files#diff-fb626514a44e1fd93551464de0ba369def2b0513a7232ce...

We could probably monitor "drop type" queries, and invalidate the relevant caches automatically. Sure it won't heal all the cases, but it might resolve many issues automatically.

^ permalink  raw  reply  [nested|flat] 10+ messages in thread

* Re: [pgjdbc/pgjdbc] issue #3049: TypeInfoCache should be clearable/resetable
@ 2023-12-04 09:29 ` "beikov (@beikov)" <[email protected]>
  8 siblings, 0 replies; 10+ messages in thread

From: beikov (@beikov) @ 2023-12-04 09:29 UTC (permalink / raw)
  To: pgjdbc/pgjdbc <[email protected]>

Thanks for linking some example code. I'll take a look as soon as I find some time.

^ permalink  raw  reply  [nested|flat] 10+ messages in thread

* Re: [pgjdbc/pgjdbc] issue #3049: TypeInfoCache should be clearable/resetable
@ 2023-12-06 08:47 ` "vlsi (@vlsi)" <[email protected]>
  8 siblings, 0 replies; 10+ messages in thread

From: vlsi (@vlsi) @ 2023-12-06 08:47 UTC (permalink / raw)
  To: pgjdbc/pgjdbc <[email protected]>

```
2023-12-06 11:21:34 FINEST QueryExecutorImpl sendParse  FE=> Parse(stmt=null,query="drop type create_drop_struct",oids={})
2023-12-06 11:21:34 FINEST QueryExecutorImpl sendBind  FE=> Bind(stmt=null,portal=null)
2023-12-06 11:21:34 FINEST QueryExecutorImpl sendDescribePortal  FE=> Describe(portal=null)
2023-12-06 11:21:34 FINEST QueryExecutorImpl sendExecute  FE=> Execute(portal=null,limit=0)
2023-12-06 11:21:34 FINEST QueryExecutorImpl sendSync  FE=> Sync
2023-12-06 11:21:34 FINEST QueryExecutorImpl processResults  <=BE ParseComplete [null]
2023-12-06 11:21:34 FINEST QueryExecutorImpl processResults  <=BE BindComplete [unnamed]
2023-12-06 11:21:34 FINEST QueryExecutorImpl processResults  <=BE NoData
2023-12-06 11:21:34 FINEST QueryExecutorImpl receiveCommandStatus  <=BE CommandStatus(DROP TYPE)
2023-12-06 11:21:34 FINEST QueryExecutorImpl receiveRFQ  <=BE ReadyForQuery(I)
2023-12-06 11:21:34 FINEST QueryExecutorImpl execute   simple execute, handler=org.postgresql.jdbc.PgStatement$StatementResultHandler@26425897, maxRows=0, fetchSize=0, flags=17
2023-12-06 11:21:34 FINEST QueryExecutorImpl sendParse  FE=> Parse(stmt=null,query="create type create_drop_struct as (v text)",oids={})
2023-12-06 11:21:34 FINEST QueryExecutorImpl sendBind  FE=> Bind(stmt=null,portal=null)
2023-12-06 11:21:34 FINEST QueryExecutorImpl sendDescribePortal  FE=> Describe(portal=null)
2023-12-06 11:21:34 FINEST QueryExecutorImpl sendExecute  FE=> Execute(portal=null,limit=0)
2023-12-06 11:21:34 FINEST QueryExecutorImpl sendSync  FE=> Sync
2023-12-06 11:21:34 FINEST QueryExecutorImpl processResults  <=BE ParseComplete [null]
2023-12-06 11:21:34 FINEST QueryExecutorImpl processResults  <=BE BindComplete [unnamed]
2023-12-06 11:21:34 FINEST QueryExecutorImpl processResults  <=BE NoData
2023-12-06 11:21:34 FINEST QueryExecutorImpl receiveCommandStatus  <=BE CommandStatus(CREATE TYPE)
2023-12-06 11:21:34 FINEST QueryExecutorImpl receiveRFQ  <=BE ReadyForQuery(I)
2023-12-06 11:21:34 FINEST QueryExecutorImpl execute   simple execute, handler=org.postgresql.jdbc.PgStatement$StatementResultHandler@73163d48, maxRows=0, fetchSize=0, flags=17
2023-12-06 11:21:34 FINEST QueryExecutorImpl sendParse  FE=> Parse(stmt=null,query="create table create_drop_struct_table (s create_drop_struct)",oids={})
2023-12-06 11:21:34 FINEST QueryExecutorImpl sendBind  FE=> Bind(stmt=null,portal=null)
2023-12-06 11:21:34 FINEST QueryExecutorImpl sendDescribePortal  FE=> Describe(portal=null)
2023-12-06 11:21:34 FINEST QueryExecutorImpl sendExecute  FE=> Execute(portal=null,limit=0)
2023-12-06 11:21:34 FINEST QueryExecutorImpl sendSync  FE=> Sync
2023-12-06 11:21:34 FINEST QueryExecutorImpl processResults  <=BE ParseComplete [null]
2023-12-06 11:21:34 FINEST QueryExecutorImpl processResults  <=BE BindComplete [unnamed]
2023-12-06 11:21:34 FINEST QueryExecutorImpl processResults  <=BE NoData
2023-12-06 11:21:34 FINEST QueryExecutorImpl receiveCommandStatus  <=BE CommandStatus(CREATE TABLE)
2023-12-06 11:21:34 FINEST QueryExecutorImpl receiveRFQ  <=BE ReadyForQuery(I)
2023-12-06 11:21:34 FINEST QueryExecutorImpl execute   simple execute, handler=org.postgresql.jdbc.PgStatement$StatementResultHandler@58c34bb3, maxRows=0, fetchSize=0, flags=21
2023-12-06 11:21:34 FINEST QueryExecutorImpl sendParse  FE=> Parse(stmt=null,query="insert into create_drop_struct_table values ($1)",oids={218711})
2023-12-06 11:21:34 FINEST QueryExecutorImpl sendBind  FE=> Bind(stmt=null,portal=null,$1=<'("abc")'>,type=<unknown:218711>)
2023-12-06 11:21:34 FINEST QueryExecutorImpl sendDescribePortal  FE=> Describe(portal=null)
2023-12-06 11:21:34 FINEST QueryExecutorImpl sendExecute  FE=> Execute(portal=null,limit=1)
2023-12-06 11:21:34 FINEST QueryExecutorImpl sendSync  FE=> Sync
```


How about invalidating all lazy-loaded type information on every `DROP TYPE`, `CREATE TYPE`, `DROP TABLE`, `CREATE TABLE`?

^ permalink  raw  reply  [nested|flat] 10+ messages in thread

* Re: [pgjdbc/pgjdbc] issue #3049: TypeInfoCache should be clearable/resetable
@ 2023-12-06 08:49 ` "beikov (@beikov)" <[email protected]>
  8 siblings, 0 replies; 10+ messages in thread

From: beikov (@beikov) @ 2023-12-06 08:49 UTC (permalink / raw)
  To: pgjdbc/pgjdbc <[email protected]>

That's certainly possible, but note that the drop type can happen on other connections as well, so it wouldn't be a full solution.

^ permalink  raw  reply  [nested|flat] 10+ messages in thread

* Re: [pgjdbc/pgjdbc] issue #3049: TypeInfoCache should be clearable/resetable
@ 2025-06-30 13:46 ` "Sanne (@Sanne)" <[email protected]>
  8 siblings, 0 replies; 10+ messages in thread

From: Sanne (@Sanne) @ 2025-06-30 13:46 UTC (permalink / raw)
  To: pgjdbc/pgjdbc <[email protected]>

Just wanting to add that if we had a shared cache across all connections similar to #345 the above proposal would be a nice fix.

^ permalink  raw  reply  [nested|flat] 10+ messages in thread


end of thread, other threads:[~2025-06-30 13:46 UTC | newest]

Thread overview: 10+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2023-11-30 18:41 [pgjdbc/pgjdbc] issue #3049: TypeInfoCache should be clearable/resetable "beikov (@beikov)" <[email protected]>
2023-11-30 19:19 ` "beikov (@beikov)" <[email protected]>
2023-12-01 11:57 ` "davecramer (@davecramer)" <[email protected]>
2023-12-01 12:03 ` "beikov (@beikov)" <[email protected]>
2023-12-01 12:08 ` "davecramer (@davecramer)" <[email protected]>
2023-12-01 12:13 ` "vlsi (@vlsi)" <[email protected]>
2023-12-04 09:29 ` "beikov (@beikov)" <[email protected]>
2023-12-06 08:47 ` "vlsi (@vlsi)" <[email protected]>
2023-12-06 08:49 ` "beikov (@beikov)" <[email protected]>
2025-06-30 13:46 ` "Sanne (@Sanne)" <[email protected]>

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox