package org.basex.query.flwor; import static org.basex.query.util.Err.*; import org.basex.query.QueryContext; import org.basex.query.QueryException; import org.basex.query.expr.Expr; import org.basex.query.item.Item; import org.basex.query.item.Value; import org.basex.query.iter.ItemCache; import org.basex.query.iter.Iter; import org.basex.query.util.ValueList; import org.basex.query.util.Var; import org.basex.util.InputInfo; import org.basex.util.hash.IntMap; import org.basex.util.list.IntList; import org.basex.util.list.ObjList; /** * Stores the grouping for a group by clause. * * @author BaseX Team 2005-12, BSD License * @author Michael Seiferle */ final class GroupPartition { /** Input information. */ private final InputInfo input; /** Order by specifier. */ private final Order order; /** Grouping variables. */ private final Var[] gv; /** Non-grouping variables. */ private final Var[][] ngv; /** Group partitioning. */ private final ObjList<GroupNode> part = new ObjList<GroupNode>(); /** Resulting sequence for non-grouping variables. */ private final ObjList<ItemCache[]> items; /** HashValue, position (with overflow bucket). */ private final IntMap<IntList> hashes = new IntMap<IntList>(); /** * Sets up an empty partitioning. * Sets up the ordering scheme. * @param g grouping variables * @param ng non-grouping variables * @param ob order by specifier * @param ii input info */ GroupPartition(final Var[] g, final Var[][] ng, final Order ob, final InputInfo ii) { gv = g; ngv = ng; order = ob; items = ngv[0].length != 0 ? new ObjList<ItemCache[]>() : null; input = ii; } /** * Adds the current grouping variable binding to the partitioning scheme. * Then the resulting non-grouping variable item sequence is built for each * candidate. * Searches the known partition hashes {@link GroupPartition#hashes} for * potential matches and checks them for equivalence. * The GroupNode candidate is ignored if it exists otherwise added to the * partitioning scheme. * @param ctx QueryContext * @throws QueryException exception */ void add(final QueryContext ctx) throws QueryException { final int gl = gv.length; final Value[] vals = new Value[gl]; for(int i = 0; i < gl; i++) { final Value val = ctx.value(ctx.vars.get(gv[i])); if(val.size() > 1) XGRP.thrw(input); vals[i] = val; } final GroupNode gn = new GroupNode(input, vals); final int h = gn.hash(); final IntList ps = hashes.get(h); int p = -1; if(ps != null) { for(int i = 0; i < ps.size(); ++i) { final int pp = ps.get(i); if(gn.eq(part.get(pp))) { p = pp; break; } } } if(p < 0) { p = part.size(); part.add(gn); IntList pos = hashes.get(h); if(pos == null) { pos = new IntList(1); hashes.add(h, pos); } pos.add(p); } final int ngl = ngv[0].length; // no non-grouping variables exist if(ngl == 0) return; // adds the current non-grouping variable bindings to the p-th partition. if(p == items.size()) items.add(new ItemCache[ngl]); final ItemCache[] sq = items.get(p); for(int i = 0; i < ngl; ++i) { ItemCache ic = sq[i]; final Value result = ctx.value(ctx.vars.get(ngv[0][i])); if(ic == null) { ic = new ItemCache(); sq[i] = ic; } ic.add(result); } } /** * Returns grouped variables. * @param ctx query context * @param ret return expression * @param ks key list * @param vs value list * @return iterator on the result set * @throws QueryException query exception */ Iter ret(final QueryContext ctx, final Expr ret, final ObjList<Item[]> ks, final ValueList vs) throws QueryException { final ItemCache ic = new ItemCache(); for(int i = 0; i < part.size(); ++i) { final GroupNode gn = part.get(i); for(int j = 0; j < gv.length; ++j) ctx.vars.add(gv[j].copy().bind(gn.vals[j], ctx)); if(items != null) { final ItemCache[] ii = items.get(i); for(int j = 0; j < ii.length; ++j) { ctx.vars.add(ngv[1][j].copy().bind(ii[j].value(), ctx)); } } if(order != null) { order.add(ctx, ret, ks, vs); } else ic.add(ctx.value(ret)); } return order != null ? ctx.iter(order.set(ks, vs)) : ic; } }