org.exist.xquery.GroupByClause Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of exist-core Show documentation
Show all versions of exist-core Show documentation
eXist-db NoSQL Database Core
package org.exist.xquery;
import com.ibm.icu.text.Collator;
import org.exist.dom.QName;
import org.exist.xquery.util.ExpressionDumper;
import org.exist.xquery.value.*;
import java.util.*;
import java.util.stream.Stream;
/**
* Implements a "group by" clause inside a FLWOR.
*
* @author wolf
*/
public class GroupByClause extends AbstractFLWORClause {
protected FLWORClause rootClause = null;
private GroupSpec[] groupSpecs;
private final Deque stack = new ArrayDeque<>();
/**
* Collect tuples and grouping vars. Because GroupByClause needs to keep
* state across calls to preEval/eval/postEval, we have to track state data
* in a separate object and push it to a stack, otherwise recursive calls
* would overwrite data.
*/
private class GroupByData {
private Map, Tuple> groupedMap = null;
private Map variables = null;
private List groupingVars = null;
private boolean initialized = false;
public GroupByData() {
// check if we can use a hash map
if (usesDefaultCollator()) {
groupedMap = new HashMap<>();
} else {
// non-default collation: must use tree map
groupedMap = new TreeMap<>(GroupByClause.this::compareKeys);
}
variables = new HashMap<>();
groupingVars = new ArrayList<>();
}
}
public GroupByClause(XQueryContext context) {
super(context);
}
@Override
public ClauseType getType() {
return ClauseType.GROUPBY;
}
@Override
public Sequence preEval(Sequence seq) throws XPathException {
stack.push(new GroupByData());
return super.preEval(seq);
}
@Override
public Sequence eval(Sequence contextSequence, Item contextItem) throws XPathException {
final GroupByData data = stack.peek();
// Evaluate group spec to create grouping key sequence
final List groupingValues = new ArrayList<>();
final List groupingKeys = new ArrayList<>();
for (GroupSpec spec: groupSpecs) {
final Sequence groupingSeq = spec.getGroupExpression().eval(null);
if (groupingSeq.getItemCount() > 1) {
throw new XPathException(this, ErrorCodes.XPTY0004, "Grouping variable " + spec.getKeyVarName() + " " +
"evaluates to more than one item");
}
final AtomicValue groupingValue = groupingSeq.isEmpty() ? AtomicValue.EMPTY_VALUE : groupingSeq.itemAt(0)
.atomize();
if (!data.initialized) {
final LocalVariable groupingVar = new LocalVariable(spec.getKeyVarName());
groupingVar.setSequenceType(new SequenceType(Type.ATOMIC, groupingValue.isEmpty() ? Cardinality
.EMPTY : Cardinality.EXACTLY_ONE));
groupingVar.setStaticType(groupingValue.getType());
data.groupingVars.add(groupingVar);
}
groupingValues.add(groupingSeq);
groupingKeys.add(groupingValue);
}
// collect the current tuples into the grouping map
final Tuple tuple = data.groupedMap.computeIfAbsent(groupingKeys, ks -> new Tuple(groupingValues));
// scan in-scope variables to collect tuples
LocalVariable nextVar = rootClause.getStartVariable();
Objects.requireNonNull(nextVar);
while(nextVar != null) {
tuple.add(nextVar.getQName(), nextVar.getValue());
if (!data.initialized) {
// on first call: initialize non-grouping variable for later use
final LocalVariable var = new LocalVariable(nextVar.getQName());
var.setSequenceType(nextVar.getSequenceType());
var.setStaticType(nextVar.getStaticType());
var.setContextDocs(nextVar.getContextDocs());
data.variables.put(var.getQName(), var);
}
nextVar = nextVar.after;
}
data.initialized = true;
return contextSequence;
}
@Override
public Sequence postEval(final Sequence seq) throws XPathException {
if (!stack.isEmpty()) {
final GroupByData data = stack.peek();
Sequence result = new ValueSequence();
final LocalVariable mark = context.markLocalVariables(false);
try {
// declare non-grouping variables
for (LocalVariable var : data.variables.values()) {
context.declareVariableBinding(var);
}
// declare grouping variables
for (LocalVariable var : data.groupingVars) {
context.declareVariableBinding(var);
}
// iterate over each group
for (Tuple tuple : data.groupedMap.values()) {
context.proceed();
// set grouping variable values
final Iterator siter = tuple.groupingValues.iterator();
for (LocalVariable var : data.groupingVars) {
if (siter.hasNext()) {
Sequence val = siter.next();
var.setValue(val);
} else {
throw new XPathException(this, "Internal error: missing grouping value");
}
}
// set values of non-grouping variables
for (Map.Entry entry : tuple.entrySet()) {
final LocalVariable var = data.variables.get(entry.getKey());
var.setValue(entry.getValue());
}
final Sequence r = returnExpr.eval(null);
result.addAll(r);
}
} finally {
stack.pop();
context.popLocalVariables(mark, result);
}
if (returnExpr instanceof FLWORClause) {
result = ((FLWORClause) returnExpr).postEval(result);
}
result = super.postEval(result);
return result;
}
return seq;
}
@Override
public void analyze(AnalyzeContextInfo contextInfo) throws XPathException {
contextInfo.setParent(this);
unordered = (contextInfo.getFlags() & UNORDERED) > 0;
final LocalVariable mark = context.markLocalVariables(false);
try {
if (groupSpecs != null) {
for (final GroupSpec spec : groupSpecs) {
final LocalVariable groupKeyVar = new LocalVariable(spec.getKeyVarName());
context.declareVariableBinding(groupKeyVar);
}
}
final AnalyzeContextInfo newContextInfo = new AnalyzeContextInfo(contextInfo);
newContextInfo.addFlag(SINGLE_STEP_EXECUTION);
for (final GroupSpec spec : groupSpecs) {
spec.analyze(newContextInfo);
}
returnExpr.analyze(newContextInfo);
} finally {
// restore the local variable stack
context.popLocalVariables(mark);
}
FLWORClause prevClause = getPreviousClause();
while (prevClause != null) {
rootClause = prevClause;
prevClause = prevClause.getPreviousClause();
}
}
public void setGroupSpecs(final GroupSpec specs[]) {
final List distinctSpecs = new ArrayList<>(specs.length);
for (int i = 0; i < specs.length; i++) {
boolean duplicate = false;
for (int j = i + 1; j < specs.length; j++) {
if (specs[i].equals(specs[j])) {
duplicate = true;
break;
}
}
if (!duplicate) {
distinctSpecs.add(specs[i]);
}
}
this.groupSpecs = distinctSpecs.toArray(new GroupSpec[distinctSpecs.size()]);
}
public GroupSpec[] getGroupSpecs() {
return groupSpecs == null ? new GroupSpec[0] : groupSpecs;
}
@Override
public void dump(ExpressionDumper dumper) {
if (groupSpecs != null) {
dumper.display("group by ");
for (int i = 0; i < groupSpecs.length; i++) {
if (i > 0)
{dumper.display(", ");}
dumper.display(groupSpecs[i].getGroupExpression().toString());
dumper.display(" as ");
dumper.display("$").display(groupSpecs[i].getKeyVarName());
}
dumper.nl();
}
}
@Override
public void resetState(boolean postOptimization) {
super.resetState(postOptimization);
stack.clear();
returnExpr.resetState(postOptimization);
for (GroupSpec spec: groupSpecs) {
spec.resetState(postOptimization);
}
}
@Override
public void accept(ExpressionVisitor visitor) {
visitor.visitGroupByClause(this);
}
/**
* Compare keys using the collator given in the group spec. Used to
* sort keys into the grouping map.
*/
private int compareKeys(List s1, List s2) {
final int c1 = s1.size();
final int c2 = s2.size();
if (c1 == c2) {
try {
for (int i = 0; i < c1; i++) {
final AtomicValue v1 = s1.get(i);
final AtomicValue v2 = s2.get(i);
final Collator collator = groupSpecs[i].getCollator();
final int r = v1.compareTo(collator, v2);
if (r != Constants.EQUAL) {
return r;
}
}
return Constants.EQUAL;
} catch (XPathException e) {
return Constants.INFERIOR;
}
}
return c1 < c2 ? Constants.INFERIOR : Constants.SUPERIOR;
}
private boolean usesDefaultCollator() {
return Stream.of(groupSpecs).allMatch(spec -> spec.getCollator() == null);
}
static class Tuple extends HashMap {
private final List groupingValues;
public Tuple(final List groupingValues) {
super();
this.groupingValues = groupingValues;
}
public void add(final QName name, final Sequence val) throws XPathException {
Sequence seq = get(name);
if (seq == null) {
final ValueSequence temp = new ValueSequence(val.getItemCount());
temp.addAll(val);
put(name, temp);
} else {
seq.addAll(val);
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy