Skip to content

Commit

Permalink
Update implementations of PagingQueryProvider
Browse files Browse the repository at this point in the history
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 spring-projects#4733

Signed-off-by: Fabrice Bibonne <[email protected]>
  • Loading branch information
hpoettker authored and FBibonne committed Feb 2, 2025
1 parent 3d2022d commit 38ec3ad
Show file tree
Hide file tree
Showing 9 changed files with 151 additions and 151 deletions.
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -27,7 +27,7 @@
* @author Mahmoud Ben Hassine
* @since 2.0
*/
public class Db2PagingQueryProvider extends SqlWindowingPagingQueryProvider {
public class Db2PagingQueryProvider extends AbstractSqlPagingQueryProvider {

@Override
public String generateFirstPageQuery(int pageSize) {
Expand All @@ -44,11 +44,6 @@ public String generateRemainingPagesQuery(int pageSize) {
}
}

@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();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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.
* <p>
* 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");
}
}

// 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;
public String generateFirstPageQuery(int pageSize) {
return SqlPagingQueryUtils.generateLimitSqlQuery(this, false, buildLimitClause(pageSize));
}

@Override
protected String getOrderedQueryAlias() {
return "TMP_ORDERED";
}

@Override
protected String getOverClause() {
return "";
}

@Override
protected String getOverSubstituteClauseStart() {
return " FROM (SELECT " + getSelectClause();
public String generateRemainingPagesQuery(int pageSize) {
if (StringUtils.hasText(getGroupClause())) {
return SqlPagingQueryUtils.generateLimitGroupedSqlQuery(this, buildLimitClause(pageSize));
}
else {
return SqlPagingQueryUtils.generateLimitSqlQuery(this, true, buildLimitClause(pageSize));
}
}

@Override
protected String getOverSubstituteClauseEnd() {
return " ) AS " + getOrderedQueryAlias();
private String buildLimitClause(int pageSize) {
return new StringBuilder("FETCH FIRST ").append(pageSize).append(" ROWS ONLY").toString();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
* @author Mahmoud Ben Hassine
* @since 2.0
*/
public class SqlServerPagingQueryProvider extends SqlWindowingPagingQueryProvider {
public class SqlServerPagingQueryProvider extends AbstractSqlPagingQueryProvider {

@Override
public String generateFirstPageQuery(int pageSize) {
Expand All @@ -45,11 +45,6 @@ public String generateRemainingPagesQuery(int pageSize) {
}
}

@Override
protected Object getSubQueryAlias() {
return "AS TMP_SUB ";
}

private String buildTopClause(int pageSize) {
return new StringBuilder().append("TOP ").append(pageSize).toString();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -26,7 +26,9 @@
* @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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
* @author Mahmoud Ben Hassine
* @since 2.0
*/
public class SybasePagingQueryProvider extends SqlWindowingPagingQueryProvider {
public class SybasePagingQueryProvider extends AbstractSqlPagingQueryProvider {

@Override
public String generateFirstPageQuery(int pageSize) {
Expand All @@ -45,11 +45,6 @@ public String generateRemainingPagesQuery(int pageSize) {
}
}

@Override
protected Object getSubQueryAlias() {
return "";
}

private String buildTopClause(int pageSize) {
return new StringBuilder().append("TOP ").append(pageSize).toString();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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<String, Order> sortKeys = new HashMap<>();
sortKeys.put("ID", Order.ASCENDING);
queryProvider.setSortKeys(sortKeys);

List<Item> firstPage = jdbcTemplate.query(queryProvider.generateFirstPageQuery(2), MAPPER);
assertEquals(List.of(new Item(1, "Spring"), new Item(2, "Batch")), firstPage);

List<Item> 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<String, Order> sortKeys = new HashMap<>();
sortKeys.put("STRING", Order.ASCENDING);
queryProvider.setSortKeys(sortKeys);

List<String> firstPage = jdbcTemplate.queryForList(queryProvider.generateFirstPageQuery(2), String.class);
assertEquals(List.of("Batch", "Infrastructure"), firstPage);

List<String> 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<Item> MAPPER = (rs, rowNum) -> new Item(rs.getInt("id"), rs.getString("string"));

}
Loading

0 comments on commit 38ec3ad

Please sign in to comment.