Add gsql format that returns result set as single Json object

Change-Id: I16d93147d4c800bbfa568ce8402fc008f8eb69cd
This commit is contained in:
Christian Aistleitner
2013-08-14 13:16:51 +02:00
parent 4c74694915
commit caf41d7dd5
2 changed files with 115 additions and 34 deletions

View File

@@ -9,7 +9,7 @@ SYNOPSIS
-------- --------
[verse] [verse]
'ssh' -p <port> <host> 'gerrit gsql' 'ssh' -p <port> <host> 'gerrit gsql'
[--format {PRETTY | JSON}] [--format {PRETTY | JSON | JSON_SINGLE}]
[-c QUERY] [-c QUERY]
DESCRIPTION DESCRIPTION
@@ -26,6 +26,8 @@ OPTIONS
for reading by a human on a sufficiently wide terminal. for reading by a human on a sufficiently wide terminal.
In JSON mode records are output as JSON objects using the In JSON mode records are output as JSON objects using the
column names as the property names, one object per line. column names as the property names, one object per line.
In JSON_SINGLE mode the whole result set is output as a
single JSON object.
-c:: -c::
Execute the single query statement supplied, and then exit. Execute the single query statement supplied, and then exit.
@@ -38,7 +40,8 @@ global capability.
SCRIPTING SCRIPTING
--------- ---------
Intended for interactive use only, unless format is JSON. Intended for interactive use only, unless format is JSON, or
JSON_SINGLE.
EXAMPLES EXAMPLES
-------- --------

View File

@@ -16,7 +16,11 @@ package com.google.gerrit.sshd.commands;
import com.google.gerrit.common.Version; import com.google.gerrit.common.Version;
import com.google.gerrit.reviewdb.server.ReviewDb; import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gwtorm.jdbc.JdbcSchema; import com.google.gwtorm.jdbc.JdbcSchema;
import com.google.gwtorm.server.OrmException; import com.google.gwtorm.server.OrmException;
import com.google.gwtorm.server.SchemaFactory; import com.google.gwtorm.server.SchemaFactory;
@@ -49,7 +53,7 @@ public class QueryShell {
} }
public static enum OutputFormat { public static enum OutputFormat {
PRETTY, JSON; PRETTY, JSON, JSON_SINGLE;
} }
private final BufferedReader in; private final BufferedReader in;
@@ -178,6 +182,7 @@ public class QueryShell {
} else { } else {
final String msg = "'\\" + line + "' not supported"; final String msg = "'\\" + line + "' not supported";
switch (outputFormat) { switch (outputFormat) {
case JSON_SINGLE:
case JSON: { case JSON: {
final JsonObject err = new JsonObject(); final JsonObject err = new JsonObject();
err.addProperty("type", "error"); err.addProperty("type", "error");
@@ -228,7 +233,7 @@ public class QueryShell {
if (outputFormat == OutputFormat.PRETTY) { if (outputFormat == OutputFormat.PRETTY) {
println(" List of relations"); println(" List of relations");
} }
showResultSet(rs, false, showResultSet(rs, false, 0,
Identity.create(rs, "TABLE_SCHEM"), Identity.create(rs, "TABLE_SCHEM"),
Identity.create(rs, "TABLE_NAME"), Identity.create(rs, "TABLE_NAME"),
Identity.create(rs, "TABLE_TYPE")); Identity.create(rs, "TABLE_TYPE"));
@@ -267,7 +272,7 @@ public class QueryShell {
if (outputFormat == OutputFormat.PRETTY) { if (outputFormat == OutputFormat.PRETTY) {
println(" Table " + tableName); println(" Table " + tableName);
} }
showResultSet(rs, true, showResultSet(rs, true, 0,
Identity.create(rs, "COLUMN_NAME"), Identity.create(rs, "COLUMN_NAME"),
new Function("TYPE") { new Function("TYPE") {
@Override @Override
@@ -365,24 +370,7 @@ public class QueryShell {
if (hasResultSet) { if (hasResultSet) {
final ResultSet rs = statement.getResultSet(); final ResultSet rs = statement.getResultSet();
try { try {
final int rowCount = showResultSet(rs, false); showResultSet(rs, false, start);
final long ms = System.currentTimeMillis() - start;
switch (outputFormat) {
case JSON: {
final JsonObject tail = new JsonObject();
tail.addProperty("type", "query-stats");
tail.addProperty("rowCount", rowCount);
tail.addProperty("runTimeMilliseconds", ms);
println(tail.toString());
break;
}
case PRETTY:
default:
println("(" + rowCount + (rowCount == 1 ? " row" : " rows")
+ "; " + ms + " ms)");
break;
}
} finally { } finally {
rs.close(); rs.close();
} }
@@ -391,6 +379,7 @@ public class QueryShell {
final int updateCount = statement.getUpdateCount(); final int updateCount = statement.getUpdateCount();
final long ms = System.currentTimeMillis() - start; final long ms = System.currentTimeMillis() - start;
switch (outputFormat) { switch (outputFormat) {
case JSON_SINGLE:
case JSON: { case JSON: {
final JsonObject tail = new JsonObject(); final JsonObject tail = new JsonObject();
tail.addProperty("type", "update-stats"); tail.addProperty("type", "update-stats");
@@ -411,19 +400,47 @@ public class QueryShell {
} }
} }
private int showResultSet(final ResultSet rs, boolean alreadyOnRow, /**
Function... show) throws SQLException { * Outputs a result set to stdout.
*
* @param rs ResultSet to show.
* @param alreadyOnRow true if rs is already on the first row. false
* otherwise.
* @param start Timestamp in milliseconds when executing the statement
* started. This timestamp is used to compute statistics about the
* statement. If no statistics should be shown, set it to 0.
* @param show Functions to map columns
* @throws SQLException
*/
private void showResultSet(final ResultSet rs, boolean alreadyOnRow,
long start, Function... show) throws SQLException {
switch (outputFormat) { switch (outputFormat) {
case JSON_SINGLE:
case JSON: case JSON:
return showResultSetJson(rs, alreadyOnRow, show); showResultSetJson(rs, alreadyOnRow, start, show);
break;
case PRETTY: case PRETTY:
default: default:
return showResultSetPretty(rs, alreadyOnRow, show); showResultSetPretty(rs, alreadyOnRow, start, show);
break;
} }
} }
private int showResultSetJson(final ResultSet rs, boolean alreadyOnRow, /**
Function... show) throws SQLException { * Outputs a result set to stdout in Json format.
*
* @param rs ResultSet to show.
* @param alreadyOnRow true if rs is already on the first row. false
* otherwise.
* @param start Timestamp in milliseconds when executing the statement
* started. This timestamp is used to compute statistics about the
* statement. If no statistics should be shown, set it to 0.
* @param show Functions to map columns
* @throws SQLException
*/
private void showResultSetJson(final ResultSet rs, boolean alreadyOnRow,
long start, Function... show) throws SQLException {
JsonArray collector = new JsonArray();
final ResultSetMetaData meta = rs.getMetaData(); final ResultSetMetaData meta = rs.getMetaData();
final Function[] columnMap; final Function[] columnMap;
if (show != null && 0 < show.length) { if (show != null && 0 < show.length) {
@@ -453,15 +470,68 @@ public class QueryShell {
} }
row.addProperty("type", "row"); row.addProperty("type", "row");
row.add("columns", cols); row.add("columns", cols);
switch (outputFormat) {
case JSON:
println(row.toString()); println(row.toString());
break;
case JSON_SINGLE:
collector.add(row);
break;
default:
final JsonObject obj = new JsonObject();
obj.addProperty("type", "error");
obj.addProperty("message", "Unsupported Json variant");
println(obj.toString());
return;
}
alreadyOnRow = false; alreadyOnRow = false;
rowCnt++; rowCnt++;
} }
return rowCnt;
JsonObject tail = null;
if (start != 0) {
tail = new JsonObject();
tail.addProperty("type", "query-stats");
tail.addProperty("rowCount", rowCnt);
final long ms = System.currentTimeMillis() - start;
tail.addProperty("runTimeMilliseconds", ms);
} }
private int showResultSetPretty(final ResultSet rs, boolean alreadyOnRow, switch (outputFormat) {
Function... show) throws SQLException { case JSON:
if (tail != null) {
println(tail.toString());
}
break;
case JSON_SINGLE:
if (tail != null) {
collector.add(tail);
}
println(collector.toString());
break;
default:
final JsonObject obj = new JsonObject();
obj.addProperty("type", "error");
obj.addProperty("message", "Unsupported Json variant");
println(obj.toString());
return;
}
}
/**
* Outputs a result set to stdout in plain text format.
*
* @param rs ResultSet to show.
* @param alreadyOnRow true if rs is already on the first row. false
* otherwise.
* @param start Timestamp in milliseconds when executing the statement
* started. This timestamp is used to compute statistics about the
* statement. If no statistics should be shown, set it to 0.
* @param show Functions to map columns
* @throws SQLException
*/
private void showResultSetPretty(final ResultSet rs, boolean alreadyOnRow,
long start, Function... show) throws SQLException {
final ResultSetMetaData meta = rs.getMetaData(); final ResultSetMetaData meta = rs.getMetaData();
final Function[] columnMap; final Function[] columnMap;
@@ -559,11 +629,18 @@ public class QueryShell {
if (dataTruncated) { if (dataTruncated) {
warning("some column data was truncated"); warning("some column data was truncated");
} }
return rows.size();
if (start != 0) {
final int rowCount = rows.size();
final long ms = System.currentTimeMillis() - start;
println("(" + rowCount + (rowCount == 1 ? " row" : " rows")
+ "; " + ms + " ms)");
}
} }
private void warning(final String msg) { private void warning(final String msg) {
switch (outputFormat) { switch (outputFormat) {
case JSON_SINGLE:
case JSON: { case JSON: {
final JsonObject obj = new JsonObject(); final JsonObject obj = new JsonObject();
obj.addProperty("type", "warning"); obj.addProperty("type", "warning");
@@ -581,6 +658,7 @@ public class QueryShell {
private void error(final SQLException err) { private void error(final SQLException err) {
switch (outputFormat) { switch (outputFormat) {
case JSON_SINGLE:
case JSON: { case JSON: {
final JsonObject obj = new JsonObject(); final JsonObject obj = new JsonObject();
obj.addProperty("type", "error"); obj.addProperty("type", "error");