Skip to content

Commit 0720d75

Browse files
authored
Merge pull request #603 from gjwatts/490-connection-error-path-handling
Additional basic DB2 input validation
2 parents 0b4b87a + ec80426 commit 0720d75

File tree

9 files changed

+227
-65
lines changed

9 files changed

+227
-65
lines changed

vertx-db2-client/src/main/java/io/vertx/db2client/DB2ConnectOptions.java

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
import io.vertx.codegen.annotations.GenIgnore;
2525
import io.vertx.core.json.JsonObject;
2626
import io.vertx.db2client.impl.DB2ConnectionUriParser;
27+
import io.vertx.db2client.impl.drda.SQLState;
28+
import io.vertx.db2client.impl.drda.SqlCode;
2729
import io.vertx.sqlclient.SqlConnectOptions;
2830

2931
/**
@@ -46,12 +48,9 @@ public static DB2ConnectOptions fromUri(String connectionUri) throws IllegalArgu
4648

4749
public static final String DEFAULT_HOST = "localhost";
4850
public static final int DEFAULT_PORT = 50000;
49-
public static final String DEFAULT_USER = "root";
50-
public static final String DEFAULT_PASSWORD = "";
51-
public static final String DEFAULT_SCHEMA = "";
5251
public static final String DEFAULT_CHARSET = "utf8";
5352
public static final boolean DEFAULT_USE_AFFECTED_ROWS = false;
54-
public static final int DEFAULT_PIPELINING_LIMIT = 1;//256; // TODO default to 256 once implemented properly
53+
public static final int DEFAULT_PIPELINING_LIMIT = 1; //256; // TODO default to 256 once implemented properly
5554
public static final Map<String, String> DEFAULT_CONNECTION_ATTRIBUTES;
5655

5756
static {
@@ -93,17 +92,29 @@ public DB2ConnectOptions setPort(int port) {
9392

9493
@Override
9594
public DB2ConnectOptions setUser(String user) {
96-
return (DB2ConnectOptions) super.setUser(user);
95+
if (user == null || user.length() < 1) {
96+
throw new DB2Exception("The user cannot be blank or null", SqlCode.MISSING_CREDENTIALS, SQLState.CONNECT_USERID_ISNULL);
97+
} else {
98+
return (DB2ConnectOptions) super.setUser(user);
99+
}
97100
}
98101

99102
@Override
100103
public DB2ConnectOptions setPassword(String password) {
101-
return (DB2ConnectOptions) super.setPassword(password);
104+
if (password == null || password.length() < 1) {
105+
throw new DB2Exception("The password cannot be blank or null", SqlCode.MISSING_CREDENTIALS, SQLState.CONNECT_PASSWORD_ISNULL);
106+
} else {
107+
return (DB2ConnectOptions) super.setPassword(password);
108+
}
102109
}
103110

104111
@Override
105112
public DB2ConnectOptions setDatabase(String database) {
106-
return (DB2ConnectOptions) super.setDatabase(database);
113+
if (database == null || database.length() < 1) {
114+
throw new DB2Exception("The database name cannot be blank or null", SqlCode.DATABASE_NOT_FOUND, SQLState.DATABASE_NOT_FOUND);
115+
} else {
116+
return (DB2ConnectOptions) super.setDatabase(database);
117+
}
107118
}
108119

109120
@Override
@@ -159,9 +170,6 @@ public DB2ConnectOptions addProperty(String key, String value) {
159170
protected void init() {
160171
this.setHost(DEFAULT_HOST);
161172
this.setPort(DEFAULT_PORT);
162-
this.setUser(DEFAULT_USER);
163-
this.setPassword(DEFAULT_PASSWORD);
164-
this.setDatabase(DEFAULT_SCHEMA);
165173
this.setPipeliningLimit(DEFAULT_PIPELINING_LIMIT);
166174
this.setProperties(new HashMap<>(DEFAULT_CONNECTION_ATTRIBUTES));
167175
}

vertx-db2-client/src/main/java/io/vertx/db2client/impl/codec/DB2Encoder.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ void write(CommandBase<?> cmd) {
9797
} else if (cmd instanceof CloseConnectionCommand) {
9898
codec = new CloseConnectionCommandCodec((CloseConnectionCommand) cmd);
9999
} else if (cmd instanceof PrepareStatementCommand) {
100-
codec = new PrepareStatementCodec((PrepareStatementCommand) cmd);
100+
codec = new PrepareStatementCodec((PrepareStatementCommand) cmd);
101101
} else if (cmd instanceof CloseStatementCommand) {
102102
codec = new CloseStatementCommandCodec((CloseStatementCommand) cmd);
103103
} else if (cmd instanceof CloseCursorCommand) {

vertx-db2-client/src/main/java/io/vertx/db2client/impl/codec/InitialHandshakeCommandCodec.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ class InitialHandshakeCommandCodec extends AuthenticationCommandBaseCodec<Connec
3333
private static final int ST_CONNECTING = 0;
3434
private static final int ST_AUTHENTICATING = 1;
3535
private static final int ST_CONNECTED = 2;
36+
private static final int ST_CONNECT_FAILED = 3;
3637

3738
private static final int TARGET_SECURITY_MEASURE = DRDAConstants.SECMEC_USRIDPWD;
3839

@@ -69,7 +70,14 @@ void decodePayload(ByteBuf payload, int payloadLength) {
6970
switch (status) {
7071
case ST_CONNECTING:
7172
response.readExchangeServerAttributes();
72-
response.readAccessSecurity(TARGET_SECURITY_MEASURE);
73+
// readAccessSecurity can throw a DB2Exception if there are problems connecting. In that case, we want to catch that exception and
74+
// make sure to set the status to something other than ST_CONNECTING so we don't try to complete the result twice (when we hit encode)
75+
try {
76+
response.readAccessSecurity(TARGET_SECURITY_MEASURE);
77+
} catch (DB2Exception de) {
78+
status = ST_CONNECT_FAILED;
79+
throw de;
80+
}
7381
status = ST_AUTHENTICATING;
7482
ByteBuf packet = allocateBuffer();
7583
int packetStartIdx = packet.writerIndex();

vertx-db2-client/src/main/java/io/vertx/db2client/impl/drda/DRDAConnectResponse.java

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1041,7 +1041,8 @@ private String parsePRDID(boolean skip) {
10411041
private void parseSECCHKreply() {
10421042
int peekCP = peekCodePoint();
10431043
if (peekCP != CodePoint.SECCHKRM) {
1044-
throwUnknownCodepoint(peekCP);
1044+
// throwUnknownCodepoint(peekCP);
1045+
parseCommonError(peekCP);
10451046
}
10461047

10471048
parseSECCHKRM();
@@ -1058,7 +1059,7 @@ private void parseSECCHKreply() {
10581059
parseSECTKN(false);
10591060
}
10601061
}
1061-
1062+
10621063
// The Security Check (SECCHKRM) Reply Message indicates the acceptability
10631064
// of the security information.
10641065
// This method throws an exception if the connection was not established
@@ -1143,7 +1144,7 @@ private void parseSECCHKRM() {
11431144
throw new DB2Exception("Missing userid, verify a user value was supplied", SqlCode.MISSING_CREDENTIALS, SQLState.CONNECT_USERID_ISNULL);
11441145
// Missing password - TODO We should catch and handle this issue *before* the call to the DB2 server
11451146
case CodePoint.SECCHKCD_10:
1146-
// Using SQL error code and state values from similar JDBC reponse
1147+
// Using SQL error code and state values from similar JDBC response
11471148
throw new DB2Exception("Missing password, verify a password value was supplied", SqlCode.MISSING_CREDENTIALS, SQLState.CONNECT_PASSWORD_ISNULL);
11481149
// Invalid credentials
11491150
case CodePoint.SECCHKCD_0E:
@@ -1315,12 +1316,15 @@ private void parseRdbAccessFailed() {
13151316
// Returned from Server:
13161317
// SVRCOD - required (8 - ERROR)
13171318
// RDBNAM - required
1319+
// SRVDGN - optional
13181320
//
13191321
private void parseRDBAFLRM() {
13201322
boolean svrcodReceived = false;
13211323
int svrcod = CodePoint.SVRCOD_INFO;
13221324
boolean rdbnamReceived = false;
1325+
boolean srvdgnReceived = false;
13231326
String rdbnam = null;
1327+
String serverDiagnostics = null;
13241328

13251329
parseLengthAndMatchCodePoint(CodePoint.RDBAFLRM);
13261330
pushLengthOnCollectionStack();
@@ -1343,6 +1347,14 @@ private void parseRDBAFLRM() {
13431347
rdbnam = parseRDBNAM(true);
13441348
peekCP = peekCodePoint();
13451349
}
1350+
1351+
// Optional code point
1352+
if (peekCP == CodePoint.SRVDGN) {
1353+
foundInPass = true;
1354+
srvdgnReceived = checkAndGetReceivedFlag(srvdgnReceived);
1355+
serverDiagnostics = parseSRVDGN();
1356+
peekCP = peekCodePoint();
1357+
}
13461358

13471359
if (!foundInPass) {
13481360
throwUnknownCodepoint(peekCP);

vertx-db2-client/src/main/java/io/vertx/db2client/impl/drda/DRDAQueryResponse.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2730,6 +2730,8 @@ private int parseCODPNT() {
27302730
void parseDTAMCHRM() {
27312731
boolean svrcodReceived = false;
27322732
int svrcod = CodePoint.SVRCOD_INFO;
2733+
boolean srvdgnReceived = false;
2734+
String serverDiagnostics = null;
27332735
boolean rdbnamReceived = false;
27342736
String rdbnam = null;
27352737

@@ -2747,13 +2749,21 @@ void parseDTAMCHRM() {
27472749
svrcod = parseSVRCOD(CodePoint.SVRCOD_ERROR, CodePoint.SVRCOD_ERROR);
27482750
peekCP = peekCodePoint();
27492751
}
2750-
2752+
27512753
if (peekCP == CodePoint.RDBNAM) {
27522754
foundInPass = true;
27532755
rdbnamReceived = checkAndGetReceivedFlag(rdbnamReceived);
27542756
rdbnam = parseRDBNAM(true);
27552757
peekCP = peekCodePoint();
27562758
}
2759+
2760+
// Optional code point
2761+
if (peekCP == CodePoint.SRVDGN) {
2762+
foundInPass = true;
2763+
srvdgnReceived = checkAndGetReceivedFlag(srvdgnReceived);
2764+
serverDiagnostics = parseSRVDGN();
2765+
peekCP = peekCodePoint();
2766+
}
27572767

27582768
if (!foundInPass) {
27592769
throwUnknownCodepoint(peekCP);

vertx-db2-client/src/main/java/io/vertx/db2client/impl/drda/NetSqlca.java

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717

1818
import java.util.Arrays;
1919

20+
import io.vertx.db2client.DB2Exception;
21+
2022
/**
2123
* A SQLCA stands for "SQL Communication Area"
2224
* The primary purpose is for tracking the SQLCode.
@@ -92,16 +94,52 @@ public static int complete(NetSqlca sqlca, int... allowedCodes) {
9294
return 0;
9395
boolean allowed = Arrays.stream(allowedCodes).anyMatch(code -> code == sqlca.sqlCode_);
9496
if (!allowed && sqlca.sqlCode_ < 0) {
97+
throwSqlError(sqlca);
9598
// TODO: May want to go through the DB2 SQL error code doc above and provide English
96-
// messages to go along with the corresponding SQLcode to save users needing to look them up
97-
throw new IllegalStateException("ERROR sqlcode=" + sqlca.sqlCode_ + " Full Sqlca: " + sqlca.toString());
99+
// messages to go along with the corresponding SQLcode to save users needing to look them up
98100
}
99101
if (!allowed && sqlca.sqlCode_ > 0) {
100102
System.out.println("WARNING sqlcode=" + sqlca.sqlCode_);
101103
}
102104
return sqlca.sqlCode_;
103105
}
104106

107+
/**
108+
* Throws a specific error message based on the passed in SQL error code
109+
* @param sqlca
110+
*/
111+
public static void throwSqlError(NetSqlca sqlca) {
112+
if (sqlca == null || sqlca.sqlCode_ == 0) {
113+
return;
114+
}
115+
// Add additional error messages to this list
116+
switch(sqlca.sqlCode_) {
117+
// The SQL syntax is invalid
118+
case SqlCode.INVALID_SQL_STATEMENT:
119+
throw new DB2Exception("The SQL syntax provided was invalid", SqlCode.INVALID_SQL_STATEMENT, sqlca.sqlState_);
120+
// The object (table?) is not defined/available
121+
case SqlCode.OBJECT_NOT_DEFINED:
122+
if (sqlca.sqlErrmc_ != null && sqlca.sqlErrmc_.trim().length() > 0)
123+
throw new DB2Exception("The object " + sqlca.sqlErrmc_ + " provided is not defined", SqlCode.OBJECT_NOT_DEFINED, sqlca.sqlState_);
124+
else
125+
throw new DB2Exception("An object provided is not defined", SqlCode.OBJECT_NOT_DEFINED, sqlca.sqlState_);
126+
// The object (table?) is not defined/available
127+
case SqlCode.COLUMN_DOES_NOT_EXIST:
128+
if (sqlca.sqlErrmc_ != null && sqlca.sqlErrmc_.trim().length() > 0)
129+
throw new DB2Exception("The column " + sqlca.sqlErrmc_ + " provided does not exist", SqlCode.COLUMN_DOES_NOT_EXIST, sqlca.sqlState_);
130+
else
131+
throw new DB2Exception("A column provided does not exist", SqlCode.COLUMN_DOES_NOT_EXIST, sqlca.sqlState_);
132+
// Invalid database specified
133+
case SqlCode.DATABASE_NOT_FOUND:
134+
if (sqlca.sqlErrmc_ != null && sqlca.sqlErrmc_.trim().length() > 0)
135+
throw new DB2Exception("The database " + sqlca.sqlErrmc_ + " provided was not found", SqlCode.DATABASE_NOT_FOUND, sqlca.sqlState_);
136+
else
137+
throw new DB2Exception("The database provided was not found", SqlCode.DATABASE_NOT_FOUND, sqlca.sqlState_);
138+
default:
139+
throw new IllegalStateException("ERROR sqlcode=" + sqlca.sqlCode_ + " Full Sqlca: " + sqlca.toString());
140+
}
141+
}
142+
105143
void setSqlerrd(int[] sqlErrd) {
106144
sqlErrd_ = sqlErrd;
107145
}

vertx-db2-client/src/main/java/io/vertx/db2client/impl/drda/SqlCode.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,17 @@ public class SqlCode {
2828
public static final int RDB_NOT_FOUND = -30061;
2929
public static final int INVALID_CREDENTIALS = -4214;
3030
public static final int MISSING_CREDENTIALS = -4461;
31+
public static final int DATABASE_NOT_FOUND = -1001;
32+
33+
// -104 is a broad error message (illegal symbol encountered in SQL statement)
34+
// and could be further broken down by adding more specific SQL error codes and handling them separately
35+
public static final int INVALID_SQL_STATEMENT = -104;
36+
37+
// The error message for this says "Object not defined in DB2" in Wikipedia and
38+
// "<name> is an undefined name" in the IBM zOS DB2 Knowledge Center
39+
// But I see it with invalid table names specified in a query
40+
public static final int OBJECT_NOT_DEFINED = -204;
41+
public static final int COLUMN_DOES_NOT_EXIST = -206;
3142

3243
private int code_;
3344

vertx-db2-client/src/test/java/io/vertx/db2client/DB2DataTypeTest.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
import java.sql.RowId;
2121
import java.time.LocalDateTime;
22+
import java.time.temporal.ChronoField;
2223
import java.util.Arrays;
2324

2425
import org.junit.Test;
@@ -124,8 +125,10 @@ public void testTimestamp(TestContext ctx) {
124125
.execute(Tuple.of(5), ctx.asyncAssertSuccess(rows -> {
125126
ctx.assertEquals(1, rows.size());
126127
Row row = rows.iterator().next();
128+
int nowNanos = now.getNano() - (1000 * now.get(ChronoField.MICRO_OF_SECOND));
129+
int dbNanos = row.getLocalDateTime(1).getNano() - (1000 * row.getLocalDateTime(1).get(ChronoField.MICRO_OF_SECOND));
127130
ctx.assertEquals(5, row.getInteger(0));
128-
ctx.assertEquals(now, row.getLocalDateTime(1));
131+
ctx.assertEquals(dbNanos > 0 ? now : now.minusNanos(nowNanos), row.getLocalDateTime(1));
129132
}));
130133
}));
131134
}));

0 commit comments

Comments
 (0)