From 7852b5268d4a3d7ba35f3eaeb844bee427e40d4d Mon Sep 17 00:00:00 2001 From: Jens Schauder Date: Tue, 2 Nov 2021 13:03:45 +0100 Subject: [PATCH 1/2] 1076-limit-offset - Prepare branch --- pom.xml | 2 +- spring-data-jdbc-distribution/pom.xml | 2 +- spring-data-jdbc/pom.xml | 4 ++-- spring-data-relational/pom.xml | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pom.xml b/pom.xml index eeaa0b9e93..8c47abe6d8 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.data spring-data-relational-parent - 2.3.0-SNAPSHOT + 2.3.0-1076-limit-offset-SNAPSHOT pom Spring Data Relational Parent diff --git a/spring-data-jdbc-distribution/pom.xml b/spring-data-jdbc-distribution/pom.xml index 03d6a5c2a0..004f3bf36d 100644 --- a/spring-data-jdbc-distribution/pom.xml +++ b/spring-data-jdbc-distribution/pom.xml @@ -14,7 +14,7 @@ org.springframework.data spring-data-relational-parent - 2.3.0-SNAPSHOT + 2.3.0-1076-limit-offset-SNAPSHOT ../pom.xml diff --git a/spring-data-jdbc/pom.xml b/spring-data-jdbc/pom.xml index af9ad0904e..a5dedb01c3 100644 --- a/spring-data-jdbc/pom.xml +++ b/spring-data-jdbc/pom.xml @@ -6,7 +6,7 @@ 4.0.0 spring-data-jdbc - 2.3.0-SNAPSHOT + 2.3.0-1076-limit-offset-SNAPSHOT Spring Data JDBC Spring Data module for JDBC repositories. @@ -15,7 +15,7 @@ org.springframework.data spring-data-relational-parent - 2.3.0-SNAPSHOT + 2.3.0-1076-limit-offset-SNAPSHOT diff --git a/spring-data-relational/pom.xml b/spring-data-relational/pom.xml index 4e42a006ec..666bccc87d 100644 --- a/spring-data-relational/pom.xml +++ b/spring-data-relational/pom.xml @@ -6,7 +6,7 @@ 4.0.0 spring-data-relational - 2.3.0-SNAPSHOT + 2.3.0-1076-limit-offset-SNAPSHOT Spring Data Relational Spring Data Relational support @@ -14,7 +14,7 @@ org.springframework.data spring-data-relational-parent - 2.3.0-SNAPSHOT + 2.3.0-1076-limit-offset-SNAPSHOT From 2a9a5643075699185f46da7e2c7d85e4700c4cc0 Mon Sep 17 00:00:00 2001 From: Jens Schauder Date: Tue, 2 Nov 2021 14:18:38 +0100 Subject: [PATCH 2/2] The default SelectRenderContext now renders lock, limit and offset clauses. Closes #1076 --- .../core/sql/render/SelectRenderContext.java | 32 +++++++++- .../sql/render/SelectRendererUnitTests.java | 61 ++++++++++++++++++- 2 files changed, 89 insertions(+), 4 deletions(-) diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/SelectRenderContext.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/SelectRenderContext.java index b420fefee4..c78f379170 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/SelectRenderContext.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/SelectRenderContext.java @@ -15,8 +15,10 @@ */ package org.springframework.data.relational.core.sql.render; +import java.util.OptionalLong; import java.util.function.Function; +import org.springframework.data.relational.core.sql.LockMode; import org.springframework.data.relational.core.sql.Select; /** @@ -26,6 +28,7 @@ * * @author Mark Paluch * @author Myeonghyeon Lee + * @author Jens Schauder * @since 1.1 */ public interface SelectRenderContext { @@ -51,12 +54,35 @@ public interface SelectRenderContext { /** * Customization hook: Rendition of a part after {@code ORDER BY}. The rendering function is called always, regardless - * whether {@code ORDER BY} exists or not. Renders an empty string by default. - * + * whether {@code ORDER BY} exists or not. + *

+ * Renders lock, limit and offset clause as appropriate. + *

+ * * @param hasOrderBy the actual value whether the {@link Select} statement has a {@code ORDER BY} clause. * @return render {@link Function} invoked after rendering {@code ORDER BY}. */ default Function afterOrderBy(boolean hasOrderBy) { - return select -> ""; + + return select -> { + + OptionalLong limit = select.getLimit(); + OptionalLong offset = select.getOffset(); + LockMode lockMode = select.getLockMode(); + + String lockPrefix = (lockMode == null) ? "" : " FOR UPDATE"; + + if (limit.isPresent() && offset.isPresent()) { + return lockPrefix + + String.format(" OFFSET %d ROWS FETCH FIRST %d ROWS ONLY", offset.getAsLong(), limit.getAsLong()); + } + if (limit.isPresent()) { + return lockPrefix + String.format(" FETCH FIRST %d ROWS ONLY", limit.getAsLong()); + } + if (offset.isPresent()) { + return lockPrefix + String.format(" OFFSET %d ROWS", offset.getAsLong()); + } + return lockPrefix; + }; } } diff --git a/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/render/SelectRendererUnitTests.java b/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/render/SelectRendererUnitTests.java index 7a223c1564..f68f86d6f5 100644 --- a/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/render/SelectRendererUnitTests.java +++ b/spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/render/SelectRendererUnitTests.java @@ -37,7 +37,7 @@ void shouldRenderSingleColumn() { Table bar = SQL.table("bar"); Column foo = bar.column("foo"); - Select select = Select.builder().select(foo).from(bar).limitOffset(1, 2).build(); + Select select = Select.builder().select(foo).from(bar).build(); assertThat(SqlRenderer.toString(select)).isEqualTo("SELECT bar.foo FROM bar"); } @@ -467,4 +467,63 @@ void shouldRenderCast() { final String rendered = SqlRenderer.toString(select); assertThat(rendered).isEqualTo("SELECT CAST(User.name AS VARCHAR2) FROM User"); } + + @Test // GH-1076 + void rendersLimitAndOffset() { + + Table table_user = SQL.table("User"); + Select select = StatementBuilder.select(table_user.column("name")).from(table_user).limitOffset(10, 5).build(); + + final String rendered = SqlRenderer.toString(select); + assertThat(rendered).isEqualTo("SELECT User.name FROM User OFFSET 5 ROWS FETCH FIRST 10 ROWS ONLY"); + } + + @Test // GH-1076 + void rendersLimit() { + + Table table_user = SQL.table("User"); + Select select = StatementBuilder.select(table_user.column("name")).from(table_user) // + .limit(3) // + .build(); + + final String rendered = SqlRenderer.toString(select); + assertThat(rendered).isEqualTo("SELECT User.name FROM User FETCH FIRST 3 ROWS ONLY"); + } + + @Test // GH-1076 + void rendersLock() { + + Table table_user = SQL.table("User"); + Select select = StatementBuilder.select(table_user.column("name")).from(table_user) // + .lock(LockMode.PESSIMISTIC_READ) // + .build(); + + final String rendered = SqlRenderer.toString(select); + assertThat(rendered).isEqualTo("SELECT User.name FROM User FOR UPDATE"); + } + + @Test // GH-1076 + void rendersLockAndOffset() { + + Table table_user = SQL.table("User"); + Select select = StatementBuilder.select(table_user.column("name")).from(table_user).offset(3) // + .lock(LockMode.PESSIMISTIC_WRITE) // + .build(); + + final String rendered = SqlRenderer.toString(select); + assertThat(rendered).isEqualTo("SELECT User.name FROM User FOR UPDATE OFFSET 3 ROWS"); + } + + @Test // GH-1076 + void rendersLockAndOffsetUsingDialect() { + + Table table_user = SQL.table("User"); + Select select = StatementBuilder.select(table_user.column("name")).from(table_user).limitOffset(3, 6) // + .lock(LockMode.PESSIMISTIC_WRITE) // + .build(); + + String rendered = SqlRenderer.create(new RenderContextFactory(PostgresDialect.INSTANCE).createRenderContext()) + .render(select); + assertThat(rendered).isEqualTo("SELECT User.name FROM User LIMIT 3 OFFSET 6 FOR UPDATE OF User"); + } }