Permalink
Browse files

feat: parse command complete message via regex (#962)

Replaces command status whitelist based parsing with a regex approach to
handle generic COMMAND OID COUNT or COMMAND COUNT responses. If the
response does not match the regex then parsing is skipped. This should allow
for automatically supporting new server responses of that same form as well as
skipping any that cannot be parsed.

Fixes #958
  • Loading branch information...
sehrope authored and vlsi committed Sep 28, 2017
1 parent fcb28c7 commit 097db5e70ae8bf193c736b11603332feadb8d544
@@ -62,13 +62,18 @@
import java.util.TimeZone;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* QueryExecutor implementation for the V3 protocol.
*/
public class QueryExecutorImpl extends QueryExecutorBase {
private static final Logger LOGGER = Logger.getLogger(QueryExecutorImpl.class.getName());
private static final Pattern COMMAND_COMPLETE_PATTERN = Pattern.compile("^([A-Za-z]++)(?: (\\d++))?+(?: (\\d++))?+$");
/**
* TimeZone of the current connection (TimeZone backend parameter)
*/
@@ -2508,34 +2513,41 @@ private String receiveCommandStatus() throws IOException {
}
private void interpretCommandStatus(String status, ResultHandler handler) {
int update_count = 0;
long insert_oid = 0;
if (status.startsWith("INSERT") || status.startsWith("UPDATE") || status.startsWith("DELETE")
|| status.startsWith("MOVE")) {
long oid = 0;
long count = 0;
Matcher matcher = COMMAND_COMPLETE_PATTERN.matcher(status);
if (matcher.matches()) {
// String command = matcher.group(1);
String group2 = matcher.group(2);
String group3 = matcher.group(3);
try {
long updates = Long.parseLong(status.substring(1 + status.lastIndexOf(' ')));
// deal with situations where the update modifies more than 2^32 rows
if (updates > Integer.MAX_VALUE) {
update_count = Statement.SUCCESS_NO_INFO;
} else {
update_count = (int) updates;
if (group3 != null) {
// COMMAND OID ROWS
oid = Long.parseLong(group2);
count = Long.parseLong(group3);
} else if (group2 != null) {
// COMMAND ROWS
count = Long.parseLong(group2);
}
if (status.startsWith("INSERT")) {
insert_oid =
Long.parseLong(status.substring(1 + status.indexOf(' '), status.lastIndexOf(' ')));
}
} catch (NumberFormatException nfe) {
} catch (NumberFormatException e) {
// As we're performing a regex validation prior to parsing, this should only
// occurr if the oid or count are out of range.
handler.handleError(new PSQLException(
GT.tr("Unable to interpret the update count in command completion tag: {0}.", status),
GT.tr("Unable to parse the count in command completion tag: {0}.", status),
PSQLState.CONNECTION_FAILURE));
return;
}
}
handler.handleCommandStatus(status, update_count, insert_oid);
int countAsInt = 0;
if (count > Integer.MAX_VALUE) {
// If command status indicates that we've modified more than Integer.MAX_VALUE rows
// then we set the result count to reflect that we cannot provide the actual number
// due to the JDBC field being an int rather than a long.
countAsInt = Statement.SUCCESS_NO_INFO;
} else if (count > 0) {
countAsInt = (int) count;
}
handler.handleCommandStatus(status, countAsInt, oid);
}
private void receiveRFQ() throws IOException {
@@ -12,6 +12,7 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import org.postgresql.core.ServerVersion;
import org.postgresql.jdbc.PgStatement;
import org.postgresql.test.TestUtil;
import org.postgresql.util.PSQLState;
@@ -125,6 +126,11 @@ public void testUpdateCount() throws SQLException {
count = stmt.executeUpdate("CREATE TEMP TABLE another_table (a int)");
assertEquals(0, count);
if (TestUtil.haveMinimumServerVersion(con, ServerVersion.v9_0)) {
count = stmt.executeUpdate("CREATE TEMP TABLE yet_another_table AS SELECT x FROM generate_series(1,10) x");
assertEquals(10, count);
}
}
@Test

0 comments on commit 097db5e

Please sign in to comment.