From 8f1b24a462327d1edebe6c38f78c8ebce7602f15 Mon Sep 17 00:00:00 2001 From: Henning Poettker Date: Sat, 23 Nov 2024 23:42:46 +0100 Subject: [PATCH] Update implementations of `PagingQueryProvider` Replaces the implementation of `DerbyPagingQueryProvider` with that corresponding to DB2 and adds an integration test that failed with the previous implementation. Deprecates `SqlWindowingPagingQueryProvider` for removal, which was effectively only used by `DerbyPagingQueryProvider`. Resolves #4733 --- .../support/Db2PagingQueryProvider.java | 9 +- .../support/DerbyPagingQueryProvider.java | 75 ++++---------- .../support/SqlServerPagingQueryProvider.java | 7 +- .../SqlWindowingPagingQueryProvider.java | 4 +- .../support/SybasePagingQueryProvider.java | 7 +- ...byPagingQueryProviderIntegrationTests.java | 98 +++++++++++++++++++ .../DerbyPagingQueryProviderTests.java | 83 ++-------------- .../SqlWindowingPagingQueryProviderTests.java | 3 +- .../support/query-provider-fixture.sql | 20 ++++ 9 files changed, 153 insertions(+), 153 deletions(-) create mode 100644 spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/DerbyPagingQueryProviderIntegrationTests.java create mode 100644 spring-batch-infrastructure/src/test/resources/org/springframework/batch/item/database/support/query-provider-fixture.sql diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/Db2PagingQueryProvider.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/Db2PagingQueryProvider.java index f29f86819..660eb430b 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/Db2PagingQueryProvider.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/Db2PagingQueryProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2021 the original author or authors. + * Copyright 2006-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,7 +27,7 @@ import org.springframework.util.StringUtils; * @author Mahmoud Ben Hassine * @since 2.0 */ -public class Db2PagingQueryProvider extends SqlWindowingPagingQueryProvider { +public class Db2PagingQueryProvider extends AbstractSqlPagingQueryProvider { @Override public String generateFirstPageQuery(int pageSize) { @@ -44,11 +44,6 @@ public class Db2PagingQueryProvider extends SqlWindowingPagingQueryProvider { } } - @Override - protected Object getSubQueryAlias() { - return "AS TMP_SUB "; - } - private String buildLimitClause(int pageSize) { return new StringBuilder().append("FETCH FIRST ").append(pageSize).append(" ROWS ONLY").toString(); } diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/DerbyPagingQueryProvider.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/DerbyPagingQueryProvider.java index b2f4ab422..015454f90 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/DerbyPagingQueryProvider.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/DerbyPagingQueryProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2023 the original author or authors. + * Copyright 2006-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,76 +16,37 @@ package org.springframework.batch.item.database.support; -import java.sql.DatabaseMetaData; -import javax.sql.DataSource; - import org.springframework.batch.item.database.PagingQueryProvider; -import org.springframework.dao.InvalidDataAccessResourceUsageException; -import org.springframework.jdbc.support.JdbcUtils; +import org.springframework.util.StringUtils; /** - * Derby implementation of a {@link PagingQueryProvider} using standard SQL:2003 windowing - * functions. These features are supported starting with Apache Derby version 10.4.1.3. - *

- * As the OVER() function does not support the ORDER BY clause a sub query is instead used - * to order the results before the ROW_NUM restriction is applied + * Derby implementation of a {@link PagingQueryProvider} using database specific features. * * @author Thomas Risberg * @author David Thexton * @author Michael Minella + * @author Henning Pöttker * @since 2.0 */ -public class DerbyPagingQueryProvider extends SqlWindowingPagingQueryProvider { - - private static final String MINIMAL_DERBY_VERSION = "10.4.1.3"; +public class DerbyPagingQueryProvider extends AbstractSqlPagingQueryProvider { @Override - public void init(DataSource dataSource) throws Exception { - super.init(dataSource); - String version = JdbcUtils.extractDatabaseMetaData(dataSource, DatabaseMetaData::getDatabaseProductVersion); - if (!isDerbyVersionSupported(version)) { - throw new InvalidDataAccessResourceUsageException( - "Apache Derby version " + version + " is not supported by this class, Only version " - + MINIMAL_DERBY_VERSION + " or later is supported"); + public String generateFirstPageQuery(int pageSize) { + return SqlPagingQueryUtils.generateLimitSqlQuery(this, false, buildLimitClause(pageSize)); + } + + @Override + public String generateRemainingPagesQuery(int pageSize) { + if (StringUtils.hasText(getGroupClause())) { + return SqlPagingQueryUtils.generateLimitGroupedSqlQuery(this, buildLimitClause(pageSize)); + } + else { + return SqlPagingQueryUtils.generateLimitSqlQuery(this, true, buildLimitClause(pageSize)); } } - // derby version numbering is M.m.f.p [ {alpha|beta} ] see - // https://db.apache.org/derby/papers/versionupgrade.html#Basic+Numbering+Scheme - private boolean isDerbyVersionSupported(String version) { - String[] minimalVersionParts = MINIMAL_DERBY_VERSION.split("\\."); - String[] versionParts = version.split("[\\. ]"); - for (int i = 0; i < minimalVersionParts.length; i++) { - int minimalVersionPart = Integer.parseInt(minimalVersionParts[i]); - int versionPart = Integer.parseInt(versionParts[i]); - if (versionPart < minimalVersionPart) { - return false; - } - else if (versionPart > minimalVersionPart) { - return true; - } - } - return true; - } - - @Override - protected String getOrderedQueryAlias() { - return "TMP_ORDERED"; - } - - @Override - protected String getOverClause() { - return ""; - } - - @Override - protected String getOverSubstituteClauseStart() { - return " FROM (SELECT " + getSelectClause(); - } - - @Override - protected String getOverSubstituteClauseEnd() { - return " ) AS " + getOrderedQueryAlias(); + private String buildLimitClause(int pageSize) { + return new StringBuilder("FETCH FIRST ").append(pageSize).append(" ROWS ONLY").toString(); } } diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/SqlServerPagingQueryProvider.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/SqlServerPagingQueryProvider.java index 51b879ed2..b1c79763b 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/SqlServerPagingQueryProvider.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/SqlServerPagingQueryProvider.java @@ -28,7 +28,7 @@ import org.springframework.util.StringUtils; * @author Mahmoud Ben Hassine * @since 2.0 */ -public class SqlServerPagingQueryProvider extends SqlWindowingPagingQueryProvider { +public class SqlServerPagingQueryProvider extends AbstractSqlPagingQueryProvider { @Override public String generateFirstPageQuery(int pageSize) { @@ -45,11 +45,6 @@ public class SqlServerPagingQueryProvider extends SqlWindowingPagingQueryProvide } } - @Override - protected Object getSubQueryAlias() { - return "AS TMP_SUB "; - } - private String buildTopClause(int pageSize) { return new StringBuilder().append("TOP ").append(pageSize).toString(); } diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/SqlWindowingPagingQueryProvider.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/SqlWindowingPagingQueryProvider.java index 1f75726aa..00e0d0471 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/SqlWindowingPagingQueryProvider.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/SqlWindowingPagingQueryProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2023 the original author or authors. + * Copyright 2006-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,7 +26,9 @@ import org.springframework.util.StringUtils; * @author Thomas Risberg * @author Michael Minella * @since 2.0 + * @deprecated since 5.2.1 with no replacement. Scheduled for removal in 6.0. */ +@Deprecated(forRemoval = true) public class SqlWindowingPagingQueryProvider extends AbstractSqlPagingQueryProvider { @Override diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/SybasePagingQueryProvider.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/SybasePagingQueryProvider.java index af6916913..ade0af526 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/SybasePagingQueryProvider.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/SybasePagingQueryProvider.java @@ -28,7 +28,7 @@ import org.springframework.util.StringUtils; * @author Mahmoud Ben Hassine * @since 2.0 */ -public class SybasePagingQueryProvider extends SqlWindowingPagingQueryProvider { +public class SybasePagingQueryProvider extends AbstractSqlPagingQueryProvider { @Override public String generateFirstPageQuery(int pageSize) { @@ -45,11 +45,6 @@ public class SybasePagingQueryProvider extends SqlWindowingPagingQueryProvider { } } - @Override - protected Object getSubQueryAlias() { - return ""; - } - private String buildTopClause(int pageSize) { return new StringBuilder().append("TOP ").append(pageSize).toString(); } diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/DerbyPagingQueryProviderIntegrationTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/DerbyPagingQueryProviderIntegrationTests.java new file mode 100644 index 000000000..fb3820c61 --- /dev/null +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/DerbyPagingQueryProviderIntegrationTests.java @@ -0,0 +1,98 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.item.database.support; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.springframework.batch.item.database.Order; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * @author Henning Pöttker + */ +class DerbyPagingQueryProviderIntegrationTests { + + private static EmbeddedDatabase embeddedDatabase; + + private static JdbcTemplate jdbcTemplate; + + @BeforeAll + static void setUp() { + embeddedDatabase = new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.DERBY) + .addScript("/org/springframework/batch/item/database/support/query-provider-fixture.sql") + .generateUniqueName(true) + .build(); + jdbcTemplate = new JdbcTemplate(embeddedDatabase); + } + + @AfterAll + static void tearDown() { + if (embeddedDatabase != null) { + embeddedDatabase.shutdown(); + } + } + + @Test + void testWithoutGrouping() { + var queryProvider = new DerbyPagingQueryProvider(); + queryProvider.setSelectClause("ID, STRING"); + queryProvider.setFromClause("TEST_TABLE"); + Map sortKeys = new HashMap<>(); + sortKeys.put("ID", Order.ASCENDING); + queryProvider.setSortKeys(sortKeys); + + List firstPage = jdbcTemplate.query(queryProvider.generateFirstPageQuery(2), MAPPER); + assertEquals(List.of(new Item(1, "Spring"), new Item(2, "Batch")), firstPage); + + List secondPage = jdbcTemplate.query(queryProvider.generateRemainingPagesQuery(2), MAPPER, 2); + assertEquals(List.of(new Item(3, "Infrastructure")), secondPage); + } + + @Test + void testWithGrouping() { + var queryProvider = new DerbyPagingQueryProvider(); + queryProvider.setSelectClause("STRING"); + queryProvider.setFromClause("GROUPING_TEST_TABLE"); + queryProvider.setGroupClause("STRING"); + Map sortKeys = new HashMap<>(); + sortKeys.put("STRING", Order.ASCENDING); + queryProvider.setSortKeys(sortKeys); + + List firstPage = jdbcTemplate.queryForList(queryProvider.generateFirstPageQuery(2), String.class); + assertEquals(List.of("Batch", "Infrastructure"), firstPage); + + List secondPage = jdbcTemplate.queryForList(queryProvider.generateRemainingPagesQuery(2), String.class, + "Infrastructure"); + assertEquals(List.of("Spring"), secondPage); + } + + private record Item(Integer id, String string) { + } + + private static final RowMapper MAPPER = (rs, rowNum) -> new Item(rs.getInt("id"), rs.getString("string")); + +} diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/DerbyPagingQueryProviderTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/DerbyPagingQueryProviderTests.java index 5bd891ddf..c93f979c9 100644 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/DerbyPagingQueryProviderTests.java +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/DerbyPagingQueryProviderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2022 the original author or authors. + * Copyright 2006-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,20 +15,9 @@ */ package org.springframework.batch.item.database.support; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.sql.Connection; -import java.sql.DatabaseMetaData; - -import javax.sql.DataSource; import org.junit.jupiter.api.Test; -import org.springframework.batch.item.database.Order; -import org.springframework.dao.InvalidDataAccessResourceUsageException; /** * @author Thomas Risberg @@ -41,43 +30,10 @@ class DerbyPagingQueryProviderTests extends AbstractSqlPagingQueryProviderTests pagingQueryProvider = new DerbyPagingQueryProvider(); } - @Test - void testInit() throws Exception { - DataSource ds = mock(); - Connection con = mock(); - DatabaseMetaData dmd = mock(); - when(dmd.getDatabaseProductVersion()).thenReturn("10.4.1.3"); - when(con.getMetaData()).thenReturn(dmd); - when(ds.getConnection()).thenReturn(con); - pagingQueryProvider.init(ds); - } - - @Test - void testInitWithRecentVersion() throws Exception { - DataSource ds = mock(); - Connection con = mock(); - DatabaseMetaData dmd = mock(); - when(dmd.getDatabaseProductVersion()).thenReturn("10.10.1.1"); - when(con.getMetaData()).thenReturn(dmd); - when(ds.getConnection()).thenReturn(con); - pagingQueryProvider.init(ds); - } - - @Test - void testInitWithUnsupportedVersion() throws Exception { - DataSource ds = mock(); - Connection con = mock(); - DatabaseMetaData dmd = mock(); - when(dmd.getDatabaseProductVersion()).thenReturn("10.2.9.9"); - when(con.getMetaData()).thenReturn(dmd); - when(ds.getConnection()).thenReturn(con); - assertThrows(InvalidDataAccessResourceUsageException.class, () -> pagingQueryProvider.init(ds)); - } - @Test @Override void testGenerateFirstPageQuery() { - String sql = "SELECT * FROM ( SELECT TMP_ORDERED.*, ROW_NUMBER() OVER () AS ROW_NUMBER FROM (SELECT id, name, age FROM foo WHERE bar = 1 ) AS TMP_ORDERED) AS TMP_SUB WHERE TMP_SUB.ROW_NUMBER <= 100 ORDER BY id ASC"; + String sql = "SELECT id, name, age FROM foo WHERE bar = 1 ORDER BY id ASC FETCH FIRST 100 ROWS ONLY"; String s = pagingQueryProvider.generateFirstPageQuery(pageSize); assertEquals(sql, s); } @@ -85,60 +41,37 @@ class DerbyPagingQueryProviderTests extends AbstractSqlPagingQueryProviderTests @Test @Override void testGenerateRemainingPagesQuery() { - String sql = "SELECT * FROM ( SELECT TMP_ORDERED.*, ROW_NUMBER() OVER () AS ROW_NUMBER FROM (SELECT id, name, age FROM foo WHERE bar = 1 ) AS TMP_ORDERED) AS TMP_SUB WHERE TMP_SUB.ROW_NUMBER <= 100 AND ((id > ?)) ORDER BY id ASC"; + String sql = "SELECT id, name, age FROM foo WHERE (bar = 1) AND ((id > ?)) ORDER BY id ASC FETCH FIRST 100 ROWS ONLY"; String s = pagingQueryProvider.generateRemainingPagesQuery(pageSize); assertEquals(sql, s); } - /** - * Older versions of Derby don't allow order by in the sub select. This should work - * with 10.6.1 and above. - */ @Test @Override - void testQueryContainsSortKey() { - String s = pagingQueryProvider.generateFirstPageQuery(pageSize).toLowerCase(); - assertTrue(s.contains("id asc"), "Wrong query: " + s); - } - - /** - * Older versions of Derby don't allow order by in the sub select. This should work - * with 10.6.1 and above. - */ - @Test - @Override - void testQueryContainsSortKeyDesc() { - pagingQueryProvider.getSortKeys().put("id", Order.DESCENDING); - String s = pagingQueryProvider.generateFirstPageQuery(pageSize).toLowerCase(); - assertTrue(s.contains("id desc"), "Wrong query: " + s); - } - - @Override - @Test void testGenerateFirstPageQueryWithGroupBy() { pagingQueryProvider.setGroupClause("dep"); - String sql = "SELECT * FROM ( SELECT TMP_ORDERED.*, ROW_NUMBER() OVER () AS ROW_NUMBER FROM (SELECT id, name, age FROM foo WHERE bar = 1 GROUP BY dep ) AS TMP_ORDERED) AS TMP_SUB WHERE TMP_SUB.ROW_NUMBER <= 100 ORDER BY id ASC"; + String sql = "SELECT id, name, age FROM foo WHERE bar = 1 GROUP BY dep ORDER BY id ASC FETCH FIRST 100 ROWS ONLY"; String s = pagingQueryProvider.generateFirstPageQuery(pageSize); assertEquals(sql, s); } - @Override @Test + @Override void testGenerateRemainingPagesQueryWithGroupBy() { pagingQueryProvider.setGroupClause("dep"); - String sql = "SELECT * FROM ( SELECT TMP_ORDERED.*, ROW_NUMBER() OVER () AS ROW_NUMBER FROM (SELECT id, name, age FROM foo WHERE bar = 1 GROUP BY dep ) AS TMP_ORDERED) AS TMP_SUB WHERE TMP_SUB.ROW_NUMBER <= 100 AND ((id > ?)) ORDER BY id ASC"; + String sql = "SELECT * FROM (SELECT id, name, age FROM foo WHERE bar = 1 GROUP BY dep) AS MAIN_QRY WHERE ((id > ?)) ORDER BY id ASC FETCH FIRST 100 ROWS ONLY"; String s = pagingQueryProvider.generateRemainingPagesQuery(pageSize); assertEquals(sql, s); } @Override String getFirstPageSqlWithMultipleSortKeys() { - return "SELECT * FROM ( SELECT TMP_ORDERED.*, ROW_NUMBER() OVER () AS ROW_NUMBER FROM (SELECT id, name, age FROM foo WHERE bar = 1 ) AS TMP_ORDERED) AS TMP_SUB WHERE TMP_SUB.ROW_NUMBER <= 100 ORDER BY name ASC, id DESC"; + return "SELECT id, name, age FROM foo WHERE bar = 1 ORDER BY name ASC, id DESC FETCH FIRST 100 ROWS ONLY"; } @Override String getRemainingSqlWithMultipleSortKeys() { - return "SELECT * FROM ( SELECT TMP_ORDERED.*, ROW_NUMBER() OVER () AS ROW_NUMBER FROM (SELECT id, name, age FROM foo WHERE bar = 1 ) AS TMP_ORDERED) AS TMP_SUB WHERE TMP_SUB.ROW_NUMBER <= 100 AND ((name > ?) OR (name = ? AND id < ?)) ORDER BY name ASC, id DESC"; + return "SELECT id, name, age FROM foo WHERE (bar = 1) AND ((name > ?) OR (name = ? AND id < ?)) ORDER BY name ASC, id DESC FETCH FIRST 100 ROWS ONLY"; } } diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/SqlWindowingPagingQueryProviderTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/SqlWindowingPagingQueryProviderTests.java index bac04788f..cd58aecfb 100644 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/SqlWindowingPagingQueryProviderTests.java +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/SqlWindowingPagingQueryProviderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2022 the original author or authors. + * Copyright 2006-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,6 +25,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; */ class SqlWindowingPagingQueryProviderTests extends AbstractSqlPagingQueryProviderTests { + @SuppressWarnings("removal") SqlWindowingPagingQueryProviderTests() { pagingQueryProvider = new SqlWindowingPagingQueryProvider(); } diff --git a/spring-batch-infrastructure/src/test/resources/org/springframework/batch/item/database/support/query-provider-fixture.sql b/spring-batch-infrastructure/src/test/resources/org/springframework/batch/item/database/support/query-provider-fixture.sql new file mode 100644 index 000000000..f32001097 --- /dev/null +++ b/spring-batch-infrastructure/src/test/resources/org/springframework/batch/item/database/support/query-provider-fixture.sql @@ -0,0 +1,20 @@ +CREATE TABLE TEST_TABLE ( + ID INTEGER NOT NULL, + STRING VARCHAR(16) NOT NULL +); + +INSERT INTO TEST_TABLE (ID, STRING) VALUES (1, 'Spring'); +INSERT INTO TEST_TABLE (ID, STRING) VALUES (2, 'Batch'); +INSERT INTO TEST_TABLE (ID, STRING) VALUES (3, 'Infrastructure'); + +CREATE TABLE GROUPING_TEST_TABLE ( + ID INTEGER NOT NULL, + STRING VARCHAR(16) NOT NULL +); + +INSERT INTO GROUPING_TEST_TABLE (ID, STRING) VALUES (1, 'Spring'); +INSERT INTO GROUPING_TEST_TABLE (ID, STRING) VALUES (2, 'Batch'); +INSERT INTO GROUPING_TEST_TABLE (ID, STRING) VALUES (3, 'Batch'); +INSERT INTO GROUPING_TEST_TABLE (ID, STRING) VALUES (4, 'Infrastructure'); +INSERT INTO GROUPING_TEST_TABLE (ID, STRING) VALUES (5, 'Infrastructure'); +INSERT INTO GROUPING_TEST_TABLE (ID, STRING) VALUES (6, 'Infrastructure'); \ No newline at end of file