/*
* 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.Comparator;
import java.util.StringTokenizer;
import com.addthis.basis.util.LessStrings;
import com.addthis.bundle.core.Bundle;
import com.addthis.bundle.core.BundleField;
import com.addthis.bundle.table.DataTable;
import com.addthis.bundle.table.DataTableFactory;
import com.addthis.bundle.util.BundleColumnBinder;
import com.addthis.bundle.util.ValueUtil;
import com.addthis.bundle.value.ValueObject;
import com.addthis.hydra.data.query.AbstractTableOp;
import io.netty.channel.ChannelProgressivePromise;
/**
* <p>This query operation <span class="hydra-summary">performs an in-memory sort that uses disk
* when necessary</span>.
* <p/>
* <p>The syntax is sort=[cols]:[type]:[direction]. [cols] is one or more columns
* separated by commas. Type is a sequence of
* ["i" or "l" or "n"] for integer number, ["d" or "f"] for floating point number, or "s" for string.
* Direction is a sequence of either "a" for ascending or "d" for descending.
* The columns are delimited by commas and the types and directions have
* no delimiter. The lengths
* of [type] and [direction] must be equal to the number of columns specified.
* <p/>
* <p>Example:</p>
* <pre>
* 0 A 3
* 1 A 1
* 1 B 2
* 0 A 5
*
* sort=0,1,2:nsn:add
*
* 0 A 5
* 0 A 3
* 1 B 2
* 1 A 1
* </pre>
*
* @user-reference
* @hydra-name sort
*/
public class OpSort extends AbstractTableOp {
private final String[] cols;
private final char[] type;
private final char[] dir;
public OpSort(DataTableFactory factory, String args, ChannelProgressivePromise queryPromise) {
super(factory, queryPromise);
StringTokenizer st = new StringTokenizer(args, ":");
cols = LessStrings.splitArray(st.hasMoreElements() ? st.nextToken() : "0", ",");
String ts = st.hasMoreElements() ? st.nextToken() : "s";
while (ts.length() < cols.length) {
ts = ts.concat(ts.substring(0, 1));
}
type = ts.toCharArray();
String ds = st.hasMoreElements() ? st.nextToken() : "a";
while (ds.length() < cols.length) {
ds = ds.concat(ds.substring(0, 1));
}
dir = ds.toCharArray();
}
@Override
public DataTable tableOp(final DataTable result) {
result.sort(new Comparator<Bundle>() {
// TODO temp hack b/c table appends are BROKEN ATM WRT table.getFormat()
// private final BundleField columns[] = new BundleColumnBinder(result, cols).getFields();
private BundleField[] columns;
public int compare(Bundle o1, Bundle o2) {
if (columns == null) {
columns = new BundleColumnBinder(o1, cols).getFields();
}
int delta = 0;
for (int i = 0; i < columns.length; i++) {
BundleField col = columns[i];
if (delta == 0) {
switch (type[i]) {
case 'i': // int
case 'l': // long
case 'n': // legacy "number"
delta = longCompare(o1.getValue(col), o2.getValue(col));
break;
case 'd': // double
case 'f': // float
delta = doubleCompare(o1.getValue(col), o2.getValue(col));
break;
case 's': // string
default:
delta = stringCompare(o1.getValue(col), o2.getValue(col));
break;
}
if (dir[i] == 'd') {
delta = -delta;
}
} else {
break;
}
}
return delta;
}
});
return result;
}
/**
* @param s1
* @param s2
* @return
*/
private int longCompare(ValueObject s1, ValueObject s2) {
if (s1 == s2) {
return 0;
}
if (s1 == null) {
return 1;
}
if (s2 == null) {
return -1;
}
return Long.compare(ValueUtil.asNumberOrParseLong(s1, 10).asLong().getLong(),
ValueUtil.asNumberOrParseLong(s2, 10).asLong().getLong());
}
/**
* @param s1
* @param s2
* @return
*/
private int doubleCompare(ValueObject s1, ValueObject s2) {
if (s1 == s2) {
return 0;
}
if (s1 == null) {
return 1;
}
if (s2 == null) {
return -1;
}
return Double.compare(ValueUtil.asNumberOrParseDouble(s1).asDouble().getDouble(),
ValueUtil.asNumberOrParseDouble(s2).asDouble().getDouble());
}
/**
* @param s1
* @param s2
* @return
*/
private int stringCompare(ValueObject s1, ValueObject s2) {
if (s1 == s2) {
return 0;
}
if (s1 == null) {
return 1;
}
if (s2 == null) {
return -1;
}
return s1.toString().compareTo(s2.toString());
}
}