Browse Source

Test quoted identifiers in schema in SimpleJdbcInsert

This commit introduces additional tests for "quoted identifier" support
in SimpleJdbcInsert when the schema itself is defined using quoted
identifiers -- for example, to use keywords as column names.

See gh-31208
pull/31518/head
Sam Brannen 2 years ago
parent
commit
54839a7126
  1. 366
      spring-jdbc/src/test/java/org/springframework/jdbc/core/simple/SimpleJdbcInsertIntegrationTests.java
  2. 1
      spring-jdbc/src/test/resources/org/springframework/jdbc/core/simple/order-data.sql
  3. 11
      spring-jdbc/src/test/resources/org/springframework/jdbc/core/simple/order-schema-with-custom-schema.sql
  4. 7
      spring-jdbc/src/test/resources/org/springframework/jdbc/core/simple/order-schema.sql

366
spring-jdbc/src/test/java/org/springframework/jdbc/core/simple/SimpleJdbcInsertIntegrationTests.java

@ -16,6 +16,7 @@
package org.springframework.jdbc.core.simple; package org.springframework.jdbc.core.simple;
import java.sql.Types;
import java.util.Map; import java.util.Map;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
@ -24,12 +25,16 @@ import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.core.io.ClassRelativeResourceLoader; import org.springframework.core.io.ClassRelativeResourceLoader;
import org.springframework.core.io.ResourceLoader;
import org.springframework.jdbc.BadSqlGrammarException;
import org.springframework.jdbc.core.SqlTypeValue;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
import org.springframework.jdbc.datasource.init.DatabasePopulator; import org.springframework.jdbc.datasource.init.DatabasePopulator;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/** /**
* Integration tests for {@link SimpleJdbcInsert} using an embedded H2 database. * Integration tests for {@link SimpleJdbcInsert} using an embedded H2 database.
@ -41,114 +46,249 @@ import static org.assertj.core.api.Assertions.assertThat;
class SimpleJdbcInsertIntegrationTests { class SimpleJdbcInsertIntegrationTests {
@Nested @Nested
class DefaultSchemaTests extends AbstractSimpleJdbcInsertIntegrationTests { class DefaultSchemaTests {
@Test @Nested
void retrieveColumnNamesFromMetadata() throws Exception { class UnquotedIdentifiersInSchemaTests extends AbstractSimpleJdbcInsertIntegrationTests {
SimpleJdbcInsert insert = new SimpleJdbcInsert(embeddedDatabase)
.withTableName("users") @Test
.usingGeneratedKeyColumns("id"); void retrieveColumnNamesFromMetadata() throws Exception {
SimpleJdbcInsert insert = new SimpleJdbcInsert(embeddedDatabase)
insert.compile(); .withTableName("users")
// NOTE: column names looked up via metadata in H2/HSQL will be UPPERCASE! .usingGeneratedKeyColumns("id");
assertThat(insert.getInsertString()).isEqualTo("INSERT INTO users (FIRST_NAME, LAST_NAME) VALUES(?, ?)");
insert.compile();
insertJaneSmith(insert); assertThat(insert.getInsertTypes()).containsExactly(Types.VARCHAR, Types.VARCHAR);
} // NOTE: column names looked up via metadata in H2/HSQL will be UPPERCASE!
assertThat(insert.getInsertString()).isEqualTo("INSERT INTO users (FIRST_NAME, LAST_NAME) VALUES(?, ?)");
@Test
void usingColumns() { insertJaneSmith(insert);
SimpleJdbcInsert insert = new SimpleJdbcInsert(embeddedDatabase) }
.withoutTableColumnMetaDataAccess()
.withTableName("users") @Test
.usingColumns("first_name", "last_name") void usingColumns() {
.usingGeneratedKeyColumns("id"); SimpleJdbcInsert insert = new SimpleJdbcInsert(embeddedDatabase)
.withoutTableColumnMetaDataAccess()
insert.compile(); .withTableName("users")
assertThat(insert.getInsertString()).isEqualTo("INSERT INTO users (first_name, last_name) VALUES(?, ?)"); .usingColumns("first_name", "last_name")
.usingGeneratedKeyColumns("id");
insertJaneSmith(insert);
insert.compile();
assertThat(insert.getInsertString()).isEqualTo("INSERT INTO users (first_name, last_name) VALUES(?, ?)");
insertJaneSmith(insert);
}
@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)
.withoutTableColumnMetaDataAccess()
.withTableName("USERS")
.usingColumns("FIRST_NAME", "LAST_NAME")
.usingGeneratedKeyColumns("id")
.usingQuotedIdentifiers();
insert.compile();
assertThat(insert.getInsertString()).isEqualToIgnoringNewLines("""
INSERT INTO "USERS" ("FIRST_NAME", "LAST_NAME") VALUES(?, ?)
""");
insertJaneSmith(insert);
}
@Override
protected String getSchemaScript() {
return "users-schema.sql";
}
@Override
protected String getDataScript() {
return "users-data.sql";
}
@Override
protected String getTableName() {
return "users";
}
} }
@Test // gh-24013 @Nested
void usingColumnsAndQuotedIdentifiers() throws Exception { class QuotedIdentifiersInSchemaTests extends AbstractSimpleJdbcInsertIntegrationTests {
// NOTE: unquoted identifiers in H2/HSQL must be converted to UPPERCASE
// since that's how they are stored in the DB metadata. @Test
SimpleJdbcInsert insert = new SimpleJdbcInsert(embeddedDatabase) void retrieveColumnNamesFromMetadata() throws Exception {
.withoutTableColumnMetaDataAccess() SimpleJdbcInsert insert = new SimpleJdbcInsert(embeddedDatabase)
.withTableName("USERS") .withTableName("Order")
.usingColumns("FIRST_NAME", "LAST_NAME") .usingGeneratedKeyColumns("id");
.usingGeneratedKeyColumns("id")
.usingQuotedIdentifiers(); insert.compile();
insert.compile(); // Since we are not quoting identifiers, the column names lookup for the "Order"
assertThat(insert.getInsertString()).isEqualToIgnoringNewLines(""" // table fails to find anything, and insert types are not populated.
INSERT INTO "USERS" ("FIRST_NAME", "LAST_NAME") VALUES(?, ?) assertThat(insert.getInsertTypes()).isEmpty();
"""); // Consequently, any subsequent attempt to execute the INSERT statement should fail.
assertThatExceptionOfType(BadSqlGrammarException.class)
insertJaneSmith(insert); .isThrownBy(() -> insert.executeAndReturnKey(Map.of("from", "start", "date", "1999")));
}
@Test // gh-24013
void usingColumnsAndQuotedIdentifiers() throws Exception {
SimpleJdbcInsert insert = new SimpleJdbcInsert(embeddedDatabase)
.withoutTableColumnMetaDataAccess()
.withTableName("Order")
.usingColumns("from", "Date")
.usingGeneratedKeyColumns("id")
.usingQuotedIdentifiers();
insert.compile();
assertThat(insert.getInsertString()).isEqualToIgnoringNewLines("""
INSERT INTO "Order" ("from", "Date") VALUES(?, ?)
""");
insertOrderEntry(insert);
}
@Override
protected ResourceLoader getResourceLoader() {
return new ClassRelativeResourceLoader(getClass());
}
@Override
protected String getSchemaScript() {
return "order-schema.sql";
}
@Override
protected String getDataScript() {
return "order-data.sql";
}
@Override
protected String getTableName() {
return "\"Order\"";
}
} }
@Override
protected String getSchemaScript() {
return "users-schema.sql";
}
@Override
protected String getUsersTableName() {
return "users";
}
} }
@Nested @Nested
class CustomSchemaTests extends AbstractSimpleJdbcInsertIntegrationTests { class CustomSchemaTests {
@Test @Nested
void usingColumnsWithSchemaName() { class UnquotedIdentifiersInSchemaTests extends AbstractSimpleJdbcInsertIntegrationTests {
SimpleJdbcInsert insert = new SimpleJdbcInsert(embeddedDatabase)
.withoutTableColumnMetaDataAccess() @Test
.withSchemaName("my_schema") void usingColumnsWithSchemaName() {
.withTableName("users") SimpleJdbcInsert insert = new SimpleJdbcInsert(embeddedDatabase)
.usingColumns("first_name", "last_name") .withoutTableColumnMetaDataAccess()
.usingGeneratedKeyColumns("id"); .withSchemaName("my_schema")
.withTableName("users")
insert.compile(); .usingColumns("first_name", "last_name")
assertThat(insert.getInsertString()).isEqualTo("INSERT INTO my_schema.users (first_name, last_name) VALUES(?, ?)"); .usingGeneratedKeyColumns("id");
insertJaneSmith(insert); insert.compile();
} assertThat(insert.getInsertString()).isEqualTo("INSERT INTO my_schema.users (first_name, last_name) VALUES(?, ?)");
@Test // gh-24013 insertJaneSmith(insert);
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. @Test // gh-24013
SimpleJdbcInsert insert = new SimpleJdbcInsert(embeddedDatabase) void usingColumnsAndQuotedIdentifiersWithSchemaName() throws Exception {
.withoutTableColumnMetaDataAccess() // NOTE: unquoted identifiers in H2/HSQL must be converted to UPPERCASE
.withSchemaName("MY_SCHEMA") // since that's how they are stored in the DB metadata.
.withTableName("USERS") SimpleJdbcInsert insert = new SimpleJdbcInsert(embeddedDatabase)
.usingColumns("FIRST_NAME", "LAST_NAME") .withoutTableColumnMetaDataAccess()
.usingGeneratedKeyColumns("id") .withSchemaName("MY_SCHEMA")
.usingQuotedIdentifiers(); .withTableName("USERS")
.usingColumns("FIRST_NAME", "LAST_NAME")
insert.compile(); .usingGeneratedKeyColumns("id")
assertThat(insert.getInsertString()).isEqualToIgnoringNewLines(""" .usingQuotedIdentifiers();
INSERT INTO "MY_SCHEMA"."USERS" ("FIRST_NAME", "LAST_NAME") VALUES(?, ?)
"""); insert.compile();
assertThat(insert.getInsertString()).isEqualToIgnoringNewLines("""
insertJaneSmith(insert); INSERT INTO "MY_SCHEMA"."USERS" ("FIRST_NAME", "LAST_NAME") VALUES(?, ?)
""");
insertJaneSmith(insert);
}
@Override
protected String getSchemaScript() {
return "users-schema-with-custom-schema.sql";
}
@Override
protected String getDataScript() {
return "users-data.sql";
}
@Override
protected String getTableName() {
return "my_schema.users";
}
} }
@Override @Nested
protected String getSchemaScript() { class QuotedIdentifiersInSchemaTests extends AbstractSimpleJdbcInsertIntegrationTests {
return "users-schema-with-custom-schema.sql";
@Test
void usingColumnsWithSchemaName() {
SimpleJdbcInsert insert = new SimpleJdbcInsert(embeddedDatabase)
.withoutTableColumnMetaDataAccess()
.withSchemaName("My_Schema")
.withTableName("Order")
.usingColumns("from", "Date")
.usingGeneratedKeyColumns("id");
insert.compile();
// Since we are not quoting identifiers, the column names lookup for the
// My_Schema.Order table results in unknown insert types.
assertThat(insert.getInsertTypes()).containsExactly(SqlTypeValue.TYPE_UNKNOWN, SqlTypeValue.TYPE_UNKNOWN);
// Consequently, any subsequent attempt to execute the INSERT statement should fail.
assertThatExceptionOfType(BadSqlGrammarException.class)
.isThrownBy(() -> insert.executeAndReturnKey(Map.of("from", "start", "date", "1999")));
}
@Test // gh-24013
void usingColumnsAndQuotedIdentifiersWithSchemaName() throws Exception {
SimpleJdbcInsert insert = new SimpleJdbcInsert(embeddedDatabase)
.withoutTableColumnMetaDataAccess()
.withSchemaName("My_Schema")
.withTableName("Order")
.usingColumns("from", "Date")
.usingGeneratedKeyColumns("id")
.usingQuotedIdentifiers();
insert.compile();
assertThat(insert.getInsertString()).isEqualToIgnoringNewLines("""
INSERT INTO "My_Schema"."Order" ("from", "Date") VALUES(?, ?)
""");
insertOrderEntry(insert);
}
@Override
protected ResourceLoader getResourceLoader() {
return new ClassRelativeResourceLoader(getClass());
}
@Override
protected String getSchemaScript() {
return "order-schema-with-custom-schema.sql";
}
@Override
protected String getDataScript() {
return "order-data.sql";
}
@Override
protected String getTableName() {
return "\"My_Schema\".\"Order\"";
}
} }
@Override
protected String getUsersTableName() {
return "my_schema.users";
}
} }
private abstract static class AbstractSimpleJdbcInsertIntegrationTests { private abstract static class AbstractSimpleJdbcInsertIntegrationTests {
@ -157,13 +297,13 @@ class SimpleJdbcInsertIntegrationTests {
@BeforeEach @BeforeEach
void createDatabase() { void createDatabase() {
this.embeddedDatabase = new EmbeddedDatabaseBuilder(new ClassRelativeResourceLoader(DatabasePopulator.class)) this.embeddedDatabase = new EmbeddedDatabaseBuilder(getResourceLoader())
.setType(EmbeddedDatabaseType.H2) .setType(EmbeddedDatabaseType.H2)
.addScript(getSchemaScript()) .addScript(getSchemaScript())
.addScript("users-data.sql") .addScript(getDataScript())
.build(); .build();
assertNumUsers(1); assertNumRows(1);
} }
@AfterEach @AfterEach
@ -171,21 +311,33 @@ class SimpleJdbcInsertIntegrationTests {
this.embeddedDatabase.shutdown(); this.embeddedDatabase.shutdown();
} }
protected void assertNumUsers(long count) { protected ResourceLoader getResourceLoader() {
return new ClassRelativeResourceLoader(DatabasePopulator.class);
}
protected void assertNumRows(long count) {
JdbcClient jdbcClient = JdbcClient.create(this.embeddedDatabase); JdbcClient jdbcClient = JdbcClient.create(this.embeddedDatabase);
long numUsers = jdbcClient.sql("select count(*) from " + getUsersTableName()).query(Long.class).single(); long numRows = jdbcClient.sql("select count(*) from " + getTableName()).query(Long.class).single();
assertThat(numUsers).isEqualTo(count); assertThat(numRows).isEqualTo(count);
} }
protected void insertJaneSmith(SimpleJdbcInsert insert) { protected void insertJaneSmith(SimpleJdbcInsert insert) {
Number id = insert.executeAndReturnKey(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); assertThat(id.intValue()).isEqualTo(2);
assertNumUsers(2); assertNumRows(2);
}
protected void insertOrderEntry(SimpleJdbcInsert insert) {
Number id = insert.executeAndReturnKey(Map.of("from", "start", "date", "1999"));
assertThat(id.intValue()).isEqualTo(2);
assertNumRows(2);
} }
protected abstract String getSchemaScript(); protected abstract String getSchemaScript();
protected abstract String getUsersTableName(); protected abstract String getDataScript();
protected abstract String getTableName();
} }

1
spring-jdbc/src/test/resources/org/springframework/jdbc/core/simple/order-data.sql

@ -0,0 +1 @@
INSERT INTO "Order" ("from", "Date") values('start', '1999');

11
spring-jdbc/src/test/resources/org/springframework/jdbc/core/simple/order-schema-with-custom-schema.sql

@ -0,0 +1,11 @@
CREATE SCHEMA IF NOT EXISTS "My_Schema";
SET SCHEMA "My_Schema";
DROP TABLE "Order" IF EXISTS;
CREATE TABLE "Order" (
id INTEGER GENERATED BY DEFAULT AS IDENTITY,
"from" VARCHAR(50) NOT NULL,
"Date" VARCHAR(50) NOT NULL
);

7
spring-jdbc/src/test/resources/org/springframework/jdbc/core/simple/order-schema.sql

@ -0,0 +1,7 @@
DROP TABLE "Order" IF EXISTS;
CREATE TABLE "Order" (
id INTEGER GENERATED BY DEFAULT AS IDENTITY,
"from" VARCHAR(50) NOT NULL,
"Date" VARCHAR(50) NOT NULL
);
Loading…
Cancel
Save