Skip to content

Commit 4a8a487

Browse files
committed
pgjdbcGH-357 Added the schema name in the query for postgres types
1 parent a13c02c commit 4a8a487

File tree

4 files changed

+94
-11
lines changed

4 files changed

+94
-11
lines changed

src/main/java/io/r2dbc/postgresql/codec/PostgresTypes.java

+51-5
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@
2121
import io.r2dbc.spi.Row;
2222
import io.r2dbc.spi.RowMetadata;
2323
import io.r2dbc.spi.Type;
24+
import java.util.ArrayList;
25+
import java.util.Arrays;
26+
import java.util.List;
2427
import reactor.core.publisher.Flux;
2528
import reactor.core.publisher.Mono;
2629
import reactor.util.annotation.Nullable;
@@ -40,10 +43,11 @@ public class PostgresTypes {
4043
public final static int NO_SUCH_TYPE = -1;
4144

4245
// parameterized with %s for the comparator (=, IN), %s for the actual criteria value and %s for a potential LIMIT 1 statement
43-
private static final String SELECT_PG_TYPE = "SELECT pg_type.* "
46+
// language=sql
47+
private static final String SELECT_PG_TYPE = "SELECT sp.schema_name, pg_type.* "
4448
+ " FROM pg_catalog.pg_type "
4549
+ " LEFT "
46-
+ " JOIN (select ns.oid as nspoid, ns.nspname, r.r "
50+
+ " JOIN (select ns.oid as nspoid, ns.nspname as schema_name, r.r "
4751
+ " from pg_namespace as ns "
4852
+ " join ( select s.r, (current_schemas(false))[s.r] as nspname "
4953
+ " from generate_series(1, array_upper(current_schemas(false), 1)) as s(r) ) as r "
@@ -66,12 +70,14 @@ public static PostgresTypes from(PostgresqlConnection connection) {
6670
}
6771

6872
/**
69-
* 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
73+
* 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
7074
* injection.
7175
*
7276
* @param typeName the type name. Must comply with the pattern {@code [a-zA-Z0-9_]+}
7377
* @return a mono emitting the {@link PostgresType} if found or {@link Mono#empty()} if not found
78+
* @deprecated in favor of {@link #lookupTypes(String...)}
7479
*/
80+
@Deprecated
7581
public Mono<PostgresType> lookupType(String typeName) {
7682
if (!TYPENAME.matcher(Assert.requireNonNull(typeName, "typeName must not be null")).matches()) {
7783
throw new IllegalArgumentException(String.format("Invalid typename %s", typeName));
@@ -81,6 +87,17 @@ public Mono<PostgresType> lookupType(String typeName) {
8187
.flatMap(it -> it.map(PostgresTypes::createType)).singleOrEmpty();
8288
}
8389

90+
/**
91+
* 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
92+
* injection.
93+
*
94+
* @param typeName the type name. Must comply with the pattern {@code [a-zA-Z0-9_]+}
95+
* @return a mono emitting the {@link PostgresType} if found or {@link Mono#empty()} if not found
96+
*/
97+
public Flux<PostgresType> lookupTypes(String... typeName) {
98+
return lookupTypes(Arrays.asList(typeName));
99+
}
100+
84101
public Flux<PostgresType> lookupTypes(Iterable<String> typeNames) {
85102

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

113131
long unsignedTyparray = typarrayOid != null ? typarrayOid : NO_SUCH_TYPE;
114132
int typarray = typarrayOid != null ? PostgresqlObjectId.toInt(typarrayOid) : NO_SUCH_TYPE;
115-
return new PostgresType(PostgresqlObjectId.toInt(oid), oid, typarray, unsignedTyparray, typname, typcategory);
133+
return new PostgresType(PostgresqlObjectId.toInt(oid), oid, typarray, unsignedTyparray, typname, typcategory, schemaName);
116134
}
117135

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

130148
private final String category;
131149

150+
/**
151+
* The name of the schema where pg_type is stored.
152+
*/
153+
private final String schemaName;
154+
132155
@Nullable
133156
private final PostgresqlObjectId objectId;
134157

158+
/**
159+
* @deprecated in favor of {@link #PostgresType(int, long, int, long, String, String, String)}
160+
*/
161+
@Deprecated
135162
public PostgresType(int oid, long unsignedOid, int typarray, long unsignedTyparray, String name, String category) {
136163
this.oid = oid;
137164
this.unsignedOid = unsignedOid;
@@ -140,6 +167,18 @@ public PostgresType(int oid, long unsignedOid, int typarray, long unsignedTyparr
140167
this.name = name;
141168
this.category = category;
142169
this.objectId = PostgresqlObjectId.isValid(oid) ? PostgresqlObjectId.valueOf(oid) : null;
170+
this.schemaName = "";
171+
}
172+
173+
public PostgresType(int oid, long unsignedOid, int typarray, long unsignedTyparray, String name, String category, String schemaName) {
174+
this.oid = oid;
175+
this.unsignedOid = unsignedOid;
176+
this.typarray = typarray;
177+
this.unsignedTyparray = unsignedTyparray;
178+
this.name = name;
179+
this.category = category;
180+
this.objectId = PostgresqlObjectId.isValid(oid) ? PostgresqlObjectId.valueOf(oid) : null;
181+
this.schemaName = schemaName;
143182
}
144183

145184
@Override
@@ -160,7 +199,7 @@ public PostgresType asArrayType() {
160199

161200
if (this.typarray > 0) {
162201

163-
return new PostgresType(this.typarray, this.unsignedTyparray, this.typarray, this.unsignedTyparray, this.name, this.category);
202+
return new PostgresType(this.typarray, this.unsignedTyparray, this.typarray, this.unsignedTyparray, this.name, this.category, this.schemaName);
164203
}
165204

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

233+
/**
234+
* @return The name of the schema where pg_type is stored.
235+
*/
236+
public String getSchemaName() {
237+
return schemaName;
238+
}
239+
194240
/**
195241
* @return {@code true} if the type is an array type (category code {@code A})
196242
*/

src/test/java/io/r2dbc/postgresql/codec/EnumCodecIntegrationTests.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ void shouldBindEnumTypeAsString() {
9797
SERVER.getJdbcOperations().execute("CREATE TABLE enum_test (the_value my_enum_with_codec);");
9898

9999
PostgresTypes types = PostgresTypes.from(this.connection);
100-
PostgresTypes.PostgresType type = types.lookupType("my_enum_with_codec").block();
100+
PostgresTypes.PostgresType type = types.lookupTypes("my_enum_with_codec").blockFirst();
101101

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

120120
PostgresTypes types = PostgresTypes.from(this.connection);
121-
PostgresTypes.PostgresType type = types.lookupType("my_enum_with_codec").block().asArrayType();
121+
PostgresTypes.PostgresType type = types.lookupTypes("my_enum_with_codec").blockFirst().asArrayType();
122122

123123
this.connection.createStatement("INSERT INTO enum_test VALUES($1)")
124124
.bind("$1", Parameters.in(type, new String[]{"HELLO", "WORLD"}))

src/test/java/io/r2dbc/postgresql/codec/EnumCodecUnitTests.java

+4
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ void shouldRegisterCodecAsFirst() {
101101
.identified("typarray", Long.class, 0L)
102102
.identified("typname", String.class, "foo")
103103
.identified("typcategory", String.class, "E")
104+
.identified("schema_name", String.class, "public")
104105
.build())
105106
.build())
106107
.build();
@@ -130,6 +131,7 @@ void shouldRegisterCodecWithoutTyparray() {
130131
.identified("oid", Long.class, 42L)
131132
.identified("typname", String.class, "foo")
132133
.identified("typcategory", String.class, "E")
134+
.identified("schema_name", String.class, "public")
133135
.build())
134136
.build())
135137
.build();
@@ -161,12 +163,14 @@ void shouldRegisterCodecAsLast() {
161163
.identified("typarray", Long.class, 0L)
162164
.identified("typname", String.class, "foo")
163165
.identified("typcategory", String.class, "E")
166+
.identified("schema_name", String.class, "public")
164167
.build())
165168
.row(MockRow.builder()
166169
.identified("oid", Long.class, 43L)
167170
.identified("typarray", Long.class, 0L)
168171
.identified("typname", String.class, "bar")
169172
.identified("typcategory", String.class, "E")
173+
.identified("schema_name", String.class, "public")
170174
.build())
171175
.build())
172176
.build();

src/test/java/io/r2dbc/postgresql/codec/PostgresTypesIntegrationTests.java

+37-4
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import io.r2dbc.postgresql.AbstractIntegrationTests;
2020
import io.r2dbc.spi.Connection;
2121
import org.junit.jupiter.api.Test;
22+
import org.springframework.jdbc.core.JdbcOperations;
2223
import reactor.core.publisher.Flux;
2324
import reactor.core.publisher.Mono;
2425
import reactor.test.StepVerifier;
@@ -35,10 +36,42 @@ class PostgresTypesIntegrationTests extends AbstractIntegrationTests {
3536
@Test
3637
void shouldLookupSingleType() {
3738

38-
Mono.usingWhen(getConnectionFactory().create(), c -> {
39-
return PostgresTypes.from(c).lookupType("varchar");
40-
}, Connection::close).map(PostgresTypes.PostgresType::getName).map(String::toLowerCase)
41-
.as(StepVerifier::create).expectNext("varchar").verifyComplete();
39+
Flux
40+
.usingWhen( //
41+
getConnectionFactory().create(), //
42+
c -> PostgresTypes.from(c).lookupTypes("varchar"), //
43+
Connection::close //
44+
)
45+
.map(PostgresTypes.PostgresType::getName)
46+
.map(String::toLowerCase)
47+
.as(StepVerifier::create)
48+
.expectNext("varchar")
49+
.verifyComplete();
50+
}
51+
52+
@Test
53+
void shouldLookupTypesInDifferentSchemas() {
54+
55+
// test enum type set up
56+
JdbcOperations jdbcOperations = SERVER.getJdbcOperations();
57+
jdbcOperations.execute("CREATE SCHEMA test_schema_1;");
58+
jdbcOperations.execute("CREATE SCHEMA test_schema_2;");
59+
jdbcOperations.execute("CREATE TYPE test_schema_1.test_enum AS ENUM ('FIRST', 'SECOND');");
60+
jdbcOperations.execute("CREATE TYPE test_schema_2.test_enum AS ENUM ('FIRST', 'SECOND');");
61+
62+
Flux
63+
.usingWhen( //
64+
getConnectionFactory().create(), //
65+
c -> c //
66+
.createStatement("SET SEARCH_PATH TO test_schema_1, test_schema_2;") //
67+
.execute() //
68+
.flatMap(unused -> PostgresTypes.from(c).lookupTypes("test_enum")), //
69+
Connection::close //
70+
)
71+
.as(StepVerifier::create)
72+
.expectNextMatches(type -> type.getName().equals("test_enum"))
73+
.expectNextMatches(type -> type.getName().equals("test_enum"))
74+
.verifyComplete();
4275
}
4376

4477
@Test

0 commit comments

Comments
 (0)