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/30079/head
Sam Brannen 1 year 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 @@ @@ -16,6 +16,7 @@
package org.springframework.jdbc.core.simple;
import java.sql.Types;
import java.util.Map;
import org.junit.jupiter.api.AfterEach;
@ -24,12 +25,16 @@ import org.junit.jupiter.api.Nested; @@ -24,12 +25,16 @@ import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
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.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
import org.springframework.jdbc.datasource.init.DatabasePopulator;
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.
@ -41,114 +46,249 @@ import static org.assertj.core.api.Assertions.assertThat; @@ -41,114 +46,249 @@ import static org.assertj.core.api.Assertions.assertThat;
class SimpleJdbcInsertIntegrationTests {
@Nested
class DefaultSchemaTests extends AbstractSimpleJdbcInsertIntegrationTests {
@Test
void retrieveColumnNamesFromMetadata() throws Exception {
SimpleJdbcInsert insert = new SimpleJdbcInsert(embeddedDatabase)
.withTableName("users")
.usingGeneratedKeyColumns("id");
insert.compile();
// 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(?, ?)");
insertJaneSmith(insert);
}
@Test
void usingColumns() {
SimpleJdbcInsert insert = new SimpleJdbcInsert(embeddedDatabase)
.withoutTableColumnMetaDataAccess()
.withTableName("users")
.usingColumns("first_name", "last_name")
.usingGeneratedKeyColumns("id");
insert.compile();
assertThat(insert.getInsertString()).isEqualTo("INSERT INTO users (first_name, last_name) VALUES(?, ?)");
insertJaneSmith(insert);
class DefaultSchemaTests {
@Nested
class UnquotedIdentifiersInSchemaTests extends AbstractSimpleJdbcInsertIntegrationTests {
@Test
void retrieveColumnNamesFromMetadata() throws Exception {
SimpleJdbcInsert insert = new SimpleJdbcInsert(embeddedDatabase)
.withTableName("users")
.usingGeneratedKeyColumns("id");
insert.compile();
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(?, ?)");
insertJaneSmith(insert);
}
@Test
void usingColumns() {
SimpleJdbcInsert insert = new SimpleJdbcInsert(embeddedDatabase)
.withoutTableColumnMetaDataAccess()
.withTableName("users")
.usingColumns("first_name", "last_name")
.usingGeneratedKeyColumns("id");
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
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);
@Nested
class QuotedIdentifiersInSchemaTests extends AbstractSimpleJdbcInsertIntegrationTests {
@Test
void retrieveColumnNamesFromMetadata() throws Exception {
SimpleJdbcInsert insert = new SimpleJdbcInsert(embeddedDatabase)
.withTableName("Order")
.usingGeneratedKeyColumns("id");
insert.compile();
// Since we are not quoting identifiers, the column names lookup for the "Order"
// table fails to find anything, and insert types are not populated.
assertThat(insert.getInsertTypes()).isEmpty();
// 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 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
class CustomSchemaTests extends AbstractSimpleJdbcInsertIntegrationTests {
@Test
void usingColumnsWithSchemaName() {
SimpleJdbcInsert insert = new SimpleJdbcInsert(embeddedDatabase)
.withoutTableColumnMetaDataAccess()
.withSchemaName("my_schema")
.withTableName("users")
.usingColumns("first_name", "last_name")
.usingGeneratedKeyColumns("id");
insert.compile();
assertThat(insert.getInsertString()).isEqualTo("INSERT INTO my_schema.users (first_name, last_name) VALUES(?, ?)");
insertJaneSmith(insert);
}
@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)
.withoutTableColumnMetaDataAccess()
.withSchemaName("MY_SCHEMA")
.withTableName("USERS")
.usingColumns("FIRST_NAME", "LAST_NAME")
.usingGeneratedKeyColumns("id")
.usingQuotedIdentifiers();
insert.compile();
assertThat(insert.getInsertString()).isEqualToIgnoringNewLines("""
INSERT INTO "MY_SCHEMA"."USERS" ("FIRST_NAME", "LAST_NAME") VALUES(?, ?)
""");
insertJaneSmith(insert);
class CustomSchemaTests {
@Nested
class UnquotedIdentifiersInSchemaTests extends AbstractSimpleJdbcInsertIntegrationTests {
@Test
void usingColumnsWithSchemaName() {
SimpleJdbcInsert insert = new SimpleJdbcInsert(embeddedDatabase)
.withoutTableColumnMetaDataAccess()
.withSchemaName("my_schema")
.withTableName("users")
.usingColumns("first_name", "last_name")
.usingGeneratedKeyColumns("id");
insert.compile();
assertThat(insert.getInsertString()).isEqualTo("INSERT INTO my_schema.users (first_name, last_name) VALUES(?, ?)");
insertJaneSmith(insert);
}
@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)
.withoutTableColumnMetaDataAccess()
.withSchemaName("MY_SCHEMA")
.withTableName("USERS")
.usingColumns("FIRST_NAME", "LAST_NAME")
.usingGeneratedKeyColumns("id")
.usingQuotedIdentifiers();
insert.compile();
assertThat(insert.getInsertString()).isEqualToIgnoringNewLines("""
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
protected String getSchemaScript() {
return "users-schema-with-custom-schema.sql";
@Nested
class QuotedIdentifiersInSchemaTests extends AbstractSimpleJdbcInsertIntegrationTests {
@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 {
@ -157,13 +297,13 @@ class SimpleJdbcInsertIntegrationTests { @@ -157,13 +297,13 @@ class SimpleJdbcInsertIntegrationTests {
@BeforeEach
void createDatabase() {
this.embeddedDatabase = new EmbeddedDatabaseBuilder(new ClassRelativeResourceLoader(DatabasePopulator.class))
this.embeddedDatabase = new EmbeddedDatabaseBuilder(getResourceLoader())
.setType(EmbeddedDatabaseType.H2)
.addScript(getSchemaScript())
.addScript("users-data.sql")
.addScript(getDataScript())
.build();
assertNumUsers(1);
assertNumRows(1);
}
@AfterEach
@ -171,21 +311,33 @@ class SimpleJdbcInsertIntegrationTests { @@ -171,21 +311,33 @@ class SimpleJdbcInsertIntegrationTests {
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);
long numUsers = jdbcClient.sql("select count(*) from " + getUsersTableName()).query(Long.class).single();
assertThat(numUsers).isEqualTo(count);
long numRows = jdbcClient.sql("select count(*) from " + getTableName()).query(Long.class).single();
assertThat(numRows).isEqualTo(count);
}
protected void insertJaneSmith(SimpleJdbcInsert insert) {
Number id = insert.executeAndReturnKey(Map.of("first_name", "Jane", "last_name", "Smith"));
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 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 @@ @@ -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 @@ @@ -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 @@ @@ -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