Revise support for quoted identifiers in SimpleJdbcInsert

Closes gh-31208
This commit is contained in:
Sam Brannen 2023-10-24 11:50:12 +02:00
parent a803206d5f
commit 71330ddb0f
5 changed files with 56 additions and 27 deletions

View File

@ -312,7 +312,7 @@ public class TableMetaDataContext {
if (schemaName != null) { if (schemaName != null) {
if (quoting) { if (quoting) {
insertStatement.append(identifierQuoteString); insertStatement.append(identifierQuoteString);
insertStatement.append(this.metaDataProvider.schemaNameToUse(schemaName)); insertStatement.append(schemaName);
insertStatement.append(identifierQuoteString); insertStatement.append(identifierQuoteString);
} }
else { else {
@ -324,7 +324,7 @@ public class TableMetaDataContext {
String tableName = getTableName(); String tableName = getTableName();
if (quoting) { if (quoting) {
insertStatement.append(identifierQuoteString); insertStatement.append(identifierQuoteString);
insertStatement.append(this.metaDataProvider.tableNameToUse(tableName)); insertStatement.append(tableName);
insertStatement.append(identifierQuoteString); insertStatement.append(identifierQuoteString);
} }
else { else {
@ -341,7 +341,7 @@ public class TableMetaDataContext {
} }
if (quoting) { if (quoting) {
insertStatement.append(identifierQuoteString); insertStatement.append(identifierQuoteString);
insertStatement.append(this.metaDataProvider.columnNameToUse(columnName)); insertStatement.append(columnName);
insertStatement.append(identifierQuoteString); insertStatement.append(identifierQuoteString);
} }
else { else {

View File

@ -275,6 +275,10 @@ public abstract class AbstractJdbcInsert {
if (getTableName() == null) { if (getTableName() == null) {
throw new InvalidDataAccessApiUsageException("Table name is required"); throw new InvalidDataAccessApiUsageException("Table name is required");
} }
if (isQuoteIdentifiers() && this.declaredColumns.isEmpty()) {
throw new InvalidDataAccessApiUsageException(
"Explicit column names must be provided when using quoted identifiers");
}
try { try {
this.jdbcTemplate.afterPropertiesSet(); this.jdbcTemplate.afterPropertiesSet();
} }

View File

@ -73,9 +73,23 @@ public interface SimpleJdbcInsertOperations {
* <p>If this method is invoked, the identifier quote string for the underlying * <p>If this method is invoked, the identifier quote string for the underlying
* database will be used to quote SQL identifiers in generated SQL statements. * database will be used to quote SQL identifiers in generated SQL statements.
* In this context, SQL identifiers refer to schema, table, and column names. * In this context, SQL identifiers refer to schema, table, and column names.
* <p>When identifiers are quoted, explicit column names must be supplied via
* {@link #usingColumns(String...)}. Furthermore, all identifiers for the
* schema name, table name, and column names must match the corresponding
* identifiers in the database's metadata regarding casing (mixed case,
* uppercase, or lowercase).
* @return this {@code SimpleJdbcInsert} (for method chaining) * @return this {@code SimpleJdbcInsert} (for method chaining)
* @since 6.1 * @since 6.1
* @see #withSchemaName(String)
* @see #withTableName(String)
* @see #usingColumns(String...)
* @see java.sql.DatabaseMetaData#getIdentifierQuoteString() * @see java.sql.DatabaseMetaData#getIdentifierQuoteString()
* @see java.sql.DatabaseMetaData#storesMixedCaseIdentifiers()
* @see java.sql.DatabaseMetaData#storesMixedCaseQuotedIdentifiers()
* @see java.sql.DatabaseMetaData#storesUpperCaseIdentifiers()
* @see java.sql.DatabaseMetaData#storesUpperCaseQuotedIdentifiers()
* @see java.sql.DatabaseMetaData#storesLowerCaseIdentifiers()
* @see java.sql.DatabaseMetaData#storesLowerCaseQuotedIdentifiers()
*/ */
SimpleJdbcInsertOperations usingQuotedIdentifiers(); SimpleJdbcInsertOperations usingQuotedIdentifiers();

View File

@ -56,25 +56,13 @@ class SimpleJdbcInsertIntegrationTests {
insertJaneSmith(insert); insertJaneSmith(insert);
} }
@Test // gh-24013
void retrieveColumnNamesFromMetadataAndUsingQuotedIdentifiers() throws Exception {
SimpleJdbcInsert insert = new SimpleJdbcInsert(embeddedDatabase)
.withTableName("users")
.usingGeneratedKeyColumns("id")
.usingQuotedIdentifiers();
insert.compile();
// NOTE: quoted identifiers in H2/HSQL will be UPPERCASE!
assertThat(insert.getInsertString()).isEqualTo("INSERT INTO \"USERS\" (\"FIRST_NAME\", \"LAST_NAME\") VALUES(?, ?)");
insertJaneSmith(insert);
}
@Test @Test
void usingColumns() { void usingColumns() {
SimpleJdbcInsert insert = new SimpleJdbcInsert(embeddedDatabase) SimpleJdbcInsert insert = new SimpleJdbcInsert(embeddedDatabase)
.withoutTableColumnMetaDataAccess()
.withTableName("users") .withTableName("users")
.usingColumns("first_name", "last_name"); .usingColumns("first_name", "last_name")
.usingGeneratedKeyColumns("id");
insert.compile(); insert.compile();
assertThat(insert.getInsertString()).isEqualTo("INSERT INTO users (first_name, last_name) VALUES(?, ?)"); assertThat(insert.getInsertString()).isEqualTo("INSERT INTO users (first_name, last_name) VALUES(?, ?)");
@ -84,13 +72,16 @@ class SimpleJdbcInsertIntegrationTests {
@Test // gh-24013 @Test // gh-24013
void usingColumnsAndQuotedIdentifiers() throws Exception { void usingColumnsAndQuotedIdentifiers() throws Exception {
// NOTE: unquoted identifiers in H2/HSQL must be converted to UPPERCASE
// since that's how they are stored in the DB metadata.
SimpleJdbcInsert insert = new SimpleJdbcInsert(embeddedDatabase) SimpleJdbcInsert insert = new SimpleJdbcInsert(embeddedDatabase)
.withTableName("users") .withoutTableColumnMetaDataAccess()
.usingColumns("first_name", "last_name") .withTableName("USERS")
.usingColumns("FIRST_NAME", "LAST_NAME")
.usingGeneratedKeyColumns("id")
.usingQuotedIdentifiers(); .usingQuotedIdentifiers();
insert.compile(); insert.compile();
// NOTE: quoted identifiers in H2/HSQL will be UPPERCASE!
assertThat(insert.getInsertString()).isEqualToIgnoringNewLines(""" assertThat(insert.getInsertString()).isEqualToIgnoringNewLines("""
INSERT INTO "USERS" ("FIRST_NAME", "LAST_NAME") VALUES(?, ?) INSERT INTO "USERS" ("FIRST_NAME", "LAST_NAME") VALUES(?, ?)
"""); """);
@ -116,9 +107,11 @@ class SimpleJdbcInsertIntegrationTests {
@Test @Test
void usingColumnsWithSchemaName() { void usingColumnsWithSchemaName() {
SimpleJdbcInsert insert = new SimpleJdbcInsert(embeddedDatabase) SimpleJdbcInsert insert = new SimpleJdbcInsert(embeddedDatabase)
.withoutTableColumnMetaDataAccess()
.withSchemaName("my_schema") .withSchemaName("my_schema")
.withTableName("users") .withTableName("users")
.usingColumns("first_name", "last_name"); .usingColumns("first_name", "last_name")
.usingGeneratedKeyColumns("id");
insert.compile(); insert.compile();
assertThat(insert.getInsertString()).isEqualTo("INSERT INTO my_schema.users (first_name, last_name) VALUES(?, ?)"); assertThat(insert.getInsertString()).isEqualTo("INSERT INTO my_schema.users (first_name, last_name) VALUES(?, ?)");
@ -128,14 +121,17 @@ class SimpleJdbcInsertIntegrationTests {
@Test // gh-24013 @Test // gh-24013
void usingColumnsAndQuotedIdentifiersWithSchemaName() throws Exception { void usingColumnsAndQuotedIdentifiersWithSchemaName() throws Exception {
// NOTE: unquoted identifiers in H2/HSQL must be converted to UPPERCASE
// since that's how they are stored in the DB metadata.
SimpleJdbcInsert insert = new SimpleJdbcInsert(embeddedDatabase) SimpleJdbcInsert insert = new SimpleJdbcInsert(embeddedDatabase)
.withSchemaName("my_schema") .withoutTableColumnMetaDataAccess()
.withTableName("users") .withSchemaName("MY_SCHEMA")
.usingColumns("first_name", "last_name") .withTableName("USERS")
.usingColumns("FIRST_NAME", "LAST_NAME")
.usingGeneratedKeyColumns("id")
.usingQuotedIdentifiers(); .usingQuotedIdentifiers();
insert.compile(); insert.compile();
// NOTE: quoted identifiers in H2/HSQL will be UPPERCASE!
assertThat(insert.getInsertString()).isEqualToIgnoringNewLines(""" assertThat(insert.getInsertString()).isEqualToIgnoringNewLines("""
INSERT INTO "MY_SCHEMA"."USERS" ("FIRST_NAME", "LAST_NAME") VALUES(?, ?) INSERT INTO "MY_SCHEMA"."USERS" ("FIRST_NAME", "LAST_NAME") VALUES(?, ?)
"""); """);
@ -182,7 +178,8 @@ class SimpleJdbcInsertIntegrationTests {
} }
protected void insertJaneSmith(SimpleJdbcInsert insert) { protected void insertJaneSmith(SimpleJdbcInsert insert) {
insert.execute(Map.of("first_name", "Jane", "last_name", "Smith")); Number id = insert.executeAndReturnKey(Map.of("first_name", "Jane", "last_name", "Smith"));
assertThat(id.intValue()).isEqualTo(2);
assertNumUsers(2); assertNumUsers(2);
} }

View File

@ -77,6 +77,20 @@ class SimpleJdbcInsertTests {
connection.close(); connection.close();
} }
@Test // gh-24013 and gh-31208
void usingQuotedIdentifiersWithoutSupplyingColumnNames() throws Exception {
SimpleJdbcInsert insert = new SimpleJdbcInsert(dataSource)
.withTableName("my_table")
.usingQuotedIdentifiers();
assertThatExceptionOfType(InvalidDataAccessApiUsageException.class)
.isThrownBy(insert::compile)
.withMessage("Explicit column names must be provided when using quoted identifiers");
// Appease the @AfterEach checks.
connection.close();
}
/** /**
* This method does not test any functionality but rather only that * This method does not test any functionality but rather only that
* configuration methods can be chained without compiler errors. * configuration methods can be chained without compiler errors.