Adding built-in sorting capability to _cat apis. (#20658)
* Adding built-in sorting capability to _cat apis. Closes #16975 * addressing pr comments * changing value types back to original implementation and fixing cosmetic issues * Changing compareTo, hashCode of value types to a better implementation * Changed value compareTos to use Double.compare instead of if statements + fixed some failed unit tests
This commit is contained in:
parent
3b578db365
commit
10ddcc4616
|
@ -30,8 +30,6 @@ import java.util.concurrent.TimeUnit;
|
|||
|
||||
import static java.util.Collections.emptyMap;
|
||||
|
||||
/**
|
||||
*/
|
||||
public class Table {
|
||||
|
||||
private List<Cell> headers = new ArrayList<>();
|
||||
|
@ -197,6 +195,22 @@ public class Table {
|
|||
return null;
|
||||
}
|
||||
|
||||
public Map<String, String> getAliasMap() {
|
||||
Map<String, String> headerAliasMap = new HashMap<>();
|
||||
for (int i = 0; i < headers.size(); i++) {
|
||||
Cell headerCell = headers.get(i);
|
||||
String headerName = headerCell.value.toString();
|
||||
if (headerCell.attr.containsKey("alias")) {
|
||||
String[] aliases = Strings.splitStringByCommaToArray(headerCell.attr.get("alias"));
|
||||
for (String alias : aliases) {
|
||||
headerAliasMap.put(alias, headerName);
|
||||
}
|
||||
}
|
||||
headerAliasMap.put(headerName, headerName);
|
||||
}
|
||||
return headerAliasMap;
|
||||
}
|
||||
|
||||
public static class Cell {
|
||||
public final Object value;
|
||||
public final Map<String, String> attr;
|
||||
|
|
|
@ -269,4 +269,4 @@ public enum ByteSizeUnit implements Writeable {
|
|||
public static ByteSizeUnit readFrom(StreamInput in) throws IOException {
|
||||
return ByteSizeUnit.fromId(in.readVInt());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,7 +29,7 @@ import java.io.IOException;
|
|||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
|
||||
public class ByteSizeValue implements Writeable {
|
||||
public class ByteSizeValue implements Writeable, Comparable<ByteSizeValue> {
|
||||
|
||||
private final long size;
|
||||
private final ByteSizeUnit unit;
|
||||
|
@ -191,15 +191,18 @@ public class ByteSizeValue implements Writeable {
|
|||
return false;
|
||||
}
|
||||
|
||||
ByteSizeValue sizeValue = (ByteSizeValue) o;
|
||||
|
||||
return getBytes() == sizeValue.getBytes();
|
||||
return compareTo((ByteSizeValue) o) == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = Long.hashCode(size);
|
||||
result = 31 * result + (unit != null ? unit.hashCode() : 0);
|
||||
return result;
|
||||
return Double.hashCode(((double) size) * unit.toBytes(1));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(ByteSizeValue other) {
|
||||
double thisValue = ((double) size) * unit.toBytes(1);
|
||||
double otherValue = ((double) other.size) * other.unit.toBytes(1);
|
||||
return Double.compare(thisValue, otherValue);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@ import org.elasticsearch.common.io.stream.Writeable;
|
|||
|
||||
import java.io.IOException;
|
||||
|
||||
public class SizeValue implements Writeable {
|
||||
public class SizeValue implements Writeable, Comparable<SizeValue> {
|
||||
|
||||
private final long size;
|
||||
private final SizeUnit sizeUnit;
|
||||
|
@ -201,18 +201,18 @@ public class SizeValue implements Writeable {
|
|||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
SizeValue sizeValue = (SizeValue) o;
|
||||
|
||||
if (size != sizeValue.size) return false;
|
||||
if (sizeUnit != sizeValue.sizeUnit) return false;
|
||||
|
||||
return true;
|
||||
return compareTo((SizeValue) o) == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = Long.hashCode(size);
|
||||
result = 31 * result + (sizeUnit != null ? sizeUnit.hashCode() : 0);
|
||||
return result;
|
||||
return Double.hashCode(((double) size) * sizeUnit.toSingles(1));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(SizeValue other) {
|
||||
double thisValue = ((double) size) * sizeUnit.toSingles(1);
|
||||
double otherValue = ((double) other.size) * other.sizeUnit.toSingles(1);
|
||||
return Double.compare(thisValue, otherValue);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,7 +39,7 @@ import java.util.Objects;
|
|||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class TimeValue implements Writeable {
|
||||
public class TimeValue implements Writeable, Comparable<TimeValue> {
|
||||
|
||||
/** How many nano-seconds in one milli-second */
|
||||
public static final long NSEC_PER_MSEC = TimeUnit.NANOSECONDS.convert(1, TimeUnit.MILLISECONDS);
|
||||
|
@ -381,17 +381,22 @@ public class TimeValue implements Writeable {
|
|||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
TimeValue timeValue = (TimeValue) o;
|
||||
return timeUnit.toNanos(duration) == timeValue.timeUnit.toNanos(timeValue.duration);
|
||||
return this.compareTo(((TimeValue) o)) == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
long normalized = timeUnit.toNanos(duration);
|
||||
return Long.hashCode(normalized);
|
||||
return Double.hashCode(((double) duration) * timeUnit.toNanos(1));
|
||||
}
|
||||
|
||||
public static long nsecToMSec(long ns) {
|
||||
return ns / NSEC_PER_MSEC;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(TimeValue timeValue) {
|
||||
double thisValue = ((double) duration) * timeUnit.toNanos(1);
|
||||
double otherValue = ((double) timeValue.duration) * timeValue.timeUnit.toNanos(1);
|
||||
return Double.compare(thisValue, otherValue);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,8 +38,12 @@ import org.elasticsearch.rest.RestStatus;
|
|||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public class RestTable {
|
||||
|
@ -59,13 +63,13 @@ public class RestTable {
|
|||
List<DisplayHeader> displayHeaders = buildDisplayHeaders(table, request);
|
||||
|
||||
builder.startArray();
|
||||
for (int row = 0; row < table.getRows().size(); row++) {
|
||||
List<Integer> rowOrder = getRowOrder(table, request);
|
||||
for (Integer row : rowOrder) {
|
||||
builder.startObject();
|
||||
for (DisplayHeader header : displayHeaders) {
|
||||
builder.field(header.display, renderValue(request, table.getAsMap().get(header.name).get(row).value));
|
||||
}
|
||||
builder.endObject();
|
||||
|
||||
}
|
||||
builder.endArray();
|
||||
return new BytesRestResponse(RestStatus.OK, builder);
|
||||
|
@ -92,7 +96,10 @@ public class RestTable {
|
|||
}
|
||||
out.append("\n");
|
||||
}
|
||||
for (int row = 0; row < table.getRows().size(); row++) {
|
||||
|
||||
List<Integer> rowOrder = getRowOrder(table, request);
|
||||
|
||||
for (Integer row: rowOrder) {
|
||||
for (int col = 0; col < headers.size(); col++) {
|
||||
DisplayHeader header = headers.get(col);
|
||||
boolean isLastColumn = col == lastHeader;
|
||||
|
@ -107,6 +114,38 @@ public class RestTable {
|
|||
return new BytesRestResponse(RestStatus.OK, BytesRestResponse.TEXT_CONTENT_TYPE, bytesOut.bytes());
|
||||
}
|
||||
|
||||
static List<Integer> getRowOrder(Table table, RestRequest request) {
|
||||
String[] columnOrdering = request.paramAsStringArray("s", null);
|
||||
|
||||
List<Integer> rowOrder = new ArrayList<>();
|
||||
for (int i = 0; i < table.getRows().size(); i++) {
|
||||
rowOrder.add(i);
|
||||
}
|
||||
|
||||
if (columnOrdering != null) {
|
||||
Map<String, String> headerAliasMap = table.getAliasMap();
|
||||
List<ColumnOrderElement> ordering = new ArrayList<>();
|
||||
for (int i = 0; i < columnOrdering.length; i++) {
|
||||
String columnHeader = columnOrdering[i];
|
||||
boolean reverse = false;
|
||||
if (columnHeader.endsWith(":desc")) {
|
||||
columnHeader = columnHeader.substring(0, columnHeader.length() - ":desc".length());
|
||||
reverse = true;
|
||||
} else if (columnHeader.endsWith(":asc")) {
|
||||
columnHeader = columnHeader.substring(0, columnHeader.length() - ":asc".length());
|
||||
}
|
||||
if (headerAliasMap.containsKey(columnHeader)) {
|
||||
ordering.add(new ColumnOrderElement(headerAliasMap.get(columnHeader), reverse));
|
||||
} else {
|
||||
throw new UnsupportedOperationException(
|
||||
String.format(Locale.ROOT, "Unable to sort by unknown sort key `%s`", columnHeader));
|
||||
}
|
||||
}
|
||||
Collections.sort(rowOrder, new TableIndexComparator(table, ordering));
|
||||
}
|
||||
return rowOrder;
|
||||
}
|
||||
|
||||
static List<DisplayHeader> buildDisplayHeaders(Table table, RestRequest request) {
|
||||
List<DisplayHeader> display = new ArrayList<>();
|
||||
if (request.hasParam("h")) {
|
||||
|
@ -368,4 +407,71 @@ public class RestTable {
|
|||
this.display = display;
|
||||
}
|
||||
}
|
||||
|
||||
static class TableIndexComparator implements Comparator<Integer> {
|
||||
private final Table table;
|
||||
private final int maxIndex;
|
||||
private final List<ColumnOrderElement> ordering;
|
||||
|
||||
TableIndexComparator(Table table, List<ColumnOrderElement> ordering) {
|
||||
this.table = table;
|
||||
this.maxIndex = table.getRows().size();
|
||||
this.ordering = ordering;
|
||||
}
|
||||
|
||||
private int compareCell(Object o1, Object o2) {
|
||||
if (o1 == null && o2 == null) {
|
||||
return 0;
|
||||
} else if (o1 == null) {
|
||||
return -1;
|
||||
} else if (o2 == null) {
|
||||
return 1;
|
||||
} else {
|
||||
if (o1 instanceof Comparable && o1.getClass().equals(o2.getClass())) {
|
||||
return ((Comparable) o1).compareTo(o2);
|
||||
} else {
|
||||
return o1.toString().compareTo(o2.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compare(Integer rowIndex1, Integer rowIndex2) {
|
||||
if (rowIndex1 < maxIndex && rowIndex1 >= 0 && rowIndex2 < maxIndex && rowIndex2 >= 0) {
|
||||
Map<String, List<Table.Cell>> tableMap = table.getAsMap();
|
||||
for (ColumnOrderElement orderingElement : ordering) {
|
||||
String column = orderingElement.getColumn();
|
||||
if (tableMap.containsKey(column)) {
|
||||
int comparison = compareCell(tableMap.get(column).get(rowIndex1).value,
|
||||
tableMap.get(column).get(rowIndex2).value);
|
||||
if (comparison != 0) {
|
||||
return orderingElement.isReversed() ? -1 * comparison : comparison;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
} else {
|
||||
throw new AssertionError(String.format(Locale.ENGLISH, "Invalid comparison of indices (%s, %s): Table has %s rows.",
|
||||
rowIndex1, rowIndex2, table.getRows().size()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static class ColumnOrderElement {
|
||||
private final String column;
|
||||
private final boolean reverse;
|
||||
|
||||
public ColumnOrderElement(String column, boolean reverse) {
|
||||
this.column = column;
|
||||
this.reverse = reverse;
|
||||
}
|
||||
|
||||
public String getColumn() {
|
||||
return column;
|
||||
}
|
||||
|
||||
public boolean isReversed() {
|
||||
return reverse;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -200,6 +200,19 @@ public class TableTests extends ESTestCase {
|
|||
|
||||
}
|
||||
|
||||
public void testAliasMap() {
|
||||
Table table = new Table();
|
||||
table.startHeaders();
|
||||
table.addCell("asdf", "alias:a");
|
||||
table.addCell("ghij", "alias:g,h");
|
||||
table.endHeaders();
|
||||
Map<String, String> aliasMap = table.getAliasMap();
|
||||
assertEquals(5, aliasMap.size());
|
||||
assertEquals("asdf", aliasMap.get("a"));
|
||||
assertEquals("ghij", aliasMap.get("g"));
|
||||
assertEquals("ghij", aliasMap.get("h"));
|
||||
}
|
||||
|
||||
private Table getTableWithHeaders() {
|
||||
Table table = new Table();
|
||||
table.startHeaders();
|
||||
|
|
|
@ -170,13 +170,52 @@ public class ByteSizeValueTests extends ESTestCase {
|
|||
}
|
||||
}
|
||||
|
||||
public void testCompareEquality() {
|
||||
long firstRandom = randomPositiveLong();
|
||||
ByteSizeUnit randomUnit = randomFrom(ByteSizeUnit.values());
|
||||
ByteSizeValue firstByteValue = new ByteSizeValue(firstRandom, randomUnit);
|
||||
ByteSizeValue secondByteValue = new ByteSizeValue(firstRandom, randomUnit);
|
||||
assertEquals(0, firstByteValue.compareTo(secondByteValue));
|
||||
}
|
||||
|
||||
public void testCompareValue() {
|
||||
long firstRandom = randomPositiveLong();
|
||||
long secondRandom = randomValueOtherThan(firstRandom, ESTestCase::randomPositiveLong);
|
||||
ByteSizeUnit unit = randomFrom(ByteSizeUnit.values());
|
||||
ByteSizeValue firstByteValue = new ByteSizeValue(firstRandom, unit);
|
||||
ByteSizeValue secondByteValue = new ByteSizeValue(secondRandom, unit);
|
||||
assertEquals(firstRandom > secondRandom, firstByteValue.compareTo(secondByteValue) > 0);
|
||||
assertEquals(secondRandom > firstRandom, secondByteValue.compareTo(firstByteValue) > 0);
|
||||
}
|
||||
|
||||
public void testCompareUnits() {
|
||||
long number = randomPositiveLong();
|
||||
ByteSizeUnit randomUnit = randomValueOtherThan(ByteSizeUnit.PB, ()->randomFrom(ByteSizeUnit.values()));
|
||||
ByteSizeValue firstByteValue = new ByteSizeValue(number, randomUnit);
|
||||
ByteSizeValue secondByteValue = new ByteSizeValue(number, ByteSizeUnit.PB);
|
||||
assertTrue(firstByteValue.compareTo(secondByteValue) < 0);
|
||||
assertTrue(secondByteValue.compareTo(firstByteValue) > 0);
|
||||
}
|
||||
|
||||
public void testEdgeCompare() {
|
||||
ByteSizeValue maxLongValuePB = new ByteSizeValue(Long.MAX_VALUE, ByteSizeUnit.PB);
|
||||
ByteSizeValue maxLongValueB = new ByteSizeValue(Long.MAX_VALUE, ByteSizeUnit.BYTES);
|
||||
assertTrue(maxLongValuePB.compareTo(maxLongValueB) > 0);
|
||||
}
|
||||
|
||||
public void testConversionHashCode() {
|
||||
ByteSizeValue firstValue = new ByteSizeValue(randomIntBetween(0, Integer.MAX_VALUE), ByteSizeUnit.GB);
|
||||
ByteSizeValue secondValue = new ByteSizeValue(firstValue.getBytes(), ByteSizeUnit.BYTES);
|
||||
assertEquals(firstValue.hashCode(), secondValue.hashCode());
|
||||
}
|
||||
|
||||
public void testSerialization() throws IOException {
|
||||
ByteSizeValue byteSizeValue = new ByteSizeValue(randomPositiveLong(), randomFrom(ByteSizeUnit.values()));
|
||||
try (BytesStreamOutput out = new BytesStreamOutput()) {
|
||||
byteSizeValue.writeTo(out);
|
||||
try (StreamInput in = out.bytes().streamInput()) {
|
||||
ByteSizeValue deserializedByteSizeValue = new ByteSizeValue(in);
|
||||
assertEquals(byteSizeValue, deserializedByteSizeValue);
|
||||
assertEquals(byteSizeValue.getBytes(), deserializedByteSizeValue.getBytes());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -67,4 +67,37 @@ public class SizeValueTests extends ESTestCase {
|
|||
assertThat(e.getMessage(), containsString("may not be negative"));
|
||||
}
|
||||
}
|
||||
|
||||
public void testCompareEquality() {
|
||||
long randomValue = randomPositiveLong();
|
||||
SizeUnit randomUnit = randomFrom(SizeUnit.values());
|
||||
SizeValue firstValue = new SizeValue(randomValue, randomUnit);
|
||||
SizeValue secondValue = new SizeValue(randomValue, randomUnit);
|
||||
assertEquals(0, firstValue.compareTo(secondValue));
|
||||
}
|
||||
|
||||
public void testCompareValue() {
|
||||
long firstRandom = randomPositiveLong();
|
||||
long secondRandom = randomValueOtherThan(firstRandom, ESTestCase::randomPositiveLong);
|
||||
SizeUnit unit = randomFrom(SizeUnit.values());
|
||||
SizeValue firstSizeValue = new SizeValue(firstRandom, unit);
|
||||
SizeValue secondSizeValue = new SizeValue(secondRandom, unit);
|
||||
assertEquals(firstRandom > secondRandom, firstSizeValue.compareTo(secondSizeValue) > 0);
|
||||
assertEquals(secondRandom > firstRandom, secondSizeValue.compareTo(firstSizeValue) > 0);
|
||||
}
|
||||
|
||||
public void testCompareUnits() {
|
||||
long number = randomPositiveLong();
|
||||
SizeUnit randomUnit = randomValueOtherThan(SizeUnit.PETA, ()->randomFrom(SizeUnit.values()));
|
||||
SizeValue firstValue = new SizeValue(number, randomUnit);
|
||||
SizeValue secondValue = new SizeValue(number, SizeUnit.PETA);
|
||||
assertTrue(firstValue.compareTo(secondValue) < 0);
|
||||
assertTrue(secondValue.compareTo(firstValue) > 0);
|
||||
}
|
||||
|
||||
public void testConversionHashCode() {
|
||||
SizeValue firstValue = new SizeValue(randomIntBetween(0, Integer.MAX_VALUE), SizeUnit.GIGA);
|
||||
SizeValue secondValue = new SizeValue(firstValue.getSingles(), SizeUnit.SINGLE);
|
||||
assertEquals(firstValue.hashCode(), secondValue.hashCode());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -224,4 +224,37 @@ public class TimeValueTests extends ESTestCase {
|
|||
assertEquals("36h", new TimeValue(36, TimeUnit.HOURS).getStringRep());
|
||||
assertEquals("1000d", new TimeValue(1000, TimeUnit.DAYS).getStringRep());
|
||||
}
|
||||
|
||||
public void testCompareEquality() {
|
||||
long randomLong = randomPositiveLong();
|
||||
TimeUnit randomUnit = randomFrom(TimeUnit.values());
|
||||
TimeValue firstValue = new TimeValue(randomLong, randomUnit);
|
||||
TimeValue secondValue = new TimeValue(randomLong, randomUnit);
|
||||
assertEquals(0, firstValue.compareTo(secondValue));
|
||||
}
|
||||
|
||||
public void testCompareValue() {
|
||||
long firstRandom = randomPositiveLong();
|
||||
long secondRandom = randomValueOtherThan(firstRandom, ESTestCase::randomPositiveLong);
|
||||
TimeUnit unit = randomFrom(TimeUnit.values());
|
||||
TimeValue firstValue = new TimeValue(firstRandom, unit);
|
||||
TimeValue secondValue = new TimeValue(secondRandom, unit);
|
||||
assertEquals(firstRandom > secondRandom, firstValue.compareTo(secondValue) > 0);
|
||||
assertEquals(secondRandom > firstRandom, secondValue.compareTo(firstValue) > 0);
|
||||
}
|
||||
|
||||
public void testCompareUnits() {
|
||||
long number = randomPositiveLong();
|
||||
TimeUnit randomUnit = randomValueOtherThan(TimeUnit.DAYS, ()->randomFrom(TimeUnit.values()));
|
||||
TimeValue firstValue = new TimeValue(number, randomUnit);
|
||||
TimeValue secondValue = new TimeValue(number, TimeUnit.DAYS);
|
||||
assertTrue(firstValue.compareTo(secondValue) < 0);
|
||||
assertTrue(secondValue.compareTo(firstValue) > 0);
|
||||
}
|
||||
|
||||
public void testConversionHashCode() {
|
||||
TimeValue firstValue = new TimeValue(randomIntBetween(0, Integer.MAX_VALUE), TimeUnit.MINUTES);
|
||||
TimeValue secondValue = new TimeValue(firstValue.getSeconds(), TimeUnit.SECONDS);
|
||||
assertEquals(firstValue.hashCode(), secondValue.hashCode());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,12 +23,12 @@ import org.elasticsearch.common.Table;
|
|||
import org.elasticsearch.common.xcontent.XContentType;
|
||||
import org.elasticsearch.rest.AbstractRestChannel;
|
||||
import org.elasticsearch.rest.RestResponse;
|
||||
import org.elasticsearch.rest.action.cat.RestTable;
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
import org.elasticsearch.test.rest.FakeRestRequest;
|
||||
import org.junit.Before;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
@ -62,11 +62,13 @@ public class RestTableTests extends ESTestCase {
|
|||
" invalidAliasesBulk: \"foo\"\n" +
|
||||
" timestamp: \"foo\"\n" +
|
||||
" epoch: \"foo\"\n";
|
||||
private Table table = new Table();
|
||||
private FakeRestRequest restRequest = new FakeRestRequest();
|
||||
private Table table;
|
||||
private FakeRestRequest restRequest;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
restRequest = new FakeRestRequest();
|
||||
table = new Table();
|
||||
table.startHeaders();
|
||||
table.addCell("bulk.foo", "alias:f;desc:foo");
|
||||
table.addCell("bulk.bar", "alias:b;desc:bar");
|
||||
|
@ -146,6 +148,110 @@ public class RestTableTests extends ESTestCase {
|
|||
assertThat(headerNames, not(hasItem("epoch")));
|
||||
}
|
||||
|
||||
public void testCompareRow() {
|
||||
Table table = new Table();
|
||||
table.startHeaders();
|
||||
table.addCell("compare");
|
||||
table.endHeaders();
|
||||
|
||||
for (Integer i : Arrays.asList(1,2,1)) {
|
||||
table.startRow();
|
||||
table.addCell(i);
|
||||
table.endRow();
|
||||
}
|
||||
|
||||
RestTable.TableIndexComparator comparator = new RestTable.TableIndexComparator(table,
|
||||
Collections.singletonList(new RestTable.ColumnOrderElement("compare", false)));
|
||||
assertTrue(comparator.compare(0,1) < 0);
|
||||
assertTrue(comparator.compare(0,2) == 0);
|
||||
assertTrue(comparator.compare(1,2) > 0);
|
||||
|
||||
RestTable.TableIndexComparator reverseComparator = new RestTable.TableIndexComparator(table,
|
||||
Collections.singletonList(new RestTable.ColumnOrderElement("compare", true)));
|
||||
|
||||
assertTrue(reverseComparator.compare(0,1) > 0);
|
||||
assertTrue(reverseComparator.compare(0,2) == 0);
|
||||
assertTrue(reverseComparator.compare(1,2) < 0);
|
||||
}
|
||||
|
||||
public void testRowOutOfBounds() {
|
||||
Table table = new Table();
|
||||
table.startHeaders();
|
||||
table.addCell("compare");
|
||||
table.endHeaders();
|
||||
RestTable.TableIndexComparator comparator = new RestTable.TableIndexComparator(table,
|
||||
Collections.singletonList(new RestTable.ColumnOrderElement("compare", false)));
|
||||
Error e = expectThrows(AssertionError.class, () -> {
|
||||
comparator.compare(0,1);
|
||||
});
|
||||
assertEquals("Invalid comparison of indices (0, 1): Table has 0 rows.", e.getMessage());
|
||||
}
|
||||
|
||||
public void testUnknownHeader() {
|
||||
Table table = new Table();
|
||||
table.startHeaders();
|
||||
table.addCell("compare");
|
||||
table.endHeaders();
|
||||
restRequest.params().put("s", "notaheader");
|
||||
Exception e = expectThrows(UnsupportedOperationException.class, () -> RestTable.getRowOrder(table, restRequest));
|
||||
assertEquals("Unable to sort by unknown sort key `notaheader`", e.getMessage());
|
||||
}
|
||||
|
||||
public void testAliasSort() {
|
||||
Table table = new Table();
|
||||
table.startHeaders();
|
||||
table.addCell("compare", "alias:c;");
|
||||
table.endHeaders();
|
||||
List<Integer> comparisonList = Arrays.asList(3,1,2);
|
||||
for (int i = 0; i < comparisonList.size(); i++) {
|
||||
table.startRow();
|
||||
table.addCell(comparisonList.get(i));
|
||||
table.endRow();
|
||||
}
|
||||
restRequest.params().put("s", "c");
|
||||
List<Integer> rowOrder = RestTable.getRowOrder(table, restRequest);
|
||||
assertEquals(Arrays.asList(1,2,0), rowOrder);
|
||||
}
|
||||
|
||||
public void testReversedSort() {
|
||||
Table table = new Table();
|
||||
table.startHeaders();
|
||||
table.addCell("reversed");
|
||||
table.endHeaders();
|
||||
List<Integer> comparisonList = Arrays.asList(0, 1, 2);
|
||||
for (int i = 0; i < comparisonList.size(); i++) {
|
||||
table.startRow();
|
||||
table.addCell(comparisonList.get(i));
|
||||
table.endRow();
|
||||
}
|
||||
restRequest.params().put("s", "reversed:desc");
|
||||
List<Integer> rowOrder = RestTable.getRowOrder(table, restRequest);
|
||||
assertEquals(Arrays.asList(2,1,0), rowOrder);
|
||||
}
|
||||
|
||||
public void testMultiSort() {
|
||||
Table table = new Table();
|
||||
table.startHeaders();
|
||||
table.addCell("compare");
|
||||
table.addCell("second.compare");
|
||||
table.endHeaders();
|
||||
List<Integer> comparisonList = Arrays.asList(3, 3, 2);
|
||||
List<Integer> secondComparisonList = Arrays.asList(2, 1, 3);
|
||||
for (int i = 0; i < comparisonList.size(); i++) {
|
||||
table.startRow();
|
||||
table.addCell(comparisonList.get(i));
|
||||
table.addCell(secondComparisonList.get(i));
|
||||
table.endRow();
|
||||
}
|
||||
restRequest.params().put("s", "compare,second.compare");
|
||||
List<Integer> rowOrder = RestTable.getRowOrder(table, restRequest);
|
||||
assertEquals(Arrays.asList(2,1,0), rowOrder);
|
||||
|
||||
restRequest.params().put("s", "compare:desc,second.compare");
|
||||
rowOrder = RestTable.getRowOrder(table, restRequest);
|
||||
assertEquals(Arrays.asList(1,0,2), rowOrder);
|
||||
}
|
||||
|
||||
private RestResponse assertResponseContentType(Map<String, String> headers, String mediaType) throws Exception {
|
||||
FakeRestRequest requestWithAcceptHeader = new FakeRestRequest.Builder().withHeaders(headers).build();
|
||||
table.startRow();
|
||||
|
|
|
@ -175,6 +175,36 @@ For example:
|
|||
--------------------------------------------------
|
||||
// NOTCONSOLE
|
||||
|
||||
[float]
|
||||
[[sort]]
|
||||
=== Sort
|
||||
|
||||
Each of the commands accepts a query string parameter `s` which sorts the table by
|
||||
the columns specified as the parameter value. Columns are specified either by name or by
|
||||
alias, and are provided as a comma separated string. By default, sorting is done in
|
||||
ascending fashion. Appending `:desc` to a column will invert the ordering for
|
||||
that column. `:asc` is also accepted but exhibits the same behavior as the default sort order.
|
||||
|
||||
For example, with a sort string `s=column1,column2:desc,column3`, the table will be
|
||||
sorted in ascending order by column1, in descending order by column2, and in ascending
|
||||
order by column3.
|
||||
|
||||
[source,sh]
|
||||
--------------------------------------------------
|
||||
GET _cat/templates?v&s=order:desc,template
|
||||
--------------------------------------------------
|
||||
//CONSOLE
|
||||
|
||||
returns:
|
||||
|
||||
[source,sh]
|
||||
--------------------------------------------------
|
||||
name template order version
|
||||
pizza_pepperoni *pepperoni* 2
|
||||
sushi_california_roll *avocado* 1 1
|
||||
pizza_hawaiian *pineapples* 1
|
||||
--------------------------------------------------
|
||||
|
||||
--
|
||||
|
||||
include::cat/alias.asciidoc[]
|
||||
|
|
|
@ -33,6 +33,10 @@
|
|||
"description": "Return help information",
|
||||
"default": false
|
||||
},
|
||||
"s": {
|
||||
"type": "list",
|
||||
"description" : "Comma-separated list of column names or column aliases to sort by"
|
||||
},
|
||||
"v": {
|
||||
"type": "boolean",
|
||||
"description": "Verbose mode. Display column headers",
|
||||
|
|
|
@ -38,6 +38,10 @@
|
|||
"description": "Return help information",
|
||||
"default": false
|
||||
},
|
||||
"s": {
|
||||
"type": "list",
|
||||
"description" : "Comma-separated list of column names or column aliases to sort by"
|
||||
},
|
||||
"v": {
|
||||
"type": "boolean",
|
||||
"description": "Verbose mode. Display column headers",
|
||||
|
|
|
@ -33,6 +33,10 @@
|
|||
"description": "Return help information",
|
||||
"default": false
|
||||
},
|
||||
"s": {
|
||||
"type": "list",
|
||||
"description" : "Comma-separated list of column names or column aliases to sort by"
|
||||
},
|
||||
"v": {
|
||||
"type": "boolean",
|
||||
"description": "Verbose mode. Display column headers",
|
||||
|
|
|
@ -38,6 +38,10 @@
|
|||
"description": "Return help information",
|
||||
"default": false
|
||||
},
|
||||
"s": {
|
||||
"type": "list",
|
||||
"description" : "Comma-separated list of column names or column aliases to sort by"
|
||||
},
|
||||
"v": {
|
||||
"type": "boolean",
|
||||
"description": "Verbose mode. Display column headers",
|
||||
|
|
|
@ -29,6 +29,10 @@
|
|||
"description": "Return help information",
|
||||
"default": false
|
||||
},
|
||||
"s": {
|
||||
"type": "list",
|
||||
"description" : "Comma-separated list of column names or column aliases to sort by"
|
||||
},
|
||||
"ts": {
|
||||
"type": "boolean",
|
||||
"description": "Set to false to disable timestamping",
|
||||
|
|
|
@ -12,6 +12,10 @@
|
|||
"type": "boolean",
|
||||
"description": "Return help information",
|
||||
"default": false
|
||||
},
|
||||
"s": {
|
||||
"type": "list",
|
||||
"description" : "Comma-separated list of column names or column aliases to sort by"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -49,6 +49,10 @@
|
|||
"description": "Set to true to return stats only for primary shards",
|
||||
"default": false
|
||||
},
|
||||
"s": {
|
||||
"type": "list",
|
||||
"description" : "Comma-separated list of column names or column aliases to sort by"
|
||||
},
|
||||
"v": {
|
||||
"type": "boolean",
|
||||
"description": "Verbose mode. Display column headers",
|
||||
|
|
|
@ -29,6 +29,10 @@
|
|||
"description": "Return help information",
|
||||
"default": false
|
||||
},
|
||||
"s": {
|
||||
"type": "list",
|
||||
"description" : "Comma-separated list of column names or column aliases to sort by"
|
||||
},
|
||||
"v": {
|
||||
"type": "boolean",
|
||||
"description": "Verbose mode. Display column headers",
|
||||
|
|
|
@ -29,6 +29,10 @@
|
|||
"description": "Return help information",
|
||||
"default": false
|
||||
},
|
||||
"s": {
|
||||
"type": "list",
|
||||
"description" : "Comma-separated list of column names or column aliases to sort by"
|
||||
},
|
||||
"v": {
|
||||
"type": "boolean",
|
||||
"description": "Verbose mode. Display column headers",
|
||||
|
|
|
@ -29,6 +29,10 @@
|
|||
"description": "Return help information",
|
||||
"default": false
|
||||
},
|
||||
"s": {
|
||||
"type": "list",
|
||||
"description" : "Comma-separated list of column names or column aliases to sort by"
|
||||
},
|
||||
"v": {
|
||||
"type": "boolean",
|
||||
"description": "Verbose mode. Display column headers",
|
||||
|
|
|
@ -29,6 +29,10 @@
|
|||
"description": "Return help information",
|
||||
"default": false
|
||||
},
|
||||
"s": {
|
||||
"type": "list",
|
||||
"description" : "Comma-separated list of column names or column aliases to sort by"
|
||||
},
|
||||
"v": {
|
||||
"type": "boolean",
|
||||
"description": "Verbose mode. Display column headers",
|
||||
|
|
|
@ -27,6 +27,10 @@
|
|||
"description": "Return help information",
|
||||
"default": false
|
||||
},
|
||||
"s": {
|
||||
"type": "list",
|
||||
"description" : "Comma-separated list of column names or column aliases to sort by"
|
||||
},
|
||||
"v": {
|
||||
"type": "boolean",
|
||||
"description": "Verbose mode. Display column headers",
|
||||
|
|
|
@ -34,6 +34,10 @@
|
|||
"description": "Return help information",
|
||||
"default": false
|
||||
},
|
||||
"s": {
|
||||
"type": "list",
|
||||
"description" : "Comma-separated list of column names or column aliases to sort by"
|
||||
},
|
||||
"v": {
|
||||
"type": "boolean",
|
||||
"description": "Verbose mode. Display column headers",
|
||||
|
|
|
@ -30,6 +30,10 @@
|
|||
"description": "Return help information",
|
||||
"default": false
|
||||
},
|
||||
"s": {
|
||||
"type": "list",
|
||||
"description" : "Comma-separated list of column names or column aliases to sort by"
|
||||
},
|
||||
"v": {
|
||||
"type": "boolean",
|
||||
"description": "Verbose mode. Display column headers",
|
||||
|
|
|
@ -25,6 +25,10 @@
|
|||
"description": "Return help information",
|
||||
"default": false
|
||||
},
|
||||
"s": {
|
||||
"type": "list",
|
||||
"description" : "Comma-separated list of column names or column aliases to sort by"
|
||||
},
|
||||
"v": {
|
||||
"type": "boolean",
|
||||
"description": "Verbose mode. Display column headers",
|
||||
|
|
|
@ -33,6 +33,10 @@
|
|||
"description": "Return help information",
|
||||
"default": false
|
||||
},
|
||||
"s": {
|
||||
"type": "list",
|
||||
"description" : "Comma-separated list of column names or column aliases to sort by"
|
||||
},
|
||||
"v": {
|
||||
"type": "boolean",
|
||||
"description": "Verbose mode. Display column headers",
|
||||
|
|
|
@ -37,6 +37,10 @@
|
|||
"description": "Return help information",
|
||||
"default": false
|
||||
},
|
||||
"s": {
|
||||
"type": "list",
|
||||
"description" : "Comma-separated list of column names or column aliases to sort by"
|
||||
},
|
||||
"v": {
|
||||
"type": "boolean",
|
||||
"description": "Verbose mode. Display column headers",
|
||||
|
|
|
@ -41,6 +41,10 @@
|
|||
"description": "Return help information",
|
||||
"default": false
|
||||
},
|
||||
"s": {
|
||||
"type": "list",
|
||||
"description" : "Comma-separated list of column names or column aliases to sort by"
|
||||
},
|
||||
"v": {
|
||||
"type": "boolean",
|
||||
"description": "Verbose mode. Display column headers",
|
||||
|
|
|
@ -33,6 +33,10 @@
|
|||
"description": "Return help information",
|
||||
"default": false
|
||||
},
|
||||
"s": {
|
||||
"type": "list",
|
||||
"description" : "Comma-separated list of column names or column aliases to sort by"
|
||||
},
|
||||
"v": {
|
||||
"type": "boolean",
|
||||
"description": "Verbose mode. Display column headers",
|
||||
|
|
|
@ -34,6 +34,10 @@
|
|||
"description": "Return help information",
|
||||
"default": false
|
||||
},
|
||||
"s": {
|
||||
"type": "list",
|
||||
"description" : "Comma-separated list of column names or column aliases to sort by"
|
||||
},
|
||||
"v": {
|
||||
"type": "boolean",
|
||||
"description": "Verbose mode. Display column headers",
|
||||
|
|
|
@ -216,3 +216,46 @@
|
|||
- \s+
|
||||
$/
|
||||
|
||||
---
|
||||
"Alias sorting":
|
||||
|
||||
- do:
|
||||
indices.create:
|
||||
index: test_index
|
||||
body:
|
||||
aliases:
|
||||
test_alias: {}
|
||||
my_alias: {}
|
||||
|
||||
- do:
|
||||
indices.create:
|
||||
index: other_index
|
||||
body:
|
||||
aliases:
|
||||
other_alias: {}
|
||||
|
||||
- do:
|
||||
cat.aliases:
|
||||
h: [alias, index]
|
||||
s: [index, alias]
|
||||
|
||||
- match:
|
||||
$body: |
|
||||
/^
|
||||
other_alias \s+ other_index\n
|
||||
my_alias \s+ test_index\n
|
||||
test_alias \s+ test_index\n
|
||||
$/
|
||||
|
||||
- do:
|
||||
cat.aliases:
|
||||
h: [alias, index]
|
||||
s: [index, "a:desc"]
|
||||
|
||||
- match:
|
||||
$body: |
|
||||
/^
|
||||
other_alias \s+ other_index\n
|
||||
test_alias \s+ test_index\n
|
||||
my_alias \s+ test_index\n
|
||||
$/
|
||||
|
|
|
@ -157,3 +157,57 @@
|
|||
- match:
|
||||
$body: |
|
||||
/^(ba(r|z) \n?){2}$/
|
||||
|
||||
---
|
||||
"Test cat indices sort":
|
||||
- do:
|
||||
indices.create:
|
||||
index: foo
|
||||
body:
|
||||
settings:
|
||||
number_of_shards: "1"
|
||||
number_of_replicas: "0"
|
||||
|
||||
- do:
|
||||
indices.create:
|
||||
index: bar
|
||||
body:
|
||||
settings:
|
||||
number_of_shards: "1"
|
||||
number_of_replicas: "0"
|
||||
|
||||
- do:
|
||||
indices.create:
|
||||
index: baz
|
||||
body:
|
||||
settings:
|
||||
number_of_shards: "1"
|
||||
number_of_replicas: "0"
|
||||
|
||||
- do:
|
||||
indices.close:
|
||||
index: bar
|
||||
|
||||
- do:
|
||||
cat.indices:
|
||||
h: [status, index]
|
||||
s: [status, index]
|
||||
|
||||
- match:
|
||||
$body: |
|
||||
/^ close \s+ bar\n
|
||||
open \s+ baz\n
|
||||
open \s+ foo\n
|
||||
$/
|
||||
|
||||
- do:
|
||||
cat.indices:
|
||||
h: [status, index]
|
||||
s: [status, "index:desc"]
|
||||
|
||||
- match:
|
||||
$body: |
|
||||
/^ close \s+ bar\n
|
||||
open \s+ foo\n
|
||||
open \s+ baz\n
|
||||
$/
|
||||
|
|
|
@ -43,3 +43,42 @@
|
|||
/^ test_cat_repo_1\s+ fs\s*\n
|
||||
test_cat_repo_2\s+ fs\s*\n
|
||||
$/
|
||||
|
||||
---
|
||||
"Test cat repositories sort":
|
||||
|
||||
- do:
|
||||
snapshot.create_repository:
|
||||
repository: test_cat_repo_1
|
||||
body:
|
||||
type: fs
|
||||
settings:
|
||||
location: "test_cat_repo_1_loc"
|
||||
|
||||
- do:
|
||||
snapshot.create_repository:
|
||||
repository: test_cat_repo_2
|
||||
body:
|
||||
type: fs
|
||||
settings:
|
||||
location: "test_cat_repo_2_loc"
|
||||
|
||||
- do:
|
||||
cat.repositories:
|
||||
s: [type, id]
|
||||
|
||||
- match:
|
||||
$body: |
|
||||
/^ test_cat_repo_1 \s+ fs \n
|
||||
test_cat_repo_2 \s+ fs \n
|
||||
$/
|
||||
|
||||
- do:
|
||||
cat.repositories:
|
||||
s: [type, "id:desc"]
|
||||
|
||||
- match:
|
||||
$body: |
|
||||
/^ test_cat_repo_2 \s+ fs \n
|
||||
test_cat_repo_1 \s+ fs \n
|
||||
$/
|
||||
|
|
|
@ -224,3 +224,40 @@
|
|||
- match:
|
||||
$body: |
|
||||
/^(ba(r|z) \n?){2}$/
|
||||
|
||||
---
|
||||
"Test cat shards sort":
|
||||
|
||||
- do:
|
||||
indices.create:
|
||||
index: foo
|
||||
body:
|
||||
settings:
|
||||
number_of_shards: "1"
|
||||
number_of_replicas: "0"
|
||||
|
||||
- do:
|
||||
indices.create:
|
||||
index: bar
|
||||
body:
|
||||
settings:
|
||||
number_of_shards: "1"
|
||||
number_of_replicas: "0"
|
||||
|
||||
- do:
|
||||
index:
|
||||
index: bar
|
||||
type: type
|
||||
body: { test: bar }
|
||||
refresh: true
|
||||
|
||||
- do:
|
||||
cat.shards:
|
||||
h: [index, store]
|
||||
s: [store]
|
||||
|
||||
- match:
|
||||
$body: |
|
||||
/^ foo \s+ (\d+|\d+[.]\d+)(kb|b)\n
|
||||
bar \s+ (\d+|\d+[.]\d+)(kb|b)\n
|
||||
$/
|
||||
|
|
|
@ -174,3 +174,50 @@
|
|||
\n
|
||||
$/
|
||||
|
||||
---
|
||||
"Sort templates":
|
||||
|
||||
- do:
|
||||
indices.put_template:
|
||||
name: test
|
||||
body:
|
||||
order: 0
|
||||
template: t*
|
||||
settings:
|
||||
number_of_shards: 1
|
||||
number_of_replicas: 0
|
||||
|
||||
- do:
|
||||
indices.put_template:
|
||||
name: test_1
|
||||
body:
|
||||
order: 0
|
||||
version: 1
|
||||
template: te*
|
||||
settings:
|
||||
number_of_shards: 1
|
||||
number_of_replicas: 0
|
||||
|
||||
- do:
|
||||
cat.templates:
|
||||
h: [name, template, version]
|
||||
s: [version]
|
||||
|
||||
- match:
|
||||
$body: |
|
||||
/^
|
||||
test \s+ t\* \s+\n
|
||||
test_1 \s+ te\* \s+ 1\n
|
||||
$/
|
||||
|
||||
- do:
|
||||
cat.templates:
|
||||
h: [name, template, version]
|
||||
s: ["version:desc"]
|
||||
|
||||
- match:
|
||||
$body: |
|
||||
/^
|
||||
test_1 \s+ te\* \s+ 1\n
|
||||
test \s+ t\* \s+\n
|
||||
$/
|
||||
|
|
Loading…
Reference in New Issue