/*
* 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 java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import com.addthis.basis.util.LessStrings;
import com.addthis.bundle.core.Bundle;
import com.addthis.bundle.core.BundleField;
import com.addthis.bundle.core.BundleFormat;
import com.addthis.bundle.core.BundleFormatted;
import com.addthis.bundle.core.list.ListBundle;
import com.addthis.bundle.core.list.ListBundleFormat;
import com.addthis.bundle.util.BundleColumnBinder;
import com.addthis.bundle.value.ValueFactory;
import com.addthis.bundle.value.ValueObject;
import com.addthis.hydra.data.query.AbstractQueryOp;
import io.netty.channel.ChannelProgressivePromise;
/**
* <b>args</b>: mergekeycolumn,mergekeycolumn:foldcolumn:foldkey,foldkey:copykeycolumn,copykeycolumn
* <p/>
* a fold operation is similar to a merge operation in that it performs a
* specialized type of merge on rows with contiguous matching (compound) keys.
* fold differs from merge in that it copies a set of column values associated
* with a column key. once all matching rows are found, a single accumulated
* row is emitted.
* <p/>
* for example, fold can turn:
* <p/>
* <table border=1>
* <tr><td>dog<td>red<td>3
* <tr><td>dog<td>green<td>4
* <tr><td>cat<td>red<td>1
* <tr><td>cat<td>green<td>2
* </table>
* <p/>
* into
* <p/>
* <table border=1>
* <tr><td>dog<td>red<td>3<td>green<td>4
* <tr><td>cat<td>red<td>1<td>green<td>2
* </table>
* <p/>
* where <b>dog/cat</b> are the values of the key column(s),
* <b>red/green</b> are the values of the fold column and
* <b>1,2,3,4</b> are the values of the folded column(s).
*
* @user-reference
* @hydra-name fold
*/
public class OpFold extends AbstractQueryOp implements BundleFormatted {
private final BundleField[] outputFields;
private final ListBundleFormat format;
private final String[] keycols;
private final String foldcol;
private final String[] foldvals;
private final String[] copycols;
private final String[] inputFields;
private Bundle folded;
private String lastkey;
public OpFold(String args, ChannelProgressivePromise queryPromise) {
super(queryPromise);
String[] seg = LessStrings.splitArray(args, ":");
keycols = LessStrings.splitArray(seg[0], ",");
foldcol = seg[1];
foldvals = LessStrings.splitArray(seg[2], ",");
copycols = LessStrings.splitArray(seg[3], ",");
List<String> cols = new ArrayList<>(keycols.length + copycols.length + 1);
// capture input fields
Collections.addAll(cols, keycols);
cols.add(foldcol);
Collections.addAll(cols, copycols);
inputFields = cols.toArray(new String[cols.size()]);
// generate output format
format = new ListBundleFormat();
outputFields = new BundleField[keycols.length + ((copycols.length + 1) * foldvals.length)];
for (int i = 0; i < outputFields.length; i++) {
outputFields[i] = format.getField(Integer.toString(i));
}
}
@Override
public BundleFormat getFormat() {
return format;
}
@Override
public void send(Bundle row) {
BundleColumnBinder inputBinder = getSourceColumnBinder(row, inputFields);
Bundle retval = null;
String key = "";
for (int i = 0; i < keycols.length; i++) {
ValueObject o = inputBinder.getColumn(row, i);
if (o != null) {
key = key.concat(o.toString());
}
}
if (folded == null || (lastkey != null && !lastkey.equals(key))) {
if (folded != null) {
retval = folded;
}
folded = new ListBundle(format);
for (BundleField nullField : outputFields) {
folded.setValue(nullField, null);
}
for (int i = 0; i < keycols.length; i++) {
folded.setValue(outputFields[i], inputBinder.getColumn(row, i));
}
for (int i = 0; i < foldvals.length; i++) {
folded.setValue(outputFields[keycols.length + (i * (copycols.length + 1))], ValueFactory.create(foldvals[i]));
}
}
lastkey = key;
String foldcolval = inputBinder.getColumn(row, keycols.length).toString();
for (int i = 0; foldcolval != null && i < foldvals.length; i++) {
if (foldvals[i].equals(foldcolval)) {
int offset = keycols.length + (i * (copycols.length + 1));
for (int j = 0; j < copycols.length; j++) {
folded.setValue(outputFields[offset + j + 1], inputBinder.getColumn(row, keycols.length + 1 + j));
}
break;
}
}
if (retval != null) {
getNext().send(retval);
}
}
@Override
public void sendComplete() {
if (folded != null) {
getNext().send(folded);
}
getNext().sendComplete();
}
}