/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.blur.shell;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import jline.console.ConsoleReader;
import org.apache.blur.shell.PagingPrintWriter.FinishedException;
import org.apache.blur.thirdparty.thrift_0_9_0.TException;
import org.apache.blur.thrift.generated.Blur;
import org.apache.blur.thrift.generated.BlurException;
import org.apache.blur.thrift.generated.BlurQuery;
import org.apache.blur.thrift.generated.BlurResult;
import org.apache.blur.thrift.generated.BlurResults;
import org.apache.blur.thrift.generated.Column;
import org.apache.blur.thrift.generated.FetchRecordResult;
import org.apache.blur.thrift.generated.FetchResult;
import org.apache.blur.thrift.generated.FetchRowResult;
import org.apache.blur.thrift.generated.Record;
import org.apache.blur.thrift.generated.Row;
import org.apache.blur.utils.BlurUtil;
import org.apache.commons.cli.CommandLine;
public class QueryCommand extends Command implements TableFirstArgCommand {
static enum RenderType {
ROW_MULTI_FAMILY, ROW_SINGLE_FAMILY
}
private int _width;
@Override
public void doit(PrintWriter outPw, Blur.Iface client, String[] args) throws CommandException, TException,
BlurException {
try {
doItInternal(client, args, outPw);
} catch (FinishedException e) {
if (Main.debug) {
e.printStackTrace();
}
}
}
private void doItInternal(Blur.Iface client, String[] args, PrintWriter out) throws FinishedException, BlurException,
TException {
CommandLine commandLine = QueryCommandHelper.parse(args, out, name() + " " + usage());
if (commandLine == null) {
return;
}
BlurQuery blurQuery = QueryCommandHelper.getBlurQuery(commandLine);
if (Main.debug) {
out.println(blurQuery);
}
_width = 100;
if (commandLine.hasOption(QueryCommandHelper.WIDTH)) {
_width = Integer.parseInt(commandLine.getOptionValue(QueryCommandHelper.WIDTH));
}
String tablename = args[1];
long s = System.nanoTime();
BlurResults blurResults = client.query(tablename, blurQuery);
long e = System.nanoTime();
long timeInNanos = e - s;
if (blurResults.getTotalResults() == 0) {
out.println("No results found in [" + timeInNanos / 1000000.0 + " ms].");
return;
}
ConsoleReader reader = getConsoleReader();
if (reader == null) {
String description = blurResults.getTotalResults() + " results found in [" + timeInNanos / 1000000.0 + " ms]. "
+ getFetchMetaData(blurResults);
out.println(description);
List<BlurResult> results = blurResults.getResults();
for (BlurResult result : results) {
print(out, result);
}
return;
}
String prompt = reader.getPrompt();
reader.setPrompt("");
final TableDisplay tableDisplay = new TableDisplay(reader);
tableDisplay.setDescription(white(blurResults.getTotalResults() + " results found in [" + timeInNanos / 1000000.0
+ " ms]. " + getFetchMetaData(blurResults)));
tableDisplay.setSeperator(" ");
try {
final AtomicBoolean viewing = new AtomicBoolean(true);
tableDisplay.addKeyHook(new Runnable() {
@Override
public void run() {
synchronized (viewing) {
viewing.set(false);
viewing.notify();
tableDisplay.setStopReadingInput(true);
}
}
}, 'q');
RenderType type = getRenderRype(blurResults);
switch (type) {
case ROW_MULTI_FAMILY:
renderRowMultiFamily(tableDisplay, blurResults);
break;
case ROW_SINGLE_FAMILY:
renderRowSingleFamily(tableDisplay, blurResults);
break;
default:
break;
}
while (viewing.get()) {
synchronized (viewing) {
try {
viewing.wait();
} catch (InterruptedException ex) {
throw new RuntimeException(ex);
}
}
}
} finally {
try {
tableDisplay.close();
} catch (IOException ex) {
if (Main.debug) {
ex.printStackTrace();
}
}
try {
Thread.sleep(100);
} catch (InterruptedException ex) {
if (Main.debug) {
ex.printStackTrace();
}
}
try {
reader.setPrompt("");
reader.clearScreen();
} catch (IOException ex) {
if (Main.debug) {
ex.printStackTrace();
}
}
out.write("\u001B[0m");
out.flush();
reader.setPrompt(prompt);
}
}
private void print(PrintWriter out, BlurResult result) {
FetchResult fetchResult = result.getFetchResult();
FetchRowResult rowResult = fetchResult.getRowResult();
if (rowResult != null) {
print(out, rowResult);
} else {
FetchRecordResult recordResult = fetchResult.getRecordResult();
print(out, recordResult);
}
}
private void print(PrintWriter out, FetchRowResult rowResult) {
Row row = rowResult.getRow();
int totalRecords = rowResult.getTotalRecords();
String id = row.getId();
List<Record> records = row.getRecords();
int index = 0;
for (Record record : records) {
print(out, id, index + 1, totalRecords, record);
index++;
}
}
private void print(PrintWriter out, String rowId, int index, int totalRecords, Record record) {
String recordId = record.getRecordId();
String family = record.getFamily();
out.print(rowId + "\t" + index + " of " + totalRecords + "\t" + recordId + "\t" + family);
print(out, record.getColumns());
out.println();
}
private void print(PrintWriter out, List<Column> columns) {
Collections.sort(columns, new Comparator<Column>() {
@Override
public int compare(Column o1, Column o2) {
String name1 = o1.getName();
String name2 = o2.getName();
return name1.compareTo(name2);
}
});
for (Column column : columns) {
out.print("\t" + column.getName() + ":" + column.getValue());
}
}
private void print(PrintWriter out, FetchRecordResult recordResult) {
String rowid = recordResult.getRowid();
Record record = recordResult.getRecord();
print(out, rowid, 0, 1, record);
}
private String getFetchMetaData(BlurResults blurResults) {
AtomicInteger rowCount = new AtomicInteger();
AtomicInteger recordCount = new AtomicInteger();
AtomicInteger columnCount = new AtomicInteger();
AtomicInteger columnSize = new AtomicInteger();
for (BlurResult blurResult : blurResults.getResults()) {
FetchResult fetchResult = blurResult.getFetchResult();
FetchRecordResult recordResult = fetchResult.getRecordResult();
if (recordResult != null) {
Record record = recordResult.getRecord();
count(record, recordCount, columnCount, columnSize);
}
FetchRowResult rowResult = fetchResult.getRowResult();
if (rowResult != null) {
Row row = rowResult.getRow();
count(row, rowCount, recordCount, columnCount, columnSize);
}
}
StringBuilder builder = new StringBuilder();
builder.append("Row [" + rowCount + "] ");
builder.append("Record [" + recordCount + "] ");
builder.append("Column [" + columnCount + "] ");
builder.append("Data (bytes) [" + columnSize + "]");
return builder.toString();
}
private void count(Row row, AtomicInteger rowCount, AtomicInteger recordCount, AtomicInteger columnCount,
AtomicInteger columnSize) {
rowCount.incrementAndGet();
List<Record> records = row.getRecords();
if (records != null) {
for (Record r : records) {
count(r, recordCount, columnCount, columnSize);
}
}
}
private void count(Record record, AtomicInteger recordCount, AtomicInteger columnCount, AtomicInteger columnSize) {
recordCount.incrementAndGet();
List<Column> columns = record.getColumns();
if (columns != null) {
for (Column column : columns) {
count(column, columnCount, columnSize);
}
}
}
private void count(Column column, AtomicInteger columnCount, AtomicInteger columnSize) {
columnCount.incrementAndGet();
String name = column.getName();
String value = column.getValue();
columnSize.addAndGet(name.length() * 2);
columnSize.addAndGet(value.length() * 2);
}
private RenderType getRenderRype(BlurResults blurResults) {
Set<String> families = new HashSet<String>();
for (BlurResult blurResult : blurResults.getResults()) {
families.addAll(getFamily(blurResult.getFetchResult()));
}
if (families.size() > 1) {
return RenderType.ROW_MULTI_FAMILY;
}
return RenderType.ROW_SINGLE_FAMILY;
}
private Set<String> getFamily(FetchResult fetchResult) {
Set<String> result = new HashSet<String>();
FetchRowResult rowResult = fetchResult.getRowResult();
if (rowResult == null) {
FetchRecordResult recordResult = fetchResult.getRecordResult();
Record record = recordResult.getRecord();
result.add(record.getFamily());
} else {
Row row = rowResult.getRow();
List<Record> records = row.getRecords();
if (records != null) {
for (Record record : records) {
result.add(record.getFamily());
}
}
}
return result;
}
private void renderRowSingleFamily(TableDisplay tableDisplay, BlurResults blurResults) {
int line = 0;
tableDisplay.setHeader(0, highlight(getTruncatedVersion("result#")));
tableDisplay.setHeader(1, highlight(getTruncatedVersion("rowid")));
tableDisplay.setHeader(2, highlight(getTruncatedVersion("recordid")));
List<String> columnsLabels = new ArrayList<String>();
int result = 0;
for (BlurResult blurResult : blurResults.getResults()) {
FetchResult fetchResult = blurResult.getFetchResult();
FetchRowResult rowResult = fetchResult.getRowResult();
if (rowResult == null) {
FetchRecordResult recordResult = fetchResult.getRecordResult();
String rowid = recordResult.getRowid();
Record record = recordResult.getRecord();
String family = record.getFamily();
if (record.getColumns() != null) {
for (Column column : record.getColumns()) {
addToTableDisplay(result, columnsLabels, tableDisplay, line, rowid, family, record.getRecordId(), column);
}
}
line++;
} else {
Row row = rowResult.getRow();
List<Record> records = row.getRecords();
if (records != null) {
for (Record record : records) {
if (record.getColumns() != null) {
for (Column column : record.getColumns()) {
addToTableDisplay(result, columnsLabels, tableDisplay, line, row.getId(), record.getFamily(),
record.getRecordId(), column);
}
}
line++;
}
}
}
result++;
}
}
private void addToTableDisplay(int result, List<String> columnsLabels, TableDisplay tableDisplay, int line,
String rowId, String family, String recordId, Column column) {
String name = family + "." + column.getName();
int indexOf = columnsLabels.indexOf(name);
if (indexOf < 0) {
indexOf = columnsLabels.size();
columnsLabels.add(name);
tableDisplay.setHeader(indexOf + 3, highlight(getTruncatedVersion(name)));
}
tableDisplay.set(0, line, white(getTruncatedVersion(toStringBinary(Integer.toString(result)))));
tableDisplay.set(1, line, white(getTruncatedVersion(toStringBinary(rowId))));
tableDisplay.set(2, line, white(getTruncatedVersion(toStringBinary(recordId))));
tableDisplay.set(indexOf + 3, line, white(getTruncatedVersion(toStringBinary(column.getValue()))));
}
private String getTruncatedVersion(String s) {
if (s.length() > _width) {
return s.substring(0, _width - 3) + "...";
}
return s;
}
private void renderRowMultiFamily(TableDisplay tableDisplay, BlurResults blurResults) {
AtomicInteger line = new AtomicInteger();
tableDisplay.setHeader(0, highlight(getTruncatedVersion("result#")));
tableDisplay.setHeader(1, highlight(getTruncatedVersion("rowid")));
tableDisplay.setHeader(2, highlight(getTruncatedVersion("recordid")));
Map<String, List<String>> columnOrder = new HashMap<String, List<String>>();
int result = 0;
for (BlurResult blurResult : blurResults.getResults()) {
FetchResult fetchResult = blurResult.getFetchResult();
FetchRowResult rowResult = fetchResult.getRowResult();
if (rowResult != null) {
Row row = rowResult.getRow();
String id = row.getId();
tableDisplay.set(1, line.get(), white(getTruncatedVersion(toStringBinary(id))));
List<Record> records = order(row.getRecords());
String currentFamily = "#";
for (Record record : records) {
currentFamily = displayRecordInRowMultiFamilyView(result, tableDisplay, line, columnOrder, currentFamily,
record);
}
} else {
String currentFamily = "#";
FetchRecordResult recordResult = fetchResult.getRecordResult();
Record record = recordResult.getRecord();
currentFamily = displayRecordInRowMultiFamilyView(result, tableDisplay, line, columnOrder, currentFamily,
record);
}
result++;
}
}
private String toStringBinary(String id) {
byte[] bs;
try {
bs = id.getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
return BlurUtil.toStringBinary(bs, 0, bs.length);
}
private String displayRecordInRowMultiFamilyView(int result, final TableDisplay tableDisplay,
final AtomicInteger line, final Map<String, List<String>> columnOrder, final String currentFamily,
final Record record) {
int c = 3;
List<String> orderedColumns = getOrderColumnValues(record, columnOrder);
String family = record.getFamily();
if (!family.equals(currentFamily)) {
List<String> list = columnOrder.get(family);
for (int i = 0; i < list.size(); i++) {
tableDisplay.set(i + c, line.get(), highlight(getTruncatedVersion(toStringBinary(family + "." + list.get(i)))));
}
tableDisplay.set(0, line.get(), white(toStringBinary(Integer.toString(result))));
line.incrementAndGet();
}
tableDisplay.set(2, line.get(), white(getTruncatedVersion(toStringBinary(record.getRecordId()))));
for (String oc : orderedColumns) {
if (oc != null) {
tableDisplay.set(c, line.get(), white(getTruncatedVersion(toStringBinary(oc))));
}
c++;
}
tableDisplay.set(0, line.get(), white(toStringBinary(Integer.toString(result))));
line.incrementAndGet();
return family;
}
private String white(String s) {
return "\u001B[0m" + s;
}
private String highlight(String s) {
return "\u001B[33m" + s;
}
private List<Record> order(List<Record> records) {
List<Record> list = new ArrayList<Record>(records);
Collections.sort(list, new Comparator<Record>() {
@Override
public int compare(Record o1, Record o2) {
String family1 = o1.getFamily();
String family2 = o2.getFamily();
String recordId1 = o1.getRecordId();
String recordId2 = o2.getRecordId();
if (family1 == null && family2 == null) {
return recordId1.compareTo(recordId2);
}
if (family1 == null) {
return -1;
}
int compareTo = family1.compareTo(family2);
if (compareTo == 0) {
return recordId1.compareTo(recordId2);
}
return compareTo;
}
});
return list;
}
private List<String> getOrderColumnValues(Record record, Map<String, List<String>> columnOrder) {
String family = record.getFamily();
List<String> columnNameList = columnOrder.get(family);
if (columnNameList == null) {
columnOrder.put(family, columnNameList = new ArrayList<String>());
}
Map<String, List<Column>> columnMap = getColumnMap(record);
Set<String> recordColumnNames = new TreeSet<String>(columnMap.keySet());
for (String cn : recordColumnNames) {
if (!columnNameList.contains(cn)) {
columnNameList.add(cn);
}
}
List<String> result = new ArrayList<String>();
for (String col : columnNameList) {
List<Column> list = columnMap.get(col);
if (list != null) {
result.add(toString(list));
} else {
result.add(null);
}
}
return result;
}
private String toString(List<Column> list) {
if (list.size() == 0) {
throw new RuntimeException("Should not happen");
}
if (list.size() == 1) {
return list.get(0).getValue();
}
StringBuilder builder = new StringBuilder();
for (int i = 0; i < list.size(); i++) {
Column column = list.get(i);
if (i != 0) {
builder.append(",");
}
builder.append("[").append(column.getValue()).append("]");
}
return builder.toString();
}
private Map<String, List<Column>> getColumnMap(Record record) {
Map<String, List<Column>> map = new HashMap<String, List<Column>>();
for (Column column : record.getColumns()) {
String name = column.getName();
List<Column> list = map.get(name);
if (list == null) {
map.put(name, list = new ArrayList<Column>());
}
list.add(column);
}
return map;
}
@Override
public String description() {
return "Query the named table. Run -h for full argument list.";
}
@Override
public String usage() {
return "<tablename> [<options>]";
}
@Override
public String name() {
return "query";
}
}