提交 3f04419e authored 作者: Thomas Mueller's avatar Thomas Mueller

H2 Console: the built-in commands are now documented.

上级 5b6845c0
......@@ -18,6 +18,8 @@ Tutorial
<h1>Tutorial</h1>
<a href="#tutorial_starting_h2_console">
Starting and Using the H2 Console</a><br />
<a href="#console_syntax">
Special H2 Console Syntax</a><br />
<a href="#console_settings">
Settings of the H2 Console</a><br />
<a href="#connecting_using_jdbc">
......@@ -228,6 +230,168 @@ press [Ctrl]+[C] in the console where the server was started (Windows),
or close the console window.
</p>
<h2 id="console_settings">Special H2 Console Syntax</h2>
<p>
The H2 Console supports a few built-in commands.
Those are interpreted within the H2 Console, that means they work with any database.
They need to be at the beginning of a statement (before any remarks),
otherwise they are not parsed correctly. If in doubt, add ';' before the command.
</p>
<table>
<tr>
<th>Command(s)</th>
<th>Description</th>
</tr>
<tr>
<td class="notranslate">
@autocommit_true;<br />
@autocommit_false;
</td>
<td>
Enable or disable autocommit.
</td>
</tr>
<tr>
<td class="notranslate">
@cancel;
</td>
<td>
Cancel the currently running statement.
</td>
</tr>
<tr>
<td class="notranslate">
@columns null null TEST;<br />
@index_info&nbsp;null&nbsp;null&nbsp;TEST;<br />
@tables;<br />
@tables null null TEST;<br />
</td>
<td>
Call the corresponding DatabaseMetaData.get method.
Patterns are case sensitive (usually identifiers are uppercase).
For information about the parameters, see the Javadoc documentation.
Missing parameters at the end are set to null. The complete list of commands is:
<code>
@attributes @best_row_identifier @catalogs @columns
@column_privileges @cross_references @exported_keys
@imported_keys @index_info @primary_keys @procedures
@procedure_columns @schemas @super_tables @super_types
@tables @table_privileges @table_types @type_info @udts
@version_columns
</code>
</td>
</tr>
<tr>
<td class="notranslate">
@edit select * from test;
</td>
<td>
Use an updatable result set.
</td>
</tr>
<tr>
<td class="notranslate">
@generated&nbsp;insert&nbsp;into&nbsp;test()&nbsp;values();
</td>
<td>
Show the result of <code>Statement.getGeneratedKeys()</code>.
</td>
</tr>
<tr>
<td class="notranslate">
@history;
</td>
<td>
Show the command history.
</td>
</tr>
<tr>
<td class="notranslate">
@info;
</td>
<td>
Display the result of various <code>Connection</code> and <code>DatabaseMetaData</code> methods.
</td>
</tr>
<tr>
<td class="notranslate">
@list select * from test;
</td>
<td>
Show the result set in list format (each column on its own line, with row numbers).
</td>
</tr>
<tr>
<td class="notranslate">
@loop 1000 select ?, ?/*rnd*/;<br />
@loop 1000 @statement select ?;
</td>
<td>
Run the statement this many times.
Parameters (<code>?</code>) are set using a loop from 0 up to x - 1.
Random values are used for each <code>?/*rnd*/</code>.
A Statement object is used instead of a PreparedStatement if <code>@statement</code> is used.
Result sets are read until <code>ResultSet.next()</code> returns <code>false</code>.
Timing information is printed.
</td>
</tr>
<tr>
<td class="notranslate">
@maxrows&nbsp;20;
</td>
<td>
Set the maximum number of rows to display.
</td>
</tr>
<tr>
<td class="notranslate">
@memory;
</td>
<td>
Show the used and free memory. This will call <code>System.gc()</code>.
</td>
</tr>
<tr>
<td class="notranslate">
@meta&nbsp;select&nbsp;1;
</td>
<td>
List the <code>ResultSetMetaData</code> after running the query.
</td>
</tr>
<tr>
<td class="notranslate">
@parameter_meta&nbsp;select&nbsp;?;
</td>
<td>
Show the result of the <code>PreparedStatement.getParameterMetaData()</code> calls.
The statement is not executed.
</td>
</tr>
<tr>
<td class="notranslate">
@prof_start;<br />
call&nbsp;hash('SHA256',&nbsp;'',&nbsp;1000000);<br />
@prof_stop;
</td>
<td>
Start / stop the built-in profiling tool.
The top 3 stack traces of the statement(s) between start and stop are listed
(if there are 3).
</td>
</tr>
<tr>
<td class="notranslate">
@transaction_isolation;<br />
@transaction_isolation&nbsp;2;
</td>
<td>
Display (without parameters) or change
(with parameters 1, 2, 4, 8) the transaction isolation level.
</td>
</tr>
</table>
<h2 id="console_settings">Settings of the H2 Console</h2>
<p>
The settings of the H2 Console are stored in a configuration file
......
......@@ -23,7 +23,9 @@ import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Types;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
......@@ -934,28 +936,6 @@ public class WebApp {
try {
Connection conn = session.getConnection();
String result;
if ("@AUTOCOMMIT TRUE".equals(sql)) {
conn.setAutoCommit(true);
result = "${text.result.autoCommitOn}";
} else if ("@AUTOCOMMIT FALSE".equals(sql)) {
conn.setAutoCommit(false);
result = "${text.result.autoCommitOff}";
} else if (sql.startsWith("@TRANSACTION_ISOLATION")) {
String s = sql.substring("@TRANSACTION_ISOLATION".length()).trim();
if (s.length() > 0) {
int level = Integer.parseInt(s);
conn.setTransactionIsolation(level);
}
result = "Transaction Isolation: " + conn.getTransactionIsolation() + "<br />";
result += Connection.TRANSACTION_READ_UNCOMMITTED + ": READ_UNCOMMITTED<br />";
result += Connection.TRANSACTION_READ_COMMITTED + ": READ_COMMITTED<br />";
result += Connection.TRANSACTION_REPEATABLE_READ + ": REPEATABLE_READ<br />";
result += Connection.TRANSACTION_SERIALIZABLE + ": SERIALIZABLE";
} else if (sql.startsWith("@SET MAXROWS ")) {
int maxrows = Integer.parseInt(sql.substring("@SET MAXROWS ".length()));
session.put("maxrows", "" + maxrows);
result = "${text.result.maxrowsSet}";
} else {
ScriptReader r = new ScriptReader(new StringReader(sql));
ArrayList<String> list = New.arrayList();
while (true) {
......@@ -968,13 +948,12 @@ public class WebApp {
StringBuilder buff = new StringBuilder();
for (int i = 0; i < list.size(); i++) {
String s = list.get(i);
if (!s.startsWith("@")) {
if (!(s.startsWith("@") && s.endsWith("."))) {
buff.append(PageParser.escapeHtml(s + ";")).append("<br />");
}
buff.append(getResult(conn, i + 1, s, list.size() == 1, false)).append("<br />");
}
result = buff.toString();
}
session.put("result", result);
} catch (Throwable e) {
session.put("result", getStackTrace(0, e, session.getContents().isH2));
......@@ -1014,7 +993,7 @@ public class WebApp {
result = "<br />" + getStackTrace(0, e, session.getContents().isH2);
error = formatAsError(e.getMessage());
}
String sql = "@EDIT " + (String) session.get("resultSetSQL");
String sql = "@edit " + (String) session.get("resultSetSQL");
Connection conn = session.getConnection();
result = error + getResult(conn, -1, sql, true, true) + result;
session.put("result", result);
......@@ -1023,42 +1002,79 @@ public class WebApp {
private ResultSet getMetaResultSet(Connection conn, String sql) throws SQLException {
DatabaseMetaData meta = conn.getMetaData();
if (sql.startsWith("@TABLES")) {
if (isBuiltIn(sql, "@best_row_identifier")) {
String[] p = split(sql);
String[] types = p[4] == null ? null : StringUtils.arraySplit(p[4], ',', false);
return meta.getTables(p[1], p[2], p[3], types);
} else if (sql.startsWith("@COLUMNS")) {
int scale = p[4] == null ? 0 : Integer.parseInt(p[4]);
boolean nullable = p[5] == null ? false : Boolean.valueOf(p[5]).booleanValue();
return meta.getBestRowIdentifier(p[1], p[2], p[3], scale, nullable);
} else if (isBuiltIn(sql, "@catalogs")) {
return meta.getCatalogs();
} else if (isBuiltIn(sql, "@columns")) {
String[] p = split(sql);
return meta.getColumns(p[1], p[2], p[3], p[4]);
} else if (sql.startsWith("@INDEX_INFO")) {
} else if (isBuiltIn(sql, "@column_privileges")) {
String[] p = split(sql);
return meta.getColumnPrivileges(p[1], p[2], p[3], p[4]);
} else if (isBuiltIn(sql, "@cross_references")) {
String[] p = split(sql);
return meta.getCrossReference(p[1], p[2], p[3], p[4], p[5], p[6]);
} else if (isBuiltIn(sql, "@exported_keys")) {
String[] p = split(sql);
return meta.getExportedKeys(p[1], p[2], p[3]);
} else if (isBuiltIn(sql, "@imported_keys")) {
String[] p = split(sql);
return meta.getImportedKeys(p[1], p[2], p[3]);
} else if (isBuiltIn(sql, "@index_info")) {
String[] p = split(sql);
boolean unique = p[4] == null ? false : Boolean.valueOf(p[4]).booleanValue();
boolean approx = p[5] == null ? false : Boolean.valueOf(p[5]).booleanValue();
return meta.getIndexInfo(p[1], p[2], p[3], unique, approx);
} else if (sql.startsWith("@PRIMARY_KEYS")) {
} else if (isBuiltIn(sql, "@primary_keys")) {
String[] p = split(sql);
return meta.getPrimaryKeys(p[1], p[2], p[3]);
} else if (sql.startsWith("@PROCEDURES")) {
} else if (isBuiltIn(sql, "@procedures")) {
String[] p = split(sql);
return meta.getProcedures(p[1], p[2], p[3]);
} else if (sql.startsWith("@PROCEDURE_COLUMNS")) {
} else if (isBuiltIn(sql, "@procedure_columns")) {
String[] p = split(sql);
return meta.getProcedureColumns(p[1], p[2], p[3], p[4]);
} else if (sql.startsWith("@SCHEMAS")) {
} else if (isBuiltIn(sql, "@schemas")) {
return meta.getSchemas();
} else if (sql.startsWith("@CATALOG")) {
SimpleResultSet rs = new SimpleResultSet();
rs.addColumn("CATALOG", Types.VARCHAR, 0, 0);
rs.addRow(conn.getCatalog());
return rs;
} else if (sql.startsWith("@MEMORY")) {
} else if (isBuiltIn(sql, "@tables")) {
String[] p = split(sql);
String[] types = p[4] == null ? null : StringUtils.arraySplit(p[4], ',', false);
return meta.getTables(p[1], p[2], p[3], types);
} else if (isBuiltIn(sql, "@table_privileges")) {
String[] p = split(sql);
return meta.getTablePrivileges(p[1], p[2], p[3]);
} else if (isBuiltIn(sql, "@table_types")) {
return meta.getTableTypes();
} else if (isBuiltIn(sql, "@type_info")) {
return meta.getTypeInfo();
} else if (isBuiltIn(sql, "@udts")) {
String[] p = split(sql);
int[] types;
if (p[4] == null) {
types = null;
} else {
String[] t = StringUtils.arraySplit(p[4], ',', false);
types = new int[t.length];
for (int i = 0; i < t.length; i++) {
types[i] = Integer.parseInt(t[i]);
}
}
return meta.getUDTs(p[1], p[2], p[3], types);
} else if (isBuiltIn(sql, "@version_columns")) {
String[] p = split(sql);
return meta.getVersionColumns(p[1], p[2], p[3]);
} else if (isBuiltIn(sql, "@memory")) {
SimpleResultSet rs = new SimpleResultSet();
rs.addColumn("Type", Types.VARCHAR, 0, 0);
rs.addColumn("Value", Types.VARCHAR, 0, 0);
rs.addColumn("KB", Types.VARCHAR, 0, 0);
rs.addRow("Used Memory", "" + Utils.getMemoryUsed());
rs.addRow("Free Memory", "" + Utils.getMemoryFree());
return rs;
} else if (sql.startsWith("@INFO")) {
} else if (isBuiltIn(sql, "@info")) {
SimpleResultSet rs = new SimpleResultSet();
rs.addColumn("KEY", Types.VARCHAR, 0, 0);
rs.addColumn("VALUE", Types.VARCHAR, 0, 0);
......@@ -1079,66 +1095,18 @@ public class WebApp {
//## Java 1.4 end ##
addDatabaseMetaData(rs, meta);
return rs;
} else if (sql.startsWith("@CATALOGS")) {
return meta.getCatalogs();
} else if (sql.startsWith("@TABLE_TYPES")) {
return meta.getTableTypes();
} else if (sql.startsWith("@COLUMN_PRIVILEGES")) {
String[] p = split(sql);
return meta.getColumnPrivileges(p[1], p[2], p[3], p[4]);
} else if (sql.startsWith("@TABLE_PRIVILEGES")) {
String[] p = split(sql);
return meta.getTablePrivileges(p[1], p[2], p[3]);
} else if (sql.startsWith("@BEST_ROW_IDENTIFIER")) {
String[] p = split(sql);
int scale = p[4] == null ? 0 : Integer.parseInt(p[4]);
boolean nullable = p[5] == null ? false : Boolean.valueOf(p[5]).booleanValue();
return meta.getBestRowIdentifier(p[1], p[2], p[3], scale, nullable);
} else if (sql.startsWith("@VERSION_COLUMNS")) {
String[] p = split(sql);
return meta.getVersionColumns(p[1], p[2], p[3]);
} else if (sql.startsWith("@IMPORTED_KEYS")) {
String[] p = split(sql);
return meta.getImportedKeys(p[1], p[2], p[3]);
} else if (sql.startsWith("@EXPORTED_KEYS")) {
String[] p = split(sql);
return meta.getExportedKeys(p[1], p[2], p[3]);
} else if (sql.startsWith("@CROSS_REFERENCE")) {
String[] p = split(sql);
return meta.getCrossReference(p[1], p[2], p[3], p[4], p[5], p[6]);
} else if (sql.startsWith("@UDTS")) {
String[] p = split(sql);
int[] types;
if (p[4] == null) {
types = null;
} else {
String[] t = StringUtils.arraySplit(p[4], ',', false);
types = new int[t.length];
for (int i = 0; i < t.length; i++) {
types[i] = Integer.parseInt(t[i]);
}
}
return meta.getUDTs(p[1], p[2], p[3], types);
} else if (sql.startsWith("@TYPE_INFO")) {
return meta.getTypeInfo();
//## Java 1.4 begin ##
} else if (sql.startsWith("@SUPER_TYPES")) {
} else if (isBuiltIn(sql, "@attributes")) {
String[] p = split(sql);
return meta.getSuperTypes(p[1], p[2], p[3]);
} else if (sql.startsWith("@SUPER_TABLES")) {
return meta.getAttributes(p[1], p[2], p[3], p[4]);
} else if (isBuiltIn(sql, "@super_tables")) {
String[] p = split(sql);
return meta.getSuperTables(p[1], p[2], p[3]);
} else if (sql.startsWith("@ATTRIBUTES")) {
} else if (isBuiltIn(sql, "@super_types")) {
String[] p = split(sql);
return meta.getAttributes(p[1], p[2], p[3], p[4]);
return meta.getSuperTypes(p[1], p[2], p[3]);
//## Java 1.4 end ##
} else if (sql.startsWith("@PROF_START")) {
if (profiler != null) {
profiler.stopCollecting();
}
profiler = new Profiler();
profiler.startCollecting();
} else if (sql.startsWith("@PROF_STOP")) {
} else if (isBuiltIn(sql, "@prof_stop")) {
if (profiler != null) {
profiler.stopCollecting();
SimpleResultSet rs = new SimpleResultSet();
......@@ -1152,7 +1120,13 @@ public class WebApp {
}
private void addDatabaseMetaData(SimpleResultSet rs, DatabaseMetaData meta) {
for (Method m : DatabaseMetaData.class.getDeclaredMethods()) {
Method[] methods = DatabaseMetaData.class.getDeclaredMethods();
Arrays.sort(methods, new Comparator<Method>() {
public int compare(Method o1, Method o2) {
return o1.toString().compareTo(o2.toString());
}
});
for (Method m : methods) {
if (m.getParameterTypes().length == 0) {
try {
Object o = m.invoke(meta);
......@@ -1208,7 +1182,13 @@ public class WebApp {
boolean generatedKeys = false;
boolean edit = false;
boolean list = false;
if ("@CANCEL".equals(sql)) {
if (isBuiltIn(sql, "@autocommit_true")) {
conn.setAutoCommit(true);
return "${text.result.autoCommitOn}";
} else if (isBuiltIn(sql, "@autocommit_false")) {
conn.setAutoCommit(false);
return "${text.result.autoCommitOff}";
} else if (isBuiltIn(sql, "@cancel")) {
stat = session.executingStatement;
if (stat != null) {
stat.cancel();
......@@ -1217,33 +1197,55 @@ public class WebApp {
buff.append("${text.result.noRunningStatement}");
}
return buff.toString();
} else if (sql.startsWith("@PARAMETER_META")) {
sql = sql.substring("@PARAMETER_META".length()).trim();
PreparedStatement prep = conn.prepareStatement(sql);
buff.append(getParameterResultSet(prep.getParameterMetaData()));
} else if (isBuiltIn(sql, "@edit")) {
edit = true;
sql = sql.substring("@edit".length()).trim();
session.put("resultSetSQL", sql);
} else if (isBuiltIn(sql, "@generated")) {
generatedKeys = true;
sql = sql.substring("@generated".length()).trim();
} else if (isBuiltIn(sql, "@history")) {
buff.append(getHistoryString());
return buff.toString();
} else if (sql.startsWith("@META")) {
metadata = true;
sql = sql.substring("@META".length()).trim();
} else if (sql.startsWith("@LIST")) {
} else if (isBuiltIn(sql, "@list")) {
list = true;
sql = sql.substring("@LIST".length()).trim();
} else if (sql.startsWith("@GENERATED")) {
generatedKeys = true;
sql = sql.substring("@GENERATED".length()).trim();
} else if (sql.startsWith("@LOOP")) {
sql = sql.substring("@LOOP".length()).trim();
sql = sql.substring("@list".length()).trim();
} else if (isBuiltIn(sql, "@loop")) {
sql = sql.substring("@loop".length()).trim();
int idx = sql.indexOf(' ');
int count = Integer.decode(sql.substring(0, idx));
sql = sql.substring(idx).trim();
return executeLoop(conn, count, sql);
} else if (sql.startsWith("@EDIT")) {
edit = true;
sql = sql.substring("@EDIT".length()).trim();
session.put("resultSetSQL", sql);
} else if ("@HISTORY".equals(sql)) {
buff.append(getHistoryString());
} else if (isBuiltIn(sql, "@maxrows")) {
int maxrows = (int) Double.parseDouble(sql.substring("@maxrows".length()).trim());
session.put("maxrows", "" + maxrows);
return "${text.result.maxrowsSet}";
} else if (isBuiltIn(sql, "@meta")) {
metadata = true;
sql = sql.substring("@meta".length()).trim();
} else if (isBuiltIn(sql, "@parameter_meta")) {
sql = sql.substring("@parameter_meta".length()).trim();
PreparedStatement prep = conn.prepareStatement(sql);
buff.append(getParameterResultSet(prep.getParameterMetaData()));
return buff.toString();
} else if (isBuiltIn(sql, "@prof_start")) {
if (profiler != null) {
profiler.stopCollecting();
}
profiler = new Profiler();
profiler.startCollecting();
return "Ok";
} else if (isBuiltIn(sql, "@transaction_isolation")) {
String s = sql.substring("@transaction_isolation".length()).trim();
if (s.length() > 0) {
int level = Integer.parseInt(s);
conn.setTransactionIsolation(level);
}
buff.append("Transaction Isolation: " + conn.getTransactionIsolation() + "<br />");
buff.append(Connection.TRANSACTION_READ_UNCOMMITTED + ": read_uncommitted<br />");
buff.append(Connection.TRANSACTION_READ_COMMITTED + ": read_committed<br />");
buff.append(Connection.TRANSACTION_REPEATABLE_READ + ": repeatable_read<br />");
buff.append(Connection.TRANSACTION_SERIALIZABLE + ": serializable");
}
if (sql.startsWith("@")) {
rs = getMetaResultSet(conn, sql);
......@@ -1292,6 +1294,10 @@ public class WebApp {
}
}
private boolean isBuiltIn(String sql, String builtIn) {
return StringUtils.startsWithIgnoreCase(sql, builtIn);
}
private String executeLoop(Connection conn, int count, String sql) throws SQLException {
ArrayList<Integer> params = New.arrayList();
int idx = 0;
......@@ -1300,9 +1306,9 @@ public class WebApp {
if (idx < 0) {
break;
}
if (sql.substring(idx).startsWith("?/*RND*/")) {
if (isBuiltIn(sql.substring(idx), "?/*rnd*/")) {
params.add(1);
sql = sql.substring(0, idx) + "?" + sql.substring(idx + "/*RND*/".length() + 1);
sql = sql.substring(0, idx) + "?" + sql.substring(idx + "/*rnd*/".length() + 1);
} else {
params.add(0);
}
......@@ -1312,8 +1318,8 @@ public class WebApp {
boolean prepared;
Random random = new Random(1);
long time = System.currentTimeMillis();
if (sql.startsWith("@STATEMENT")) {
sql = sql.substring("@STATEMENT".length()).trim();
if (isBuiltIn(sql, "@statement")) {
sql = sql.substring("@statement".length()).trim();
prepared = false;
Statement stat = conn.createStatement();
for (int i = 0; !stop && i < count; i++) {
......@@ -1333,7 +1339,6 @@ public class WebApp {
// maybe get the data as well
}
rs.close();
// maybe close result set
}
}
} else {
......@@ -1571,7 +1576,7 @@ public class WebApp {
if (!edit && isUpdatable && allowEdit) {
buff.append("<br /><br /><form name=\"editResult\" method=\"post\" action=\"query.do?jsessionid=${sessionId}\" target=\"h2result\">" +
"<input type=\"submit\" class=\"button\" value=\"${text.resultEdit.editResult}\" />" +
"<input type=\"hidden\" name=\"sql\" value=\"@EDIT ").
"<input type=\"hidden\" name=\"sql\" value=\"@edit ").
append(PageParser.escapeHtmlData(sql)).
append("\" /></form>");
}
......
......@@ -80,6 +80,21 @@ public class StringUtils {
return s.toLowerCase(Locale.ENGLISH);
}
/**
* Check is a string starts with another string, ignoring the case.
*
* @param s the string to check (must be longer than start)
* @param start the prefix of s
* @return true if start is a prefix of s
*/
public static boolean startsWithIgnoreCase(String s, String start) {
if (s.length() < start.length()) {
return false;
}
return s.substring(0, start.length()).equalsIgnoreCase(start);
}
/**
* Convert a string to a SQL literal. Null is converted to NULL. The text is
* enclosed in single quotes. If there are any special characters, the method
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论