
oracle.nosql.driver.query.SFWIter Maven / Gradle / Ivy
/*-
* Copyright (c) 2011, 2020 Oracle and/or its affiliates. All rights reserved.
*
* Licensed under the Universal Permissive License v 1.0 as shown at
* https://oss.oracle.com/licenses/upl/
*/
package oracle.nosql.driver.query;
import java.io.IOException;
import oracle.nosql.driver.values.FieldValue;
import oracle.nosql.driver.values.MapValue;
import oracle.nosql.driver.values.NullValue;
import oracle.nosql.driver.util.ByteInputStream;
import oracle.nosql.driver.util.SerializationUtil;
/**
* SFWIter is used for:
* (a) project out result columns that do not appear in the SELECT list of
* the query, but are included in the results fetched from the proxy,
* because the are order-by columns or primary-key columns used for
* duplicate elimination.
* (b) For group-by and aggregation queries, regroup and reaggregate the
* partial gropus/aggregates received from the proxy.
* (c) implement offset and limit.
*/
public class SFWIter extends PlanIter {
public static class SFWIterState extends PlanIterState {
private long theOffset;
private long theLimit;
private long theNumResults;
private FieldValue[] theGBTuple;
private boolean theHaveGBTuple;
SFWIterState(SFWIter iter) {
theGBTuple = new FieldValue[iter.theColumnIters.length];
}
@Override
public void reset(PlanIter iter) {
super.reset(iter);
theNumResults = 0;
theHaveGBTuple = false;
}
}
private final PlanIter theFromIter;
private final String theFromVarName;
private final PlanIter[] theColumnIters;
private final String[] theColumnNames;
private boolean theIsSelectStar;
private final int theNumGBColumns;
private final PlanIter theOffsetIter;
private final PlanIter theLimitIter;
SFWIter(ByteInputStream in, short serialVersion) throws IOException {
super(in, serialVersion);
theColumnNames = SerializationUtil.readStringArray(in);
theNumGBColumns = in.readInt();
theFromVarName = SerializationUtil.readString(in);
theIsSelectStar = in.readBoolean();
theColumnIters = deserializeIters(in, serialVersion);
theFromIter = deserializeIter(in, serialVersion);
theOffsetIter = deserializeIter(in, serialVersion);
theLimitIter = deserializeIter(in, serialVersion);
}
@Override
public PlanIterKind getKind() {
return PlanIterKind.SFW;
}
@Override
PlanIter getInputIter() {
return theFromIter;
}
@Override
public void open(RuntimeControlBlock rcb) {
SFWIterState state = new SFWIterState(this);
rcb.setState(theStatePos, state);
theFromIter.open(rcb);
for (PlanIter columnIter : theColumnIters) {
columnIter.open(rcb);
}
computeOffsetLimit(rcb);
}
@Override
public boolean next(RuntimeControlBlock rcb) {
SFWIterState state = (SFWIterState)rcb.getState(theStatePos);
if (state.isDone()) {
return false;
}
if (state.theNumResults >= state.theLimit) {
state.done();
return false;
}
/* while loop for skipping offset results */
while (true) {
boolean more = computeNextResult(rcb, state);
if (!more) {
return false;
}
/*
* Even though we have a result, the state may be DONE. This is the
* case when the result is the last group tuple in a grouping SFW.
* In this case, if we have not reached the offset yet, we should
* ignore this result and return false.
*/
if (state.isDone() && state.theOffset > 0) {
return false;
}
if (state.theOffset == 0) {
++state.theNumResults;
break;
}
--state.theOffset;
}
return true;
}
boolean computeNextResult(RuntimeControlBlock rcb, SFWIterState state) {
/* while loop for group by */
while (true) {
boolean more = theFromIter.next(rcb);
if (!more) {
if (!rcb.reachedLimit()) {
state.done();
}
if (theNumGBColumns >= 0) {
return produceLastGroup(rcb, state);
}
return false;
}
/*
* Compute the exprs in the SELECT list. If this is a grouping
* SFW, compute only the group-by columns. However, skip this
* computation if this is not a grouping SFW and it has an offset
* that has not been reached yet.
*/
if (theNumGBColumns < 0 && state.theOffset > 0) {
return true;
}
int numCols = (theNumGBColumns >= 0 ?
theNumGBColumns :
theColumnIters.length);
int i = 0;
for (i = 0; i < numCols; ++i) {
PlanIter columnIter = theColumnIters[i];
more = columnIter.next(rcb);
if (!more) {
if (theNumGBColumns > 0) {
columnIter.reset(rcb);
break;
}
rcb.setRegVal(columnIter.getResultReg(),
NullValue.getInstance());
} else {
if (rcb.getTraceLevel() >= 3) {
rcb.trace("SFW: Value for SFW column " + i + " = " +
rcb.getRegVal(columnIter.getResultReg()));
}
}
columnIter.reset(rcb);
}
if (i < numCols) {
continue;
}
if (theNumGBColumns < 0) {
if (theIsSelectStar) {
break;
}
MapValue result = new MapValue();
rcb.setRegVal(theResultReg, result);
for (i = 0; i < theColumnIters.length; ++i) {
PlanIter columnIter = theColumnIters[i];
FieldValue value = rcb.getRegVal(columnIter.getResultReg());
result.put(theColumnNames[i], value);
}
break;
}
if (groupInputTuple(rcb, state)) {
break;
}
}
return true;
}
/*
* This method checks whether the current input tuple (a) starts the
* first group, i.e. it is the very 1st tuple in the input stream, or
* (b) belongs to the current group, or (c) starts a new group otherwise.
* The method returns true in case (c), indicating that an output tuple
* is ready to be returned to the consumer of this SFW. Otherwise, false
* is returned.
*/
boolean groupInputTuple(RuntimeControlBlock rcb, SFWIterState state) {
int numCols = theColumnIters.length;
/*
* If this is the very first input tuple, start the first group and
* go back to compute next input tuple.
*/
if (!state.theHaveGBTuple) {
for (int i = 0; i < theNumGBColumns; ++i) {
state.theGBTuple[i] = rcb.getRegVal(
theColumnIters[i].getResultReg());
}
for (int i = theNumGBColumns; i < numCols; ++i) {
theColumnIters[i].next(rcb);
theColumnIters[i].reset(rcb);
}
state.theHaveGBTuple = true;
if (rcb.getTraceLevel() >= 2) {
rcb.trace("SFW: Started first group:");
traceCurrentGroup(rcb, state);
}
return false;
}
/*
* Compare the current input tuple with the current group tuple.
*/
int j;
for (j = 0; j < theNumGBColumns; ++j) {
FieldValue newval = rcb.getRegVal(theColumnIters[j].getResultReg());
FieldValue curval = state.theGBTuple[j];
if (!newval.equals(curval)) {
break;
}
}
/*
* If the input tuple is in current group, update the aggregate
* functions and go back to compute the next input tuple.
*/
if (j == theNumGBColumns) {
if (rcb.getTraceLevel() >= 2) {
rcb.trace("SFW: Input tuple belongs to current group:");
traceCurrentGroup(rcb, state);
}
for (int i = theNumGBColumns; i < numCols; ++i) {
theColumnIters[i].next(rcb);
theColumnIters[i].reset(rcb);
}
return false;
}
/*
* Input tuple starts new group. We must finish up the current group,
* produce a result (output tuple) from it, and init the new group.
*/
// 1. Get the final aggregate values for the current group and store
// them in theGBTuple.
for (int i = theNumGBColumns; i < numCols; ++i) {
state.theGBTuple[i] = theColumnIters[i].getAggrValue(rcb, true);
}
// 2. Create a result MapValue out of the GB tuple
MapValue result = new MapValue();
rcb.setRegVal(theResultReg, result);
for (int i = 0; i < theColumnIters.length; ++i) {
result.put(theColumnNames[i], state.theGBTuple[i]);
}
if (rcb.getTraceLevel() >= 2) {
rcb.trace("SFW: Current group done: " + result);
}
// 3. Put the values of the grouping columns into the GB tuple
for (int i = 0; i < theNumGBColumns; ++i) {
PlanIter columnIter = theColumnIters[i];
state.theGBTuple[i] = rcb.getRegVal(columnIter.getResultReg());
}
// 4. Compute the values of the aggregate functions.
for (int i = theNumGBColumns; i < numCols; ++i) {
theColumnIters[i].next(rcb);
theColumnIters[i].reset(rcb);
}
if (rcb.getTraceLevel() >= 2) {
rcb.trace("SFW: Started new group:");
traceCurrentGroup(rcb, state);
}
return true;
}
boolean produceLastGroup(RuntimeControlBlock rcb, SFWIterState state) {
if (rcb.reachedLimit()) {
return false;
}
/*
* If there is no group, return false.
*/
if (!state.theHaveGBTuple) {
return false;
}
MapValue result = new MapValue();
rcb.setRegVal(theResultReg, result);
for (int i = 0; i < theNumGBColumns; ++i) {
result.put(theColumnNames[i], state.theGBTuple[i]);
}
for (int i = theNumGBColumns; i < theColumnIters.length; ++i) {
result.put(theColumnNames[i],
theColumnIters[i].getAggrValue(rcb, true));
}
if (rcb.getTraceLevel() >= 2) {
rcb.trace("SFW: Produced last group : " + result);
}
return true;
}
@Override
public void reset(RuntimeControlBlock rcb) {
theFromIter.reset(rcb);
for (PlanIter columnIter : theColumnIters) {
columnIter.reset(rcb);
}
if (theOffsetIter != null) {
theOffsetIter.reset(rcb);
}
if (theLimitIter != null) {
theLimitIter.reset(rcb);
}
SFWIterState state = (SFWIterState)rcb.getState(theStatePos);
state.reset(this);
computeOffsetLimit(rcb);
}
@Override
public void close(RuntimeControlBlock rcb) {
SFWIterState state = (SFWIterState)rcb.getState(theStatePos);
if (state == null) {
return;
}
theFromIter.close(rcb);
for (PlanIter columnIter : theColumnIters) {
columnIter.close(rcb);
}
if (theOffsetIter != null) {
theOffsetIter.close(rcb);
}
if (theLimitIter != null) {
theLimitIter.close(rcb);
}
state.close();
}
private void computeOffsetLimit(RuntimeControlBlock rcb) {
SFWIterState state = (SFWIterState)rcb.getState(theStatePos);
long offset = 0;
long limit = -1;
if (theOffsetIter != null) {
theOffsetIter.open(rcb);
theOffsetIter.next(rcb);
FieldValue val = rcb.getRegVal(theOffsetIter.getResultReg());
offset = val.getLong();
if (offset < 0) {
throw new QueryException(
"Offset can not be a negative number",
theOffsetIter.theLocation);
}
if (offset > Integer.MAX_VALUE) {
throw new QueryException(
"Offset can not be greater than Integer.MAX_VALUE",
theOffsetIter.theLocation);
}
}
if (theLimitIter != null) {
theLimitIter.open(rcb);
theLimitIter.next(rcb);
FieldValue val = rcb.getRegVal(theLimitIter.getResultReg());
limit = val.getLong();
if (limit < 0) {
throw new QueryException(
"Limit can not be a negative number",
theLimitIter.theLocation);
}
if (limit > Integer.MAX_VALUE) {
throw new QueryException(
"Limit can not be greater than Integer.MAX_VALUE",
theOffsetIter.theLocation);
}
}
if (limit < 0) {
limit = Long.MAX_VALUE;
}
state.theOffset = offset;
state.theLimit = limit;
}
@Override
protected void displayContent(StringBuilder sb, QueryFormatter formatter) {
formatter.indent(sb);
sb.append("FROM:\n");
theFromIter.display(sb, formatter);
sb.append(" as");
sb.append(" " + theFromVarName);
sb.append("\n\n");
if (theNumGBColumns >= 0) {
formatter.indent(sb);
sb.append("GROUP BY:\n");
formatter.indent(sb);
if (theNumGBColumns == 0) {
sb.append("No grouping expressions");
} else if (theNumGBColumns == 1) {
sb.append(
"Grouping by the first expression in the SELECT list");
} else {
sb.append("Grouping by the first " + theNumGBColumns +
" expressions in the SELECT list");
}
sb.append("\n\n");
}
formatter.indent(sb);
sb.append("SELECT:\n");
for (int i = 0; i < theColumnIters.length; ++i) {
theColumnIters[i].display(sb, formatter);
if (i < theColumnIters.length - 1) {
sb.append(",\n");
}
}
if (theOffsetIter != null) {
sb.append("\n\n");
formatter.indent(sb);
sb.append("OFFSET:\n");
theOffsetIter.display(sb, formatter);
}
if (theLimitIter != null) {
sb.append("\n\n");
formatter.indent(sb);
sb.append("LIMIT:\n");
theLimitIter.display(sb, formatter);
}
}
void traceCurrentGroup(RuntimeControlBlock rcb, SFWIterState state) {
for (int i = 0; i < theNumGBColumns; ++i) {
rcb.trace("SFW: Val " + i + " = " + state.theGBTuple[i]);
}
for (int i = theNumGBColumns; i < theColumnIters.length; ++i) {
rcb.trace("SFW: Val " + i + " = " +
theColumnIters[i].getAggrValue(rcb, false));
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy