Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Consider search_path when looking up type OIDs in TypeInfoCache #52

Merged
merged 4 commits into from
Jun 10, 2013
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
47 changes: 40 additions & 7 deletions org/postgresql/jdbc2/TypeInfoCache.java
Original file line number Diff line number Diff line change
Expand Up @@ -185,11 +185,29 @@ public synchronized int getSQLType(String pgTypeName) throws SQLException
// 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 ";
if (_conn.haveMinimumServerVersion("7.3")) {
sql += "pg_catalog.";
String sql;
if (_conn.haveMinimumServerVersion("8.0")) {
// 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 " +
" join ( select s.r, (current_schemas(false))[s.r] as nspname " +
// -- go with older way of unnesting array to be compatible with 8.0
" 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;";
} else if (_conn.haveMinimumServerVersion("7.3")) {
sql = "SELECT typinput='array_in'::regproc, typtype FROM pg_catalog.pg_type WHERE typname = ? ORDER BY oid DESC LIMIT 1";
} else {
sql = "SELECT typinput='array_in'::regproc, typtype FROM pg_type WHERE typname = ? LIMIT 1";
}
sql += "pg_type WHERE typname = ?";

_getTypeInfoStatement = _conn.prepareStatement(sql);
}
Expand Down Expand Up @@ -233,10 +251,25 @@ public synchronized int getPGType(String pgTypeName) throws SQLException

if (_getOidStatement == null) {
String sql;
if (_conn.haveMinimumServerVersion("7.3")) {
sql = "SELECT oid FROM pg_catalog.pg_type WHERE typname = ?";
if (_conn.haveMinimumServerVersion("8.0")) {
// see comments in @getSQLType()
sql = "SELECT pg_type.oid " +
" 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 " +
// -- go with older way of unnesting array to be compatible with 8.0
" 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;";
} else if (_conn.haveMinimumServerVersion("7.3")) {
sql = "SELECT oid FROM pg_catalog.pg_type WHERE typname = ? ORDER BY oid DESC LIMIT 1";
} else {
sql = "SELECT oid FROM pg_type WHERE typname = ?";
sql = "SELECT oid FROM pg_type WHERE typname = ? ORDER BY oid DESC LIMIT 1";
}

_getOidStatement = _conn.prepareStatement(sql);
Expand Down
49 changes: 49 additions & 0 deletions org/postgresql/test/TestUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,55 @@ public static void closeDB(Connection con) throws SQLException
con.close();
}

/*
* Helper - creates a test schema for use by a test
*/
public static void createSchema(Connection con,
String schema) throws SQLException
{
Statement st = con.createStatement();
try
{
// Drop the schema
dropSchema(con, schema);

// Now create the schema
String sql = "CREATE SCHEMA " + schema;

st.executeUpdate(sql);
}
finally
{
st.close();
}
}

/*
* Helper - drops a schema
*/
public static void dropSchema(Connection con, String schema) throws SQLException
{
Statement stmt = con.createStatement();
try
{
String sql = "DROP SCHEMA " + schema;
if (haveMinimumServerVersion(con, "7.3"))
{
sql += " CASCADE ";
}
stmt.executeUpdate(sql);
}
catch (SQLException ex)
{
// Since every create schema issues a drop schema
// it's easy to get a schema doesn't exist error.
// we want to ignore these, but if we're in a
// transaction then we've got trouble
if (!con.getAutoCommit())
throw ex;
}
}

/*
* Helper - creates a test table for use by a test
*/
Expand Down
1 change: 1 addition & 0 deletions org/postgresql/test/jdbc2/Jdbc2TestSuite.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ public static TestSuite suite() throws Exception
suite.addTestSuite(ConnectionTest.class);
suite.addTestSuite(DatabaseMetaDataTest.class);
suite.addTestSuite(DatabaseMetaDataPropertiesTest.class);
suite.addTestSuite(SearchPathLookupTest.class);
suite.addTestSuite(EncodingTest.class);

// Connectivity/Protocols
Expand Down
126 changes: 126 additions & 0 deletions org/postgresql/test/jdbc2/SearchPathLookupTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
/*-------------------------------------------------------------------------
*
* Copyright (c) 2004-2013, PostgreSQL Global Development Group
*
*
*-------------------------------------------------------------------------
*/
package org.postgresql.test.jdbc2;

import org.postgresql.PGConnection;
import org.postgresql.core.BaseConnection;
import org.postgresql.core.TypeInfo;
import org.postgresql.test.TestUtil;
import junit.framework.TestCase;
import java.sql.*;

/*
* TestCase to test the internal functionality of org.postgresql.jdbc2.DatabaseMetaData
*
*/

public class SearchPathLookupTest extends TestCase
{

private BaseConnection con;
/*
* Constructor
*/
public SearchPathLookupTest(String name)
{
super(name);
}

// TODO: make @getMetaData() consider search_path as well

/**
* This usecase is most common, here the object we are searching for is
* in the current_schema (the first schema in the search_path)
*/
public void testSearchPathNormalLookup() throws Exception
{
con = (BaseConnection) TestUtil.openDB();
Statement stmt = con.createStatement();
try {
TestUtil.createSchema( con, "first_schema" );
TestUtil.createTable( con, "first_schema.x", "first_schema_field_n int4");
TestUtil.createSchema( con, "second_schema" );
TestUtil.createTable( con, "second_schema.x", "second_schema_field_n text");
TestUtil.createSchema( con, "third_schema" );
TestUtil.createTable( con, "third_schema.x", "third_schema_field_n float");
TestUtil.createSchema( con, "last_schema" );
TestUtil.createTable( con, "last_schema.x", "last_schema_field_n text");
stmt.execute("SET search_path TO third_schema;");
TypeInfo typeInfo = con.getTypeInfo();
int OID = typeInfo.getPGType("x");
ResultSet rs = stmt.executeQuery("SELECT 'third_schema.x'::regtype::oid");
assertTrue(rs.next());
assertEquals(OID, rs.getInt(1));
assertTrue(!rs.next());
TestUtil.dropSchema( con, "first_schema" );
TestUtil.dropSchema( con, "second_schema" );
TestUtil.dropSchema( con, "third_schema" );
TestUtil.dropSchema( con, "last_schema" );
} finally {
if ( stmt != null ) stmt.close();
TestUtil.closeDB( con );
}
}

/**
* This usecase is for the situations, when an object is located in a schema,
* that is in the search_path, but not in the current_schema, for example
* a public schema or some kind of schema, that is used for keeping utility objects.
*/
public void testSearchPathHiddenLookup() throws Exception
{
con = (BaseConnection) TestUtil.openDB();
Statement stmt = con.createStatement();
try {
TestUtil.createSchema( con, "first_schema" );
TestUtil.createTable( con, "first_schema.x", "first_schema_field_n int4");
TestUtil.createSchema( con, "second_schema" );
TestUtil.createTable( con, "second_schema.y", "second_schema_field_n text");
TestUtil.createSchema( con, "third_schema" );
TestUtil.createTable( con, "third_schema.x", "third_schema_field_n float");
TestUtil.createSchema( con, "last_schema" );
TestUtil.createTable( con, "last_schema.y", "last_schema_field_n text");
stmt.execute("SET search_path TO first_schema, second_schema, last_schema, public;");
TypeInfo typeInfo = con.getTypeInfo();
int OID = typeInfo.getPGType("y");
ResultSet rs = stmt.executeQuery("SELECT 'second_schema.y'::regtype::oid");
assertTrue(rs.next());
assertEquals(OID, rs.getInt(1));
assertTrue(!rs.next());
TestUtil.dropSchema( con, "first_schema" );
TestUtil.dropSchema( con, "second_schema" );
TestUtil.dropSchema( con, "third_schema" );
TestUtil.dropSchema( con, "last_schema" );
} finally {
if ( stmt != null ) stmt.close();
TestUtil.closeDB( con );
}
}

public void testSearchPathBackwardsCompatibleLookup() throws Exception
{
con = (BaseConnection) TestUtil.openDB();
Statement stmt = con.createStatement();
try {
TestUtil.createSchema( con, "first_schema" );
TestUtil.createTable( con, "first_schema.x", "first_schema_field int4");
TestUtil.createSchema( con, "second_schema" );
TestUtil.createTable( con, "second_schema.x", "second_schema_field text");
TypeInfo typeInfo = con.getTypeInfo();
int OID = typeInfo.getPGType("x");
ResultSet rs = stmt.executeQuery("SELECT oid FROM pg_type WHERE typname = 'x' ORDER BY oid DESC LIMIT 1");
assertTrue(rs.next());
assertEquals(OID, rs.getInt(1));
assertTrue(!rs.next());
TestUtil.dropSchema( con, "first_schema" );
TestUtil.dropSchema( con, "second_schema" );
} finally {
TestUtil.closeDB( con );
}
}
}