All Downloads are FREE. Search and download functionalities are using the official Maven repository.
Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.h2.command.query.SelectGroups Maven / Gradle / Ivy
/*
* 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.command.query;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;
import org.h2.engine.SessionLocal;
import org.h2.expression.Expression;
import org.h2.expression.analysis.DataAnalysisOperation;
import org.h2.expression.analysis.PartitionData;
import org.h2.value.Value;
import org.h2.value.ValueRow;
/**
* Grouped data for aggregates.
*
*
* Call sequence:
*
*
* {@link #reset()}.
* For each source row {@link #nextSource()} should be invoked.
* {@link #done()}.
* {@link #next()} is invoked inside a loop until it returns null.
*
*
* Call sequence for lazy group sorted result:
*
*
* {@link #resetLazy()} (not required before the first execution).
* For each source group {@link #nextLazyGroup()} should be invoked.
* For each source row {@link #nextLazyRow()} should be invoked. Each group
* can have one or more rows.
*
*/
public abstract class SelectGroups {
private static final class Grouped extends SelectGroups {
private final int[] groupIndex;
/**
* Map of group-by key to group-by expression data e.g. AggregateData
*/
private TreeMap groupByData;
/**
* Key into groupByData that produces currentGroupByExprData. Not used
* in lazy mode.
*/
private ValueRow currentGroupsKey;
/**
* Cursor for {@link #next()} method.
*/
private Iterator> cursor;
Grouped(SessionLocal session, ArrayList expressions, int[] groupIndex) {
super(session, expressions);
this.groupIndex = groupIndex;
}
@Override
public void reset() {
super.reset();
groupByData = new TreeMap<>(session.getDatabase().getCompareMode());
currentGroupsKey = null;
cursor = null;
}
@Override
public void nextSource() {
if (groupIndex == null) {
currentGroupsKey = ValueRow.EMPTY;
} else {
Value[] keyValues = new Value[groupIndex.length];
// update group
for (int i = 0; i < groupIndex.length; i++) {
int idx = groupIndex[i];
Expression expr = expressions.get(idx);
keyValues[i] = expr.getValue(session);
}
currentGroupsKey = ValueRow.get(keyValues);
}
Object[] values = groupByData.get(currentGroupsKey);
if (values == null) {
values = createRow();
groupByData.put(currentGroupsKey, values);
}
currentGroupByExprData = values;
currentGroupRowId++;
}
@Override
void updateCurrentGroupExprData() {
// this can be null in lazy mode
if (currentGroupsKey != null) {
// since we changed the size of the array, update the object in
// the groups map
groupByData.put(currentGroupsKey, currentGroupByExprData);
}
}
@Override
public void done() {
super.done();
if (groupIndex == null && groupByData.size() == 0) {
groupByData.put(ValueRow.EMPTY, createRow());
}
cursor = groupByData.entrySet().iterator();
}
@Override
public ValueRow next() {
if (cursor.hasNext()) {
Map.Entry entry = cursor.next();
currentGroupByExprData = entry.getValue();
currentGroupRowId++;
return entry.getKey();
}
return null;
}
@Override
public void remove() {
cursor.remove();
currentGroupByExprData = null;
currentGroupRowId--;
}
@Override
public void resetLazy() {
super.resetLazy();
currentGroupsKey = null;
}
}
private static final class Plain extends SelectGroups {
private ArrayList rows;
/**
* Cursor for {@link #next()} method.
*/
private Iterator cursor;
Plain(SessionLocal session, ArrayList expressions) {
super(session, expressions);
}
@Override
public void reset() {
super.reset();
rows = new ArrayList<>();
cursor = null;
}
@Override
public void nextSource() {
Object[] values = createRow();
rows.add(values);
currentGroupByExprData = values;
currentGroupRowId++;
}
@Override
void updateCurrentGroupExprData() {
rows.set(rows.size() - 1, currentGroupByExprData);
}
@Override
public void done() {
super.done();
cursor = rows.iterator();
}
@Override
public ValueRow next() {
if (cursor.hasNext()) {
currentGroupByExprData = cursor.next();
currentGroupRowId++;
return ValueRow.EMPTY;
}
return null;
}
}
/**
* The database session.
*/
final SessionLocal session;
/**
* The query's column list, including invisible expressions such as order by expressions.
*/
final ArrayList expressions;
/**
* The array of current group-by expression data e.g. AggregateData.
*/
Object[] currentGroupByExprData;
/**
* Maps an expression object to an index, to use in accessing the Object[]
* pointed to by groupByData.
*/
private final HashMap exprToIndexInGroupByData = new HashMap<>();
/**
* Maps an window expression object to its data.
*/
private final HashMap windowData = new HashMap<>();
/**
* Maps an partitioned window expression object to its data.
*/
private final HashMap> windowPartitionData = new HashMap<>();
/**
* The id of the current group.
*/
int currentGroupRowId;
/**
* Creates new instance of grouped data.
*
* @param session
* the session
* @param expressions
* the expressions
* @param isGroupQuery
* is this query is a group query
* @param groupIndex
* the indexes of group expressions, or null
* @return new instance of the grouped data.
*/
public static SelectGroups getInstance(SessionLocal session, ArrayList expressions,
boolean isGroupQuery, int[] groupIndex) {
return isGroupQuery ? new Grouped(session, expressions, groupIndex) : new Plain(session, expressions);
}
SelectGroups(SessionLocal session, ArrayList expressions) {
this.session = session;
this.expressions = expressions;
}
/**
* Is there currently a group-by active.
*
* @return {@code true} if there is currently a group-by active,
* otherwise returns {@code false}.
*/
public boolean isCurrentGroup() {
return currentGroupByExprData != null;
}
/**
* Get the group-by data for the current group and the passed in expression.
*
* @param expr
* expression
* @return expression data or null
*/
public final Object getCurrentGroupExprData(Expression expr) {
Integer index = exprToIndexInGroupByData.get(expr);
if (index == null) {
return null;
}
return currentGroupByExprData[index];
}
/**
* Set the group-by data for the current group and the passed in expression.
*
* @param expr
* expression
* @param obj
* expression data to set
*/
public final void setCurrentGroupExprData(Expression expr, Object obj) {
Integer index = exprToIndexInGroupByData.get(expr);
if (index != null) {
assert currentGroupByExprData[index] == null;
currentGroupByExprData[index] = obj;
return;
}
index = exprToIndexInGroupByData.size();
exprToIndexInGroupByData.put(expr, index);
if (index >= currentGroupByExprData.length) {
currentGroupByExprData = Arrays.copyOf(currentGroupByExprData, currentGroupByExprData.length * 2);
updateCurrentGroupExprData();
}
currentGroupByExprData[index] = obj;
}
/**
* Creates new object arrays to holds group-by data.
*
* @return new object array to holds group-by data.
*/
final Object[] createRow() {
return new Object[Math.max(exprToIndexInGroupByData.size(), expressions.size())];
}
/**
* Get the window data for the specified expression.
*
* @param expr
* expression
* @param partitionKey
* a key of partition
* @return expression data or null
*/
public final PartitionData getWindowExprData(DataAnalysisOperation expr, Value partitionKey) {
if (partitionKey == null) {
return windowData.get(expr);
} else {
TreeMap map = windowPartitionData.get(expr);
return map != null ? map.get(partitionKey) : null;
}
}
/**
* Set the window data for the specified expression.
*
* @param expr
* expression
* @param partitionKey
* a key of partition
* @param obj
* window expression data to set
*/
public final void setWindowExprData(DataAnalysisOperation expr, Value partitionKey, PartitionData obj) {
if (partitionKey == null) {
Object old = windowData.put(expr, obj);
assert old == null;
} else {
TreeMap map = windowPartitionData.get(expr);
if (map == null) {
map = new TreeMap<>(session.getDatabase().getCompareMode());
windowPartitionData.put(expr, map);
}
map.put(partitionKey, obj);
}
}
/**
* Update group-by data specified by implementation.
*/
abstract void updateCurrentGroupExprData();
/**
* Returns identity of the current row. Used by aggregates to check whether
* they already processed this row or not.
*
* @return identity of the current row
*/
public int getCurrentGroupRowId() {
return currentGroupRowId;
}
/**
* Resets this group data for reuse.
*/
public void reset() {
currentGroupByExprData = null;
exprToIndexInGroupByData.clear();
windowData.clear();
windowPartitionData.clear();
currentGroupRowId = 0;
}
/**
* Invoked for each source row to evaluate group key and setup all necessary
* data for aggregates.
*/
public abstract void nextSource();
/**
* Invoked after all source rows are evaluated.
*/
public void done() {
currentGroupRowId = 0;
}
/**
* Returns the key of the next group.
*
* @return the key of the next group, or null
*/
public abstract ValueRow next();
/**
* Removes the data for the current key.
*
* @see #next()
*/
public void remove() {
throw new UnsupportedOperationException();
}
/**
* Resets this group data for reuse in lazy mode.
*/
public void resetLazy() {
currentGroupByExprData = null;
currentGroupRowId = 0;
}
/**
* Moves group data to the next group in lazy mode.
*/
public void nextLazyGroup() {
currentGroupByExprData = new Object[Math.max(exprToIndexInGroupByData.size(), expressions.size())];
}
/**
* Moves group data to the next row in lazy mode.
*/
public void nextLazyRow() {
currentGroupRowId++;
}
}