Browse Source
This allows us to use analytic functions aka windowing functions in generated select statements. Closes #1019pull/1128/head
10 changed files with 449 additions and 17 deletions
@ -0,0 +1,94 @@
@@ -0,0 +1,94 @@
|
||||
/* |
||||
* Copyright 2021 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.sql; |
||||
|
||||
import java.util.Arrays; |
||||
|
||||
/** |
||||
* Represents an analytic function, also known as windowing function |
||||
* |
||||
* @author Jens Schauder |
||||
* @since 2.7 |
||||
*/ |
||||
public class AnalyticFunction extends AbstractSegment implements Expression { |
||||
|
||||
private final SimpleFunction function; |
||||
private final Partition partition; |
||||
private final OrderBy orderBy; |
||||
|
||||
public static AnalyticFunction create(String function, Expression... arguments) { |
||||
|
||||
return new AnalyticFunction(SimpleFunction.create(function, Arrays.asList(arguments)), new Partition(), |
||||
new OrderBy()); |
||||
} |
||||
|
||||
private AnalyticFunction(SimpleFunction function, Partition partition, OrderBy orderBy) { |
||||
|
||||
super(function, partition, orderBy); |
||||
|
||||
this.function = function; |
||||
this.partition = partition; |
||||
this.orderBy = orderBy; |
||||
} |
||||
|
||||
public AnalyticFunction partitionBy(Expression... partitionBy) { |
||||
|
||||
return new AnalyticFunction(function, new Partition(partitionBy), orderBy); |
||||
} |
||||
|
||||
public AnalyticFunction orderBy(OrderByField... orderBy) { |
||||
return new AnalyticFunction(function, partition, new OrderBy(orderBy)); |
||||
} |
||||
|
||||
public AnalyticFunction orderBy(Expression... orderByExpression) { |
||||
|
||||
final OrderByField[] orderByFields = Arrays.stream(orderByExpression) //
|
||||
.map(OrderByField::from) //
|
||||
.toArray(OrderByField[]::new); |
||||
|
||||
return new AnalyticFunction(function, partition, new OrderBy(orderByFields)); |
||||
} |
||||
|
||||
public AliasedAnalyticFunction as(String alias) { |
||||
return new AliasedAnalyticFunction(this, SqlIdentifier.unquoted(alias)); |
||||
} |
||||
|
||||
public AliasedAnalyticFunction as(SqlIdentifier alias) { |
||||
return new AliasedAnalyticFunction(this, alias); |
||||
} |
||||
|
||||
public static class Partition extends SegmentList<Expression> { |
||||
Partition(Expression... expressions) { |
||||
super(expressions); |
||||
} |
||||
} |
||||
|
||||
private static class AliasedAnalyticFunction extends AnalyticFunction implements Aliased { |
||||
|
||||
private final SqlIdentifier alias; |
||||
|
||||
AliasedAnalyticFunction(AnalyticFunction analyticFunction, SqlIdentifier alias) { |
||||
|
||||
super(analyticFunction.function, analyticFunction.partition, analyticFunction.orderBy); |
||||
this.alias = alias; |
||||
} |
||||
|
||||
@Override |
||||
public SqlIdentifier getAlias() { |
||||
return alias; |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,29 @@
@@ -0,0 +1,29 @@
|
||||
/* |
||||
* Copyright 2021 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.sql; |
||||
|
||||
/** |
||||
* Represents an `ORDER BY` clause. Currently, only used in {@link AnalyticFunction}. |
||||
* |
||||
* @author Jens Schauder |
||||
* @since 2.7 |
||||
*/ |
||||
public class OrderBy extends SegmentList<OrderByField> { |
||||
|
||||
OrderBy(OrderByField... fields) { |
||||
super(fields); |
||||
} |
||||
} |
||||
@ -0,0 +1,29 @@
@@ -0,0 +1,29 @@
|
||||
/* |
||||
* Copyright 2021 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.sql; |
||||
|
||||
/** |
||||
* A list of {@link Segment} instances. Normally used by inheritance to derive a more specific list. |
||||
* |
||||
* @see org.springframework.data.relational.core.sql.AnalyticFunction.Partition |
||||
* @see OrderBy |
||||
* @param <T> the type of the elements. |
||||
*/ |
||||
public class SegmentList<T extends Segment> extends AbstractSegment { |
||||
SegmentList(T... segments) { |
||||
super(segments); |
||||
} |
||||
} |
||||
@ -0,0 +1,102 @@
@@ -0,0 +1,102 @@
|
||||
/* |
||||
* Copyright 2021 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.sql.render; |
||||
|
||||
import org.springframework.data.relational.core.sql.AnalyticFunction; |
||||
import org.springframework.data.relational.core.sql.OrderBy; |
||||
import org.springframework.data.relational.core.sql.SimpleFunction; |
||||
import org.springframework.data.relational.core.sql.Visitable; |
||||
import org.springframework.lang.Nullable; |
||||
|
||||
/** |
||||
* Renderer for {@link AnalyticFunction}. Uses a {@link RenderTarget} to call back for render results. |
||||
* |
||||
* @author Jens Schauder |
||||
* @since 2.7 |
||||
*/ |
||||
class AnalyticFunctionVisitor extends TypedSingleConditionRenderSupport<AnalyticFunction> implements PartRenderer { |
||||
|
||||
private final StringBuilder part = new StringBuilder(); |
||||
private final RenderContext context; |
||||
@Nullable private PartRenderer delegate; |
||||
private boolean addSpace = false; |
||||
|
||||
AnalyticFunctionVisitor(RenderContext context) { |
||||
super(context); |
||||
this.context = context; |
||||
} |
||||
|
||||
@Override |
||||
Delegation enterNested(Visitable segment) { |
||||
|
||||
if (segment instanceof SimpleFunction) { |
||||
|
||||
delegate = new SimpleFunctionVisitor(context); |
||||
return Delegation.delegateTo((DelegatingVisitor) delegate); |
||||
} |
||||
|
||||
if (segment instanceof AnalyticFunction.Partition) { |
||||
|
||||
delegate = new SegmentListVisitor("PARTITION BY ", ", ", new ExpressionVisitor(context)); |
||||
return Delegation.delegateTo((DelegatingVisitor) delegate); |
||||
} |
||||
|
||||
if (segment instanceof OrderBy) { |
||||
|
||||
delegate = new SegmentListVisitor("ORDER BY ", ", ", new OrderByClauseVisitor(context)); |
||||
return Delegation.delegateTo((DelegatingVisitor) delegate); |
||||
} |
||||
return super.enterNested(segment); |
||||
} |
||||
|
||||
@Override |
||||
Delegation leaveNested(Visitable segment) { |
||||
|
||||
if (delegate instanceof SimpleFunctionVisitor) { |
||||
|
||||
part.append(delegate.getRenderedPart()); |
||||
part.append(" OVER("); |
||||
} |
||||
|
||||
if (delegate instanceof SegmentListVisitor) { |
||||
|
||||
final CharSequence renderedPart = delegate.getRenderedPart(); |
||||
if (renderedPart.length() != 0) { |
||||
|
||||
if (addSpace) { |
||||
part.append(' '); |
||||
} |
||||
part.append(renderedPart); |
||||
addSpace = true; |
||||
} |
||||
} |
||||
|
||||
return super.leaveNested(segment); |
||||
} |
||||
|
||||
@Override |
||||
Delegation leaveMatched(AnalyticFunction segment) { |
||||
|
||||
part.append(")"); |
||||
|
||||
return super.leaveMatched(segment); |
||||
} |
||||
|
||||
@Override |
||||
public CharSequence getRenderedPart() { |
||||
return part; |
||||
} |
||||
} |
||||
@ -0,0 +1,83 @@
@@ -0,0 +1,83 @@
|
||||
/* |
||||
* Copyright 2021 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.sql.render; |
||||
|
||||
import org.springframework.data.relational.core.sql.SegmentList; |
||||
import org.springframework.data.relational.core.sql.Visitable; |
||||
import org.springframework.util.Assert; |
||||
|
||||
/** |
||||
* A part rendering visitor for lists of segments. It can be set up depending on the elements in the list it should |
||||
* handle and the way elemnts should get separated when rendered. |
||||
* |
||||
* @author Jens Schauder |
||||
* @since 2.7 |
||||
*/ |
||||
class SegmentListVisitor extends TypedSubtreeVisitor<SegmentList<?>> implements PartRenderer { |
||||
|
||||
private final StringBuilder part = new StringBuilder(); |
||||
private final String start; |
||||
private final String separator; |
||||
private final DelegatingVisitor nestedVisitor; |
||||
|
||||
private boolean first = true; |
||||
|
||||
/** |
||||
* @param start a {@literal String} to be rendered before the first element if there is at least one element. Must not |
||||
* be {@literal null}. |
||||
* @param separator a {@literal String} to be rendered between elements. Must not be {@literal null}. |
||||
* @param nestedVisitor the {@link org.springframework.data.relational.core.sql.Visitor} responsible for rendering the |
||||
* elements of the list. Must not be {@literal null}. |
||||
*/ |
||||
SegmentListVisitor(String start, String separator, DelegatingVisitor nestedVisitor) { |
||||
|
||||
Assert.notNull(start, "Start must not be null."); |
||||
Assert.notNull(separator, "Separator must not be null."); |
||||
Assert.notNull(nestedVisitor, "Nested Visitor must not be null."); |
||||
Assert.isInstanceOf(PartRenderer.class, nestedVisitor, "Nested visitor must implement PartRenderer"); |
||||
|
||||
this.start = start; |
||||
this.separator = separator; |
||||
this.nestedVisitor = nestedVisitor; |
||||
} |
||||
|
||||
@Override |
||||
Delegation enterNested(Visitable segment) { |
||||
|
||||
if (first) { |
||||
part.append(start); |
||||
first = false; |
||||
} else { |
||||
part.append(separator); |
||||
} |
||||
|
||||
return Delegation.delegateTo(nestedVisitor); |
||||
} |
||||
|
||||
@Override |
||||
Delegation leaveNested(Visitable segment) { |
||||
|
||||
part.append(((PartRenderer) nestedVisitor).getRenderedPart()); |
||||
|
||||
return super.leaveNested(segment); |
||||
} |
||||
|
||||
@Override |
||||
public CharSequence getRenderedPart() { |
||||
|
||||
return part; |
||||
} |
||||
} |
||||
Loading…
Reference in new issue