Skip to content

Commit c6e1d56

Browse files
committed
Reduce object allocations by use int[] instead of Collection<Integer>
[#138]
1 parent def5d63 commit c6e1d56

11 files changed

+120
-86
lines changed

src/main/java/io/r2dbc/postgresql/DefaultPortalNameSupplier.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ private DefaultPortalNameSupplier() {
3131

3232
@Override
3333
public String get() {
34-
return String.format("B_%d", COUNTER.getAndIncrement());
34+
return "B_%d" + COUNTER.getAndIncrement();
3535
}
3636

3737
}

src/main/java/io/r2dbc/postgresql/IndefiniteStatementCache.java

+33-9
Original file line numberDiff line numberDiff line change
@@ -21,17 +21,16 @@
2121
import io.r2dbc.postgresql.client.ExtendedQueryMessageFlow;
2222
import io.r2dbc.postgresql.util.Assert;
2323
import reactor.core.publisher.Mono;
24-
import reactor.util.function.Tuple2;
25-
import reactor.util.function.Tuples;
2624

27-
import java.util.HashMap;
28-
import java.util.List;
25+
import java.util.Arrays;
2926
import java.util.Map;
27+
import java.util.TreeMap;
28+
import java.util.concurrent.ConcurrentHashMap;
3029
import java.util.concurrent.atomic.AtomicInteger;
3130

3231
final class IndefiniteStatementCache implements StatementCache {
3332

34-
private final Map<Tuple2<String, List<Integer>>, Mono<String>> cache = new HashMap<>();
33+
private final Map<String, Map<int[], Mono<String>>> cache = new ConcurrentHashMap<>();
3534

3635
private final Client client;
3736

@@ -45,9 +44,35 @@ final class IndefiniteStatementCache implements StatementCache {
4544
public Mono<String> getName(Binding binding, String sql) {
4645
Assert.requireNonNull(binding, "binding must not be null");
4746
Assert.requireNonNull(sql, "sql must not be null");
47+
Map<int[], Mono<String>> typedMap = this.cache.computeIfAbsent(sql, ignore -> new TreeMap<>((o1, o2) -> {
4848

49-
return this.cache.computeIfAbsent(Tuples.of(sql, binding.getParameterTypes()),
50-
tuple -> this.parse(tuple.getT1(), tuple.getT2()));
49+
if (Arrays.equals(o1, o2)) {
50+
return 0;
51+
}
52+
53+
if (o1.length != o2.length) {
54+
return o1.length - o2.length;
55+
}
56+
57+
for (int i = 0; i < o1.length; i++) {
58+
59+
int cmp = Integer.compare(o1[i], o2[i]);
60+
61+
if (cmp != 0) {
62+
return cmp;
63+
}
64+
}
65+
66+
return 0;
67+
}));
68+
69+
Mono<String> mono = typedMap.get(binding.getParameterTypes());
70+
if (mono == null) {
71+
mono = this.parse(sql, binding.getParameterTypes());
72+
typedMap.put(binding.getParameterTypes(), mono);
73+
}
74+
75+
return mono;
5176
}
5277

5378
@Override
@@ -59,7 +84,7 @@ public String toString() {
5984
'}';
6085
}
6186

62-
private Mono<String> parse(String sql, List<Integer> types) {
87+
private Mono<String> parse(String sql, int[] types) {
6388
String name = String.format("S_%d", this.counter.getAndIncrement());
6489

6590
ExceptionFactory factory = ExceptionFactory.withSql(name);
@@ -69,5 +94,4 @@ private Mono<String> parse(String sql, List<Integer> types) {
6994
.then(Mono.just(name))
7095
.cache();
7196
}
72-
7397
}

src/main/java/io/r2dbc/postgresql/PostgresqlColumnMetadata.java

+1-4
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ public boolean equals(Object o) {
7373

7474
@Override
7575
public Class<?> getJavaType() {
76-
return codecs.preferredType(this.nativeType, this.format);
76+
return this.codecs.preferredType(this.nativeType, this.format);
7777
}
7878

7979
@Override
@@ -106,9 +106,6 @@ public String toString() {
106106
}
107107

108108
static PostgresqlColumnMetadata toColumnMetadata(Codecs codecs, Field field) {
109-
Assert.requireNonNull(codecs, "codecs must not be null");
110-
Assert.requireNonNull(field, "field must not be null");
111-
112109
return new PostgresqlColumnMetadata(codecs, field);
113110
}
114111

src/main/java/io/r2dbc/postgresql/PostgresqlResult.java

+28-17
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import io.r2dbc.postgresql.message.backend.CommandComplete;
2222
import io.r2dbc.postgresql.message.backend.DataRow;
2323
import io.r2dbc.postgresql.message.backend.EmptyQueryResponse;
24+
import io.r2dbc.postgresql.message.backend.ErrorResponse;
2425
import io.r2dbc.postgresql.message.backend.PortalSuspended;
2526
import io.r2dbc.postgresql.message.backend.RowDescription;
2627
import io.r2dbc.postgresql.util.Assert;
@@ -29,6 +30,7 @@
2930
import io.r2dbc.spi.RowMetadata;
3031
import reactor.core.publisher.Flux;
3132
import reactor.core.publisher.Mono;
33+
import reactor.core.publisher.SynchronousSink;
3234

3335
import java.util.function.BiFunction;
3436
import java.util.function.Predicate;
@@ -52,42 +54,51 @@ final class PostgresqlResult implements io.r2dbc.postgresql.api.PostgresqlResult
5254

5355
private volatile RowDescription rowDescription;
5456

55-
PostgresqlResult(Codecs codecs, Flux<BackendMessage> messages, ExceptionFactory factory) {
56-
this.codecs = Assert.requireNonNull(codecs, "codecs must not be null");
57-
this.messages = Assert.requireNonNull(messages, "messages must not be null");
58-
this.factory = Assert.requireNonNull(factory, "factory must not be null");
57+
private PostgresqlResult(Codecs codecs, Flux<BackendMessage> messages, ExceptionFactory factory) {
58+
this.codecs = codecs;
59+
this.messages = messages;
60+
this.factory = factory;
5961
}
6062

6163
@Override
64+
@SuppressWarnings({"rawtypes", "unchecked"})
6265
public Mono<Integer> getRowsUpdated() {
6366

6467
return this.messages
65-
.handle(this.factory::handleErrorResponse)
66-
.doOnNext(message -> {
68+
.<Integer>handle((message, sink) -> {
69+
70+
if (message instanceof ErrorResponse) {
71+
this.factory.handleErrorResponse(message, (SynchronousSink) sink);
72+
return;
73+
}
74+
6775
if (message instanceof DataRow) {
6876
((DataRow) message).release();
6977
}
70-
})
71-
.ofType(CommandComplete.class)
72-
.singleOrEmpty()
73-
.handle((commandComplete, sink) -> {
74-
Integer rowCount = commandComplete.getRows();
75-
if (rowCount != null) {
76-
sink.next(rowCount);
77-
} else {
78-
sink.complete();
78+
79+
if (message instanceof CommandComplete) {
80+
81+
Integer rowCount = ((CommandComplete) message).getRows();
82+
if (rowCount != null) {
83+
sink.next(rowCount);
84+
}
7985
}
80-
});
86+
}).singleOrEmpty();
8187
}
8288

8389
@Override
90+
@SuppressWarnings({"rawtypes", "unchecked"})
8491
public <T> Flux<T> map(BiFunction<Row, RowMetadata, ? extends T> f) {
8592
Assert.requireNonNull(f, "f must not be null");
8693

8794
return this.messages.takeUntil(TAKE_UNTIL)
88-
.handle(this.factory::handleErrorResponse)
8995
.handle((message, sink) -> {
9096

97+
if (message instanceof ErrorResponse) {
98+
this.factory.handleErrorResponse(message, (SynchronousSink) sink);
99+
return;
100+
}
101+
91102
if (message instanceof RowDescription) {
92103
this.rowDescription = (RowDescription) message;
93104
this.metadata = PostgresqlRowMetadata.toRowMetadata(this.codecs, (RowDescription) message);

src/main/java/io/r2dbc/postgresql/client/Binding.java

+38-5
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import io.r2dbc.postgresql.message.Format;
2121
import io.r2dbc.postgresql.util.Assert;
2222
import org.reactivestreams.Publisher;
23+
import reactor.core.publisher.Flux;
2324
import reactor.core.publisher.Mono;
2425

2526
import java.util.ArrayList;
@@ -39,6 +40,8 @@ public final class Binding {
3940

4041
private final List<Parameter> parameters;
4142

43+
private final int[] types;
44+
4245
/**
4346
* Create a new instance.
4447
*
@@ -47,6 +50,7 @@ public final class Binding {
4750
public Binding(int expectedSize) {
4851
this.expectedSize = expectedSize;
4952
this.parameters = new ArrayList<>(Collections.nCopies(expectedSize, UNSPECIFIED));
53+
this.types = new int[expectedSize];
5054
}
5155

5256
/**
@@ -57,15 +61,15 @@ public Binding(int expectedSize) {
5761
* @return this {@link Binding}
5862
* @throws IllegalArgumentException if {@code index} or {@code parameter} is {@code null}
5963
*/
60-
public Binding add(Integer index, Parameter parameter) {
61-
Assert.requireNonNull(index, "index must not be null");
64+
public Binding add(int index, Parameter parameter) {
6265
Assert.requireNonNull(parameter, "parameter must not be null");
6366

6467
if (index >= this.expectedSize) {
6568
throw new IndexOutOfBoundsException(String.format("Binding index %d when only %d parameters are expected", index, this.expectedSize));
6669
}
6770

6871
this.parameters.set(index, parameter);
72+
this.types[index] = parameter.getType();
6973

7074
return this;
7175
}
@@ -97,8 +101,15 @@ public List<Format> getParameterFormats() {
97101
*
98102
* @return the types of the parameters in the binding
99103
*/
100-
public List<Integer> getParameterTypes() {
101-
return getTransformedParameters(Parameter::getType);
104+
public int[] getParameterTypes() {
105+
106+
for (int i = 0; i < this.parameters.size(); i++) {
107+
Parameter parameter = this.parameters.get(i);
108+
if (parameter == UNSPECIFIED) {
109+
throw new IllegalStateException(String.format("No parameter specified for index %d", i));
110+
}
111+
}
112+
return this.types;
102113
}
103114

104115
/**
@@ -110,6 +121,10 @@ public List<Publisher<? extends ByteBuf>> getParameterValues() {
110121
return getTransformedParameters(Parameter::getValue);
111122
}
112123

124+
Flux<Publisher<? extends ByteBuf>> parameterValues() {
125+
return Flux.fromIterable(this.parameters).map(Parameter::getValue);
126+
}
127+
113128
@Override
114129
public int hashCode() {
115130
return Objects.hash(this.parameters);
@@ -119,6 +134,10 @@ public boolean isEmpty() {
119134
return this.parameters.isEmpty();
120135
}
121136

137+
public int size() {
138+
return this.parameters.size();
139+
}
140+
122141
@Override
123142
public String toString() {
124143
return "Binding{" +
@@ -140,14 +159,28 @@ public void validate() {
140159
}
141160

142161
private <T> List<T> getTransformedParameters(Function<Parameter, T> transformer) {
143-
List<T> transformed = new ArrayList<>(this.parameters.size());
162+
163+
if (this.parameters.isEmpty()) {
164+
return Collections.emptyList();
165+
}
166+
167+
List<T> transformed = null;
144168

145169
for (int i = 0; i < this.parameters.size(); i++) {
146170
Parameter parameter = this.parameters.get(i);
147171
if (parameter == UNSPECIFIED) {
148172
throw new IllegalStateException(String.format("No parameter specified for index %d", i));
149173
}
150174

175+
if (transformed == null) {
176+
if (this.parameters.size() == 1) {
177+
return Collections.singletonList(transformer.apply(parameter));
178+
}
179+
180+
transformed = new ArrayList<>(this.parameters.size());
181+
}
182+
183+
151184
transformed.add(transformer.apply(parameter));
152185
}
153186

src/test/java/io/r2dbc/postgresql/IndefiniteStatementCacheTest.java

+4-4
Original file line numberDiff line numberDiff line change
@@ -50,11 +50,11 @@ void constructorNoClient() {
5050
void getName() {
5151
// @formatter:off
5252
Client client = TestClient.builder()
53-
.expectRequest(new Parse("S_0", Collections.singletonList(100), "test-query"), new Describe("S_0", ExecutionType.STATEMENT), Sync.INSTANCE)
53+
.expectRequest(new Parse("S_0", new int[]{100}, "test-query"), new Describe("S_0", ExecutionType.STATEMENT), Sync.INSTANCE)
5454
.thenRespond(ParseComplete.INSTANCE)
55-
.expectRequest(new Parse("S_1", Collections.singletonList(200), "test-query"), new Describe("S_1", ExecutionType.STATEMENT), Sync.INSTANCE)
55+
.expectRequest(new Parse("S_1", new int[]{200}, "test-query"), new Describe("S_1", ExecutionType.STATEMENT), Sync.INSTANCE)
5656
.thenRespond(ParseComplete.INSTANCE)
57-
.expectRequest(new Parse("S_2", Collections.singletonList(200), "test-query-2"), new Describe("S_2", ExecutionType.STATEMENT), Sync.INSTANCE)
57+
.expectRequest(new Parse("S_2", new int[]{200}, "test-query-2"), new Describe("S_2", ExecutionType.STATEMENT), Sync.INSTANCE)
5858
.thenRespond(ParseComplete.INSTANCE)
5959
.build();
6060
// @formatter:on
@@ -86,7 +86,7 @@ void getName() {
8686
void getNameErrorResponse() {
8787
// @formatter:off
8888
Client client = TestClient.builder()
89-
.expectRequest(new Parse("S_0", Collections.singletonList(100), "test-query"), new Describe("S_0", ExecutionType.STATEMENT), Sync.INSTANCE)
89+
.expectRequest(new Parse("S_0", new int[]{100}, "test-query"), new Describe("S_0", ExecutionType.STATEMENT), Sync.INSTANCE)
9090
.thenRespond(new ErrorResponse(Collections.emptyList()))
9191
.build();
9292
// @formatter:on

src/test/java/io/r2dbc/postgresql/PostgresqlColumnMetadataTest.java

-24
Original file line numberDiff line numberDiff line change
@@ -26,18 +26,6 @@
2626

2727
final class PostgresqlColumnMetadataTest {
2828

29-
@Test
30-
void constructorNoName() {
31-
assertThatIllegalArgumentException().isThrownBy(() -> new PostgresqlColumnMetadata(null, null))
32-
.withMessage("codecs must not be null");
33-
}
34-
35-
@Test
36-
void constructorNoNativeType() {
37-
assertThatIllegalArgumentException().isThrownBy(() -> new PostgresqlColumnMetadata(MockCodecs.empty(), null))
38-
.withMessage("field must not be null");
39-
}
40-
4129
@Test
4230
void toColumnMetadata() {
4331
MockCodecs codecs = MockCodecs.builder()
@@ -52,16 +40,4 @@ void toColumnMetadata() {
5240
assertThat(columnMetadata.getPrecision()).isEqualTo(400);
5341
}
5442

55-
@Test
56-
void toColumnMetadataNoCodecs() {
57-
assertThatIllegalArgumentException().isThrownBy(() -> PostgresqlColumnMetadata.toColumnMetadata(null, new Field((short) 100, 200, 300, (short) 400, FORMAT_TEXT, "test-name", 500)))
58-
.withMessage("codecs must not be null");
59-
}
60-
61-
@Test
62-
void toColumnMetadataNoField() {
63-
assertThatIllegalArgumentException().isThrownBy(() -> PostgresqlColumnMetadata.toColumnMetadata(MockCodecs.empty(), null))
64-
.withMessage("field must not be null");
65-
}
66-
6743
}

src/test/java/io/r2dbc/postgresql/PostgresqlResultTest.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,13 @@ final class PostgresqlResultTest {
3333

3434
@Test
3535
void constructorNoCodec() {
36-
assertThatIllegalArgumentException().isThrownBy(() -> new PostgresqlResult(null, Flux.empty(), ExceptionFactory.INSTANCE))
36+
assertThatIllegalArgumentException().isThrownBy(() -> PostgresqlResult.toResult(null, Flux.empty(), ExceptionFactory.INSTANCE))
3737
.withMessage("codecs must not be null");
3838
}
3939

4040
@Test
4141
void constructorNoRowMetadata() {
42-
assertThatIllegalArgumentException().isThrownBy(() -> new PostgresqlResult(MockCodecs.empty(), null, ExceptionFactory.INSTANCE))
42+
assertThatIllegalArgumentException().isThrownBy(() -> PostgresqlResult.toResult(MockCodecs.empty(), null, ExceptionFactory.INSTANCE))
4343
.withMessage("messages must not be null");
4444
}
4545

src/test/java/io/r2dbc/postgresql/client/BindingTest.java

-6
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,6 @@
3333

3434
final class BindingTest {
3535

36-
@Test
37-
void addNoIndex() {
38-
assertThatIllegalArgumentException().isThrownBy(() -> new Binding(1).add(null, new Parameter(FORMAT_TEXT, 100, NULL_VALUE)))
39-
.withMessage("index must not be null");
40-
}
41-
4236
@Test
4337
void addNoParameter() {
4438
assertThatIllegalArgumentException().isThrownBy(() -> new Binding(1).add(1, null))

0 commit comments

Comments
 (0)