Skip to content

GH-357 Added the schema name in the query for postgres types #679

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

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 51 additions & 5 deletions src/main/java/io/r2dbc/postgresql/codec/PostgresTypes.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@
import io.r2dbc.spi.Row;
import io.r2dbc.spi.RowMetadata;
import io.r2dbc.spi.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.util.annotation.Nullable;
Expand All @@ -40,10 +43,11 @@ public class PostgresTypes {
public final static int NO_SUCH_TYPE = -1;

// parameterized with %s for the comparator (=, IN), %s for the actual criteria value and %s for a potential LIMIT 1 statement
private static final String SELECT_PG_TYPE = "SELECT pg_type.* "
// language=sql
private static final String SELECT_PG_TYPE = "SELECT sp.schema_name, pg_type.* "
+ " FROM pg_catalog.pg_type "
+ " LEFT "
+ " JOIN (select ns.oid as nspoid, ns.nspname, r.r "
+ " JOIN (select ns.oid as nspoid, ns.nspname as schema_name, 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 "
Expand All @@ -66,12 +70,14 @@ public static PostgresTypes from(PostgresqlConnection connection) {
}

/**
* Lookup Postgres types by {@code typname}. Please note that {@code typname} inlined to use simple statements. Therefore, {@code typname} gets verified against {@link #TYPENAME} to prevent SQL
* Lookup Postgres types by {@code typname}. Please note that {@code typeName} inlined to use simple statements. Therefore, {@code typname} gets verified against {@link #TYPENAME} to prevent SQL
* injection.
*
* @param typeName the type name. Must comply with the pattern {@code [a-zA-Z0-9_]+}
* @return a mono emitting the {@link PostgresType} if found or {@link Mono#empty()} if not found
* @deprecated in favor of {@link #lookupTypes(String...)}
*/
@Deprecated
public Mono<PostgresType> lookupType(String typeName) {
if (!TYPENAME.matcher(Assert.requireNonNull(typeName, "typeName must not be null")).matches()) {
throw new IllegalArgumentException(String.format("Invalid typename %s", typeName));
Expand All @@ -81,6 +87,17 @@ public Mono<PostgresType> lookupType(String typeName) {
.flatMap(it -> it.map(PostgresTypes::createType)).singleOrEmpty();
}

/**
* Lookup Postgres types by {@code typname}. Please note that {@code typeName} inlined to use simple statements. Therefore, {@code typeName} gets verified against {@link #TYPENAME} to prevent SQL
* injection.
*
* @param typeName the type name. Must comply with the pattern {@code [a-zA-Z0-9_]+}
* @return a mono emitting the {@link PostgresType} if found or {@link Mono#empty()} if not found
*/
public Flux<PostgresType> lookupTypes(String... typeName) {
return lookupTypes(Arrays.asList(typeName));
}

public Flux<PostgresType> lookupTypes(Iterable<String> typeNames) {

StringJoiner joiner = new StringJoiner(",", "(", ")");
Expand Down Expand Up @@ -108,11 +125,12 @@ private static PostgresType createType(Row row, RowMetadata rowMetadata) {
Long oid = row.get("oid", Long.class);
String typname = row.get("typname", String.class);
String typcategory = row.get("typcategory", String.class);
String schemaName = row.get("schema_name", String.class);
Long typarrayOid = rowMetadata.contains("typarray") ? row.get("typarray", Long.class) : null;

long unsignedTyparray = typarrayOid != null ? typarrayOid : NO_SUCH_TYPE;
int typarray = typarrayOid != null ? PostgresqlObjectId.toInt(typarrayOid) : NO_SUCH_TYPE;
return new PostgresType(PostgresqlObjectId.toInt(oid), oid, typarray, unsignedTyparray, typname, typcategory);
return new PostgresType(PostgresqlObjectId.toInt(oid), oid, typarray, unsignedTyparray, typname, typcategory, schemaName);
}

public static class PostgresType implements Type, PostgresTypeIdentifier {
Expand All @@ -129,9 +147,18 @@ public static class PostgresType implements Type, PostgresTypeIdentifier {

private final String category;

/**
* The name of the schema where pg_type is stored.
*/
private final String schemaName;

@Nullable
private final PostgresqlObjectId objectId;

/**
* @deprecated in favor of {@link #PostgresType(int, long, int, long, String, String, String)}
*/
@Deprecated
public PostgresType(int oid, long unsignedOid, int typarray, long unsignedTyparray, String name, String category) {
this.oid = oid;
this.unsignedOid = unsignedOid;
Expand All @@ -140,6 +167,18 @@ public PostgresType(int oid, long unsignedOid, int typarray, long unsignedTyparr
this.name = name;
this.category = category;
this.objectId = PostgresqlObjectId.isValid(oid) ? PostgresqlObjectId.valueOf(oid) : null;
this.schemaName = "";
}

public PostgresType(int oid, long unsignedOid, int typarray, long unsignedTyparray, String name, String category, String schemaName) {
this.oid = oid;
this.unsignedOid = unsignedOid;
this.typarray = typarray;
this.unsignedTyparray = unsignedTyparray;
this.name = name;
this.category = category;
this.objectId = PostgresqlObjectId.isValid(oid) ? PostgresqlObjectId.valueOf(oid) : null;
this.schemaName = schemaName;
}

@Override
Expand All @@ -160,7 +199,7 @@ public PostgresType asArrayType() {

if (this.typarray > 0) {

return new PostgresType(this.typarray, this.unsignedTyparray, this.typarray, this.unsignedTyparray, this.name, this.category);
return new PostgresType(this.typarray, this.unsignedTyparray, this.typarray, this.unsignedTyparray, this.name, this.category, this.schemaName);
}

throw new IllegalStateException("No array type available for " + this);
Expand Down Expand Up @@ -191,6 +230,13 @@ public String getName() {
return this.name;
}

/**
* @return The name of the schema where pg_type is stored.
*/
public String getSchemaName() {
return schemaName;
}

/**
* @return {@code true} if the type is an array type (category code {@code A})
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ void shouldBindEnumTypeAsString() {
SERVER.getJdbcOperations().execute("CREATE TABLE enum_test (the_value my_enum_with_codec);");

PostgresTypes types = PostgresTypes.from(this.connection);
PostgresTypes.PostgresType type = types.lookupType("my_enum_with_codec").block();
PostgresTypes.PostgresType type = types.lookupTypes("my_enum_with_codec").blockFirst();

this.connection.createStatement("INSERT INTO enum_test VALUES($1)")
.bind("$1", Parameters.in(type, "HELLO"))
Expand All @@ -118,7 +118,7 @@ void shouldBindEnumArrayTypeAsString() {
SERVER.getJdbcOperations().execute("CREATE TABLE enum_test (the_value my_enum_with_codec[]);");

PostgresTypes types = PostgresTypes.from(this.connection);
PostgresTypes.PostgresType type = types.lookupType("my_enum_with_codec").block().asArrayType();
PostgresTypes.PostgresType type = types.lookupTypes("my_enum_with_codec").blockFirst().asArrayType();

this.connection.createStatement("INSERT INTO enum_test VALUES($1)")
.bind("$1", Parameters.in(type, new String[]{"HELLO", "WORLD"}))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ void shouldRegisterCodecAsFirst() {
.identified("typarray", Long.class, 0L)
.identified("typname", String.class, "foo")
.identified("typcategory", String.class, "E")
.identified("schema_name", String.class, "public")
.build())
.build())
.build();
Expand Down Expand Up @@ -130,6 +131,7 @@ void shouldRegisterCodecWithoutTyparray() {
.identified("oid", Long.class, 42L)
.identified("typname", String.class, "foo")
.identified("typcategory", String.class, "E")
.identified("schema_name", String.class, "public")
.build())
.build())
.build();
Expand Down Expand Up @@ -161,12 +163,14 @@ void shouldRegisterCodecAsLast() {
.identified("typarray", Long.class, 0L)
.identified("typname", String.class, "foo")
.identified("typcategory", String.class, "E")
.identified("schema_name", String.class, "public")
.build())
.row(MockRow.builder()
.identified("oid", Long.class, 43L)
.identified("typarray", Long.class, 0L)
.identified("typname", String.class, "bar")
.identified("typcategory", String.class, "E")
.identified("schema_name", String.class, "public")
.build())
.build())
.build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import io.r2dbc.postgresql.AbstractIntegrationTests;
import io.r2dbc.spi.Connection;
import org.junit.jupiter.api.Test;
import org.springframework.jdbc.core.JdbcOperations;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
Expand All @@ -35,10 +36,42 @@ class PostgresTypesIntegrationTests extends AbstractIntegrationTests {
@Test
void shouldLookupSingleType() {

Mono.usingWhen(getConnectionFactory().create(), c -> {
return PostgresTypes.from(c).lookupType("varchar");
}, Connection::close).map(PostgresTypes.PostgresType::getName).map(String::toLowerCase)
.as(StepVerifier::create).expectNext("varchar").verifyComplete();
Flux
.usingWhen( //
getConnectionFactory().create(), //
c -> PostgresTypes.from(c).lookupTypes("varchar"), //
Connection::close //
)
.map(PostgresTypes.PostgresType::getName)
.map(String::toLowerCase)
.as(StepVerifier::create)
.expectNext("varchar")
.verifyComplete();
}

@Test
void shouldLookupTypesInDifferentSchemas() {

// test enum type set up
JdbcOperations jdbcOperations = SERVER.getJdbcOperations();
jdbcOperations.execute("CREATE SCHEMA test_schema_1;");
jdbcOperations.execute("CREATE SCHEMA test_schema_2;");
jdbcOperations.execute("CREATE TYPE test_schema_1.test_enum AS ENUM ('FIRST', 'SECOND');");
jdbcOperations.execute("CREATE TYPE test_schema_2.test_enum AS ENUM ('FIRST', 'SECOND');");

Flux
.usingWhen( //
getConnectionFactory().create(), //
c -> c //
.createStatement("SET SEARCH_PATH TO test_schema_1, test_schema_2;") //
.execute() //
.flatMap(unused -> PostgresTypes.from(c).lookupTypes("test_enum")), //
Connection::close //
)
.as(StepVerifier::create)
.expectNextMatches(type -> type.getName().equals("test_enum"))
.expectNextMatches(type -> type.getName().equals("test_enum"))
.verifyComplete();
}

@Test
Expand Down