8143165: Add Statement.isSimpleIdentifier and update enquoteLiteral

Reviewed-by: rriggs, joehw
This commit is contained in:
Lance Andersen 2015-11-25 15:28:30 -05:00
parent 063afcee2f
commit 0c8069758d
2 changed files with 127 additions and 13 deletions

View File

@ -1397,9 +1397,10 @@ public interface Statement extends Wrapper, AutoCloseable {
* @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 @@ public interface Statement extends Wrapper, AutoCloseable {
*
* The default implementation will throw a {@code SQLException} if:
* <ul>
* <li>{@code identifier} contains a null character or double quote, and is not
* <li>{@code identifier} contains a {@code null} character or double quote and is not
* a simple SQL identifier.</li>
* <li>The length of {@code identifier} is less than 1 or greater than 128 characters
* </ul>
@ -1501,14 +1502,14 @@ public interface Statement extends Wrapper, AutoCloseable {
* @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 @@ public interface Statement extends Wrapper, AutoCloseable {
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:
* <ul>
* <li>The string is not enclosed in double quotes</li>
* <li>The first character is an alphabetic character from a through z, or
* from A through Z</li>
* <li>The string only contains alphanumeric characters or the character
* "_"</li>
* <li>The string is between 1 and 128 characters in length inclusive</li>
* </ul>
*
* <blockquote>
* <table border = 1 cellspacing=0 cellpadding=5 >
* <caption>Examples of the conversion:</caption>
* <tr>
* <th>identifier</th>
* <th>Simple Identifier</th>
*
* <tr>
* <td align='center'>Hello</td>
* <td align='center'>true</td>
* </tr>
* <tr>
* <td align='center'>G'Day</td>
* <td align='center'>false</td>
* </tr>
* <tr>
* <td align='center'>"Bruce Wayne"</td>
* <td align='center'>false</td>
* </tr>
* <tr>
* <td align='center'>GoodDay$</td>
* <td align='center'>false</td>
* </tr>
* <tr>
* <td align='center'>Hello"World</td>
* <td align='center'>false</td>
* </tr>
* <tr>
* <td align='center'>"Hello"World"</td>
* <td align='center'>false</td>
* </tr>
* </table>
* </blockquote>
* @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();
}
}

View File

@ -24,6 +24,7 @@ package test.sql;
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,18 +34,29 @@ import util.StubStatement;
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) {
public void test00(String s, String expected) throws SQLException {
assertEquals(stmt.enquoteLiteral(s), expected);
}
@ -53,7 +65,7 @@ public class StatementTests extends BaseTest {
* enquoteLiteral is null
*/
@Test(expectedExceptions = NullPointerException.class)
public void test01() {
public void test01() throws SQLException {
stmt.enquoteLiteral(null);
}
@ -89,6 +101,24 @@ public class StatementTests extends BaseTest {
}
/*
* 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
@ -114,6 +144,10 @@ public class StatementTests extends BaseTest {
@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 @@ public class StatementTests extends BaseTest {
*/
@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},};
}
}