Skip to content

Commit 0388315

Browse files
committed
Adds support for windowing functions.
This allows us to use analytic functions aka windowing functions in generated select statements. Closes #1019
1 parent 1acb666 commit 0388315

File tree

10 files changed

+449
-17
lines changed

10 files changed

+449
-17
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
/*
2+
* Copyright 2021 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.sql;
17+
18+
import java.util.Arrays;
19+
20+
/**
21+
* Represents an analytic function, also known as windowing function
22+
*
23+
* @author Jens Schauder
24+
* @since 2.7
25+
*/
26+
public class AnalyticFunction extends AbstractSegment implements Expression {
27+
28+
private final SimpleFunction function;
29+
private final Partition partition;
30+
private final OrderBy orderBy;
31+
32+
public static AnalyticFunction create(String function, Expression... arguments) {
33+
34+
return new AnalyticFunction(SimpleFunction.create(function, Arrays.asList(arguments)), new Partition(),
35+
new OrderBy());
36+
}
37+
38+
private AnalyticFunction(SimpleFunction function, Partition partition, OrderBy orderBy) {
39+
40+
super(function, partition, orderBy);
41+
42+
this.function = function;
43+
this.partition = partition;
44+
this.orderBy = orderBy;
45+
}
46+
47+
public AnalyticFunction partitionBy(Expression... partitionBy) {
48+
49+
return new AnalyticFunction(function, new Partition(partitionBy), orderBy);
50+
}
51+
52+
public AnalyticFunction orderBy(OrderByField... orderBy) {
53+
return new AnalyticFunction(function, partition, new OrderBy(orderBy));
54+
}
55+
56+
public AnalyticFunction orderBy(Expression... orderByExpression) {
57+
58+
final OrderByField[] orderByFields = Arrays.stream(orderByExpression) //
59+
.map(OrderByField::from) //
60+
.toArray(OrderByField[]::new);
61+
62+
return new AnalyticFunction(function, partition, new OrderBy(orderByFields));
63+
}
64+
65+
public AliasedAnalyticFunction as(String alias) {
66+
return new AliasedAnalyticFunction(this, SqlIdentifier.unquoted(alias));
67+
}
68+
69+
public AliasedAnalyticFunction as(SqlIdentifier alias) {
70+
return new AliasedAnalyticFunction(this, alias);
71+
}
72+
73+
public static class Partition extends SegmentList<Expression> {
74+
Partition(Expression... expressions) {
75+
super(expressions);
76+
}
77+
}
78+
79+
private static class AliasedAnalyticFunction extends AnalyticFunction implements Aliased {
80+
81+
private final SqlIdentifier alias;
82+
83+
AliasedAnalyticFunction(AnalyticFunction analyticFunction, SqlIdentifier alias) {
84+
85+
super(analyticFunction.function, analyticFunction.partition, analyticFunction.orderBy);
86+
this.alias = alias;
87+
}
88+
89+
@Override
90+
public SqlIdentifier getAlias() {
91+
return alias;
92+
}
93+
}
94+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*
2+
* Copyright 2021 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.sql;
17+
18+
/**
19+
* Represents an `ORDER BY` clause. Currently, only used in {@link AnalyticFunction}.
20+
*
21+
* @author Jens Schauder
22+
* @since 2.7
23+
*/
24+
public class OrderBy extends SegmentList<OrderByField> {
25+
26+
OrderBy(OrderByField... fields) {
27+
super(fields);
28+
}
29+
}

spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/OrderByField.java

+8-8
Original file line numberDiff line numberDiff line change
@@ -46,24 +46,24 @@ private OrderByField(Expression expression, @Nullable Direction direction, NullH
4646
}
4747

4848
/**
49-
* Creates a new {@link OrderByField} from a {@link Column} applying default ordering.
49+
* Creates a new {@link OrderByField} from an {@link Expression} applying default ordering.
5050
*
51-
* @param column must not be {@literal null}.
51+
* @param expression must not be {@literal null}.
5252
* @return the {@link OrderByField}.
5353
*/
54-
public static OrderByField from(Column column) {
55-
return new OrderByField(column, null, NullHandling.NATIVE);
54+
public static OrderByField from(Expression expression) {
55+
return new OrderByField(expression, null, NullHandling.NATIVE);
5656
}
5757

5858
/**
59-
* Creates a new {@link OrderByField} from a {@link Column} applying a given ordering.
59+
* Creates a new {@link OrderByField} from an {@link Expression} applying a given ordering.
6060
*
61-
* @param column must not be {@literal null}.
61+
* @param expression must not be {@literal null}.
6262
* @param direction order direction
6363
* @return the {@link OrderByField}.
6464
*/
65-
public static OrderByField from(Column column, Direction direction) {
66-
return new OrderByField(column, direction, NullHandling.NATIVE);
65+
public static OrderByField from(Expression expression, Direction direction) {
66+
return new OrderByField(expression, direction, NullHandling.NATIVE);
6767
}
6868

6969
/**
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*
2+
* Copyright 2021 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.sql;
17+
18+
/**
19+
* A list of {@link Segment} instances. Normally used by inheritance to derive a more specific list.
20+
*
21+
* @see org.springframework.data.relational.core.sql.AnalyticFunction.Partition
22+
* @see OrderBy
23+
* @param <T> the type of the elements.
24+
*/
25+
public class SegmentList<T extends Segment> extends AbstractSegment {
26+
SegmentList(T... segments) {
27+
super(segments);
28+
}
29+
}

spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/SimpleFunction.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@
2929
*/
3030
public class SimpleFunction extends AbstractSegment implements Expression {
3131

32-
private String functionName;
33-
private List<Expression> expressions;
32+
private final String functionName;
33+
private final List<Expression> expressions;
3434

3535
private SimpleFunction(String functionName, List<Expression> expressions) {
3636

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
/*
2+
* Copyright 2021 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.sql.render;
17+
18+
import org.springframework.data.relational.core.sql.AnalyticFunction;
19+
import org.springframework.data.relational.core.sql.OrderBy;
20+
import org.springframework.data.relational.core.sql.SimpleFunction;
21+
import org.springframework.data.relational.core.sql.Visitable;
22+
import org.springframework.lang.Nullable;
23+
24+
/**
25+
* Renderer for {@link AnalyticFunction}. Uses a {@link RenderTarget} to call back for render results.
26+
*
27+
* @author Jens Schauder
28+
* @since 2.7
29+
*/
30+
class AnalyticFunctionVisitor extends TypedSingleConditionRenderSupport<AnalyticFunction> implements PartRenderer {
31+
32+
private final StringBuilder part = new StringBuilder();
33+
private final RenderContext context;
34+
@Nullable private PartRenderer delegate;
35+
private boolean addSpace = false;
36+
37+
AnalyticFunctionVisitor(RenderContext context) {
38+
super(context);
39+
this.context = context;
40+
}
41+
42+
@Override
43+
Delegation enterNested(Visitable segment) {
44+
45+
if (segment instanceof SimpleFunction) {
46+
47+
delegate = new SimpleFunctionVisitor(context);
48+
return Delegation.delegateTo((DelegatingVisitor) delegate);
49+
}
50+
51+
if (segment instanceof AnalyticFunction.Partition) {
52+
53+
delegate = new SegmentListVisitor("PARTITION BY ", ", ", new ExpressionVisitor(context));
54+
return Delegation.delegateTo((DelegatingVisitor) delegate);
55+
}
56+
57+
if (segment instanceof OrderBy) {
58+
59+
delegate = new SegmentListVisitor("ORDER BY ", ", ", new OrderByClauseVisitor(context));
60+
return Delegation.delegateTo((DelegatingVisitor) delegate);
61+
}
62+
return super.enterNested(segment);
63+
}
64+
65+
@Override
66+
Delegation leaveNested(Visitable segment) {
67+
68+
if (delegate instanceof SimpleFunctionVisitor) {
69+
70+
part.append(delegate.getRenderedPart());
71+
part.append(" OVER(");
72+
}
73+
74+
if (delegate instanceof SegmentListVisitor) {
75+
76+
final CharSequence renderedPart = delegate.getRenderedPart();
77+
if (renderedPart.length() != 0) {
78+
79+
if (addSpace) {
80+
part.append(' ');
81+
}
82+
part.append(renderedPart);
83+
addSpace = true;
84+
}
85+
}
86+
87+
return super.leaveNested(segment);
88+
}
89+
90+
@Override
91+
Delegation leaveMatched(AnalyticFunction segment) {
92+
93+
part.append(")");
94+
95+
return super.leaveMatched(segment);
96+
}
97+
98+
@Override
99+
public CharSequence getRenderedPart() {
100+
return part;
101+
}
102+
}

spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/ExpressionVisitor.java

+7
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,13 @@ Delegation enterMatched(Expression segment) {
8282
return Delegation.delegateTo(visitor);
8383
}
8484

85+
if (segment instanceof AnalyticFunction) {
86+
87+
AnalyticFunctionVisitor visitor = new AnalyticFunctionVisitor(context);
88+
partRenderer = visitor;
89+
return Delegation.delegateTo(visitor);
90+
}
91+
8592
if (segment instanceof Column) {
8693

8794
Column column = (Column) segment;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/*
2+
* Copyright 2021 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.sql.render;
17+
18+
import org.springframework.data.relational.core.sql.SegmentList;
19+
import org.springframework.data.relational.core.sql.Visitable;
20+
import org.springframework.util.Assert;
21+
22+
/**
23+
* A part rendering visitor for lists of segments. It can be set up depending on the elements in the list it should
24+
* handle and the way elemnts should get separated when rendered.
25+
*
26+
* @author Jens Schauder
27+
* @since 2.7
28+
*/
29+
class SegmentListVisitor extends TypedSubtreeVisitor<SegmentList<?>> implements PartRenderer {
30+
31+
private final StringBuilder part = new StringBuilder();
32+
private final String start;
33+
private final String separator;
34+
private final DelegatingVisitor nestedVisitor;
35+
36+
private boolean first = true;
37+
38+
/**
39+
* @param start a {@literal String} to be rendered before the first element if there is at least one element. Must not
40+
* be {@literal null}.
41+
* @param separator a {@literal String} to be rendered between elements. Must not be {@literal null}.
42+
* @param nestedVisitor the {@link org.springframework.data.relational.core.sql.Visitor} responsible for rendering the
43+
* elements of the list. Must not be {@literal null}.
44+
*/
45+
SegmentListVisitor(String start, String separator, DelegatingVisitor nestedVisitor) {
46+
47+
Assert.notNull(start, "Start must not be null.");
48+
Assert.notNull(separator, "Separator must not be null.");
49+
Assert.notNull(nestedVisitor, "Nested Visitor must not be null.");
50+
Assert.isInstanceOf(PartRenderer.class, nestedVisitor, "Nested visitor must implement PartRenderer");
51+
52+
this.start = start;
53+
this.separator = separator;
54+
this.nestedVisitor = nestedVisitor;
55+
}
56+
57+
@Override
58+
Delegation enterNested(Visitable segment) {
59+
60+
if (first) {
61+
part.append(start);
62+
first = false;
63+
} else {
64+
part.append(separator);
65+
}
66+
67+
return Delegation.delegateTo(nestedVisitor);
68+
}
69+
70+
@Override
71+
Delegation leaveNested(Visitable segment) {
72+
73+
part.append(((PartRenderer) nestedVisitor).getRenderedPart());
74+
75+
return super.leaveNested(segment);
76+
}
77+
78+
@Override
79+
public CharSequence getRenderedPart() {
80+
81+
return part;
82+
}
83+
}

0 commit comments

Comments
 (0)