From d1dc39564e01a66c3f7f36ffbced0c7b7a1f8429 Mon Sep 17 00:00:00 2001 From: "Kita, Maksim" Date: Mon, 18 Nov 2019 10:20:15 +0300 Subject: [PATCH] Support escaping in SimpleJdbcInsert #13874 --- .../GenericTableMetaDataProvider.java | 19 +++++++++++ .../core/metadata/TableMetaDataContext.java | 25 +++++++++++++- .../core/metadata/TableMetaDataProvider.java | 7 ++++ .../jdbc/core/simple/AbstractJdbcInsert.java | 14 ++++++++ .../jdbc/core/simple/SimpleJdbcInsert.java | 6 ++++ .../simple/SimpleJdbcInsertOperations.java | 7 ++++ .../core/simple/SimpleJdbcInsertTests.java | 33 +++++++++++++++++++ 7 files changed, 110 insertions(+), 1 deletion(-) diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/metadata/GenericTableMetaDataProvider.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/metadata/GenericTableMetaDataProvider.java index 5c691b5c854e..ac87879db938 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/metadata/GenericTableMetaDataProvider.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/metadata/GenericTableMetaDataProvider.java @@ -76,6 +76,8 @@ public class GenericTableMetaDataProvider implements TableMetaDataProvider { /** Collection of TableParameterMetaData objects. */ private List tableParameterMetaData = new ArrayList<>(); + /** the string used to quote SQL identifiers. */ + private String identifierQuoteString = ""; /** * Constructor used to initialize with provided database meta-data. @@ -212,6 +214,15 @@ public void initializeWithMetaData(DatabaseMetaData databaseMetaData) throws SQL logger.warn("Error retrieving 'DatabaseMetaData.storesLowerCaseIdentifiers': " + ex.getMessage()); } } + + try { + this.identifierQuoteString = databaseMetaData.getIdentifierQuoteString(); + } + catch (SQLException ex) { + if (logger.isWarnEnabled()) { + logger.warn("Error retrieving 'DatabaseMetaData.getIdentifierQuoteString': " + ex.getMessage()); + } + } } @Override @@ -304,6 +315,14 @@ protected String getDatabaseVersion() { return this.databaseVersion; } + /** + * Provide access to identifier quote string. + */ + @Override + public String getIdentifierQuoteString() { + return this.identifierQuoteString; + } + /** * Method supporting the meta-data processing for a table. */ diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/metadata/TableMetaDataContext.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/metadata/TableMetaDataContext.java index ca57f5287494..4666704e5a5c 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/metadata/TableMetaDataContext.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/metadata/TableMetaDataContext.java @@ -78,6 +78,9 @@ public class TableMetaDataContext { // Are we using generated key columns private boolean generatedKeyColumnsUsed = false; + // Are we using escaping for SQL identifiers + private boolean usingEscaping = false; + /** * Set the name of the table for this context. @@ -275,13 +278,24 @@ public String createInsertString(String... generatedKeyNames) { for (String key : generatedKeyNames) { keys.add(key.toUpperCase()); } + String identifierQuoteString = ""; + if (this.metaDataProvider != null && this.usingEscaping) { + identifierQuoteString = this.metaDataProvider.getIdentifierQuoteString(); + } StringBuilder insertStatement = new StringBuilder(); insertStatement.append("INSERT INTO "); if (getSchemaName() != null) { + insertStatement.append(identifierQuoteString); insertStatement.append(getSchemaName()); insertStatement.append("."); + insertStatement.append(getTableName()); + insertStatement.append(identifierQuoteString); + } + else { + insertStatement.append(identifierQuoteString); + insertStatement.append(getTableName()); + insertStatement.append(identifierQuoteString); } - insertStatement.append(getTableName()); insertStatement.append(" ("); int columnCount = 0; for (String columnName : getTableColumns()) { @@ -290,7 +304,9 @@ public String createInsertString(String... generatedKeyNames) { if (columnCount > 1) { insertStatement.append(", "); } + insertStatement.append(identifierQuoteString); insertStatement.append(columnName); + insertStatement.append(identifierQuoteString); } } insertStatement.append(") VALUES("); @@ -390,4 +406,11 @@ public boolean isGeneratedKeysColumnNameArraySupported() { return obtainMetaDataProvider().isGeneratedKeysColumnNameArraySupported(); } + public boolean isUsingEscaping() { + return this.usingEscaping; + } + + public void setUsingEscaping(boolean usingEscaping) { + this.usingEscaping = usingEscaping; + } } diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/metadata/TableMetaDataProvider.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/metadata/TableMetaDataProvider.java index f5af3e914e8a..9f6052a7aed3 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/metadata/TableMetaDataProvider.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/metadata/TableMetaDataProvider.java @@ -123,4 +123,11 @@ void initializeWithTableColumnMetaData(DatabaseMetaData databaseMetaData, @Nulla */ List getTableParameterMetaData(); + /** + * Retrieves the string used to quote SQL identifiers. This method returns a space " " if identifier quoting is not supported. + * {@link DatabaseMetaData#getIdentifierQuoteString()} + * @return database identifier quote string. + */ + String getIdentifierQuoteString(); + } diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/simple/AbstractJdbcInsert.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/simple/AbstractJdbcInsert.java index ce40e961697d..e0daa0f04f78 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/simple/AbstractJdbcInsert.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/simple/AbstractJdbcInsert.java @@ -235,6 +235,20 @@ public int[] getInsertTypes() { return this.insertTypes; } + /** + * Set using using escaping. + */ + public void setUsingEscaping(boolean usingEscaping) { + this.tableMetaDataContext.setUsingEscaping(usingEscaping); + } + + /** + * Get using escaping. + */ + public boolean isUsingEscaping() { + return this.tableMetaDataContext.isUsingEscaping(); + } + //------------------------------------------------------------------------- // Methods handling compilation issues diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/simple/SimpleJdbcInsert.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/simple/SimpleJdbcInsert.java index bf5995a7339d..c819b2d0b8d7 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/simple/SimpleJdbcInsert.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/simple/SimpleJdbcInsert.java @@ -112,6 +112,12 @@ public SimpleJdbcInsertOperations includeSynonymsForTableColumnMetaData() { return this; } + @Override + public SimpleJdbcInsert usingEscaping(boolean usingEscaping) { + setUsingEscaping(usingEscaping); + return this; + } + @Override public int execute(Map args) { return doExecute(args); diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/simple/SimpleJdbcInsertOperations.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/simple/SimpleJdbcInsertOperations.java index 8261a29ec4dd..22b0d8c0ceb7 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/simple/SimpleJdbcInsertOperations.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/simple/SimpleJdbcInsertOperations.java @@ -80,6 +80,13 @@ public interface SimpleJdbcInsertOperations { */ SimpleJdbcInsertOperations includeSynonymsForTableColumnMetaData(); + /** + * Specify should sql identifiers be quoted. + * @param usingEscaping should sql identifiers be quoted + * @return the instance of this SimpleJdbcInsert + */ + SimpleJdbcInsertOperations usingEscaping(boolean usingEscaping); + /** * Execute the insert using the values passed in. diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/core/simple/SimpleJdbcInsertTests.java b/spring-jdbc/src/test/java/org/springframework/jdbc/core/simple/SimpleJdbcInsertTests.java index ec399d3c2817..6a586008012e 100644 --- a/spring-jdbc/src/test/java/org/springframework/jdbc/core/simple/SimpleJdbcInsertTests.java +++ b/spring-jdbc/src/test/java/org/springframework/jdbc/core/simple/SimpleJdbcInsertTests.java @@ -29,6 +29,7 @@ import org.springframework.dao.InvalidDataAccessApiUsageException; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; @@ -81,4 +82,36 @@ public void testNoSuchTable() throws Exception { verify(resultSet).close(); } + @Test + public void testSimpleJdbcInsert() { + SimpleJdbcInsert jdbcInsert = new SimpleJdbcInsert(dataSource).withTableName("T").usingColumns("F", "S"); + jdbcInsert.compile(); + String expected = "INSERT INTO T (F, S) VALUES(?, ?)"; + String actual = jdbcInsert.getInsertString(); + assertThat(actual).isEqualTo(expected); + } + + @Test + public void testSimpleJdbcInsertWithEscapingWithSchemaName() throws Exception { + SimpleJdbcInsert jdbcInsert = new SimpleJdbcInsert(dataSource).withSchemaName("S").withTableName("T").usingColumns("F", "S").usingEscaping(true); + + given(databaseMetaData.getIdentifierQuoteString()).willReturn("`"); + + jdbcInsert.compile(); + String expected = "INSERT INTO `S.T` (`F`, `S`) VALUES(?, ?)"; + String actual = jdbcInsert.getInsertString(); + assertThat(actual).isEqualTo(expected); + } + @Test + public void testSimpleJdbcInsertWithEscapingWithoutSchemaName() throws Exception { + SimpleJdbcInsert jdbcInsert = new SimpleJdbcInsert(dataSource).withTableName("T").usingColumns("F", "S").usingEscaping(true); + + given(databaseMetaData.getIdentifierQuoteString()).willReturn("`"); + + jdbcInsert.compile(); + String expected = "INSERT INTO `T` (`F`, `S`) VALUES(?, ?)"; + String actual = jdbcInsert.getInsertString(); + assertThat(actual).isEqualTo(expected); + } + }