Mercurial > hg > monetdb-java
changeset 436:b32946647c77
Extended PreparedStatement.setString() json string validity test for json parameters to now also accept: numbers (with optional fractions and exponent), string literals between double quotes, and keywords: true, false and null.
Also extended and migrated test Bug_PrepStmtSetString_6382 to JDBC_API_Tester with no output on success.
author | Martin van Dinther <martin.van.dinther@monetdbsolutions.com> |
---|---|
date | Wed, 10 Feb 2021 17:51:12 +0100 (2021-02-10) |
parents | e029aa705fdb |
children | 1333c19b0c1b |
files | src/main/java/org/monetdb/jdbc/MonetPreparedStatement.java tests/JDBC_API_Tester.java |
diffstat | 2 files changed, 316 insertions(+), 16 deletions(-) [+] |
line wrap: on
line diff
--- a/src/main/java/org/monetdb/jdbc/MonetPreparedStatement.java +++ b/src/main/java/org/monetdb/jdbc/MonetPreparedStatement.java @@ -2251,26 +2251,120 @@ public class MonetPreparedStatement castprefix = "inet "; break; case "json": - // There is no support for JSON in standard java class libraries. - // Possibly we could use org.json.simple.JSONObject or other/faster libs - // javax.json.Json is not released yet (see https://json-processing-spec.java.net/) - // see also https://github.com/fabienrenaud/java-json-benchmark - // Note that it would make our JDBC driver dependent of an external jar - // and we don't want that. + { + // do a quick JSON string validity check to prevent failing exec #(..., ...) calls + // which destroy the prepared statement(s), see bug 6351 and 6832 + String conversionIssue = null; + if (x.isEmpty()) { + conversionIssue = "It may not be empty"; + } else { + // scan for first and last non-whitespace character + int start = 0; + int end = x.length() -1; + // find first non white space character + char firstc = x.charAt(start); + while ((firstc == ' ' || firstc == '\t' || firstc == '\n' || firstc == '\r') + && (start < end)) { + start++; + firstc = x.charAt(start); + } + // find last non white space character + char lastc = x.charAt(end); + while ((lastc == ' ' || lastc == '\t' || lastc == '\n' || lastc == '\r') + && (start < end)) { + end--; + lastc = x.charAt(end); + } - // do simplistic check if x represents a valid json string to prevent - // failing exec #(..., ...) calls which destroy the prepared statement, see bug 6351 - if (x.isEmpty() || - (x.startsWith("{") && !x.endsWith("}")) || - (x.startsWith("[") && !x.endsWith("]")) - // TODO check completely if x represents a valid json string - ) - throw new SQLDataException("Invalid json string. It does not start with { or [ and end with } or ]", "22M32"); - - // TODO check completely if x represents a valid json string + if (end - start <= 0) { + conversionIssue = "It may not be empty"; + } else { + switch (firstc) { + case '{': // start of an object + if (lastc != '}') + conversionIssue = "It does not end with }"; + break; + case '[': // start of an array + if (lastc != ']') + conversionIssue = "It does not end with ]"; + break; + case '"': // start of a string + if (lastc != '"') + conversionIssue = "It does not end with \""; + break; + case 'n': // start of literal: null + if (lastc != 'l' || (end - start != 3) || !"null".equals(x.trim())) + conversionIssue = "It does not match: null"; + break; + case 'f': // start of literal: false + if (lastc != 'e' || (end - start != 4) || !"false".equals(x.trim())) + conversionIssue = "It does not match: false"; + break; + case 't': // start of literal: true + if (lastc != 'e' || (end - start != 3) || !"true".equals(x.trim())) + conversionIssue = "It does not match: true"; + break; + case '-': // start of a number with a negative sign + case '+': // start of a number with a positive sign + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + // start of a number in format: [ - | + ] int [ frac ] [ exp ] + // check if it continues with more digits + if (start < end) + start++; + firstc = x.charAt(start); + while (firstc >= '0' && firstc <= '9' && start < end) { + start++; + firstc = x.charAt(start); + } + // check if it continues with optional fractions + if (firstc == '.' && start < end) { + // yes, consume the digits of the fraction + start++; + firstc = x.charAt(start); + while (firstc >= '0' && firstc <= '9' && start < end) { + start++; + firstc = x.charAt(start); + } + } + // check if it continues with optional exponent + if ((firstc == 'E' || firstc == 'e') && start < end) { + // yes, consume the optional sign + start++; + firstc = x.charAt(start); + if ((firstc == '+' || firstc == '-') && start < end) { + start++; + firstc = x.charAt(start); + } + // yes, consume the digits of the exponnent + while ((firstc >= '0' && firstc <= '9') && start < end) { + start++; + firstc = x.charAt(start); + } + } + if (start != end) + conversionIssue = "It does not represent a valid number"; + break; + default: + conversionIssue = "Invalid first character: " + firstc; + break; + } + } + } + if (conversionIssue != null) + throw new SQLDataException("Invalid json string. " + conversionIssue, "22M32"); castprefix = "json "; break; + } case "url": try { // also check if x represents a valid url string to prevent
--- a/tests/JDBC_API_Tester.java +++ b/tests/JDBC_API_Tester.java @@ -78,6 +78,7 @@ final public class JDBC_API_Tester { jt.Test_Sbatching(); jt.Test_Smoreresults(); jt.Test_Wrapper(); + jt.Bug_PrepStmtSetString_6382(); jt.closeConx(jt.con); } @@ -3029,6 +3030,211 @@ final public class JDBC_API_Tester { } } + private void Bug_PrepStmtSetString_6382() { + sb.setLength(0); // clear the output log buffer + + Statement stmt = null; + PreparedStatement pstmt = null; + ResultSet rs = null; + final String tableName = "PrepStmtSetString_6382"; + try { + // >> true: auto commit should be on by default + sb.append("0. true\t").append(con.getAutoCommit()).append("\n"); + + stmt = con.createStatement(); + sb.append("1. Creating table " + tableName); + if (stmt.executeUpdate("CREATE TABLE " + tableName + " (myint INT, myvarchar VARCHAR(15), myjson JSON, myuuid UUID, myurl URL, myinet INET)") != Statement.SUCCESS_NO_INFO) + sb.append("Wrong return status\n"); + + sb.append("\n2. Insert row 1, "); + stmt.executeUpdate("INSERT INTO " + tableName + " VALUES (1, 'row 1', '{}', uuid '34c8deb5-e608-406b-beda-6a951f73d455', 'https://www.monetdb.org/', '128.0.0.1')"); + sb.append("2, "); + stmt.executeUpdate("INSERT INTO " + tableName + " VALUES (2, 'row 2', '[]', NULL, NULL, NULL)"); + sb.append("3, "); + stmt.executeUpdate("INSERT INTO " + tableName + " VALUES (3, 'row 3', '\"abc\"', NULL, NULL, NULL)"); + sb.append("4, "); + stmt.executeUpdate("INSERT INTO " + tableName + " VALUES (4, 'row 4', 'true', NULL, NULL, NULL)"); + sb.append("5\n"); + stmt.executeUpdate("INSERT INTO " + tableName + " VALUES (5, 'row 5', '-0.123', NULL, NULL, NULL)"); + + sb.append("Creating a prepared statement with 6 parameters and inserting rows using setInt(), setString(), setNull(), setNString(), setURL(), setObject().\n"); + pstmt = con.prepareStatement("INSERT INTO " + tableName + " VALUES (?,?, ? ,?,? , ?)"); + ParameterMetaData pmd = pstmt.getParameterMetaData(); + int pcount = pmd.getParameterCount(); + sb.append("Prepared Statement has " + pcount + " parameters:" + (pcount != 6 ? " ERROR: Expected 6 parameters!" : "")).append("\n"); + for (int p = 1; p <= pcount; p++) { + sb.append(" Parameter " + p + " type is: " + pmd.getParameterTypeName(p) + ". JDBC SQL type: " + pmd.getParameterType(p)).append("\n"); + } + + int row = 6; + pstmt.setInt(1, row); + pstmt.setString(2, "row " + row); + pstmt.setString(3, "{\"menu\": {\n \"id\": \"file\",\n \"value\": \"File\",\n \"popup\": {\n \"menuitem\": [\n {\"value\": \"New\", \"onclick\": \"CreateNewDoc()\"},\n {\"value\": \"Open\", \"onclick\": \"OpenDoc()\"},\n {\"value\": \"Close\", \"onclick\": \"CloseDoc()\"}\n ]\n }\n}}"); + pstmt.setNull(4, 0); + pstmt.setNull(5, 0); + pstmt.setNull(6, 0); + sb.append("Inserting row " + row).append("\n"); + int inserted = pstmt.executeUpdate(); + sb.append("Inserted " + inserted + " row").append("\n"); + + row++; // row 7 + pstmt.setShort(1, (short)row); + pstmt.setNString(2, "row " + row); + pstmt.setNull(3, 0); + pstmt.setString(4, "4a148b7d-8d47-4e1e-a21e-09a71abf2215"); + sb.append("Inserting row " + row).append("\n"); + inserted = pstmt.executeUpdate(); + sb.append("Inserted " + inserted + " row").append("\n"); + + row++; // row 8 + pstmt.setLong(1, (long)row); + pstmt.setString(2, "row " + row); + pstmt.setObject(3, "+3.1415E-06"); + pstmt.setNull(4, 0); + try { + pstmt.setURL(5, new java.net.URL("https://www.cwi.nl/")); + } catch (java.net.MalformedURLException mfe) { + sb.append(mfe).append("\n"); + } + sb.append("Inserting row " + row).append("\n"); + inserted = pstmt.executeUpdate(); + sb.append("Inserted " + inserted + " row").append("\n"); + + row++; // row 9 + pstmt.setBigDecimal(1, new java.math.BigDecimal(row)); + pstmt.setNString(2, "row " + row); + pstmt.setNull(5, 0); + pstmt.setString(6, "127.255.255.255"); + sb.append("Inserting row " + row).append("\n"); + inserted = pstmt.executeUpdate(); + sb.append("Inserted " + inserted + " row").append("\n"); + + /* also test generic setObject(int, String) */ + row++; // row 10 + pstmt.setObject(1, Integer.valueOf(row)); + pstmt.setObject(2, "row " + row); + pstmt.setObject(3, "[{\"menu\": {\n \"header\": \"SVG Viewer\",\n \"items\": [\n {\"id\": \"Open\"},\n {\"id\": \"OpenNew\", \"label\": \"Open New\"},\n null,\n {\"id\": \"ZoomIn\", \"label\": \"Zoom In\"},\n {\"id\": \"ZoomOut\", \"label\": \"Zoom Out\"},\n {\"id\": \"OriginalView\", \"label\": \"Original View\"},\n null,\n {\"id\": \"Quality\"},\n {\"id\": \"Pause\"},\n {\"id\": \"Mute\"},\n null,\n {\"id\": \"Help\"},\n {\"id\": \"About\", \"label\": \"About Adobe CVG Viewer...\"}\n ]\n}}]"); + pstmt.setObject(4, "b39dc76e-4faf-4fd9-bc1e-17df48acf764"); + pstmt.setObject(5, "https://en.wikipedia.org/wiki/IP_address"); + pstmt.setObject(6, "223.255.255.255"); + sb.append("Inserting row " + row).append("\n"); + inserted = pstmt.executeUpdate(); + sb.append("Inserted " + inserted + " row").append("\n"); + + row++; // row 11 + pstmt.setObject(1, new java.math.BigDecimal(row)); + pstmt.setObject(2, "row " + row); + pstmt.setObject(3, "null"); + pstmt.setObject(4, java.util.UUID.fromString("ff125769-b63c-4c3c-859f-5b84a9349e24")); + URL myURL = new URL(); + try { + myURL.fromString("https://en.wikipedia.org/wiki/IP_address"); + pstmt.setObject(5, myURL); + } catch (Exception mfe) { + sb.append(mfe).append("\n"); + } + INET myINET = new INET(); + myINET.fromString("223.234.245.255"); + pstmt.setObject(6, myINET); + sb.append("Inserting row " + row).append("\n"); + inserted = pstmt.executeUpdate(); + sb.append("Inserted " + inserted + " row").append("\n"); + + sb.append("List contents of TABLE " + tableName + " after " + row + " rows inserted").append("\n"); + rs = stmt.executeQuery("SELECT * FROM " + tableName + " ORDER BY 1"); + ResultSetMetaData rsmd = rs.getMetaData(); + int colcount = rsmd.getColumnCount(); + sb.append("Query has " + colcount + " output columns." + (colcount != 6 ? " ERROR: Expected 6 columns!" : "")).append("\n"); + row = 0; + while (rs.next()) { + sb.append("row " + ++row); + for (int c = 1; c <= colcount; c++) { + sb.append("\t" + rs.getString(c)); + } + sb.append("\n"); + } + } catch (SQLException e) { + sb.append("FAILED: ").append(e.getMessage()).append("\n"); + } + + try { + sb.append("Cleanup TABLE " + tableName).append("\n"); + stmt.executeUpdate("DROP TABLE " + tableName); + } catch (SQLException e) { + sb.append("FAILED: ").append(e.getMessage()).append("\n"); + } + + closeStmtResSet(stmt, rs; + closeStmtResSet(pstmt, null); + + compareExpectedOutput("Bug_PrepStmtSetString_6382", + "0. true true\n" + + "1. Creating table PrepStmtSetString_6382\n" + + "2. Insert row 1, 2, 3, 4, 5\n" + + "Creating a prepared statement with 6 parameters and inserting rows using setInt(), setString(), setNull(), setNString(), setURL(), setObject().\n" + + "Prepared Statement has 6 parameters:\n" + + " Parameter 1 type is: int. JDBC SQL type: 4\n" + + " Parameter 2 type is: varchar. JDBC SQL type: 12\n" + + " Parameter 3 type is: json. JDBC SQL type: 12\n" + + " Parameter 4 type is: uuid. JDBC SQL type: 12\n" + + " Parameter 5 type is: url. JDBC SQL type: 12\n" + + " Parameter 6 type is: inet. JDBC SQL type: 12\n" + + "Inserting row 6\n" + + "Inserted 1 row\n" + + "Inserting row 7\n" + + "Inserted 1 row\n" + + "Inserting row 8\n" + + "Inserted 1 row\n" + + "Inserting row 9\n" + + "Inserted 1 row\n" + + "Inserting row 10\n" + + "Inserted 1 row\n" + + "Inserting row 11\n" + + "Inserted 1 row\n" + + "List contents of TABLE PrepStmtSetString_6382 after 11 rows inserted\n" + + "Query has 6 output columns.\n" + + "row 1 1 row 1 {} 34c8deb5-e608-406b-beda-6a951f73d455 https://www.monetdb.org/ 128.0.0.1\n" + + "row 2 2 row 2 [] null null null\n" + + "row 3 3 row 3 \"abc\" null null null\n" + + "row 4 4 row 4 true null null null\n" + + "row 5 5 row 5 -0.123 null null null\n" + + "row 6 6 row 6 {\"menu\": {\n" + + " \"id\": \"file\",\n" + + " \"value\": \"File\",\n" + + " \"popup\": {\n" + + " \"menuitem\": [\n" + + " {\"value\": \"New\", \"onclick\": \"CreateNewDoc()\"},\n" + + " {\"value\": \"Open\", \"onclick\": \"OpenDoc()\"},\n" + + " {\"value\": \"Close\", \"onclick\": \"CloseDoc()\"}\n" + + " ]\n" + + " }\n" + + "}} null null null\n" + + "row 7 7 row 7 null 4a148b7d-8d47-4e1e-a21e-09a71abf2215 null null\n" + + "row 8 8 row 8 +3.1415E-06 null https://www.cwi.nl/ null\n" + + "row 9 9 row 9 +3.1415E-06 null null 127.255.255.255\n" + + "row 10 10 row 10 [{\"menu\": {\n" + + " \"header\": \"SVG Viewer\",\n" + + " \"items\": [\n" + + " {\"id\": \"Open\"},\n" + + " {\"id\": \"OpenNew\", \"label\": \"Open New\"},\n" + + " null,\n" + + " {\"id\": \"ZoomIn\", \"label\": \"Zoom In\"},\n" + + " {\"id\": \"ZoomOut\", \"label\": \"Zoom Out\"},\n" + + " {\"id\": \"OriginalView\", \"label\": \"Original View\"},\n" + + " null,\n" + + " {\"id\": \"Quality\"},\n" + + " {\"id\": \"Pause\"},\n" + + " {\"id\": \"Mute\"},\n" + + " null,\n" + + " {\"id\": \"Help\"},\n" + + " {\"id\": \"About\", \"label\": \"About Adobe CVG Viewer...\"}\n" + + " ]\n" + + "}}] b39dc76e-4faf-4fd9-bc1e-17df48acf764 https://en.wikipedia.org/wiki/IP_address 223.255.255.255\n" + + "row 11 11 row 11 null ff125769-b63c-4c3c-859f-5b84a9349e24 https://en.wikipedia.org/wiki/IP_address 223.234.245.255\n" + + "Cleanup TABLE PrepStmtSetString_6382\n"); + } + + // some private utility methods for showing table content and params meta data private void showTblContents(String tblnm) { Statement stmt = null;