Skip to content

Commit 7dc0653

Browse files
committed
Revise quoted identifier support in SimpleJdbcInsert
Prior to this commit and the previous commit, SimpleJdbcInsert did not provide built-in support for "quoted identifiers". Consequently, if any column names conflicted with keywords or functions from the underlying database, you had to manually quote the column names when specifying them via `usingColumns(...)`, and there was unfortunately no way to quote schema and table names. The previous commit provided rudimentary support for quoted SQL identifiers (schema, table, and column names) by querying java.sql.DatabaseMetaData.getIdentifierQuoteString() to determine the quote string. It also introduced `usingEscaping(boolean)` in `SimpleJdbcInsertOperations` to enable the feature. However, it incorrectly quoted the schema and table names together, and it did not take into account the fact that a quoted identifier should respect the casing (uppercase vs. lowercase) of the underlying database's metadata. This commit revises quoted identifier support in `SimpleJdbcInsert` by: - renaming `usingEscaping(boolean)` to `usingQuotedIdentifiers()` - quoting schema and table names separately - respecting the casing (uppercase vs. lowercase) of the underlying database's metadata when quoting identifiers - introducing integration tests against an in-memory H2 database See gh-13874 Closes gh-24013
1 parent d390347 commit 7dc0653

File tree

9 files changed

+326
-81
lines changed

9 files changed

+326
-81
lines changed

spring-jdbc/src/main/java/org/springframework/jdbc/core/metadata/GenericTableMetaDataProvider.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2021 the original author or authors.
2+
* Copyright 2002-2023 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -77,8 +77,8 @@ public class GenericTableMetaDataProvider implements TableMetaDataProvider {
7777
/** Collection of TableParameterMetaData objects. */
7878
private final List<TableParameterMetaData> tableParameterMetaData = new ArrayList<>();
7979

80-
/** the string used to quote SQL identifiers. */
81-
private String identifierQuoteString = "";
80+
/** The string used to quote SQL identifiers. */
81+
private String identifierQuoteString = " ";
8282

8383
/**
8484
* Constructor used to initialize with provided database meta-data.
@@ -305,7 +305,9 @@ protected String getDatabaseVersion() {
305305
}
306306

307307
/**
308-
* Provide access to identifier quote string.
308+
* Provide access to the identifier quote string.
309+
* @since 6.1
310+
* @see java.sql.DatabaseMetaData#getIdentifierQuoteString()
309311
*/
310312
@Override
311313
public String getIdentifierQuoteString() {

spring-jdbc/src/main/java/org/springframework/jdbc/core/metadata/TableMetaDataContext.java

Lines changed: 64 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2021 the original author or authors.
2+
* Copyright 2002-2023 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -36,6 +36,7 @@
3636
import org.springframework.lang.Nullable;
3737
import org.springframework.util.Assert;
3838
import org.springframework.util.CollectionUtils;
39+
import org.springframework.util.StringUtils;
3940

4041
/**
4142
* Class to manage context meta-data used for the configuration
@@ -72,16 +73,16 @@ public class TableMetaDataContext {
7273
// Should we override default for including synonyms for meta-data lookups
7374
private boolean overrideIncludeSynonymsDefault = false;
7475

76+
// Are we using generated key columns?
77+
private boolean generatedKeyColumnsUsed = false;
78+
79+
// Are we quoting identifiers?
80+
private boolean quoteIdentifiers = false;
81+
7582
// The provider of table meta-data
7683
@Nullable
7784
private TableMetaDataProvider metaDataProvider;
7885

79-
// Are we using generated key columns
80-
private boolean generatedKeyColumnsUsed = false;
81-
82-
// Are we using escaping for SQL identifiers
83-
private boolean usingEscaping = false;
84-
8586

8687
/**
8788
* Set the name of the table for this context.
@@ -142,7 +143,6 @@ public boolean isAccessTableColumnMetaData() {
142143
return this.accessTableColumnMetaData;
143144
}
144145

145-
146146
/**
147147
* Specify whether we should override default for accessing synonyms.
148148
*/
@@ -157,6 +157,28 @@ public boolean isOverrideIncludeSynonymsDefault() {
157157
return this.overrideIncludeSynonymsDefault;
158158
}
159159

160+
/**
161+
* Specify whether we are quoting SQL identifiers.
162+
* <p>Defaults to {@code false}. If set to {@code true}, the identifier
163+
* quote string for the underlying database will be used to quote SQL
164+
* identifiers in generated SQL statements.
165+
* @param quoteIdentifiers whether identifiers should be quoted
166+
* @since 6.1
167+
* @see java.sql.DatabaseMetaData#getIdentifierQuoteString()
168+
*/
169+
public void setQuoteIdentifiers(boolean quoteIdentifiers) {
170+
this.quoteIdentifiers = quoteIdentifiers;
171+
}
172+
173+
/**
174+
* Are we quoting identifiers?
175+
* @since 6.1
176+
* @see #setQuoteIdentifiers(boolean)
177+
*/
178+
public boolean isQuoteIdentifiers() {
179+
return this.quoteIdentifiers;
180+
}
181+
160182
/**
161183
* Get a List of the table column names.
162184
*/
@@ -269,7 +291,6 @@ public List<Object> matchInParameterValuesWithInsertColumns(Map<String, ?> inPar
269291
return values;
270292
}
271293

272-
273294
/**
274295
* Build the insert string based on configuration and meta-data information.
275296
* @return the insert string to be used
@@ -279,24 +300,37 @@ public String createInsertString(String... generatedKeyNames) {
279300
for (String key : generatedKeyNames) {
280301
keys.add(key.toUpperCase());
281302
}
282-
String identifierQuoteString = "";
283-
if (this.metaDataProvider != null && this.usingEscaping) {
284-
identifierQuoteString = this.metaDataProvider.getIdentifierQuoteString();
285-
}
303+
304+
String identifierQuoteString = (isQuoteIdentifiers() ?
305+
obtainMetaDataProvider().getIdentifierQuoteString() : null);
306+
boolean quoting = StringUtils.hasText(identifierQuoteString);
307+
286308
StringBuilder insertStatement = new StringBuilder();
287309
insertStatement.append("INSERT INTO ");
288-
if (getSchemaName() != null) {
289-
insertStatement.append(identifierQuoteString);
290-
insertStatement.append(getSchemaName());
310+
311+
String schemaName = getSchemaName();
312+
if (schemaName != null) {
313+
if (quoting) {
314+
insertStatement.append(identifierQuoteString);
315+
insertStatement.append(this.metaDataProvider.schemaNameToUse(schemaName));
316+
insertStatement.append(identifierQuoteString);
317+
}
318+
else {
319+
insertStatement.append(schemaName);
320+
}
291321
insertStatement.append('.');
292-
insertStatement.append(getTableName());
293-
insertStatement.append(identifierQuoteString);
294322
}
295-
else {
323+
324+
String tableName = getTableName();
325+
if (quoting) {
296326
insertStatement.append(identifierQuoteString);
297-
insertStatement.append(getTableName());
327+
insertStatement.append(this.metaDataProvider.tableNameToUse(tableName));
298328
insertStatement.append(identifierQuoteString);
299329
}
330+
else {
331+
insertStatement.append(tableName);
332+
}
333+
300334
insertStatement.append(" (");
301335
int columnCount = 0;
302336
for (String columnName : getTableColumns()) {
@@ -305,21 +339,26 @@ public String createInsertString(String... generatedKeyNames) {
305339
if (columnCount > 1) {
306340
insertStatement.append(", ");
307341
}
308-
insertStatement.append(identifierQuoteString);
309-
insertStatement.append(columnName);
310-
insertStatement.append(identifierQuoteString);
342+
if (quoting) {
343+
insertStatement.append(identifierQuoteString);
344+
insertStatement.append(this.metaDataProvider.columnNameToUse(columnName));
345+
insertStatement.append(identifierQuoteString);
346+
}
347+
else {
348+
insertStatement.append(columnName);
349+
}
311350
}
312351
}
313352
insertStatement.append(") VALUES(");
314353
if (columnCount < 1) {
315354
if (this.generatedKeyColumnsUsed) {
316355
if (logger.isDebugEnabled()) {
317356
logger.debug("Unable to locate non-key columns for table '" +
318-
getTableName() + "' so an empty insert statement is generated");
357+
tableName + "' so an empty insert statement is generated");
319358
}
320359
}
321360
else {
322-
String message = "Unable to locate columns for table '" + getTableName()
361+
String message = "Unable to locate columns for table '" + tableName
323362
+ "' so an insert statement can't be generated.";
324363
if (isAccessTableColumnMetaData()) {
325364
message += " Consider specifying explicit column names -- for example, via SimpleJdbcInsert#usingColumns().";
@@ -397,11 +436,4 @@ public boolean isGeneratedKeysColumnNameArraySupported() {
397436
return obtainMetaDataProvider().isGeneratedKeysColumnNameArraySupported();
398437
}
399438

400-
public boolean isUsingEscaping() {
401-
return this.usingEscaping;
402-
}
403-
404-
public void setUsingEscaping(boolean usingEscaping) {
405-
this.usingEscaping = usingEscaping;
406-
}
407439
}

spring-jdbc/src/main/java/org/springframework/jdbc/core/metadata/TableMetaDataProvider.java

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2018 the original author or authors.
2+
* Copyright 2002-2023 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -140,9 +140,12 @@ void initializeWithTableColumnMetaData(DatabaseMetaData databaseMetaData, @Nulla
140140
List<TableParameterMetaData> getTableParameterMetaData();
141141

142142
/**
143-
* Retrieves the string used to quote SQL identifiers. This method returns a space " " if identifier quoting is not supported.
144-
* {@link DatabaseMetaData#getIdentifierQuoteString()}
145-
* @return database identifier quote string.
143+
* Get the string used to quote SQL identifiers.
144+
* <p>This method returns a space ({@code " "}) if identifier quoting is not
145+
* supported.
146+
* @return database identifier quote string
147+
* @since 6.1
148+
* @see DatabaseMetaData#getIdentifierQuoteString()
146149
*/
147150
String getIdentifierQuoteString();
148151

spring-jdbc/src/main/java/org/springframework/jdbc/core/simple/AbstractJdbcInsert.java

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -237,17 +237,25 @@ public int[] getInsertTypes() {
237237
}
238238

239239
/**
240-
* Set using using escaping.
240+
* Specify whether SQL identifiers should be quoted.
241+
* <p>Defaults to {@code false}. If set to {@code true}, the identifier
242+
* quote string for the underlying database will be used to quote SQL
243+
* identifiers in generated SQL statements.
244+
* @param quoteIdentifiers whether identifiers should be quoted
245+
* @since 6.1
246+
* @see java.sql.DatabaseMetaData#getIdentifierQuoteString()
241247
*/
242-
public void setUsingEscaping(boolean usingEscaping) {
243-
this.tableMetaDataContext.setUsingEscaping(usingEscaping);
248+
public void setQuoteIdentifiers(boolean quoteIdentifiers) {
249+
this.tableMetaDataContext.setQuoteIdentifiers(quoteIdentifiers);
244250
}
245251

246252
/**
247-
* Get using escaping.
253+
* Get the {@code quoteIdentifiers} flag.
254+
* @since 6.1
255+
* @see #setQuoteIdentifiers(boolean)
248256
*/
249-
public boolean isUsingEscaping() {
250-
return this.tableMetaDataContext.isUsingEscaping();
257+
public boolean isQuoteIdentifiers() {
258+
return this.tableMetaDataContext.isQuoteIdentifiers();
251259
}
252260

253261

spring-jdbc/src/main/java/org/springframework/jdbc/core/simple/SimpleJdbcInsert.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -103,20 +103,20 @@ public SimpleJdbcInsert usingGeneratedKeyColumns(String... columnNames) {
103103
}
104104

105105
@Override
106-
public SimpleJdbcInsertOperations withoutTableColumnMetaDataAccess() {
107-
setAccessTableColumnMetaData(false);
106+
public SimpleJdbcInsert usingQuotedIdentifiers() {
107+
setQuoteIdentifiers(true);
108108
return this;
109109
}
110110

111111
@Override
112-
public SimpleJdbcInsertOperations includeSynonymsForTableColumnMetaData() {
113-
setOverrideIncludeSynonymsDefault(true);
112+
public SimpleJdbcInsertOperations withoutTableColumnMetaDataAccess() {
113+
setAccessTableColumnMetaData(false);
114114
return this;
115115
}
116116

117117
@Override
118-
public SimpleJdbcInsert usingEscaping(boolean usingEscaping) {
119-
setUsingEscaping(usingEscaping);
118+
public SimpleJdbcInsertOperations includeSynonymsForTableColumnMetaData() {
119+
setOverrideIncludeSynonymsDefault(true);
120120
return this;
121121
}
122122

spring-jdbc/src/main/java/org/springframework/jdbc/core/simple/SimpleJdbcInsertOperations.java

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2018 the original author or authors.
2+
* Copyright 2002-2023 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -68,6 +68,17 @@ public interface SimpleJdbcInsertOperations {
6868
*/
6969
SimpleJdbcInsertOperations usingGeneratedKeyColumns(String... columnNames);
7070

71+
/**
72+
* Specify that SQL identifiers should be quoted.
73+
* <p>If this method is invoked, the identifier quote string for the underlying
74+
* database will be used to quote SQL identifiers in generated SQL statements.
75+
* In this context, SQL identifiers refer to schema, table, and column names.
76+
* @return this {@code SimpleJdbcInsert} (for method chaining)
77+
* @since 6.1
78+
* @see java.sql.DatabaseMetaData#getIdentifierQuoteString()
79+
*/
80+
SimpleJdbcInsertOperations usingQuotedIdentifiers();
81+
7182
/**
7283
* Turn off any processing of column meta-data information obtained via JDBC.
7384
* @return this {@code SimpleJdbcInsert} (for method chaining)
@@ -82,14 +93,6 @@ public interface SimpleJdbcInsertOperations {
8293
*/
8394
SimpleJdbcInsertOperations includeSynonymsForTableColumnMetaData();
8495

85-
/**
86-
* Specify should sql identifiers be quoted.
87-
* @param usingEscaping should sql identifiers be quoted
88-
* @return the instance of this SimpleJdbcInsert
89-
*/
90-
SimpleJdbcInsertOperations usingEscaping(boolean usingEscaping);
91-
92-
9396
/**
9497
* Execute the insert using the values passed in.
9598
* @param args a Map containing column names and corresponding value

0 commit comments

Comments
 (0)