Skip to content

Commit

Permalink
Merge pull request #3817 from katzyn/any_value
Browse files Browse the repository at this point in the history
Add ANY_VALUE() aggregate function
  • Loading branch information
katzyn committed Jun 9, 2023
2 parents 1f984b0 + 380fe6f commit 1990523
Show file tree
Hide file tree
Showing 9 changed files with 239 additions and 63 deletions.
3 changes: 2 additions & 1 deletion h2/src/main/org/h2/command/ddl/DropTable.java
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,8 @@ private boolean prepareDrop() {
}
}
if (!dependencies.isEmpty()) {
throw DbException.get(ErrorCode.CANNOT_DROP_2, table.getName(), String.join(", ", new HashSet<>(dependencies)));
throw DbException.get(ErrorCode.CANNOT_DROP_2, table.getName(),
String.join(", ", new HashSet<>(dependencies)));
}
}
table.lock(session, Table.EXCLUSIVE_LOCK);
Expand Down
51 changes: 43 additions & 8 deletions h2/src/main/org/h2/expression/aggregate/Aggregate.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
import org.h2.table.ColumnResolver;
import org.h2.table.Table;
import org.h2.table.TableFilter;
import org.h2.util.StringUtils;
import org.h2.util.json.JsonConstructorUtils;
import org.h2.value.CompareMode;
import org.h2.value.DataType;
Expand Down Expand Up @@ -128,6 +129,7 @@ public Aggregate(AggregateType aggregateType, Expression[] args, Select select,
addAggregate("VAR_SAMP", AggregateType.VAR_SAMP);
addAggregate("VAR", AggregateType.VAR_SAMP);
addAggregate("VARIANCE", AggregateType.VAR_SAMP);
addAggregate("ANY_VALUE", AggregateType.ANY_VALUE);
addAggregate("ANY", AggregateType.ANY);
addAggregate("SOME", AggregateType.ANY);
// PostgreSQL compatibility
Expand Down Expand Up @@ -474,6 +476,11 @@ protected Object createAggregateData() {
case REGR_INTERCEPT:
case REGR_R2:
return new AggregateDataCorr(aggregateType);
case ANY_VALUE:
if (!distinct) {
return new AggregateDataAnyValue();
}
break;
case LISTAGG: // NULL values are excluded by Aggregate
case ARRAY_AGG:
return new AggregateDataCollecting(distinct, orderByList != null, NullCollectionMode.USED_OR_IMPOSSIBLE);
Expand Down Expand Up @@ -591,6 +598,15 @@ public Value getAggregatedValue(SessionLocal session, Object aggregateData) {
return collect(session, c, new AggregateDataStdVar(aggregateType));
}
break;
case ANY_VALUE:
if (distinct) {
Value[] values = ((AggregateDataCollecting) data).getArray();
if (values == null) {
return ValueNull.INSTANCE;
}
return values[session.getRandom().nextInt(values.length)];
}
break;
case HISTOGRAM:
return getHistogram(session, data);
case LISTAGG:
Expand Down Expand Up @@ -798,10 +814,15 @@ private Value getListagg(SessionLocal session, AggregateData data) {
private StringBuilder getListaggError(Value[] array, String separator) {
StringBuilder builder = new StringBuilder(getListaggItem(array[0]));
for (int i = 1, count = array.length; i < count; i++) {
builder.append(separator).append(getListaggItem(array[i]));
if (builder.length() > Constants.MAX_STRING_LENGTH) {
throw DbException.getValueTooLongException("CHARACTER VARYING", builder.substring(0, 81), -1L);
String s = getListaggItem(array[i]);
long length = (long) builder.length() + separator.length() + s.length();
if (length > Constants.MAX_STRING_LENGTH) {
int limit = 81;
StringUtils.appendToLength(builder, separator, limit);
StringUtils.appendToLength(builder, s, limit);
throw DbException.getValueTooLongException("CHARACTER VARYING", builder.substring(0, limit), -1L);
}
builder.append(separator).append(s);
}
return builder;
}
Expand All @@ -812,17 +833,24 @@ private StringBuilder getListaggTruncate(Value[] array, String separator, String
String[] strings = new String[count];
String s = getListaggItem(array[0]);
strings[0] = s;
final int estimatedLength = (int) Math.min(Integer.MAX_VALUE, s.length() * (long)count);
final int estimatedLength = (int) Math.min(Constants.MAX_STRING_LENGTH, s.length() * (long) count);
final StringBuilder builder = new StringBuilder(estimatedLength);
builder.append(s);
loop: for (int i = 1; i < count; i++) {
builder.append(separator).append(strings[i] = s = getListaggItem(array[i]));
strings[i] = s = getListaggItem(array[i]);
int length = builder.length();
if (length > Constants.MAX_STRING_LENGTH) {
long longLength = (long) length + separator.length() + s.length();
if (longLength > Constants.MAX_STRING_LENGTH) {
if (longLength - s.length() >= Constants.MAX_STRING_LENGTH) {
i--;
} else {
builder.append(separator);
length = (int) longLength;
}
for (; i > 0; i--) {
length -= strings[i].length();
builder.setLength(length);
builder.append(filter);
StringUtils.appendToLength(builder, filter, Constants.MAX_STRING_LENGTH + 1);
if (!withoutCount) {
builder.append('(').append(count - i).append(')');
}
Expand All @@ -835,6 +863,7 @@ private StringBuilder getListaggTruncate(Value[] array, String separator, String
builder.append(filter).append('(').append(count).append(')');
break;
}
builder.append(separator).append(s);
}
return builder;
}
Expand Down Expand Up @@ -987,6 +1016,7 @@ public Expression optimize(SessionLocal session) {
break;
case MIN:
case MAX:
case ANY_VALUE:
break;
case STDDEV_POP:
case STDDEV_SAMP:
Expand Down Expand Up @@ -1267,7 +1297,8 @@ public boolean isEverything(ExpressionVisitor visitor) {
if (filterCondition != null && !filterCondition.isEverything(visitor)) {
return false;
}
if (visitor.getType() == ExpressionVisitor.OPTIMIZABLE_AGGREGATE) {
switch (visitor.getType()) {
case ExpressionVisitor.OPTIMIZABLE_AGGREGATE:
switch (aggregateType) {
case COUNT:
if (distinct || args[0].getNullable() != Column.NOT_NULLABLE) {
Expand All @@ -1293,6 +1324,10 @@ public boolean isEverything(ExpressionVisitor visitor) {
default:
return false;
}
case ExpressionVisitor.DETERMINISTIC:
if (aggregateType == AggregateType.ANY_VALUE) {
return false;
}
}
for (Expression arg : args) {
if (!arg.isEverything(visitor)) {
Expand Down
68 changes: 68 additions & 0 deletions h2/src/main/org/h2/expression/aggregate/AggregateDataAnyValue.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* Copyright 2004-2023 H2 Group. Multiple-Licensed under the MPL 2.0,
* and the EPL 1.0 (https://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.expression.aggregate;

import java.util.ArrayList;
import java.util.Random;

import org.h2.engine.SessionLocal;
import org.h2.value.Value;
import org.h2.value.ValueNull;

/**
* Data stored while calculating an ANY_VALUE aggregate.
*/
final class AggregateDataAnyValue extends AggregateData {

private static final int MAX_VALUES = 256;

ArrayList<Value> values = new ArrayList<>();

private long filter = -1L;

/**
* Creates new instance of data for ANY_VALUE.
*/
AggregateDataAnyValue() {
}

@Override
void add(SessionLocal session, Value v) {
if (v == ValueNull.INSTANCE) {
return;
}
long filter = this.filter;
if (filter == Long.MIN_VALUE || (session.getRandom().nextLong() | filter) == filter) {
values.add(v);
if (values.size() == MAX_VALUES) {
compact(session);
}
}
}

private void compact(SessionLocal session) {
filter <<= 1;
Random random = session.getRandom();
for (int s = 0, t = 0; t < MAX_VALUES / 2; s += 2, t++) {
int idx = s;
if (random.nextBoolean()) {
idx++;
}
values.set(t, values.get(idx));
}
values.subList(MAX_VALUES / 2, MAX_VALUES).clear();
}

@Override
Value getValue(SessionLocal session) {
int count = values.size();
if (count == 0) {
return ValueNull.INSTANCE;
}
return values.get(session.getRandom().nextInt(count));
}

}
5 changes: 5 additions & 0 deletions h2/src/main/org/h2/expression/aggregate/AggregateType.java
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,11 @@ public enum AggregateType {
*/
VAR_SAMP,

/**
* The aggregate type for ANY_VALUE(expression).
*/
ANY_VALUE,

/**
* The aggregate type for ANY(expression).
*/
Expand Down
18 changes: 18 additions & 0 deletions h2/src/main/org/h2/res/help.csv
Original file line number Diff line number Diff line change
Expand Up @@ -6976,6 +6976,24 @@ Aggregates are only allowed in select statements.
VAR_SAMP(X)
"

"Aggregate Functions (General)","ANY_VALUE","
ANY_VALUE( @h2@ [ DISTINCT|ALL ] value )
[FILTER (WHERE expression)] [OVER windowNameOrSpecification]
","
Returns any non-NULL value from aggregated values.
If no rows are selected, the result is NULL.
This function uses the same pseudo random generator as [RAND()](https://h2database.com/html/functions.html#rand)
function.

If DISTINCT is specified, each distinct value will be returned with approximately the same probability
as other distinct values. If it isn't specified, more frequent values will be returned with higher probability
than less frequent.

Aggregates are only allowed in select statements.
","
ANY_VALUE(X)
"

"Aggregate Functions (General)","BIT_AND_AGG","
{@h2@{BIT_AND_AGG}|@c@{BIT_AND}}@h2@(expression)
@h2@ [FILTER (WHERE expression)] @h2@ [OVER windowNameOrSpecification]
Expand Down
22 changes: 22 additions & 0 deletions h2/src/main/org/h2/util/StringUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -1348,6 +1348,28 @@ public static StringBuilder appendZeroPadded(StringBuilder builder, int length,
return builder.append(s);
}

/**
* Appends the specified string or its part to the specified builder with
* maximum builder length limit.
*
* @param builder the string builder
* @param s the string to append
* @param length the length limit
* @return the specified string builder
*/
public static StringBuilder appendToLength(StringBuilder builder, String s, int length) {
int builderLength = builder.length();
if (builderLength < length) {
int need = length - builderLength;
if (need >= s.length()) {
builder.append(s);
} else {
builder.append(s, 0, need);
}
}
return builder;
}

/**
* Escape table or schema patterns used for DatabaseMetaData functions.
*
Expand Down

0 comments on commit 1990523

Please sign in to comment.