Skip to content

DATAJDBC-357 - Introduce dialect support to render paginated queries. #125

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

<groupId>org.springframework.data</groupId>
<artifactId>spring-data-relational-parent</artifactId>
<version>1.1.0.BUILD-SNAPSHOT</version>
<version>1.1.0.DATAJDBC-278-SNAPSHOT</version>
<packaging>pom</packaging>

<name>Spring Data Relational Parent</name>
Expand Down
2 changes: 1 addition & 1 deletion spring-data-jdbc-distribution/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-relational-parent</artifactId>
<version>1.1.0.BUILD-SNAPSHOT</version>
<version>1.1.0.DATAJDBC-278-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
4 changes: 2 additions & 2 deletions spring-data-jdbc/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<modelVersion>4.0.0</modelVersion>

<artifactId>spring-data-jdbc</artifactId>
<version>1.1.0.BUILD-SNAPSHOT</version>
<version>1.1.0.DATAJDBC-278-SNAPSHOT</version>

<name>Spring Data JDBC</name>
<description>Spring Data module for JDBC repositories.</description>
Expand All @@ -14,7 +14,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-relational-parent</artifactId>
<version>1.1.0.BUILD-SNAPSHOT</version>
<version>1.1.0.DATAJDBC-278-SNAPSHOT</version>
</parent>

<properties>
Expand Down
4 changes: 2 additions & 2 deletions spring-data-relational/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@
<modelVersion>4.0.0</modelVersion>

<artifactId>spring-data-relational</artifactId>
<version>1.1.0.BUILD-SNAPSHOT</version>
<version>1.1.0.DATAJDBC-278-SNAPSHOT</version>

<name>Spring Data Relational</name>
<description>Spring Data Relational support</description>

<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-relational-parent</artifactId>
<version>1.1.0.BUILD-SNAPSHOT</version>
<version>1.1.0.DATAJDBC-278-SNAPSHOT</version>
</parent>

<properties>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
/*
* Copyright 2019 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.data.relational.core.dialect;

import lombok.RequiredArgsConstructor;

import java.util.OptionalLong;
import java.util.function.Function;

import org.springframework.data.relational.core.sql.Select;
import org.springframework.data.relational.core.sql.render.SelectRenderContext;

/**
* Base class for {@link Dialect} implementations.
*
* @author Mark Paluch
* @since 1.1
*/
public abstract class AbstractDialect implements Dialect {

/*
* (non-Javadoc)
* @see org.springframework.data.relational.core.dialect.Dialect#getSelectContext()
*/
@Override
public SelectRenderContext getSelectContext() {

Function<Select, ? extends CharSequence> afterOrderBy = getAfterOrderBy();

return new DialectSelectRenderContext(afterOrderBy);
}

/**
* Returns a {@link Function afterOrderBy Function}. Typically used for pagination.
*
* @return the {@link Function} called on {@code afterOrderBy}.
*/
protected Function<Select, CharSequence> getAfterOrderBy() {

Function<Select, ? extends CharSequence> afterOrderBy;

LimitClause limit = limit();

switch (limit.getClausePosition()) {

case AFTER_ORDER_BY:
afterOrderBy = new AfterOrderByLimitRenderFunction(limit);
break;

default:
throw new UnsupportedOperationException(String.format("Clause position %s not supported!", limit));
}

return afterOrderBy.andThen(PrependWithLeadingWhitespace.INSTANCE);
}

/**
* {@link SelectRenderContext} derived from {@link Dialect} specifics.
*/
class DialectSelectRenderContext implements SelectRenderContext {

private final Function<Select, ? extends CharSequence> afterOrderBy;

DialectSelectRenderContext(Function<Select, ? extends CharSequence> afterOrderBy) {
this.afterOrderBy = afterOrderBy;
}

/*
* (non-Javadoc)
* @see org.springframework.data.relational.core.sql.render.SelectRenderContext#afterOrderBy(boolean)
*/
@Override
public Function<Select, ? extends CharSequence> afterOrderBy(boolean hasOrderBy) {
return afterOrderBy;
}
}

/**
* After {@code ORDER BY} function rendering the {@link LimitClause}.
*/
@RequiredArgsConstructor
static class AfterOrderByLimitRenderFunction implements Function<Select, CharSequence> {

private final LimitClause clause;

/*
* (non-Javadoc)
* @see java.util.function.Function#apply(java.lang.Object)
*/
@Override
public CharSequence apply(Select select) {

OptionalLong limit = select.getLimit();
OptionalLong offset = select.getOffset();

if (limit.isPresent() && offset.isPresent()) {
return clause.getLimitOffset(limit.getAsLong(), offset.getAsLong());
}

if (limit.isPresent()) {
return clause.getLimit(limit.getAsLong());
}

if (offset.isPresent()) {
return clause.getOffset(offset.getAsLong());
}

return "";
}
}

/**
* Prepends a non-empty rendering result with a leading whitespace,
*/
@RequiredArgsConstructor
enum PrependWithLeadingWhitespace implements Function<CharSequence, CharSequence> {

INSTANCE;

/*
* (non-Javadoc)
* @see java.util.function.Function#apply(java.lang.Object)
*/
@Override
public CharSequence apply(CharSequence charSequence) {

if (charSequence.length() == 0) {
return charSequence;
}

return " " + charSequence;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* Copyright 2019 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.data.relational.core.dialect;

/**
* Interface declaring methods that express how a dialect supports array-typed columns.
*
* @author Mark Paluch
* @since 1.1
*/
public interface ArrayColumns {

/**
* Returns {@literal true} if the dialect supports array-typed columns.
*
* @return {@literal true} if the dialect supports array-typed columns.
*/
boolean isSupported();

/**
* Translate the {@link Class user type} of an array into the dialect-specific type. This method considers only the
* component type.
*
* @param userType component type of the array.
* @return the dialect-supported array type.
* @throws UnsupportedOperationException if array typed columns are not supported.
* @throws IllegalArgumentException if the {@code userType} is not a supported array type.
*/
Class<?> getArrayType(Class<?> userType);

/**
* Default {@link ArrayColumns} implementation for dialects that do not support array-typed columns.
*/
enum Unsupported implements ArrayColumns {

INSTANCE;

/*
* (non-Javadoc)
* @see org.springframework.data.relational.core.dialect.ArrayColumns#isSupported()
*/
@Override
public boolean isSupported() {
return false;
}

/*
* (non-Javadoc)
* @see org.springframework.data.relational.core.dialect.ArrayColumns#getArrayType(java.lang.Class)
*/
@Override
public Class<?> getArrayType(Class<?> userType) {
throw new UnsupportedOperationException("Array types not supported");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* Copyright 2019 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.data.relational.core.dialect;

import org.springframework.data.relational.core.sql.render.SelectRenderContext;

/**
* Represents a dialect that is implemented by a particular database. Please note that not all features are supported by
* all vendors. Dialects typically express this with feature flags. Methods for unsupported functionality may throw
* {@link UnsupportedOperationException}.
*
* @author Mark Paluch
* @author Jens Schauder
* @since 1.1
*/
public interface Dialect {

/**
* Return the {@link LimitClause} used by this dialect.
*
* @return the {@link LimitClause} used by this dialect.
*/
LimitClause limit();

/**
* Returns the array support object that describes how array-typed columns are supported by this dialect.
*
* @return the array support object that describes how array-typed columns are supported by this dialect.
*/
default ArrayColumns getArraySupport() {
return ArrayColumns.Unsupported.INSTANCE;
}

/**
* Obtain the {@link SelectRenderContext}.
*
* @return the {@link SelectRenderContext}.
*/
SelectRenderContext getSelectContext();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* Copyright 2019 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.data.relational.core.dialect;

/**
* A clause representing Dialect-specific {@code LIMIT}.
*
* @author Mark Paluch
* @since 1.1
*/
public interface LimitClause {

/**
* Returns the {@code LIMIT} clause to limit results.
*
* @param limit the actual limit to use.
* @return rendered limit clause.
* @see #getLimitOffset(long, long)
*/
String getLimit(long limit);

/**
* Returns the {@code OFFSET} clause to consume rows at a given offset.
*
* @param limit the actual limit to use.
* @return rendered limit clause.
* @see #getLimitOffset(long, long)
*/
String getOffset(long limit);

/**
* Returns a combined {@code LIMIT/OFFSET} clause that limits results and starts consumption at the given
* {@code offset}.
*
* @param limit the actual limit to use.
* @param offset the offset to start from.
* @return rendered limit clause.
*/
String getLimitOffset(long limit, long offset);

/**
* Returns the {@link Position} where to apply the {@link #getOffset(long) clause}.
*/
Position getClausePosition();

/**
* Enumeration of where to render the clause within the SQL statement.
*/
enum Position {

/**
* Append the clause at the end of the statement.
*/
AFTER_ORDER_BY
}
}
Loading