Skip to content

Commit 9b4ec54

Browse files
authored
Postgres query timeout (#264)
1 parent e434dfa commit 9b4ec54

8 files changed

Lines changed: 76 additions & 4 deletions

File tree

document-store/src/main/java/org/hypertrace/core/documentstore/TypesafeDatastoreConfigAdapter.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ public DatastoreConfig convert(final Config config) {
9393
connectionConfig.credentials(),
9494
connectionConfig.applicationName(),
9595
connectionConfig.connectionPoolConfig(),
96+
connectionConfig.queryTimeout(),
9697
connectionConfig.customParameters()) {
9798
@Override
9899
public String toConnectionString() {

document-store/src/main/java/org/hypertrace/core/documentstore/model/config/ConnectionConfig.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ public ConnectionConfig build() {
126126
credentials,
127127
applicationName,
128128
connectionPoolConfig,
129+
queryTimeout,
129130
customParameters);
130131
}
131132

document-store/src/main/java/org/hypertrace/core/documentstore/model/config/postgres/PostgresConnectionConfig.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import static java.util.Collections.unmodifiableList;
44
import static java.util.function.Predicate.not;
55

6+
import java.time.Duration;
67
import java.util.ArrayList;
78
import java.util.List;
89
import java.util.Map;
@@ -36,6 +37,7 @@ public class PostgresConnectionConfig extends ConnectionConfig {
3637

3738
@NonNull String applicationName;
3839
@NonNull ConnectionPoolConfig connectionPoolConfig;
40+
@NonNull Duration queryTimeout;
3941

4042
public static ConnectionConfigBuilder builder() {
4143
return ConnectionConfig.builder().type(DatabaseType.POSTGRES);
@@ -47,6 +49,7 @@ public PostgresConnectionConfig(
4749
@Nullable final ConnectionCredentials credentials,
4850
@NonNull final String applicationName,
4951
@Nullable final ConnectionPoolConfig connectionPoolConfig,
52+
@NonNull final Duration queryTimeout,
5053
@NonNull final Map<String, String> customParameters) {
5154
super(
5255
ensureSingleEndpoint(endpoints),
@@ -55,6 +58,7 @@ public PostgresConnectionConfig(
5558
customParameters);
5659
this.applicationName = applicationName;
5760
this.connectionPoolConfig = getConnectionPoolConfigOrDefault(connectionPoolConfig);
61+
this.queryTimeout = queryTimeout;
5862
}
5963

6064
public String toConnectionString() {

document-store/src/main/java/org/hypertrace/core/documentstore/postgres/PostgresClient.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,10 @@ public Map<String, String> getCustomParameters() {
6969
return connectionConfig.customParameters();
7070
}
7171

72+
public int getQueryTimeoutSeconds() {
73+
return (int) connectionConfig.queryTimeout().toSeconds();
74+
}
75+
7276
public void close() {
7377
if (connection != null) {
7478
try {

document-store/src/main/java/org/hypertrace/core/documentstore/postgres/PostgresCollection.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ public PostgresCollection(final PostgresClient client, final String collectionNa
113113
this.tableIdentifier = tableIdentifier;
114114
this.subDocUpdater =
115115
new PostgresSubDocumentUpdater(new PostgresQueryBuilder(this.tableIdentifier));
116-
this.queryExecutor = new PostgresQueryExecutor();
116+
this.queryExecutor = new PostgresQueryExecutor(client.getQueryTimeoutSeconds());
117117
this.updateValidator = new CommonUpdateValidator();
118118
}
119119

document-store/src/main/java/org/hypertrace/core/documentstore/postgres/PostgresQueryExecutor.java

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,19 @@
66
import java.sql.PreparedStatement;
77
import java.sql.ResultSet;
88
import java.sql.SQLException;
9-
import lombok.AllArgsConstructor;
109
import lombok.extern.slf4j.Slf4j;
1110
import org.hypertrace.core.documentstore.postgres.query.v1.PostgresQueryParser;
1211
import org.hypertrace.core.documentstore.postgres.query.v1.transformer.PostgresQueryTransformer;
1312

1413
@Slf4j
15-
@AllArgsConstructor
1614
public class PostgresQueryExecutor {
1715

16+
private final int queryTimeoutSeconds;
17+
18+
public PostgresQueryExecutor(int queryTimeoutSeconds) {
19+
this.queryTimeoutSeconds = queryTimeoutSeconds;
20+
}
21+
1822
static org.hypertrace.core.documentstore.query.Query transformAndLog(
1923
org.hypertrace.core.documentstore.query.Query query) {
2024
log.debug("Original query before transformation: {}", query);
@@ -28,7 +32,8 @@ protected ResultSet execute(final Connection connection, PostgresQueryParser que
2832
final String sqlQuery = queryParser.parse();
2933
final Params params = queryParser.getParamsBuilder().build();
3034
// this is closed when the corresponding ResultSet is closed in the iterators
31-
PreparedStatement preparedStatement = buildPreparedStatement(sqlQuery, params, connection);
35+
PreparedStatement preparedStatement =
36+
buildPreparedStatement(sqlQuery, params, connection, queryTimeoutSeconds);
3237
try {
3338
log.debug("Executing SQL query: {}", sqlQuery);
3439
return preparedStatement.executeQuery();
@@ -45,7 +50,16 @@ protected ResultSet execute(final Connection connection, PostgresQueryParser que
4550

4651
public PreparedStatement buildPreparedStatement(
4752
String sqlQuery, Params params, Connection connection) throws SQLException {
53+
return buildPreparedStatement(sqlQuery, params, connection, this.queryTimeoutSeconds);
54+
}
55+
56+
public PreparedStatement buildPreparedStatement(
57+
String sqlQuery, Params params, Connection connection, int queryTimeoutSeconds)
58+
throws SQLException {
4859
PreparedStatement preparedStatement = connection.prepareStatement(sqlQuery);
60+
if (queryTimeoutSeconds > 0) {
61+
preparedStatement.setQueryTimeout(queryTimeoutSeconds);
62+
}
4963
enrichPreparedStatementWithParams(preparedStatement, params);
5064
return preparedStatement;
5165
}

document-store/src/test/java/org/hypertrace/core/documentstore/model/config/TypesafeConfigDatastoreConfigExtractorTest.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ void testBuildPostgres() {
158158
.poolMaxConnectionsKey(MAX_CONNECTIONS_KEY)
159159
.poolConnectionAccessTimeoutKey(CONNECTION_ACCESS_TIMEOUT_KEY)
160160
.poolConnectionSurrenderTimeoutKey(CONNECTION_SURRENDER_TIMEOUT_KEY)
161+
.queryTimeoutKey(QUERY_TIMEOUT_KEY)
161162
.extract()
162163
.connectionConfig();
163164
final ConnectionConfig expected =
@@ -179,6 +180,7 @@ void testBuildPostgres() {
179180
.connectionSurrenderTimeout(surrenderTimeout)
180181
.build())
181182
.aggregationPipelineMode(SORT_OPTIMIZED_IF_POSSIBLE)
183+
.queryTimeout(queryTimeout)
182184
.build();
183185

184186
assertEquals(expected, config);
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package org.hypertrace.core.documentstore.postgres;
2+
3+
import static org.mockito.Mockito.verify;
4+
import static org.mockito.Mockito.when;
5+
6+
import java.sql.Connection;
7+
import java.sql.PreparedStatement;
8+
import java.sql.SQLException;
9+
import org.junit.jupiter.api.Test;
10+
import org.junit.jupiter.api.extension.ExtendWith;
11+
import org.mockito.Mock;
12+
import org.mockito.junit.jupiter.MockitoExtension;
13+
14+
@ExtendWith(MockitoExtension.class)
15+
class PostgresQueryExecutorTest {
16+
17+
@Mock private Connection mockConnection;
18+
@Mock private PreparedStatement mockPreparedStatement;
19+
20+
@Test
21+
void testPreparedStatementUsesConfiguredTimeout() throws SQLException {
22+
int configuredTimeoutSeconds = 45;
23+
String sqlQuery = "SELECT * FROM test_table";
24+
Params params = Params.newBuilder().build();
25+
26+
when(mockConnection.prepareStatement(sqlQuery)).thenReturn(mockPreparedStatement);
27+
28+
PostgresQueryExecutor executor = new PostgresQueryExecutor(configuredTimeoutSeconds);
29+
executor.buildPreparedStatement(sqlQuery, params, mockConnection);
30+
verify(mockPreparedStatement).setQueryTimeout(configuredTimeoutSeconds);
31+
}
32+
33+
@Test
34+
void testPreparedStatementUsesOverridenTimeout() throws SQLException {
35+
int defaultTimeoutSeconds = 30;
36+
String sqlQuery = "SELECT * FROM test_table";
37+
Params params = Params.newBuilder().build();
38+
39+
when(mockConnection.prepareStatement(sqlQuery)).thenReturn(mockPreparedStatement);
40+
41+
PostgresQueryExecutor executor = new PostgresQueryExecutor(defaultTimeoutSeconds);
42+
// override timeout to 45s
43+
executor.buildPreparedStatement(sqlQuery, params, mockConnection, 45);
44+
verify(mockPreparedStatement).setQueryTimeout(45);
45+
}
46+
}

0 commit comments

Comments
 (0)