--- old/src/java.sql/share/classes/java/sql/Statement.java 2015-11-24 14:39:41.000000000 -0500 +++ new/src/java.sql/share/classes/java/sql/Statement.java 2015-11-24 14:39:40.000000000 -0500 @@ -1397,9 +1397,10 @@ * @param val a character string * @return A string enclosed by single quotes with every single quote * converted to two single quotes - * @throws NullPointerException if val is null + * @throws NullPointerException if val is {@code null} + * @throws SQLException if a database access error occurs */ - default String enquoteLiteral(String val) { + default String enquoteLiteral(String val) throws SQLException { return "'" + val.replace("'", "''") + "'"; } @@ -1437,7 +1438,7 @@ * * The default implementation will throw a {@code SQLException} if: * @@ -1501,14 +1502,14 @@ * @throws SQLException if identifier is not a valid identifier * @throws SQLFeatureNotSupportedException if the datasource does not support * delimited identifiers - * @throws NullPointerException if identifier is null + * @throws NullPointerException if identifier is {@code null} */ default String enquoteIdentifier(String identifier, boolean alwaysQuote) throws SQLException { int len = identifier.length(); if (len < 1 || len > 128) { throw new SQLException("Invalid name"); } - if (Pattern.compile("[\\p{Alpha}][\\p{Alnum}_]+").matcher(identifier).matches()) { + if (Pattern.compile("[\\p{Alpha}][\\p{Alnum}_]*").matcher(identifier).matches()) { return alwaysQuote ? "\"" + identifier + "\"" : identifier; } if (identifier.matches("^\".+\"$")) { @@ -1520,4 +1521,65 @@ throw new SQLException("Invalid name"); } } + + /** + * Retrieves whether {@code identifier} is a simple SQL identifier. + * + * @implSpec The default implementation uses the following criteria to + * determine a valid simple SQL identifier: + * + * + *
+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Examples of the conversion:
identifierSimple Identifier
Hellotrue
G'Dayfalse
"Bruce Wayne"false
GoodDay$false
Hello"Worldfalse
"Hello"World"false
+ *
+ * @implNote JDBC driver implementations may need to provide their own + * implementation of this method in order to meet the requirements of the + * underlying datasource. + * @param identifier a SQL identifier + * @return true if a simple SQL identifier, false otherwise + * @throws NullPointerException if identifier is {@code null} + * @throws SQLException if a database access error occurs + */ + default boolean isSimpleIdentifier(String identifier) throws SQLException { + int len = identifier.length(); + return len >= 1 && len <= 128 + && Pattern.compile("[\\p{Alpha}][\\p{Alnum}_]*").matcher(identifier).matches(); + } } --- old/test/java/sql/testng/test/sql/StatementTests.java 2015-11-24 14:39:43.000000000 -0500 +++ new/test/java/sql/testng/test/sql/StatementTests.java 2015-11-24 14:39:42.000000000 -0500 @@ -24,6 +24,7 @@ import java.sql.SQLException; import static org.testng.Assert.assertEquals; +import org.testng.annotations.BeforeClass; import org.testng.annotations.BeforeMethod; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; @@ -33,16 +34,27 @@ public class StatementTests extends BaseTest { protected StubStatement stmt; + protected static String maxIdentifier; @BeforeMethod public void setUpMethod() throws Exception { stmt = new StubStatement(); } + @BeforeClass + public static void setUpClass() throws Exception { + int maxLen = 128; + StringBuilder s = new StringBuilder(maxLen); + for (int i = 0; i < maxLen; i++) { + s.append('a'); + } + maxIdentifier = s.toString(); + } /* * Verify that enquoteLiteral creates a valid literal and converts every * single quote to two single quotes */ + @Test(dataProvider = "validEnquotedLiteralValues") public void test00(String s, String expected) { assertEquals(stmt.enquoteLiteral(s), expected); @@ -90,6 +102,24 @@ } /* + * Validate that isSimpleIdentifier returns the expected value + */ + @Test(dataProvider = "simpleIdentifierValues") + public void test05(String s, boolean expected) throws SQLException { + assertEquals(stmt.isSimpleIdentifier(s), expected); + } + + /* + * Validate a NullPointerException is thrown is the string passed to + * isSimpleIdentifier is null + */ + @Test(expectedExceptions = NullPointerException.class) + public void test06() throws SQLException { + stmt.isSimpleIdentifier(null); + + } + + /* * DataProvider used to provide strings that will be used to validate * that enquoteLiteral converts a string to a literal and every instance of * a single quote will be converted into two single quotes in the literal. @@ -114,6 +144,10 @@ @DataProvider(name = "validIdentifierValues") protected Object[][] validEnquotedIdentifierValues() { return new Object[][]{ + {"b", false, "b"}, + {"b", true, "\"b\""}, + {maxIdentifier, false, maxIdentifier}, + {maxIdentifier, true, "\"" + maxIdentifier + "\""}, {"Hello", false, "Hello"}, {"Hello", true, "\"Hello\""}, {"G'Day", false, "\"G'Day\""}, @@ -130,16 +164,34 @@ */ @DataProvider(name = "invalidIdentifierValues") protected Object[][] invalidEnquotedIdentifierValues() { - int invalidLen = 129; - StringBuilder s = new StringBuilder(invalidLen); - for (int i = 0; i < invalidLen; i++) { - s.append('a'); - } return new Object[][]{ {"Hel\"lo", false}, {"\"Hel\"lo\"", true}, {"Hello" + '\0', false}, {"", false}, - {s.toString(), false},}; + {maxIdentifier + 'a', false}, + }; + } + + /* + * DataProvider used to provide strings that will be used to validate + * that isSimpleIdentifier returns the correct value based on the + * identifier specified. + */ + @DataProvider(name = "simpleIdentifierValues") + protected Object[][] simpleIdentifierValues() { + return new Object[][]{ + {"b", true}, + {"Hello", true}, + {"\"Gotham\"", false}, + {"G'Day", false}, + {"Bruce Wayne", false}, + {"GoodDay$", false}, + {"Dick_Grayson", true}, + {"Batmobile1966", true}, + {maxIdentifier, true}, + {maxIdentifier + 'a', false}, + {"", false},}; } + }