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 @@ |
|||||||
|
/* |
||||||
|
* 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 @@ |
|||||||
|
/* |
||||||
|
* 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 @@ |
|||||||
|
/* |
||||||
|
* 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 @@ |
|||||||
|
/* |
||||||
|
* 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 @@ |
|||||||
|
/* |
||||||
|
* 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