/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.addthis.hydra.data.query.op;
import com.addthis.basis.util.LessStrings;
import com.addthis.bundle.channel.DataChannelError;
import com.addthis.bundle.core.Bundle;
import com.addthis.hydra.data.query.AbstractQueryOp;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.netty.channel.ChannelProgressivePromise;
/**
* This query operation <span class="hydra-summary">limits the number of rows that are generated</span>.
* <p/>
* There are two forms of this operation. "sendCount=N" limits the number of rows that
* are emitted to N rows. "sendCount=M:N" skips over the first M rows and then limits the
* number of rows that are emitted to N rows.
*
* @user-reference
* @hydra-name limit
*/
public class OpLimit extends AbstractQueryOp {
private static final Logger log = LoggerFactory.getLogger(OpLimit.class);
/** used to ensure sendComplete is only called once; not needed in theory, but unclear */
private boolean done;
private int sendCount;
private int skipCount;
private final int originalSkipCount;
public OpLimit(int sendCount, int skipCount, ChannelProgressivePromise queryPromise) {
super(queryPromise);
this.originalSkipCount = sendCount;
this.skipCount = skipCount;
this.sendCount = sendCount;
if (originalSkipCount <= 0) {
throw new IllegalArgumentException("sendCount must be > 0");
}
}
public OpLimit(String args, ChannelProgressivePromise queryPromise) {
super(queryPromise);
String[] v = LessStrings.splitArray(args, ":");
if (v.length == 1) {
this.originalSkipCount = Integer.parseInt(v[0]);
this.skipCount = 0;
} else if (v.length == 2) {
this.originalSkipCount = Integer.parseInt(v[1]);
this.skipCount = Integer.parseInt(v[0]);
} else {
throw new IllegalArgumentException("OpLimit requires [1,2] integer parameters");
}
this.sendCount = originalSkipCount;
if (originalSkipCount <= 0) {
throw new IllegalArgumentException("sendCount must be > 0");
}
}
@Override
public void send(Bundle row) throws DataChannelError {
// skipCount bundles until skipCount is reached
if (skipCount > 0) {
skipCount--;
return;
}
// emit bundles until sendCount is reached
if (sendCount > 0) {
sendCount--;
getNext().send(row);
// if we just emitted the last bundle, complete the operation
if (sendCount == 0) {
sendComplete();
opPromise.trySuccess(); // best-effort signal to parent op-processor and source
log.debug("OpLimit: sendCount reached {} and sendComplete has been called",
originalSkipCount);
}
} else {
// TODO: signal to sender so they don't have to pro-actively check the promise
log.trace("received bundle after sendCount reached; possibly expected to some extent");
}
}
@Override
public void sendComplete() {
if (!done) {
done = true;
getNext().sendComplete();
}
}
}