Skip to content

Commit 1b3d43d

Browse files
committed
[CALCITE-7592] Merge main branch
2 parents cdb119b + 9acb374 commit 1b3d43d

33 files changed

Lines changed: 641 additions & 79 deletions

build.gradle.kts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import com.github.vlsi.gradle.release.RepositoryType
2626
import de.thetaphi.forbiddenapis.gradle.CheckForbiddenApis
2727
import de.thetaphi.forbiddenapis.gradle.CheckForbiddenApisExtension
2828
import net.ltgt.gradle.errorprone.errorprone
29+
import org.apache.calcite.buildtools.asmchecker.AsmCheckerTask
2930
import org.apache.calcite.buildtools.buildext.dsl.ParenthesisBalancer
3031
import org.gradle.api.tasks.testing.logging.TestExceptionFormat
3132

@@ -37,6 +38,7 @@ plugins {
3738
publishing
3839
// Verification
3940
checkstyle
41+
id("calcite.asmchecker")
4042
calcite.buildext
4143
jacoco
4244
id("jacoco-report-aggregation")
@@ -930,6 +932,15 @@ allprojects {
930932
}
931933
jvmArgs("-Xmx6g")
932934
}
935+
register<AsmCheckerTask>("bytecodeCheck") {
936+
group = LifecycleBasePlugin.VERIFICATION_GROUP
937+
description = "Checks the bytecode of every .class file in the build directory using ASM."
938+
dependsOn("classes")
939+
}
940+
941+
check {
942+
dependsOn("bytecodeCheck")
943+
}
933944
hepLargePlanModeTestIncludes[project.path]?.let { includes ->
934945
val hepLargePlanModeTask = register<Test>("testHepLargePlanMode") {
935946
group = LifecycleBasePlugin.VERIFICATION_GROUP

buildSrc/gradle.properties

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,6 @@ kotlin.code.style=official
2020
# Plugins
2121
com.github.autostyle.version=3.2
2222
com.github.vlsi.vlsi-release-plugins.version=1.52
23+
24+
# Dependencies (keep in sync with root gradle.properties)
25+
asm.version=9.9.1

buildSrc/settings.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ pluginManagement {
2424
}
2525
}
2626

27+
include("asmchecker")
2728
include("javacc")
2829
include("fmpp")
2930
include("buildext")
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to you under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
dependencies {
19+
val asmVersion = providers.gradleProperty("asm.version").get()
20+
implementation("org.ow2.asm:asm:$asmVersion")
21+
implementation("org.ow2.asm:asm-analysis:$asmVersion")
22+
implementation("org.ow2.asm:asm-commons:$asmVersion")
23+
implementation("org.ow2.asm:asm-tree:$asmVersion")
24+
implementation("org.ow2.asm:asm-util:$asmVersion")
25+
}
26+
27+
gradlePlugin {
28+
plugins {
29+
register("asmchecker") {
30+
id = "calcite.asmchecker"
31+
implementationClass = "org.apache.calcite.buildtools.asmchecker.AsmCheckerPlugin"
32+
}
33+
}
34+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to you under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package org.apache.calcite.buildtools.asmchecker
19+
20+
import org.gradle.api.Plugin
21+
import org.gradle.api.Project
22+
23+
open class AsmCheckerPlugin : Plugin<Project> {
24+
override fun apply(target: Project) {
25+
}
26+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to you under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package org.apache.calcite.buildtools.asmchecker
19+
20+
import java.nio.file.Files
21+
import java.nio.file.Paths
22+
import org.gradle.api.DefaultTask
23+
import org.gradle.api.tasks.CacheableTask
24+
import org.gradle.api.tasks.TaskAction
25+
import org.objectweb.asm.ClassReader
26+
import org.objectweb.asm.ClassWriter
27+
import org.objectweb.asm.commons.ClassRemapper
28+
import org.objectweb.asm.commons.Remapper
29+
import org.objectweb.asm.util.CheckClassAdapter
30+
31+
@CacheableTask
32+
open class AsmCheckerTask : DefaultTask() {
33+
34+
@TaskAction
35+
fun run() {
36+
project.layout.buildDirectory.get().asFile.walk()
37+
.onEnter { dir ->
38+
// the classes in spark/build/sparkServer/classes, generated by SparkHandlerImpl,
39+
// have invalid bytecode, so exclude them
40+
"sparkServer${java.io.File.separator}classes" !in dir.path }
41+
.filter { file -> file.getName().lowercase().endsWith(".class") }
42+
.forEach {
43+
val classReader = ClassReader(Files.readAllBytes(Paths.get(it.getPath())))
44+
val classVisitor = CheckClassAdapter(ClassWriter(ClassWriter.COMPUTE_MAXS))
45+
val classRemapper = ClassRemapper(classVisitor, object : Remapper() {})
46+
try {
47+
classReader.accept(classRemapper, ClassReader.EXPAND_FRAMES)
48+
} catch (e: java.lang.RuntimeException) {
49+
throw java.lang.RuntimeException("Invalid bytecode file:" + it, e)
50+
}
51+
}
52+
}
53+
}

core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableAggregateBase.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import org.apache.calcite.plan.RelOptCluster;
3131
import org.apache.calcite.plan.RelTraitSet;
3232
import org.apache.calcite.rel.RelCollations;
33+
import org.apache.calcite.rel.RelFieldCollation;
3334
import org.apache.calcite.rel.RelNode;
3435
import org.apache.calcite.rel.core.Aggregate;
3536
import org.apache.calcite.rel.core.AggregateCall;
@@ -264,6 +265,20 @@ protected void createAccumulatorAdders(
264265
for (int index : agg.call.getArgList()) {
265266
args.add(RexInputRef.of(index, inputTypes));
266267
}
268+
// Percentile functions such as PERCENTILE_CONT and
269+
// PERCENTILE_DISC take the fraction as their only argument, but
270+
// aggregate over the WITHIN GROUP (ORDER BY ...) column. Expose
271+
// that collation column as an extra argument so that the
272+
// accumulator can collect its values (already sorted by
273+
// SourceSorter).
274+
if (agg.call.getAggregation().isPercentile()) {
275+
for (RelFieldCollation fieldCollation
276+
: agg.call.collation.getFieldCollations()) {
277+
args.add(
278+
RexInputRef.of(fieldCollation.getFieldIndex(),
279+
inputTypes));
280+
}
281+
}
267282
return args;
268283
}
269284

core/src/main/java/org/apache/calcite/adapter/enumerable/RexImpTable.java

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -499,6 +499,8 @@
499499
import static org.apache.calcite.sql.fun.SqlStdOperatorTable.OCTET_LENGTH;
500500
import static org.apache.calcite.sql.fun.SqlStdOperatorTable.OR;
501501
import static org.apache.calcite.sql.fun.SqlStdOperatorTable.OVERLAY;
502+
import static org.apache.calcite.sql.fun.SqlStdOperatorTable.PERCENTILE_CONT;
503+
import static org.apache.calcite.sql.fun.SqlStdOperatorTable.PERCENTILE_DISC;
502504
import static org.apache.calcite.sql.fun.SqlStdOperatorTable.PI;
503505
import static org.apache.calcite.sql.fun.SqlStdOperatorTable.PLUS;
504506
import static org.apache.calcite.sql.fun.SqlStdOperatorTable.POSITION;
@@ -1326,6 +1328,8 @@ void populate3() {
13261328
defineAgg(SINGLE_VALUE, SingleValueImplementor.class);
13271329
defineAgg(COLLECT, CollectImplementor.class);
13281330
defineAgg(ARRAY_AGG, CollectImplementor.class);
1331+
defineAgg(PERCENTILE_CONT, PercentileImplementor.class);
1332+
defineAgg(PERCENTILE_DISC, PercentileImplementor.class);
13291333
defineAgg(LISTAGG, ListaggImplementor.class);
13301334
defineAgg(FUSION, FusionImplementor.class);
13311335
defineAgg(MODE, ModeImplementor.class);
@@ -1967,6 +1971,60 @@ static class CollectImplementor extends StrictAggImplementor {
19671971
}
19681972
}
19691973

1974+
/** Implementor for the {@code PERCENTILE_CONT} and {@code PERCENTILE_DISC}
1975+
* aggregate functions.
1976+
*
1977+
* <p>The fraction is the sole argument of the aggregate call, while the
1978+
* values whose percentile is computed come from the
1979+
* {@code WITHIN GROUP (ORDER BY ...)} column, which
1980+
* {@link EnumerableAggregateBase#createAccumulatorAdders} exposes as an extra
1981+
* argument. The input rows are sorted by {@code SourceSorter} before being
1982+
* accumulated, so the collected values are already in order. */
1983+
static class PercentileImplementor extends StrictAggImplementor {
1984+
@Override public List<Type> getNotNullState(AggContext info) {
1985+
final List<Type> types = new ArrayList<>();
1986+
types.add(List.class);
1987+
types.add(double.class);
1988+
return types;
1989+
}
1990+
1991+
@Override protected void implementNotNullReset(AggContext info,
1992+
AggResetContext reset) {
1993+
reset.currentBlock().add(
1994+
Expressions.statement(
1995+
Expressions.assign(reset.accumulator().get(0),
1996+
Expressions.new_(ArrayList.class))));
1997+
reset.currentBlock().add(
1998+
Expressions.statement(
1999+
Expressions.assign(reset.accumulator().get(1),
2000+
Expressions.constant(0d))));
2001+
}
2002+
2003+
@Override protected void implementNotNullAdd(AggContext info,
2004+
AggAddContext add) {
2005+
add.currentBlock().add(
2006+
Expressions.statement(
2007+
Expressions.assign(add.accumulator().get(1),
2008+
EnumUtils.convert(add.arguments().get(0), double.class))));
2009+
2010+
add.currentBlock().add(
2011+
Expressions.statement(
2012+
Expressions.call(add.accumulator().get(0),
2013+
BuiltInMethod.COLLECTION_ADD.method,
2014+
Expressions.box(add.arguments().get(1)))));
2015+
}
2016+
2017+
@Override protected Expression implementNotNullResult(AggContext info,
2018+
AggResultContext result) {
2019+
final BuiltInMethod method =
2020+
info.aggregation().kind == SqlKind.PERCENTILE_DISC
2021+
? BuiltInMethod.PERCENTILE_DISC
2022+
: BuiltInMethod.PERCENTILE_CONT;
2023+
return Expressions.call(method.method, result.accumulator().get(0),
2024+
result.accumulator().get(1));
2025+
}
2026+
}
2027+
19702028
/** Implementor for the {@code LISTAGG} aggregate function. */
19712029
static class ListaggImplementor extends StrictAggImplementor {
19722030
@Override protected void implementNotNullReset(AggContext info,

core/src/main/java/org/apache/calcite/rel/core/TableModify.java

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,9 @@
3232
import org.apache.calcite.rel.metadata.RelMetadataQuery;
3333
import org.apache.calcite.rel.type.RelDataType;
3434
import org.apache.calcite.rel.type.RelDataTypeFactory;
35+
import org.apache.calcite.rel.type.RelDataTypeField;
3536
import org.apache.calcite.rex.RexNode;
37+
import org.apache.calcite.rex.RexUtil;
3638
import org.apache.calcite.sql.SqlKind;
3739
import org.apache.calcite.sql.type.SqlTypeUtil;
3840

@@ -261,9 +263,37 @@ public boolean isMerge() {
261263
null);
262264
}
263265

266+
inputRowType = expectedInputRowTypeForAssignment(inputRowType);
264267
return inputRowType;
265268
}
266269

270+
private RelDataType expectedInputRowTypeForAssignment(
271+
RelDataType expectedRowType) {
272+
final RelDataType actualRowType = getInput().getRowType();
273+
if (actualRowType.getFieldCount() != expectedRowType.getFieldCount()) {
274+
return expectedRowType;
275+
}
276+
final RelDataTypeFactory typeFactory = getCluster().getTypeFactory();
277+
final RelDataTypeFactory.Builder builder = typeFactory.builder();
278+
boolean changed = false;
279+
final List<RelDataTypeField> expectedFields = expectedRowType.getFieldList();
280+
final List<RelDataTypeField> actualFields = actualRowType.getFieldList();
281+
for (int i = 0; i < expectedFields.size(); i++) {
282+
final RelDataTypeField expectedField = expectedFields.get(i);
283+
final RelDataType actualType = actualFields.get(i).getType();
284+
final RelDataType expectedType = expectedField.getType();
285+
if (!SqlTypeUtil.equalSansNullability(typeFactory, actualType, expectedType)
286+
&& SqlTypeUtil.canAssignFrom(expectedType, actualType)
287+
&& !RexUtil.isLosslessCast(actualType, expectedType)) {
288+
builder.add(expectedField.getName(), actualType);
289+
changed = true;
290+
} else {
291+
builder.add(expectedField);
292+
}
293+
}
294+
return changed ? builder.build() : expectedRowType;
295+
}
296+
267297
@Override public RelWriter explainTerms(RelWriter pw) {
268298
return super.explainTerms(pw)
269299
.item("table", table.getQualifiedName())

core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@
133133
import java.util.List;
134134
import java.util.Locale;
135135
import java.util.Map;
136+
import java.util.NoSuchElementException;
136137
import java.util.Objects;
137138
import java.util.Set;
138139
import java.util.TimeZone;
@@ -4069,6 +4070,55 @@ public static BigDecimal mod(BigDecimal b0, BigDecimal b1) {
40694070
return bigDecimals[1];
40704071
}
40714072

4073+
// PERCENTILE_CONT / PERCENTILE_DISC
4074+
4075+
/** Support the PERCENTILE_CONT aggregate function.
4076+
*
4077+
* <p>The {@code values} list must already be sorted according to the
4078+
* {@code WITHIN GROUP (ORDER BY ...)} clause. The fraction must be in the
4079+
* range 0 to 1 inclusive. The result is a linear interpolation between the
4080+
* two values that surround the desired position. */
4081+
public static BigDecimal percentileCont(List<? extends Number> values,
4082+
double fraction) {
4083+
final int n = values.size();
4084+
if (n == 0) {
4085+
throw new NoSuchElementException(
4086+
"PERCENTILE_CONT is not defined on an empty group");
4087+
}
4088+
final double rank = fraction * (n - 1);
4089+
final int lo = (int) Math.floor(rank);
4090+
final int hi = (int) Math.ceil(rank);
4091+
final BigDecimal loValue = toBigDecimal(values.get(lo));
4092+
if (lo == hi) {
4093+
return loValue;
4094+
}
4095+
final BigDecimal hiValue = toBigDecimal(values.get(hi));
4096+
final BigDecimal frac = BigDecimal.valueOf(rank - lo);
4097+
return loValue.add(hiValue.subtract(loValue).multiply(frac));
4098+
}
4099+
4100+
/** Support the PERCENTILE_DISC aggregate function.
4101+
*
4102+
* <p>The {@code values} list must already be sorted according to the
4103+
* {@code WITHIN GROUP (ORDER BY ...)} clause. The fraction must be in the
4104+
* range 0 to 1 inclusive. The result is an actual value from the group: the
4105+
* first whose cumulative distribution is greater than or equal to the
4106+
* fraction. */
4107+
public static Object percentileDisc(List<?> values, double fraction) {
4108+
final int n = values.size();
4109+
if (n == 0) {
4110+
throw new NoSuchElementException(
4111+
"PERCENTILE_DISC is not defined on an empty group");
4112+
}
4113+
int index = (int) Math.ceil(fraction * n) - 1;
4114+
if (index < 0) {
4115+
index = 0;
4116+
} else if (index >= n) {
4117+
index = n - 1;
4118+
}
4119+
return requireNonNull(values.get(index));
4120+
}
4121+
40724122
// FLOOR
40734123

40744124
public static double floor(double b0) {

0 commit comments

Comments
 (0)