summaryrefslogtreecommitdiffstats
path: root/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/QueryShell.java
diff options
context:
space:
mode:
Diffstat (limited to 'gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/QueryShell.java')
-rw-r--r--gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/QueryShell.java781
1 files changed, 0 insertions, 781 deletions
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
deleted file mode 100644
index 9651f39dd2..0000000000
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/QueryShell.java
+++ /dev/null
@@ -1,781 +0,0 @@
-// Copyright (C) 2009 The Android Open Source Project
-//
-// Licensed 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 com.google.gerrit.sshd.commands;
-
-import static java.nio.charset.StandardCharsets.UTF_8;
-
-import com.google.gerrit.common.TimeUtil;
-import com.google.gerrit.common.Version;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.reviewdb.server.ReviewDbUtil;
-import com.google.gerrit.server.schema.ReviewDbFactory;
-import com.google.gson.JsonArray;
-import com.google.gson.JsonObject;
-import com.google.gwtorm.jdbc.JdbcSchema;
-import com.google.gwtorm.server.OrmException;
-import com.google.gwtorm.server.SchemaFactory;
-import com.google.inject.Inject;
-import com.google.inject.assistedinject.Assisted;
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.OutputStream;
-import java.io.OutputStreamWriter;
-import java.io.PrintWriter;
-import java.sql.Connection;
-import java.sql.DatabaseMetaData;
-import java.sql.ResultSet;
-import java.sql.ResultSetMetaData;
-import java.sql.SQLException;
-import java.sql.Statement;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.TreeMap;
-
-/** Simple interactive SQL query tool. */
-public class QueryShell {
- public interface Factory {
- QueryShell create(@Assisted InputStream in, @Assisted OutputStream out);
- }
-
- public enum OutputFormat {
- PRETTY,
- JSON,
- JSON_SINGLE
- }
-
- private final BufferedReader in;
- private final PrintWriter out;
- private final SchemaFactory<ReviewDb> dbFactory;
- private OutputFormat outputFormat = OutputFormat.PRETTY;
-
- private ReviewDb db;
- private Connection connection;
- private Statement statement;
-
- @Inject
- QueryShell(
- @ReviewDbFactory final SchemaFactory<ReviewDb> dbFactory,
- @Assisted final InputStream in,
- @Assisted final OutputStream out) {
- this.dbFactory = dbFactory;
- this.in = new BufferedReader(new InputStreamReader(in, UTF_8));
- this.out = new PrintWriter(new OutputStreamWriter(out, UTF_8));
- }
-
- public void setOutputFormat(OutputFormat fmt) {
- outputFormat = fmt;
- }
-
- public void run() {
- try {
- db = ReviewDbUtil.unwrapDb(dbFactory.open());
- try {
- connection = ((JdbcSchema) db).getConnection();
- connection.setAutoCommit(true);
-
- statement = connection.createStatement();
- try {
- showBanner();
- readEvalPrintLoop();
- } finally {
- statement.close();
- statement = null;
- }
- } finally {
- db.close();
- db = null;
- }
- } catch (OrmException | SQLException err) {
- out.println("fatal: Cannot open connection: " + err.getMessage());
- } finally {
- out.flush();
- }
- }
-
- public void execute(String query) {
- try {
- db = dbFactory.open();
- try {
- connection = ((JdbcSchema) db).getConnection();
- connection.setAutoCommit(true);
-
- statement = connection.createStatement();
- try {
- executeStatement(query);
- } finally {
- statement.close();
- statement = null;
- }
- } finally {
- db.close();
- db = null;
- }
- } catch (OrmException | SQLException err) {
- out.println("fatal: Cannot open connection: " + err.getMessage());
- } finally {
- out.flush();
- }
- }
-
- private void readEvalPrintLoop() {
- final StringBuilder buffer = new StringBuilder();
- boolean executed = false;
- for (; ; ) {
- if (outputFormat == OutputFormat.PRETTY) {
- print(buffer.length() == 0 || executed ? "gerrit> " : " -> ");
- }
- String line = readLine();
- if (line == null) {
- return;
- }
-
- if (line.startsWith("\\")) {
- // Shell command, check the various cases we recognize
- //
- line = line.substring(1);
- if (line.equals("h") || line.equals("?")) {
- showHelp();
-
- } else if (line.equals("q")) {
- if (outputFormat == OutputFormat.PRETTY) {
- println("Bye");
- }
- return;
-
- } else if (line.equals("r")) {
- buffer.setLength(0);
- executed = false;
-
- } else if (line.equals("p")) {
- println(buffer.toString());
-
- } else if (line.equals("g")) {
- if (buffer.length() > 0) {
- executeStatement(buffer.toString());
- executed = true;
- }
-
- } else if (line.equals("d")) {
- listTables();
-
- } else if (line.startsWith("d ")) {
- showTable(line.substring(2).trim());
-
- } else {
- final String msg = "'\\" + line + "' not supported";
- switch (outputFormat) {
- case JSON_SINGLE:
- case JSON:
- {
- final JsonObject err = new JsonObject();
- err.addProperty("type", "error");
- err.addProperty("message", msg);
- println(err.toString());
- break;
- }
- case PRETTY:
- default:
- println("ERROR: " + msg);
- println("");
- showHelp();
- break;
- }
- }
- continue;
- }
-
- if (executed) {
- buffer.setLength(0);
- executed = false;
- }
- if (buffer.length() > 0) {
- buffer.append('\n');
- }
- buffer.append(line);
-
- if (buffer.length() > 0 && buffer.charAt(buffer.length() - 1) == ';') {
- executeStatement(buffer.toString());
- executed = true;
- }
- }
- }
-
- private void listTables() {
- final DatabaseMetaData meta;
- try {
- meta = connection.getMetaData();
- } catch (SQLException e) {
- error(e);
- return;
- }
-
- final String[] types = {"TABLE", "VIEW"};
- try (ResultSet rs = meta.getTables(null, null, null, types)) {
- if (outputFormat == OutputFormat.PRETTY) {
- println(" List of relations");
- }
- showResultSet(
- rs,
- false,
- 0,
- Identity.create(rs, "TABLE_SCHEM"),
- Identity.create(rs, "TABLE_NAME"),
- Identity.create(rs, "TABLE_TYPE"));
- } catch (SQLException e) {
- error(e);
- }
-
- println("");
- }
-
- private void showTable(String tableName) {
- final DatabaseMetaData meta;
- try {
- meta = connection.getMetaData();
-
- if (meta.storesUpperCaseIdentifiers()) {
- tableName = tableName.toUpperCase();
- } else if (meta.storesLowerCaseIdentifiers()) {
- tableName = tableName.toLowerCase();
- }
- } catch (SQLException e) {
- error(e);
- return;
- }
-
- try (ResultSet rs = meta.getColumns(null, null, tableName, null)) {
- if (!rs.next()) {
- throw new SQLException("Table " + tableName + " not found");
- }
-
- if (outputFormat == OutputFormat.PRETTY) {
- println(" Table " + tableName);
- }
- showResultSet(
- rs,
- true,
- 0,
- Identity.create(rs, "COLUMN_NAME"),
- new Function("TYPE") {
- @Override
- String apply(ResultSet rs) throws SQLException {
- String type = rs.getString("TYPE_NAME");
- switch (rs.getInt("DATA_TYPE")) {
- case java.sql.Types.CHAR:
- case java.sql.Types.VARCHAR:
- type += "(" + rs.getInt("COLUMN_SIZE") + ")";
- break;
- }
-
- String def = rs.getString("COLUMN_DEF");
- if (def != null && !def.isEmpty()) {
- type += " DEFAULT " + def;
- }
-
- int nullable = rs.getInt("NULLABLE");
- if (nullable == DatabaseMetaData.columnNoNulls) {
- type += " NOT NULL";
- }
- return type;
- }
- });
- } catch (SQLException e) {
- error(e);
- return;
- }
-
- try (ResultSet rs = meta.getIndexInfo(null, null, tableName, false, true)) {
- Map<String, IndexInfo> indexes = new TreeMap<>();
- while (rs.next()) {
- final String indexName = rs.getString("INDEX_NAME");
- IndexInfo def = indexes.get(indexName);
- if (def == null) {
- def = new IndexInfo();
- def.name = indexName;
- indexes.put(indexName, def);
- }
-
- if (!rs.getBoolean("NON_UNIQUE")) {
- def.unique = true;
- }
-
- final int pos = rs.getInt("ORDINAL_POSITION");
- final String col = rs.getString("COLUMN_NAME");
- String desc = rs.getString("ASC_OR_DESC");
- if ("D".equals(desc)) {
- desc = " DESC";
- } else {
- desc = "";
- }
- def.addColumn(pos, col + desc);
-
- String filter = rs.getString("FILTER_CONDITION");
- if (filter != null && !filter.isEmpty()) {
- def.filter.append(filter);
- }
- }
-
- if (outputFormat == OutputFormat.PRETTY) {
- println("");
- println("Indexes on " + tableName + ":");
- for (IndexInfo def : indexes.values()) {
- println(" " + def);
- }
- }
- } catch (SQLException e) {
- error(e);
- return;
- }
-
- println("");
- }
-
- private void executeStatement(String sql) {
- final long start = TimeUtil.nowMs();
- final boolean hasResultSet;
- try {
- hasResultSet = statement.execute(sql);
- } catch (SQLException e) {
- error(e);
- return;
- }
-
- try {
- if (hasResultSet) {
- try (ResultSet rs = statement.getResultSet()) {
- showResultSet(rs, false, start);
- }
-
- } else {
- final int updateCount = statement.getUpdateCount();
- final long ms = TimeUtil.nowMs() - start;
- switch (outputFormat) {
- case JSON_SINGLE:
- case JSON:
- {
- final JsonObject tail = new JsonObject();
- tail.addProperty("type", "update-stats");
- tail.addProperty("rowCount", updateCount);
- tail.addProperty("runTimeMilliseconds", ms);
- println(tail.toString());
- break;
- }
-
- case PRETTY:
- default:
- println("UPDATE " + updateCount + "; " + ms + " ms");
- break;
- }
- }
- } catch (SQLException e) {
- error(e);
- }
- }
-
- /**
- * 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(ResultSet rs, boolean alreadyOnRow, long start, Function... show)
- throws SQLException {
- switch (outputFormat) {
- case JSON_SINGLE:
- case JSON:
- showResultSetJson(rs, alreadyOnRow, start, show);
- break;
- case PRETTY:
- default:
- showResultSetPretty(rs, alreadyOnRow, start, show);
- break;
- }
- }
-
- /**
- * 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) {
- columnMap = show;
-
- } else {
- final int colCnt = meta.getColumnCount();
- columnMap = new Function[colCnt];
- for (int colId = 0; colId < colCnt; colId++) {
- final int p = colId + 1;
- final String name = meta.getColumnLabel(p);
- columnMap[colId] = new Identity(p, name);
- }
- }
-
- int rowCnt = 0;
- while (alreadyOnRow || rs.next()) {
- final JsonObject row = new JsonObject();
- final JsonObject cols = new JsonObject();
- for (Function function : columnMap) {
- String v = function.apply(rs);
- if (v == null) {
- continue;
- }
- cols.addProperty(function.name.toLowerCase(), v);
- }
- row.addProperty("type", "row");
- row.add("columns", cols);
- switch (outputFormat) {
- case JSON:
- println(row.toString());
- break;
- case JSON_SINGLE:
- collector.add(row);
- break;
- case PRETTY:
- default:
- final JsonObject obj = new JsonObject();
- obj.addProperty("type", "error");
- obj.addProperty("message", "Unsupported Json variant");
- println(obj.toString());
- return;
- }
- alreadyOnRow = false;
- rowCnt++;
- }
-
- JsonObject tail = null;
- if (start != 0) {
- tail = new JsonObject();
- tail.addProperty("type", "query-stats");
- tail.addProperty("rowCount", rowCnt);
- final long ms = TimeUtil.nowMs() - 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;
- case PRETTY:
- default:
- final JsonObject obj = new JsonObject();
- obj.addProperty("type", "error");
- obj.addProperty("message", "Unsupported Json variant");
- println(obj.toString());
- }
- }
-
- /**
- * 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;
- if (show != null && 0 < show.length) {
- columnMap = show;
-
- } else {
- final int colCnt = meta.getColumnCount();
- columnMap = new Function[colCnt];
- for (int colId = 0; colId < colCnt; colId++) {
- final int p = colId + 1;
- final String name = meta.getColumnLabel(p);
- columnMap[colId] = new Identity(p, name);
- }
- }
-
- final int colCnt = columnMap.length;
- final int[] widths = new int[colCnt];
- for (int c = 0; c < colCnt; c++) {
- widths[c] = columnMap[c].name.length();
- }
-
- final List<String[]> rows = new ArrayList<>();
- while (alreadyOnRow || rs.next()) {
- final String[] row = new String[columnMap.length];
- for (int c = 0; c < colCnt; c++) {
- row[c] = columnMap[c].apply(rs);
- if (row[c] == null) {
- row[c] = "NULL";
- }
- widths[c] = Math.max(widths[c], row[c].length());
- }
- rows.add(row);
- alreadyOnRow = false;
- }
-
- final StringBuilder b = new StringBuilder();
- for (int c = 0; c < colCnt; c++) {
- if (0 < c) {
- b.append(" | ");
- }
-
- String n = columnMap[c].name;
- if (widths[c] < n.length()) {
- n = n.substring(0, widths[c]);
- }
- b.append(n);
-
- if (c < colCnt - 1) {
- for (int pad = n.length(); pad < widths[c]; pad++) {
- b.append(' ');
- }
- }
- }
- println(" " + b.toString());
-
- b.setLength(0);
- for (int c = 0; c < colCnt; c++) {
- if (0 < c) {
- b.append("-+-");
- }
- for (int pad = 0; pad < widths[c]; pad++) {
- b.append('-');
- }
- }
- println(" " + b.toString());
-
- boolean dataTruncated = false;
- for (String[] row : rows) {
- b.setLength(0);
- b.append(' ');
-
- for (int c = 0; c < colCnt; c++) {
- final int max = widths[c];
- if (0 < c) {
- b.append(" | ");
- }
-
- String s = row[c];
- if (1 < colCnt && max < s.length()) {
- s = s.substring(0, max);
- dataTruncated = true;
- }
- b.append(s);
-
- if (c < colCnt - 1) {
- for (int pad = s.length(); pad < max; pad++) {
- b.append(' ');
- }
- }
- }
- println(b.toString());
- }
-
- if (dataTruncated) {
- warning("some column data was truncated");
- }
-
- if (start != 0) {
- final int rowCount = rows.size();
- final long ms = TimeUtil.nowMs() - start;
- println("(" + rowCount + (rowCount == 1 ? " row" : " rows") + "; " + ms + " ms)");
- }
- }
-
- private void warning(String msg) {
- switch (outputFormat) {
- case JSON_SINGLE:
- case JSON:
- {
- final JsonObject obj = new JsonObject();
- obj.addProperty("type", "warning");
- obj.addProperty("message", msg);
- println(obj.toString());
- break;
- }
-
- case PRETTY:
- default:
- println("WARNING: " + msg);
- break;
- }
- }
-
- private void error(SQLException err) {
- switch (outputFormat) {
- case JSON_SINGLE:
- case JSON:
- {
- final JsonObject obj = new JsonObject();
- obj.addProperty("type", "error");
- obj.addProperty("message", err.getMessage());
- println(obj.toString());
- break;
- }
-
- case PRETTY:
- default:
- println("ERROR: " + err.getMessage());
- break;
- }
- }
-
- private void print(String s) {
- out.print(s);
- out.flush();
- }
-
- private void println(String s) {
- out.print(s);
- out.print('\n');
- out.flush();
- }
-
- private String readLine() {
- try {
- return in.readLine();
- } catch (IOException e) {
- return null;
- }
- }
-
- private void showBanner() {
- if (outputFormat == OutputFormat.PRETTY) {
- println("Welcome to Gerrit Code Review " + Version.getVersion());
- try {
- print("(");
- print(connection.getMetaData().getDatabaseProductName());
- print(" ");
- print(connection.getMetaData().getDatabaseProductVersion());
- println(")");
- } catch (SQLException err) {
- error(err);
- }
- println("");
- println("Type '\\h' for help. Type '\\r' to clear the buffer.");
- println("");
- }
- }
-
- private void showHelp() {
- final StringBuilder help = new StringBuilder();
- help.append("General\n");
- help.append(" \\q quit\n");
-
- help.append("\n");
- help.append("Query Buffer\n");
- help.append(" \\g execute the query buffer\n");
- help.append(" \\p display the current buffer\n");
- help.append(" \\r clear the query buffer\n");
-
- help.append("\n");
- help.append("Informational\n");
- help.append(" \\d list all tables\n");
- help.append(" \\d NAME describe table\n");
-
- help.append("\n");
- print(help.toString());
- }
-
- private abstract static class Function {
- final String name;
-
- Function(String name) {
- this.name = name;
- }
-
- abstract String apply(ResultSet rs) throws SQLException;
- }
-
- private static class Identity extends Function {
- static Identity create(ResultSet rs, String name) throws SQLException {
- return new Identity(rs.findColumn(name), name);
- }
-
- final int colId;
-
- Identity(int colId, String name) {
- super(name);
- this.colId = colId;
- }
-
- @Override
- String apply(ResultSet rs) throws SQLException {
- return rs.getString(colId);
- }
- }
-
- private static class IndexInfo {
- String name;
- boolean unique;
- final Map<Integer, String> columns = new TreeMap<>();
- final StringBuilder filter = new StringBuilder();
-
- void addColumn(int pos, String column) {
- columns.put(Integer.valueOf(pos), column);
- }
-
- @Override
- public String toString() {
- final StringBuilder r = new StringBuilder();
- r.append(name);
- if (unique) {
- r.append(" UNIQUE");
- }
- r.append(" (");
- boolean first = true;
- for (String c : columns.values()) {
- if (!first) {
- r.append(", ");
- }
- r.append(c);
- first = false;
- }
- r.append(")");
- if (filter.length() > 0) {
- r.append(" WHERE ");
- r.append(filter);
- }
- return r.toString();
- }
- }
-}