Skip to content

Commit

Permalink
Fix issue #2215 (#2217)
Browse files Browse the repository at this point in the history
Correctly handle the non-signedness of OIDs in relation to signed java integers in the new OID-based type cache.

Previously, this would cause 'bad value for type int'-errors, now this is correctly handled both ways in this part of the code.

Co-authored-by: Matthias van de Meent <matthias.vandemeent@cofano.nl>
  • Loading branch information
MMeent and Matthias van de Meent committed Jul 29, 2021
1 parent 0e99251 commit 151b287
Show file tree
Hide file tree
Showing 4 changed files with 104 additions and 6 deletions.
26 changes: 26 additions & 0 deletions pgjdbc/src/main/java/org/postgresql/core/TypeInfo.java
Expand Up @@ -116,4 +116,30 @@ void addCoreType(String pgTypeName, Integer oid, Integer sqlType, String javaCla
* @throws SQLException if something goes wrong
*/
boolean requiresQuotingSqlType(int sqlType) throws SQLException;

/**
* <p>Java Integers are signed 32-bit integers, but oids are unsigned 32-bit integers.
* We therefore read them as positive long values and then force them into signed integers
* (wrapping around into negative values when required) or we'd be unable to correctly
* handle the upper half of the oid space.</p>
*
* <p>This function handles the mapping of uint32-values in the long to java integers, and
* throws for values that are out of range.</p>
*
* @param oid the oid as a long.
* @return the (internal) signed integer representation of the (unsigned) oid.
* @throws SQLException if the long has a value outside of the range representable by uint32
*/
int longOidToInt(long oid) throws SQLException;

/**
* Java Integers are signed 32-bit integers, but oids are unsigned 32-bit integers.
* We must therefore first map the (internal) integer representation to a positive long
* value before sending it to postgresql, or we would be unable to correctly handle the
* upper half of the oid space because these negative values are disallowed as OID values.
*
* @param oid the (signed) integer oid to convert into a long.
* @return the non-negative value of this oid, stored as a java long.
*/
long intOidToLong(int oid);
}
12 changes: 9 additions & 3 deletions pgjdbc/src/main/java/org/postgresql/jdbc/PgDatabaseMetaData.java
Expand Up @@ -2666,12 +2666,18 @@ public ResultSet getUDTs(@Nullable String catalog, @Nullable String schemaPatter
+ java.sql.Types.DISTINCT
+ " end as data_type, pg_catalog.obj_description(t.oid, 'pg_type') "
+ "as remarks, CASE WHEN t.typtype = 'd' then (select CASE";
TypeInfo typeInfo = connection.getTypeInfo();

StringBuilder sqlwhen = new StringBuilder();
for (Iterator<Integer> i = connection.getTypeInfo().getPGTypeOidsWithSQLTypes(); i.hasNext(); ) {
for (Iterator<Integer> i = typeInfo.getPGTypeOidsWithSQLTypes(); i.hasNext(); ) {
Integer typOid = i.next();
int sqlType = connection.getTypeInfo().getSQLType(typOid);
sqlwhen.append(" when t.oid = ").append(typOid).append(" then ").append(sqlType);
// NB: Java Integers are signed 32-bit integers, but oids are unsigned 32-bit integers.
// We must therefore map it to a positive long value before writing it into the query,
// or we'll be unable to correctly handle ~ half of the oid space.
long longTypOid = typeInfo.intOidToLong(typOid);
int sqlType = typeInfo.getSQLType(typOid);

sqlwhen.append(" when t.oid = ").append(longTypOid).append(" then ").append(sqlType);
}
sql += sqlwhen.toString();

Expand Down
20 changes: 17 additions & 3 deletions pgjdbc/src/main/java/org/postgresql/jdbc/TypeInfoCache.java
Expand Up @@ -279,7 +279,7 @@ public void cacheSQLTypes() throws SQLException {
pgNameToSQLType.put(typeName, type);
}

Integer typeOid = castNonNull(rs.getInt("oid"));
Integer typeOid = longOidToInt(castNonNull(rs.getLong("oid")));
if (!oidToSQLType.containsKey(typeOid)) {
oidToSQLType.put(typeOid, type);
}
Expand Down Expand Up @@ -310,11 +310,11 @@ public synchronized int getSQLType(int typeOid) throws SQLException {
return i;
}

LOGGER.log(Level.FINEST, "querying SQL typecode for pg type oid '{0}'", typeOid);
LOGGER.log(Level.FINEST, "querying SQL typecode for pg type oid '{0}'", intOidToLong(typeOid));

PreparedStatement getTypeInfoStatement = prepareGetTypeInfoStatement();

getTypeInfoStatement.setInt(1, typeOid);
getTypeInfoStatement.setLong(1, intOidToLong(typeOid));

// Go through BaseStatement to avoid transaction start.
if (!((BaseStatement) getTypeInfoStatement)
Expand Down Expand Up @@ -973,4 +973,18 @@ public boolean requiresQuotingSqlType(int sqlType) throws SQLException {
}
return true;
}

@Override
public int longOidToInt(long oid) throws SQLException {
if ((oid & 0xFFFF_FFFF_0000_0000L) != 0) {
throw new PSQLException(GT.tr("Value is not an OID: {0}", oid), PSQLState.NUMERIC_VALUE_OUT_OF_RANGE);
}

return (int) oid;
}

@Override
public long intOidToLong(int oid) {
return ((long) oid) & 0xFFFFFFFFL;
}
}
Expand Up @@ -9,7 +9,11 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

import org.postgresql.core.TypeInfo;
import org.postgresql.jdbc.PgConnection;
import org.postgresql.test.TestUtil;
import org.postgresql.util.PSQLException;
import org.postgresql.util.PSQLState;

import org.junit.After;
import org.junit.Before;
Expand All @@ -18,6 +22,7 @@
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;

public class DatabaseMetaDataTest {
Expand Down Expand Up @@ -102,4 +107,51 @@ public void testGetCorrectSQLTypeForShadowedTypes() throws Exception {

assertFalse(rs.next());
}

@Test
public void testLargeOidIsHandledCorrectly() throws SQLException {
TypeInfo ti = conn.unwrap(PgConnection.class).getTypeInfo();

try {
ti.getSQLType((int) 4294967295L); // (presumably) unused OID 4294967295, which is 2**32 - 1
} catch (PSQLException ex) {
assertEquals(ex.getSQLState(), PSQLState.NO_DATA.getState());
}
}

@Test
public void testOidConversion() throws SQLException {
TypeInfo ti = conn.unwrap(PgConnection.class).getTypeInfo();
int oid = 0;
long loid = 0;
assertEquals(oid, ti.longOidToInt(loid));
assertEquals(loid, ti.intOidToLong(oid));

oid = Integer.MAX_VALUE;
loid = Integer.MAX_VALUE;
assertEquals(oid, ti.longOidToInt(loid));
assertEquals(loid, ti.intOidToLong(oid));

oid = Integer.MIN_VALUE;
loid = 1L << 31;
assertEquals(oid, ti.longOidToInt(loid));
assertEquals(loid, ti.intOidToLong(oid));

oid = -1;
loid = 0xFFFFFFFFL;
assertEquals(oid, ti.longOidToInt(loid));
assertEquals(loid, ti.intOidToLong(oid));
}

@Test(expected = PSQLException.class)
public void testOidConversionThrowsForNegativeLongValues() throws SQLException {
TypeInfo ti = conn.unwrap(PgConnection.class).getTypeInfo();
ti.longOidToInt(-1);
}

@Test(expected = PSQLException.class)
public void testOidConversionThrowsForTooLargeLongValues() throws SQLException {
TypeInfo ti = conn.unwrap(PgConnection.class).getTypeInfo();
ti.longOidToInt(1L << 32);
}
}

0 comments on commit 151b287

Please sign in to comment.