Merge "Add gsql format that returns result set as single Json object"
diff --git a/Documentation/cmd-gsql.txt b/Documentation/cmd-gsql.txt
index 7ddc41f..3c1fd31 100644
--- a/Documentation/cmd-gsql.txt
+++ b/Documentation/cmd-gsql.txt
@@ -9,7 +9,7 @@
--------
[verse]
'ssh' -p <port> <host> 'gerrit gsql'
- [--format {PRETTY | JSON}]
+ [--format {PRETTY | JSON | JSON_SINGLE}]
[-c QUERY]
DESCRIPTION
@@ -26,6 +26,8 @@
for reading by a human on a sufficiently wide terminal.
In JSON mode records are output as JSON objects using the
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::
Execute the single query statement supplied, and then exit.
@@ -38,7 +40,8 @@
SCRIPTING
---------
-Intended for interactive use only, unless format is JSON.
+Intended for interactive use only, unless format is JSON, or
+JSON_SINGLE.
EXAMPLES
--------
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/QueryShell.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/QueryShell.java
index 93c6af2..d979745 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/QueryShell.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/QueryShell.java
@@ -16,7 +16,11 @@
import com.google.gerrit.common.Version;
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.JsonParser;
import com.google.gwtorm.jdbc.JdbcSchema;
import com.google.gwtorm.server.OrmException;
import com.google.gwtorm.server.SchemaFactory;
@@ -49,7 +53,7 @@
}
public static enum OutputFormat {
- PRETTY, JSON;
+ PRETTY, JSON, JSON_SINGLE;
}
private final BufferedReader in;
@@ -178,6 +182,7 @@
} else {
final String msg = "'\\" + line + "' not supported";
switch (outputFormat) {
+ case JSON_SINGLE:
case JSON: {
final JsonObject err = new JsonObject();
err.addProperty("type", "error");
@@ -228,7 +233,7 @@
if (outputFormat == OutputFormat.PRETTY) {
println(" List of relations");
}
- showResultSet(rs, false,
+ showResultSet(rs, false, 0,
Identity.create(rs, "TABLE_SCHEM"),
Identity.create(rs, "TABLE_NAME"),
Identity.create(rs, "TABLE_TYPE"));
@@ -267,7 +272,7 @@
if (outputFormat == OutputFormat.PRETTY) {
println(" Table " + tableName);
}
- showResultSet(rs, true,
+ showResultSet(rs, true, 0,
Identity.create(rs, "COLUMN_NAME"),
new Function("TYPE") {
@Override
@@ -365,24 +370,7 @@
if (hasResultSet) {
final ResultSet rs = statement.getResultSet();
try {
- final int rowCount = showResultSet(rs, false);
- 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;
- }
+ showResultSet(rs, false, start);
} finally {
rs.close();
}
@@ -391,6 +379,7 @@
final int updateCount = statement.getUpdateCount();
final long ms = System.currentTimeMillis() - start;
switch (outputFormat) {
+ case JSON_SINGLE:
case JSON: {
final JsonObject tail = new JsonObject();
tail.addProperty("type", "update-stats");
@@ -411,19 +400,47 @@
}
}
- 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) {
+ case JSON_SINGLE:
case JSON:
- return showResultSetJson(rs, alreadyOnRow, show);
+ showResultSetJson(rs, alreadyOnRow, start, show);
+ break;
case PRETTY:
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 Function[] columnMap;
if (show != null && 0 < show.length) {
@@ -453,15 +470,68 @@
}
row.addProperty("type", "row");
row.add("columns", cols);
- println(row.toString());
+ switch (outputFormat) {
+ case JSON:
+ 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;
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);
+ }
+
+ switch (outputFormat) {
+ 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;
+ }
}
- private int showResultSetPretty(final ResultSet rs, boolean alreadyOnRow,
- Function... show) throws SQLException {
+ /**
+ * 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 Function[] columnMap;
@@ -559,11 +629,18 @@
if (dataTruncated) {
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) {
switch (outputFormat) {
+ case JSON_SINGLE:
case JSON: {
final JsonObject obj = new JsonObject();
obj.addProperty("type", "warning");
@@ -581,6 +658,7 @@
private void error(final SQLException err) {
switch (outputFormat) {
+ case JSON_SINGLE:
case JSON: {
final JsonObject obj = new JsonObject();
obj.addProperty("type", "error");