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 (quoting) {
insertStatement.append(identifierQuoteString);
insertStatement.append(this.metaDataProvider.schemaNameToUse(schemaName));
insertStatement.append(schemaName);
insertStatement.append(identifierQuoteString);
}
else {
@ -324,7 +324,7 @@ public class TableMetaDataContext {
String tableName = getTableName();
if (quoting) {
insertStatement.append(identifierQuoteString);
insertStatement.append(this.metaDataProvider.tableNameToUse(tableName));
insertStatement.append(tableName);
insertStatement.append(identifierQuoteString);
}
else {
@ -341,7 +341,7 @@ public class TableMetaDataContext {
}
if (quoting) {
insertStatement.append(identifierQuoteString);
insertStatement.append(this.metaDataProvider.columnNameToUse(columnName));
insertStatement.append(columnName);
insertStatement.append(identifierQuoteString);
}
else {

View File

@ -275,6 +275,10 @@ public abstract class AbstractJdbcInsert {
if (getTableName() == null) {
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 {
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
* database will be used to quote SQL identifiers in generated SQL statements.
* 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)
* @since 6.1
* @see #withSchemaName(String)
* @see #withTableName(String)
* @see #usingColumns(String...)
* @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();

View File

@ -56,25 +56,13 @@ class SimpleJdbcInsertIntegrationTests {
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
void usingColumns() {
SimpleJdbcInsert insert = new SimpleJdbcInsert(embeddedDatabase)
.withoutTableColumnMetaDataAccess()
.withTableName("users")
.usingColumns("first_name", "last_name");
.usingColumns("first_name", "last_name")
.usingGeneratedKeyColumns("id");
insert.compile();
assertThat(insert.getInsertString()).isEqualTo("INSERT INTO users (first_name, last_name) VALUES(?, ?)");
@ -84,13 +72,16 @@ class SimpleJdbcInsertIntegrationTests {
@Test // gh-24013
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)
.withTableName("users")
.usingColumns("first_name", "last_name")
.withoutTableColumnMetaDataAccess()
.withTableName("USERS")
.usingColumns("FIRST_NAME", "LAST_NAME")
.usingGeneratedKeyColumns("id")
.usingQuotedIdentifiers();
insert.compile();
// NOTE: quoted identifiers in H2/HSQL will be UPPERCASE!
assertThat(insert.getInsertString()).isEqualToIgnoringNewLines("""
INSERT INTO "USERS" ("FIRST_NAME", "LAST_NAME") VALUES(?, ?)
""");
@ -116,9 +107,11 @@ class SimpleJdbcInsertIntegrationTests {
@Test
void usingColumnsWithSchemaName() {
SimpleJdbcInsert insert = new SimpleJdbcInsert(embeddedDatabase)
.withoutTableColumnMetaDataAccess()
.withSchemaName("my_schema")
.withTableName("users")
.usingColumns("first_name", "last_name");
.usingColumns("first_name", "last_name")
.usingGeneratedKeyColumns("id");
insert.compile();
assertThat(insert.getInsertString()).isEqualTo("INSERT INTO my_schema.users (first_name, last_name) VALUES(?, ?)");
@ -128,14 +121,17 @@ class SimpleJdbcInsertIntegrationTests {
@Test // gh-24013
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)
.withSchemaName("my_schema")
.withTableName("users")
.usingColumns("first_name", "last_name")
.withoutTableColumnMetaDataAccess()
.withSchemaName("MY_SCHEMA")
.withTableName("USERS")
.usingColumns("FIRST_NAME", "LAST_NAME")
.usingGeneratedKeyColumns("id")
.usingQuotedIdentifiers();
insert.compile();
// NOTE: quoted identifiers in H2/HSQL will be UPPERCASE!
assertThat(insert.getInsertString()).isEqualToIgnoringNewLines("""
INSERT INTO "MY_SCHEMA"."USERS" ("FIRST_NAME", "LAST_NAME") VALUES(?, ?)
""");
@ -182,7 +178,8 @@ class SimpleJdbcInsertIntegrationTests {
}
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);
}

View File

@ -77,6 +77,20 @@ class SimpleJdbcInsertTests {
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
* configuration methods can be chained without compiler errors.