blob: 0de0809c4c5fbce8aaf5770f32fe917321d8d005 [file] [log] [blame]
/**
* 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.tajo.cli.tsql;
import jline.TerminalFactory;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.apache.tajo.QueryId;
import org.apache.tajo.SessionVars;
import org.apache.tajo.TajoConstants;
import org.apache.tajo.TajoProtos;
import org.apache.tajo.catalog.TableDesc;
import org.apache.tajo.catalog.statistics.TableStats;
import org.apache.tajo.client.QueryStatus;
import org.apache.tajo.util.FileUtil;
import org.fusesource.jansi.Ansi;
import java.io.InputStream;
import java.io.PrintWriter;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import static com.google.common.base.Strings.repeat;
import static java.lang.Math.max;
import static java.lang.Math.min;
import static org.fusesource.jansi.Ansi.ansi;
import static org.fusesource.jansi.internal.CLibrary.STDOUT_FILENO;
import static org.fusesource.jansi.internal.CLibrary.isatty;
public class DefaultTajoCliOutputFormatter implements TajoCliOutputFormatter {
private int printPauseRecords;
private boolean printPause;
private boolean printErrorTrace;
private String nullChar;
public static final char QUIT_COMMAND = 'q';
public static final boolean REAL_TERMINAL = detectRealTerminal();
@Override
public void init(TajoCli.TajoCliContext context) {
this.printPause = context.getBool(SessionVars.CLI_PAGING_ENABLED);
this.printPauseRecords = context.getInt(SessionVars.CLI_PAGE_ROWS);
this.printErrorTrace = context.getBool(SessionVars.CLI_DISPLAY_ERROR_TRACE);
this.nullChar = context.get(SessionVars.CLI_NULL_CHAR);
}
@Override
public void setScriptMode() {
this.printPause = false;
}
private String getQuerySuccessMessage(TableDesc tableDesc, float responseTime, int totalPrintedRows, String postfix,
boolean endOfTuple) {
TableStats stat = tableDesc.getStats();
String volume = stat == null ? (endOfTuple ? "0 B" : "unknown bytes") :
FileUtil.humanReadableByteCount(stat.getNumBytes(), false);
long resultRows = stat == null ? TajoConstants.UNKNOWN_ROW_NUMBER : stat.getNumRows();
String displayRowNum;
if (resultRows == TajoConstants.UNKNOWN_ROW_NUMBER) {
if (endOfTuple) {
displayRowNum = totalPrintedRows + " rows";
} else {
displayRowNum = "unknown row number";
}
} else {
displayRowNum = resultRows + " rows";
}
return "(" + displayRowNum + ", " + getResponseTimeReadable(responseTime) + ", " + volume + " " + postfix + ")";
}
protected String getResponseTimeReadable(float responseTime) {
return responseTime + " sec";
}
@Override
public void printResult(PrintWriter sout, InputStream sin, TableDesc tableDesc,
float responseTime, ResultSet res) throws Exception {
long resultRows = tableDesc.getStats() == null ? -1 : tableDesc.getStats().getNumRows();
if (resultRows == -1) {
resultRows = Integer.MAX_VALUE;
}
if (res == null) {
sout.println(getQuerySuccessMessage(tableDesc, responseTime, 0, "inserted", true));
return;
}
ResultSetMetaData rsmd = res.getMetaData();
int numOfColumns = rsmd.getColumnCount();
for (int i = 1; i <= numOfColumns; i++) {
if (i > 1) sout.print(", ");
String columnName = rsmd.getColumnName(i);
sout.print(columnName);
}
sout.println("\n-------------------------------");
int numOfPrintedRows = 0;
int totalPrintedRows = 0;
boolean endOfTuple = true;
while (res.next()) {
for (int i = 1; i <= numOfColumns; i++) {
if (i > 1) sout.print(", ");
String columnValue = res.getString(i);
if(res.wasNull()){
sout.print(nullChar);
} else {
sout.print(columnValue);
}
}
sout.println();
sout.flush();
numOfPrintedRows++;
totalPrintedRows++;
if (printPause && printPauseRecords > 0 && totalPrintedRows < resultRows && numOfPrintedRows >= printPauseRecords) {
if (resultRows < Integer.MAX_VALUE) {
sout.print("(" + totalPrintedRows + "/" + resultRows + " rows, continue... 'q' is quit)");
} else {
sout.print("(" + totalPrintedRows + " rows, continue... 'q' is quit)");
}
sout.flush();
if (sin != null) {
if (sin.read() == QUIT_COMMAND) {
endOfTuple = false;
sout.println();
break;
}
}
numOfPrintedRows = 0;
sout.println();
}
}
sout.println(getQuerySuccessMessage(tableDesc, responseTime, totalPrintedRows, "selected", endOfTuple));
sout.flush();
}
@Override
public void printNoResult(PrintWriter sout) {
sout.println("(0 rows)");
sout.flush();
}
@Override
public void printProgress(PrintWriter sout, QueryStatus status) {
int terminalWidth = TerminalFactory.get().getWidth();
int progressWidth = (min(terminalWidth, 100) - 75) + 17; // progress bar is 17-42 characters wide
int progress = (int)(status.getProgress() * 100.0f);
String responseTime = getResponseTimeReadable((float)((status.getFinishTime() - status.getSubmitTime()) / 1000.0));
String progressBar = formatProgressBar(progressWidth, progress);
reprintProgressLine(sout, progressBar, progress, responseTime, status);
}
public String formatProgressBar(int width, int progress) {
if (progress == 0) {
return repeat(" ", width);
}
// compute nominal lengths
int completeLength = min(width, ceil(progress * width, 100));
int remainLength = width;
int runningLength = 1;
// adjust to fix rounding errors
if (((completeLength + runningLength + remainLength) != width) && (remainLength > 0)) {
remainLength = max(0, width - completeLength - runningLength);
}
if (((completeLength + runningLength + remainLength) > width) && (progress > 0)) {
completeLength = max(0, width - runningLength - remainLength);
}
return repeat("=", completeLength) + repeat(">", runningLength) + repeat(" ", remainLength);
}
private int ceil(int dividend, int divisor) {
return ((dividend + divisor) - 1) / divisor;
}
public void reprintProgressLine(PrintWriter out, String progressBar, int progress, String responseTime,
QueryStatus status) {
// [=====>> ] 10% 3.18 sec
String lineFormat = "[%s] %d%% %s";
if (isRealTerminal()) {
if (status.getState() == TajoProtos.QueryState.QUERY_SUCCEEDED) {
progressBar = "@|green " + progressBar + "|@";
}
else if (status.getState() == TajoProtos.QueryState.QUERY_ERROR ||
status.getState() == TajoProtos.QueryState.QUERY_FAILED ||
status.getState() == TajoProtos.QueryState.QUERY_KILLED) {
progressBar = "@|red " + progressBar + "|@";
}
String line = String.format(lineFormat, progressBar, progress, responseTime);
out.print(ansi().eraseLine(Ansi.Erase.ALL).a('\r').render(line));
}
else {
String line = String.format(lineFormat, progressBar, progress, responseTime);
out.print('\n' + line);
}
out.flush();
}
public boolean isRealTerminal() {
return REAL_TERMINAL;
}
@Override
public void printMessage(PrintWriter sout, String message) {
sout.println(message);
sout.flush();
}
@Override
public void printErrorMessage(PrintWriter sout, Throwable t) {
sout.println(parseErrorMessage(t.getMessage()));
if (printErrorTrace) {
sout.println(ExceptionUtils.getStackTrace(t));
}
sout.flush();
}
@Override
public void printErrorMessage(PrintWriter sout, String message) {
sout.println(parseErrorMessage(message));
sout.flush();
}
@Override
public void printKilledMessage(PrintWriter sout, QueryId queryId) {
sout.println(TajoCli.KILL_PREFIX + queryId);
sout.flush();
}
@Override
public void printErrorMessage(PrintWriter sout, QueryStatus status) {
if (status.getErrorMessage() != null && !status.getErrorMessage().isEmpty()) {
printErrorMessage(sout, parseErrorMessage(status.getErrorMessage()));
} else {
printErrorMessage(sout, "No error message");
}
if (printErrorTrace && status.getErrorTrace() != null && !status.getErrorTrace().isEmpty()) {
sout.println(status.getErrorTrace());
}
sout.flush();
}
public static String parseErrorMessage(String message) {
if (message == null) {
return TajoCli.ERROR_PREFIX + "No error message";
}
int index = message.indexOf(TajoCli.ERROR_PREFIX);
if (index < 0) {
message = TajoCli.ERROR_PREFIX + message;
} else {
message = message.substring(index);
}
return message;
}
/**
* borrowed from Presto
*/
private static boolean detectRealTerminal() {
// If the jansi.passthrough property is set, then don't interpret
// any of the ansi sequences.
if (Boolean.parseBoolean(System.getProperty("jansi.passthrough"))) {
return true;
}
// If the jansi.strip property is set, then we just strip
// the ansi escapes.
if (Boolean.parseBoolean(System.getProperty("jansi.strip"))) {
return false;
}
String os = System.getProperty("os.name");
if (os.startsWith("Windows")) {
// We could support this, but we'd need a windows box
return true;
}
// We must be on some unix variant..
try {
// check if standard out is a terminal
if (isatty(STDOUT_FILENO) == 0) {
return false;
}
}
catch (NoClassDefFoundError | UnsatisfiedLinkError ignore) {
// These errors happen if the JNI lib is not available for your platform.
}
return true;
}
}