Skip to content

Commit 488ef53

Browse files
mp911deschauder
authored andcommitted
DATAJDBC-357 - Dialect support pagination and array support.
We now support Dialect abstractions to consider vendor-specific deviations in SQL query rendering. Dialects support right now: Feature flags for array support Limit and Offset handling The rendering is extended by considering rendering callback hook functions. Current dialects: Postgres SQL SQL Server (2012) MySQL Original pull request: #125.
1 parent 141f2d7 commit 488ef53

24 files changed

+1407
-26
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
/*
2+
* Copyright 2019 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.relational.core.dialect;
17+
18+
import lombok.RequiredArgsConstructor;
19+
20+
import java.util.OptionalLong;
21+
import java.util.function.Function;
22+
23+
import org.springframework.data.relational.core.sql.Select;
24+
import org.springframework.data.relational.core.sql.render.SelectRenderContext;
25+
26+
/**
27+
* Base class for {@link Dialect} implementations.
28+
*
29+
* @author Mark Paluch
30+
* @since 1.1
31+
*/
32+
public abstract class AbstractDialect implements Dialect {
33+
34+
/*
35+
* (non-Javadoc)
36+
* @see org.springframework.data.relational.core.dialect.Dialect#getSelectContext()
37+
*/
38+
@Override
39+
public SelectRenderContext getSelectContext() {
40+
41+
Function<Select, ? extends CharSequence> afterOrderBy = getAfterOrderBy();
42+
43+
return new DialectSelectRenderContext(afterOrderBy);
44+
}
45+
46+
/**
47+
* Returns a {@link Function afterOrderBy Function}. Typically used for pagination.
48+
*
49+
* @return the {@link Function} called on {@code afterOrderBy}.
50+
*/
51+
protected Function<Select, CharSequence> getAfterOrderBy() {
52+
53+
Function<Select, ? extends CharSequence> afterOrderBy;
54+
55+
LimitClause limit = limit();
56+
57+
switch (limit.getClausePosition()) {
58+
59+
case AFTER_ORDER_BY:
60+
afterOrderBy = new AfterOrderByLimitRenderFunction(limit);
61+
break;
62+
63+
default:
64+
throw new UnsupportedOperationException(String.format("Clause position %s not supported!", limit));
65+
}
66+
67+
return afterOrderBy.andThen(PrependWithLeadingWhitespace.INSTANCE);
68+
}
69+
70+
/**
71+
* {@link SelectRenderContext} derived from {@link Dialect} specifics.
72+
*/
73+
class DialectSelectRenderContext implements SelectRenderContext {
74+
75+
private final Function<Select, ? extends CharSequence> afterOrderBy;
76+
77+
DialectSelectRenderContext(Function<Select, ? extends CharSequence> afterOrderBy) {
78+
this.afterOrderBy = afterOrderBy;
79+
}
80+
81+
/*
82+
* (non-Javadoc)
83+
* @see org.springframework.data.relational.core.sql.render.SelectRenderContext#afterOrderBy(boolean)
84+
*/
85+
@Override
86+
public Function<Select, ? extends CharSequence> afterOrderBy(boolean hasOrderBy) {
87+
return afterOrderBy;
88+
}
89+
}
90+
91+
/**
92+
* After {@code ORDER BY} function rendering the {@link LimitClause}.
93+
*/
94+
@RequiredArgsConstructor
95+
static class AfterOrderByLimitRenderFunction implements Function<Select, CharSequence> {
96+
97+
private final LimitClause clause;
98+
99+
/*
100+
* (non-Javadoc)
101+
* @see java.util.function.Function#apply(java.lang.Object)
102+
*/
103+
@Override
104+
public CharSequence apply(Select select) {
105+
106+
OptionalLong limit = select.getLimit();
107+
OptionalLong offset = select.getOffset();
108+
109+
if (limit.isPresent() && offset.isPresent()) {
110+
return clause.getLimitOffset(limit.getAsLong(), offset.getAsLong());
111+
}
112+
113+
if (limit.isPresent()) {
114+
return clause.getLimit(limit.getAsLong());
115+
}
116+
117+
if (offset.isPresent()) {
118+
return clause.getOffset(offset.getAsLong());
119+
}
120+
121+
return "";
122+
}
123+
}
124+
125+
/**
126+
* Prepends a non-empty rendering result with a leading whitespace,
127+
*/
128+
@RequiredArgsConstructor
129+
enum PrependWithLeadingWhitespace implements Function<CharSequence, CharSequence> {
130+
131+
INSTANCE;
132+
133+
/*
134+
* (non-Javadoc)
135+
* @see java.util.function.Function#apply(java.lang.Object)
136+
*/
137+
@Override
138+
public CharSequence apply(CharSequence charSequence) {
139+
140+
if (charSequence.length() == 0) {
141+
return charSequence;
142+
}
143+
144+
return " " + charSequence;
145+
}
146+
}
147+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/*
2+
* Copyright 2019 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.relational.core.dialect;
17+
18+
/**
19+
* Interface declaring methods that express how a dialect supports array-typed columns.
20+
*
21+
* @author Mark Paluch
22+
* @since 1.1
23+
*/
24+
public interface ArrayColumns {
25+
26+
/**
27+
* Returns {@literal true} if the dialect supports array-typed columns.
28+
*
29+
* @return {@literal true} if the dialect supports array-typed columns.
30+
*/
31+
boolean isSupported();
32+
33+
/**
34+
* Translate the {@link Class user type} of an array into the dialect-specific type. This method considers only the
35+
* component type.
36+
*
37+
* @param userType component type of the array.
38+
* @return the dialect-supported array type.
39+
* @throws UnsupportedOperationException if array typed columns are not supported.
40+
* @throws IllegalArgumentException if the {@code userType} is not a supported array type.
41+
*/
42+
Class<?> getArrayType(Class<?> userType);
43+
44+
/**
45+
* Default {@link ArrayColumns} implementation for dialects that do not support array-typed columns.
46+
*/
47+
enum Unsupported implements ArrayColumns {
48+
49+
INSTANCE;
50+
51+
/*
52+
* (non-Javadoc)
53+
* @see org.springframework.data.relational.core.dialect.ArrayColumns#isSupported()
54+
*/
55+
@Override
56+
public boolean isSupported() {
57+
return false;
58+
}
59+
60+
/*
61+
* (non-Javadoc)
62+
* @see org.springframework.data.relational.core.dialect.ArrayColumns#getArrayType(java.lang.Class)
63+
*/
64+
@Override
65+
public Class<?> getArrayType(Class<?> userType) {
66+
throw new UnsupportedOperationException("Array types not supported");
67+
}
68+
}
69+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/*
2+
* Copyright 2019 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.relational.core.dialect;
17+
18+
import org.springframework.data.relational.core.sql.render.SelectRenderContext;
19+
20+
/**
21+
* Represents a dialect that is implemented by a particular database. Please note that not all features are supported by
22+
* all vendors. Dialects typically express this with feature flags. Methods for unsupported functionality may throw
23+
* {@link UnsupportedOperationException}.
24+
*
25+
* @author Mark Paluch
26+
* @author Jens Schauder
27+
* @since 1.1
28+
*/
29+
public interface Dialect {
30+
31+
/**
32+
* Return the {@link LimitClause} used by this dialect.
33+
*
34+
* @return the {@link LimitClause} used by this dialect.
35+
*/
36+
LimitClause limit();
37+
38+
/**
39+
* Returns the array support object that describes how array-typed columns are supported by this dialect.
40+
*
41+
* @return the array support object that describes how array-typed columns are supported by this dialect.
42+
*/
43+
default ArrayColumns getArraySupport() {
44+
return ArrayColumns.Unsupported.INSTANCE;
45+
}
46+
47+
/**
48+
* Obtain the {@link SelectRenderContext}.
49+
*
50+
* @return the {@link SelectRenderContext}.
51+
*/
52+
SelectRenderContext getSelectContext();
53+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/*
2+
* Copyright 2019 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.relational.core.dialect;
17+
18+
/**
19+
* A clause representing Dialect-specific {@code LIMIT}.
20+
*
21+
* @author Mark Paluch
22+
* @since 1.1
23+
*/
24+
public interface LimitClause {
25+
26+
/**
27+
* Returns the {@code LIMIT} clause to limit results.
28+
*
29+
* @param limit the actual limit to use.
30+
* @return rendered limit clause.
31+
* @see #getLimitOffset(long, long)
32+
*/
33+
String getLimit(long limit);
34+
35+
/**
36+
* Returns the {@code OFFSET} clause to consume rows at a given offset.
37+
*
38+
* @param limit the actual limit to use.
39+
* @return rendered limit clause.
40+
* @see #getLimitOffset(long, long)
41+
*/
42+
String getOffset(long limit);
43+
44+
/**
45+
* Returns a combined {@code LIMIT/OFFSET} clause that limits results and starts consumption at the given
46+
* {@code offset}.
47+
*
48+
* @param limit the actual limit to use.
49+
* @param offset the offset to start from.
50+
* @return rendered limit clause.
51+
*/
52+
String getLimitOffset(long limit, long offset);
53+
54+
/**
55+
* Returns the {@link Position} where to apply the {@link #getOffset(long) clause}.
56+
*/
57+
Position getClausePosition();
58+
59+
/**
60+
* Enumeration of where to render the clause within the SQL statement.
61+
*/
62+
enum Position {
63+
64+
/**
65+
* Append the clause at the end of the statement.
66+
*/
67+
AFTER_ORDER_BY
68+
}
69+
}

0 commit comments

Comments
 (0)