@ -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 ( ) ;
assertNumUser s ( 1 ) ;
assertNumRow s ( 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 numRow s = jdbcClient . sql ( "select count(*) from " + getTableName ( ) ) . query ( Long . class ) . single ( ) ;
assertThat ( numRow s ) . 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 ( ) ;
}