Unverified 提交 e329948b authored 作者: Evgenij Ryazanov's avatar Evgenij Ryazanov 提交者: GitHub

Merge pull request #1726 from katzyn/ifNotExists

Disable remote database creation by default
......@@ -1200,7 +1200,7 @@ options <code>-webAllowOthers, -tcpAllowOthers, -pgAllowOthers</code>.
<p>
If you enable remote access using
<code>-tcpAllowOthers</code> or <code>-pgAllowOthers</code>,
please also consider using the options <code>-baseDir, -ifExists</code>,
please also consider using the options <code>-baseDir</code>,
so that remote users can not create new databases
or access existing databases with weak passwords.
When using the option <code>-baseDir</code>, only databases within that directory may be accessed.
......@@ -1209,7 +1209,7 @@ Ensure the existing accessible databases are protected using strong passwords.
<p>
If you enable remote access using <code>-webAllowOthers</code>,
please ensure the web server can only be accessed from trusted networks.
The options <code>-baseDir, -ifExists</code> don't protect
The options <code>-baseDir</code> don't protect
access to the tools section, prevent remote shutdown of the web server,
changes to the preferences, the saved connection settings,
or access to other databases accessible from the system.
......@@ -1422,25 +1422,25 @@ CREATE SPATIAL INDEX GEO_TABLE_SPATIAL_INDEX ON GEO_TABLE(THE_GEOM);
</pre>
<p>
To query the table using geometry envelope intersection,
use the operation <code>&&</code>, as in PostGIS:
use the operation <code>&amp;&amp;</code>, as in PostGIS:
</p>
<pre>
SELECT * FROM GEO_TABLE
WHERE THE_GEOM && 'POLYGON ((490 490, 536 490, 536 515, 490 515, 490 490))';
WHERE THE_GEOM &amp;&amp; 'POLYGON ((490 490, 536 490, 536 515, 490 515, 490 490))';
</pre>
<p>
You can verify that the spatial index is used using the "explain plan" feature:
</p>
<pre>
EXPLAIN SELECT * FROM GEO_TABLE
WHERE THE_GEOM && 'POLYGON ((490 490, 536 490, 536 515, 490 515, 490 490))';
WHERE THE_GEOM &amp;&amp; 'POLYGON ((490 490, 536 490, 536 515, 490 515, 490 490))';
-- Result
SELECT
GEO_TABLE.GID,
GEO_TABLE.THE_GEOM
FROM PUBLIC.GEO_TABLE
/* PUBLIC.GEO_TABLE_SPATIAL_INDEX:
THE_GEOM && 'POLYGON ((490 490, 536 490, 536 515, 490 515, 490 490))' */
THE_GEOM &amp;&amp; 'POLYGON ((490 490, 536 490, 536 515, 490 515, 490 490))' */
WHERE INTERSECTS(THE_GEOM,
'POLYGON ((490 490, 536 490, 536 515, 490 515, 490 490))')
</pre>
......
......@@ -97,7 +97,8 @@ No, currently commercial support is not available.
<h3 id="create_database">How to Create a New Database?</h3>
<p>
By default, a new database is automatically created if it does not yet exist.
By default, a new database is automatically created if it does not yet exist when
<a href="features.html#database_url">embedded</a> URL is used.
See <a href="tutorial.html#creating_new_databases">Creating New Databases</a>.
</p>
......
......@@ -499,7 +499,8 @@ For more information about the algorithms, see
<h2 id="database_only_if_exists">Opening a Database Only if it Already Exists</h2>
<p>
By default, when an application calls <code>DriverManager.getConnection(url, ...)</code>
and the database specified in the URL does not yet exist, a new (empty) database is created.
with <a href="features.html#database_url">embedded</a> URL and the database specified in the URL does not yet exist,
a new (empty) database is created.
In some situations, it is better to restrict creating new databases, and only allow to open
existing databases. To do this, add <code>;IFEXISTS=TRUE</code>
to the database URL. In this case, if the database does not already exist, an exception is thrown when
......
......@@ -436,6 +436,7 @@ Supported settings are:
<ul><li><code>webAllowOthers</code>: allow other computers to connect.
</li><li><code>webPort</code>: the port of the H2 Console
</li><li><code>webSSL</code>: use encrypted TLS (HTTPS) connections.
</li><li><code>webAdminPassword</code>: password to access preferences and tools of H2 Console.
</li></ul>
<p>
In addition to those settings, the properties of the last recently used connection
......@@ -474,14 +475,18 @@ In this database, user names are not case sensitive, but passwords are.
<h2 id="creating_new_databases">Creating New Databases</h2>
<p>
By default, if the database specified in the URL does not yet exist, a new (empty)
database is created automatically. The user that created the database automatically becomes
the administrator of this database.
By default, if the database specified in the <a href="features.html#database_url">embedded</a> URL does not yet exist,
a new (empty) database is created automatically.
The user that created the database automatically becomes the administrator of this database.
</p>
<p>
Auto-creating new database can be disabled, see
Auto-creation of databases can be disabled, see
<a href="features.html#database_only_if_exists">Opening a Database Only if it Already Exists</a>.
</p>
<p>
H2 Console does not allow creation of databases by default.
If H2 Console is launched with icon in the system tray its context menu can be used to create a new database.
</p>
<h2 id="using_server">Using the Server</h2>
<p>
......
......@@ -611,13 +611,13 @@ public class ErrorCode {
public static final int PARAMETER_NOT_SET_1 = 90012;
/**
* The error with code <code>90013</code> is thrown when
* trying to open a database that does not exist using the flag
* IFEXISTS=TRUE, or when trying to access a database object with a catalog
* name that does not match the database name. Example:
* The error with code <code>90013</code> is thrown when trying to open a
* database that does not exist remotely without enabling remote database
* creation first, or using the flag IFEXISTS=TRUE, or when trying to access
* a database object with a catalog name that does not match the database
* name.
* <pre>
* CREATE TABLE TEST(ID INT);
* SELECT XYZ.PUBLIC.TEST.ID FROM TEST;
* jdbc:h2:./database_that_does_not_exist
* </pre>
*/
public static final int DATABASE_NOT_FOUND_1 = 90013;
......
......@@ -36,6 +36,6 @@ org.h2.tools.RunScript.main=Options are case sensitive. Supported options are\:\
org.h2.tools.Script=Creates a SQL script file by extracting the schema and data of a database.
org.h2.tools.Script.main=Options are case sensitive. Supported options are\:\n[-help] or [-?] Print the list of options\n[-url "<url>"] The database URL (jdbc\:...)\n[-user <user>] The user name (default\: sa)\n[-password <pwd>] The password\n[-script <file>] The target script file name (default\: backup.sql)\n[-options ...] A list of options (only for embedded H2, see SCRIPT)\n[-quiet] Do not print progress information
org.h2.tools.Server=Starts the H2 Console (web-) server, TCP, and PG server.
org.h2.tools.Server.main=When running without options, -tcp, -web, -browser and -pg are started.\nOptions are case sensitive. Supported options are\:\n[-help] or [-?] Print the list of options\n[-web] Start the web server with the H2 Console\n[-webAllowOthers] Allow other computers to connect - see below\n[-webDaemon] Use a daemon thread\n[-webPort <port>] The port (default\: 8082)\n[-webSSL] Use encrypted (HTTPS) connections\n[-browser] Start a browser connecting to the web server\n[-tcp] Start the TCP server\n[-tcpAllowOthers] Allow other computers to connect - see below\n[-tcpDaemon] Use a daemon thread\n[-tcpPort <port>] The port (default\: 9092)\n[-tcpSSL] Use encrypted (SSL) connections\n[-tcpPassword <pwd>] The password for shutting down a TCP server\n[-tcpShutdown "<url>"] Stop the TCP server; example\: tcp\://localhost\n[-tcpShutdownForce] Do not wait until all connections are closed\n[-pg] Start the PG server\n[-pgAllowOthers] Allow other computers to connect - see below\n[-pgDaemon] Use a daemon thread\n[-pgPort <port>] The port (default\: 5435)\n[-properties "<dir>"] Server properties (default\: ~, disable\: null)\n[-baseDir <dir>] The base directory for H2 databases (all servers)\n[-ifExists] Only existing databases may be opened (all servers)\n[-trace] Print additional trace information (all servers)\n[-key <from> <to>] Allows to map a database name to another (all servers)\nThe options -xAllowOthers are potentially risky.\nFor details, see Advanced Topics / Protection against Remote Access.
org.h2.tools.Server.main=When running without options, -tcp, -web, -browser and -pg are started.\nOptions are case sensitive. Supported options are\:\n[-help] or [-?] Print the list of options\n[-web] Start the web server with the H2 Console\n[-webAllowOthers] Allow other computers to connect - see below\n[-webDaemon] Use a daemon thread\n[-webPort <port>] The port (default\: 8082)\n[-webSSL] Use encrypted (HTTPS) connections\n[-webAdminPassword] Password of DB Console administrator\n[-browser] Start a browser connecting to the web server\n[-tcp] Start the TCP server\n[-tcpAllowOthers] Allow other computers to connect - see below\n[-tcpDaemon] Use a daemon thread\n[-tcpPort <port>] The port (default\: 9092)\n[-tcpSSL] Use encrypted (SSL) connections\n[-tcpPassword <pwd>] The password for shutting down a TCP server\n[-tcpShutdown "<url>"] Stop the TCP server; example\: tcp\://localhost\n[-tcpShutdownForce] Do not wait until all connections are closed\n[-pg] Start the PG server\n[-pgAllowOthers] Allow other computers to connect - see below\n[-pgDaemon] Use a daemon thread\n[-pgPort <port>] The port (default\: 5435)\n[-properties "<dir>"] Server properties (default\: ~, disable\: null)\n[-baseDir <dir>] The base directory for H2 databases (all servers)\n[-ifExists] Only existing databases may be opened (all servers)\n[-ifNotExists] Databases are created when accessed\n[-trace] Print additional trace information (all servers)\n[-key <from> <to>] Allows to map a database name to another (all servers)\nThe options -xAllowOthers are potentially risky.\nFor details, see Advanced Topics / Protection against Remote Access.
org.h2.tools.Shell=Interactive command line tool to access a database using JDBC.
org.h2.tools.Shell.main=Options are case sensitive. Supported options are\:\n[-help] or [-?] Print the list of options\n[-url "<url>"] The database URL (jdbc\:h2\:...)\n[-user <user>] The user name\n[-password <pwd>] The password\n[-driver <class>] The JDBC driver class to use (not required in most cases)\n[-sql "<statements>"] Execute the SQL statements and exit\n[-properties "<dir>"] Load the server properties from this directory\nIf special characters don't work as expected, you may need to use\n -Dfile.encoding\=UTF-8 (Mac OS X) or CP850 (Windows).
......@@ -63,7 +63,7 @@ public class TcpServer implements Service {
private String baseDir;
private boolean allowOthers;
private boolean isDaemon;
private boolean ifExists;
private boolean ifExists = true;
private Connection managementDb;
private PreparedStatement managementDbAdd;
private PreparedStatement managementDbRemove;
......@@ -187,6 +187,8 @@ public class TcpServer implements Service {
isDaemon = true;
} else if (Tool.isOption(a, "-ifExists")) {
ifExists = true;
} else if (Tool.isOption(a, "-ifNotExists")) {
ifExists = false;
}
}
org.h2.Driver.load();
......
......@@ -79,7 +79,7 @@ public class PgServer implements Service {
private String baseDir;
private boolean allowOthers;
private boolean isDaemon;
private boolean ifExists;
private boolean ifExists = true;
private String key, keyDatabase;
@Override
......@@ -100,6 +100,8 @@ public class PgServer implements Service {
isDaemon = true;
} else if (Tool.isOption(a, "-ifExists")) {
ifExists = true;
} else if (Tool.isOption(a, "-ifNotExists")) {
ifExists = false;
} else if (Tool.isOption(a, "-key")) {
key = args[++i];
keyDatabase = args[++i];
......
......@@ -168,6 +168,14 @@ public class WebApp {
trace(file);
if (file.endsWith(".do")) {
file = process(file);
} else if (file.endsWith(".jsp")) {
switch (file) {
case "admin.jsp":
case "tools.jsp":
if (!checkAdmin(file)) {
file = process("adminLogin.do");
}
}
}
return file;
}
......@@ -207,46 +215,86 @@ public class WebApp {
private String process(String file) {
trace("process " + file);
while (file.endsWith(".do")) {
if ("login.do".equals(file)) {
switch (file) {
case "login.do":
file = login();
} else if ("index.do".equals(file)) {
break;
case "index.do":
file = index();
} else if ("logout.do".equals(file)) {
break;
case "logout.do":
file = logout();
} else if ("settingRemove.do".equals(file)) {
break;
case "settingRemove.do":
file = settingRemove();
} else if ("settingSave.do".equals(file)) {
break;
case "settingSave.do":
file = settingSave();
} else if ("test.do".equals(file)) {
break;
case "test.do":
file = test();
} else if ("query.do".equals(file)) {
break;
case "query.do":
file = query();
} else if ("tables.do".equals(file)) {
break;
case "tables.do":
file = tables();
} else if ("editResult.do".equals(file)) {
break;
case "editResult.do":
file = editResult();
} else if ("getHistory.do".equals(file)) {
break;
case "getHistory.do":
file = getHistory();
} else if ("admin.do".equals(file)) {
file = admin();
} else if ("adminSave.do".equals(file)) {
file = adminSave();
} else if ("adminStartTranslate.do".equals(file)) {
file = adminStartTranslate();
} else if ("adminShutdown.do".equals(file)) {
file = adminShutdown();
} else if ("autoCompleteList.do".equals(file)) {
break;
case "admin.do":
file = checkAdmin(file) ? admin() : "adminLogin.do";
break;
case "adminSave.do":
file = checkAdmin(file) ? adminSave() : "adminLogin.do";
break;
case "adminStartTranslate.do":
file = checkAdmin(file) ? adminStartTranslate() : "adminLogin.do";
break;
case "adminShutdown.do":
file = checkAdmin(file) ? adminShutdown() : "adminLogin.do";
break;
case "autoCompleteList.do":
file = autoCompleteList();
} else if ("tools.do".equals(file)) {
file = tools();
} else {
break;
case "tools.do":
file = checkAdmin(file) ? tools() : "adminLogin.do";
break;
case "adminLogin.do":
file = adminLogin();
break;
default:
file = "error.jsp";
break;
}
}
trace("return " + file);
return file;
}
private boolean checkAdmin(String file) {
Boolean b = (Boolean) session.get("admin");
if (b != null && b) {
return true;
}
session.put("adminBack", file);
return false;
}
private String adminLogin() {
String password = attributes.getProperty("password");
if (password == null || password.isEmpty() || !server.checkAdminPassword(password)) {
return "adminLogin.jsp";
}
String back = (String) session.remove("adminBack");
session.put("admin", true);
return back != null ? back : "admin.do";
}
private String autoCompleteList() {
String query = (String) attributes.get("query");
boolean lowercase = false;
......@@ -358,6 +406,10 @@ public class WebApp {
boolean ssl = Utils.parseBoolean((String) attributes.get("ssl"), false, false);
prop.setProperty("webSSL", String.valueOf(ssl));
server.setSSL(ssl);
byte[] adminPassword = server.getAdminPassword();
if (adminPassword != null) {
prop.setProperty("webAdminPassword", StringUtils.convertBytesToHex(adminPassword));
}
server.saveProperties(prop);
} catch (Exception e) {
trace(e.toString());
......@@ -983,6 +1035,7 @@ public class WebApp {
} catch (Exception e) {
trace(e.toString());
}
session.remove("admin");
return "index.do";
}
......
......@@ -16,6 +16,7 @@ import java.sql.Connection;
import java.sql.SQLException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
......@@ -28,6 +29,7 @@ import java.util.Set;
import org.h2.engine.Constants;
import org.h2.engine.SysProperties;
import org.h2.message.DbException;
import org.h2.security.SHA256;
import org.h2.server.Service;
import org.h2.server.ShutdownHandler;
import org.h2.store.fs.FileUtils;
......@@ -154,6 +156,7 @@ public class WebServer implements Service {
private final Set<WebThread> running =
Collections.synchronizedSet(new HashSet<WebThread>());
private boolean ssl;
private byte[] adminPassword;
private final HashMap<String, ConnectionInfo> connInfoMap = new HashMap<>();
private long lastTimeoutCheck;
......@@ -164,7 +167,7 @@ public class WebServer implements Service {
private String url;
private ShutdownHandler shutdownHandler;
private Thread listenerThread;
private boolean ifExists;
private boolean ifExists = true;
private boolean trace;
private TranslateThread translateThread;
private boolean allowChunked = true;
......@@ -278,6 +281,7 @@ public class WebServer implements Service {
"webSSL", false);
allowOthers = SortedProperties.getBooleanProperty(prop,
"webAllowOthers", false);
setAdminPassword(SortedProperties.getStringProperty(prop, "webAdminPassword", null));
commandHistoryString = prop.getProperty(COMMAND_HISTORY);
for (int i = 0; args != null && i < args.length; i++) {
String a = args[i];
......@@ -294,6 +298,10 @@ public class WebServer implements Service {
SysProperties.setBaseDir(baseDir);
} else if (Tool.isOption(a, "-ifExists")) {
ifExists = true;
} else if (Tool.isOption(a, "-ifNotExists")) {
ifExists = false;
} else if (Tool.isOption(a, "-webAdminPassword")) {
setAdminPassword(args[++i]);
} else if (Tool.isOption(a, "-properties")) {
// already set
i++;
......@@ -677,6 +685,9 @@ public class WebServer implements Service {
Boolean.toString(SortedProperties.getBooleanProperty(old, "webAllowOthers", allowOthers)));
prop.setProperty("webSSL",
Boolean.toString(SortedProperties.getBooleanProperty(old, "webSSL", ssl)));
if (adminPassword != null) {
prop.setProperty("webAdminPassword", StringUtils.convertBytesToHex(adminPassword));
}
if (commandHistoryString != null) {
prop.setProperty(COMMAND_HISTORY, commandHistoryString);
}
......@@ -846,4 +857,36 @@ public class WebServer implements Service {
return allowChunked;
}
byte[] getAdminPassword() {
return adminPassword;
}
void setAdminPassword(String password) {
if (password == null || password.isEmpty()) {
adminPassword = null;
return;
}
if (password.length() == 128) {
try {
adminPassword = StringUtils.convertHexToBytes(password);
return;
} catch (Exception ex) {}
}
byte[] salt = MathUtils.secureRandomBytes(32);
byte[] hash = SHA256.getHashWithSalt(password.getBytes(StandardCharsets.UTF_8), salt);
byte[] total = Arrays.copyOf(salt, 64);
System.arraycopy(hash, 0, total, 32, 32);
adminPassword = total;
}
boolean checkAdminPassword(String password) {
if (adminPassword == null) {
return false;
}
byte[] salt = Arrays.copyOf(adminPassword, 32);
byte[] hash = new byte[32];
System.arraycopy(adminPassword, 32, hash, 0, 32);
return Utils.compareSecure(hash, SHA256.getHashWithSalt(password.getBytes(StandardCharsets.UTF_8), salt));
}
}
......@@ -98,9 +98,10 @@ class WebSession {
* Remove a session attribute from the map.
*
* @param key the key
* @return value that was associated with the key, or null
*/
void remove(String key) {
map.remove(key);
Object remove(String key) {
return map.remove(key);
}
/**
......
......@@ -6,7 +6,7 @@ a.password=Пароль
a.remoteConnectionsDisabled=Извините, удаленные подключения ('webAllowOthers') запрещены на этом сервере.
a.title=H2 Console
a.tools=Инструменты
a.user=Пользователь Имя
a.user=Имя пользователя
admin.executing=Выполняется
admin.ip=IP
admin.lastAccess=Последний Вход
......@@ -20,7 +20,7 @@ adminConnection=Безопасность подключения
adminHttp=Используйте незашифрованные HTTP-соединения
adminHttps=Используйте SSL (HTTPS) соединения
adminLocal=Разрешены только локальные подключения
adminLogin=Администратор Логин
adminLogin=Административный вход
adminLoginCancel=Отменить
adminLoginOk=OK
adminLogout=Выход
......
......@@ -15,7 +15,7 @@ Initial Developer: H2 Group
${text.adminTitle}
</h1>
<p>
<a href="index.do?jsessionid=${sessionId}">${text.adminLogout}</a>
<a href="logout.do?jsessionid=${sessionId}">${text.adminLogout}</a>
</p>
<hr />
<form name="admin" method="post" action="adminSave.do?jsessionid=${sessionId}">
......
......@@ -10,7 +10,7 @@ Initial Developer: H2 Group
<link rel="stylesheet" type="text/css" href="stylesheet.css" />
</head>
<body style="margin: 20px">
<form name="adminLogin" method="post" action="admin.do?jsessionid=${sessionId}">
<form name="adminLogin" method="post" action="adminLogin.do?jsessionid=${sessionId}">
<table class="login" cellspacing="0" cellpadding="0">
<tr class="login">
<th class="login">${text.adminLogin}</th>
......
......@@ -72,7 +72,7 @@ public class Console extends Tool implements ShutdownHandler {
/**
* This tool starts the H2 Console (web-) server, as well as the TCP and PG
* server. For JDK 1.6, a system tray icon is created, for platforms that
* server. A system tray icon is created, for platforms that
* support it. Otherwise, a small window opens.
*
* @param args the command line arguments
......
......@@ -6,6 +6,7 @@
package org.h2.tools;
import java.awt.Button;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Frame;
......@@ -19,8 +20,10 @@ import java.awt.MenuItem;
import java.awt.Panel;
import java.awt.PopupMenu;
import java.awt.SystemColor;
import java.awt.TextArea;
import java.awt.TextField;
import java.awt.Toolkit;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
......@@ -28,6 +31,7 @@ import java.awt.event.MouseListener;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.io.IOException;
import java.sql.DriverManager;
import java.util.Locale;
import java.util.concurrent.TimeUnit;
......@@ -39,11 +43,18 @@ import org.h2.util.Utils;
public class GUIConsole extends Console implements ActionListener, MouseListener, WindowListener {
private long lastOpenNs;
private Frame frame;
private boolean trayIconUsed;
private Font font;
private Button startBrowser;
private Frame statusFrame;
private TextField urlText;
private Button startBrowser;
private Frame createFrame;
private TextField pathField, userField, passwordField;
private Button createButton;
private TextArea errorArea;
private Object tray;
private Object trayIcon;
......@@ -53,7 +64,7 @@ public class GUIConsole extends Console implements ActionListener, MouseListener
loadFont();
try {
if (!createTrayIcon()) {
showWindow();
showStatusWindow();
}
} catch (Exception e) {
e.printStackTrace();
......@@ -77,9 +88,9 @@ public class GUIConsole extends Console implements ActionListener, MouseListener
@Override
public void shutdown() {
super.shutdown();
if (frame != null) {
frame.dispose();
frame = null;
if (statusFrame != null) {
statusFrame.dispose();
statusFrame = null;
}
if (trayIconUsed) {
try {
......@@ -129,6 +140,11 @@ public class GUIConsole extends Console implements ActionListener, MouseListener
itemConsole.addActionListener(this);
itemConsole.setFont(font);
menuConsole.add(itemConsole);
MenuItem itemCreate = new MenuItem("Create a new database...");
itemCreate.setActionCommand("showCreate");
itemCreate.addActionListener(this);
itemCreate.setFont(font);
menuConsole.add(itemCreate);
MenuItem itemStatus = new MenuItem("Status");
itemStatus.setActionCommand("status");
itemStatus.addActionListener(this);
......@@ -178,21 +194,21 @@ public class GUIConsole extends Console implements ActionListener, MouseListener
}
}
private void showWindow() {
if (frame != null) {
private void showStatusWindow() {
if (statusFrame != null) {
return;
}
frame = new Frame("H2 Console");
frame.addWindowListener(this);
statusFrame = new Frame("H2 Console");
statusFrame.addWindowListener(this);
Image image = loadImage("/org/h2/res/h2.png");
if (image != null) {
frame.setIconImage(image);
statusFrame.setIconImage(image);
}
frame.setResizable(false);
frame.setBackground(SystemColor.control);
statusFrame.setResizable(false);
statusFrame.setBackground(SystemColor.control);
GridBagLayout layout = new GridBagLayout();
frame.setLayout(layout);
statusFrame.setLayout(layout);
// the main panel keeps everything together
Panel mainPanel = new Panel(layout);
......@@ -242,15 +258,15 @@ public class GUIConsole extends Console implements ActionListener, MouseListener
startBrowser.addActionListener(this);
startBrowser.setFont(font);
mainPanel.add(startBrowser, constraintsButton);
frame.add(mainPanel, constraintsPanel);
statusFrame.add(mainPanel, constraintsPanel);
int width = 300, height = 120;
frame.setSize(width, height);
statusFrame.setSize(width, height);
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
frame.setLocation((screenSize.width - width) / 2,
statusFrame.setLocation((screenSize.width - width) / 2,
(screenSize.height - height) / 2);
try {
frame.setVisible(true);
statusFrame.setVisible(true);
} catch (Throwable t) {
// ignore
// some systems don't support this method, for example IKVM
......@@ -258,8 +274,8 @@ public class GUIConsole extends Console implements ActionListener, MouseListener
}
try {
// ensure this window is in front of the browser
frame.setAlwaysOnTop(true);
frame.setAlwaysOnTop(false);
statusFrame.setAlwaysOnTop(true);
statusFrame.setAlwaysOnTop(false);
} catch (Throwable t) {
// ignore
}
......@@ -279,6 +295,161 @@ public class GUIConsole extends Console implements ActionListener, MouseListener
}
}
private void showCreateDatabase() {
if (createFrame != null) {
return;
}
createFrame = new Frame("H2 Console");
createFrame.addWindowListener(this);
Image image = loadImage("/org/h2/res/h2.png");
if (image != null) {
createFrame.setIconImage(image);
}
createFrame.setResizable(false);
createFrame.setBackground(SystemColor.control);
GridBagLayout layout = new GridBagLayout();
createFrame.setLayout(layout);
// the main panel keeps everything together
Panel mainPanel = new Panel(layout);
GridBagConstraints constraints;
constraints = new GridBagConstraints();
constraints.fill = GridBagConstraints.HORIZONTAL;
constraints.gridx = 0;
constraints.gridy = 0;
Label urlLabel = new Label("Database path:", Label.LEFT);
urlLabel.setFont(font);
mainPanel.add(urlLabel, constraints);
constraints = new GridBagConstraints();
constraints.fill = GridBagConstraints.HORIZONTAL;
constraints.gridy = 0;
constraints.weightx = 1d;
constraints.insets = new Insets(0, 5, 0, 0);
constraints.gridx = 1;
pathField = new TextField();
pathField.setFont(font);
pathField.setText("./test");
mainPanel.add(pathField, constraints);
constraints = new GridBagConstraints();
constraints.fill = GridBagConstraints.HORIZONTAL;
constraints.gridx = 0;
constraints.gridy = 1;
Label userLabel = new Label("Username:", Label.LEFT);
userLabel.setFont(font);
mainPanel.add(userLabel, constraints);
constraints = new GridBagConstraints();
constraints.fill = GridBagConstraints.HORIZONTAL;
constraints.gridy = 1;
constraints.weightx = 1d;
constraints.insets = new Insets(0, 5, 0, 0);
constraints.gridx = 1;
userField = new TextField();
userField.setFont(font);
userField.setText("sa");
mainPanel.add(userField, constraints);
constraints = new GridBagConstraints();
constraints.fill = GridBagConstraints.HORIZONTAL;
constraints.gridx = 0;
constraints.gridy = 2;
Label passwordLabel = new Label("Password:", Label.LEFT);
passwordLabel.setFont(font);
mainPanel.add(passwordLabel, constraints);
constraints = new GridBagConstraints();
constraints.fill = GridBagConstraints.HORIZONTAL;
constraints.gridy = 2;
constraints.weightx = 1d;
constraints.insets = new Insets(0, 5, 0, 0);
constraints.gridx = 1;
passwordField = new TextField();
passwordField.setFont(font);
mainPanel.add(passwordField, constraints);
constraints = new GridBagConstraints();
constraints.gridx = 0;
constraints.gridwidth = 2;
constraints.insets = new Insets(10, 0, 0, 0);
constraints.gridy = 3;
constraints.anchor = GridBagConstraints.EAST;
createButton = new Button("Create");
createButton.setFocusable(false);
createButton.setActionCommand("create");
createButton.addActionListener(this);
createButton.setFont(font);
mainPanel.add(createButton, constraints);
constraints = new GridBagConstraints();
constraints.fill = GridBagConstraints.HORIZONTAL;
constraints.gridy = 4;
constraints.weightx = 1d;
constraints.insets = new Insets(10, 0, 0, 0);
constraints.gridx = 0;
constraints.gridwidth = 2;
errorArea = new TextArea();
errorArea.setFont(font);
errorArea.setEditable(false);
mainPanel.add(errorArea, constraints);
constraints = new GridBagConstraints();
constraints.gridx = 0;
constraints.weightx = 1d;
constraints.weighty = 1d;
constraints.fill = GridBagConstraints.BOTH;
constraints.insets = new Insets(0, 10, 0, 10);
constraints.gridy = 0;
createFrame.add(mainPanel, constraints);
int width = 300, height = 300;
createFrame.setSize(width, height);
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
createFrame.setLocation((screenSize.width - width) / 2,
(screenSize.height - height) / 2);
try {
createFrame.setVisible(true);
} catch (Throwable t) {
// ignore
// some systems don't support this method, for example IKVM
// however it still works
}
try {
// ensure this window is in front of the browser
createFrame.setAlwaysOnTop(true);
createFrame.setAlwaysOnTop(false);
} catch (Throwable t) {
// ignore
}
}
private void createDatabase() {
if (web == null || createFrame == null) {
return;
}
String path = pathField.getText(), user = userField.getText(), password = passwordField.getText();
if (password.isEmpty()) {
errorArea.setForeground(Color.RED);
errorArea.setText("Specify a password");
return;
}
String url = "jdbc:h2:" + path;
try {
DriverManager.getConnection(url, user, password).close();
errorArea.setForeground(new Color(0, 0x99, 0));
errorArea.setText("Database was created successfully.\n\n"
+ "JDBC URL for H2 Console:\n"
+ url);
} catch (Exception ex) {
errorArea.setForeground(Color.RED);
errorArea.setText(ex.getMessage());
}
}
/**
* INTERNAL
*/
......@@ -289,11 +460,17 @@ public class GUIConsole extends Console implements ActionListener, MouseListener
shutdown();
} else if ("console".equals(command)) {
startBrowser();
} else if ("showCreate".equals(command)) {
showCreateDatabase();
} else if ("status".equals(command)) {
showWindow();
} else if (startBrowser == e.getSource()) {
showStatusWindow();
} else {
// for some reason, IKVM ignores setActionCommand
startBrowser();
if (startBrowser == e.getSource()) {
startBrowser();
} else if (createButton == e.getSource()) {
createDatabase();
}
}
}
......@@ -345,8 +522,14 @@ public class GUIConsole extends Console implements ActionListener, MouseListener
@Override
public void windowClosing(WindowEvent e) {
if (trayIconUsed) {
frame.dispose();
frame = null;
Window window = e.getWindow();
if (window == statusFrame) {
statusFrame.dispose();
statusFrame = null;
} else if (window == createFrame) {
createFrame.dispose();
createFrame = null;
}
} else {
shutdown();
}
......
......@@ -70,6 +70,8 @@ public class Server extends Tool implements Runnable, ShutdownHandler {
* <td>The port (default: 8082)</td></tr>
* <tr><td>[-webSSL]</td>
* <td>Use encrypted (HTTPS) connections</td></tr>
* <tr><td>[-webAdminPassword]</td>
* <td>Password of DB Console administrator</td></tr>
* <tr><td>[-browser]</td>
* <td>Start a browser connecting to the web server</td></tr>
* <tr><td>[-tcp]</td>
......@@ -102,6 +104,8 @@ public class Server extends Tool implements Runnable, ShutdownHandler {
* <td>The base directory for H2 databases (all servers)</td></tr>
* <tr><td>[-ifExists]</td>
* <td>Only existing databases may be opened (all servers)</td></tr>
* <tr><td>[-ifNotExists]</td>
* <td>Databases are created when accessed</td></tr>
* <tr><td>[-trace]</td>
* <td>Print additional trace information (all servers)</td></tr>
* <tr><td>[-key &lt;from&gt; &lt;to&gt;]</td>
......@@ -135,6 +139,8 @@ public class Server extends Tool implements Runnable, ShutdownHandler {
// no parameters
} else if ("-webPort".equals(arg)) {
i++;
} else if ("-webAdminPassword".equals(arg)) {
i += 2;
} else {
throwUnsupportedOption(arg);
}
......@@ -194,6 +200,8 @@ public class Server extends Tool implements Runnable, ShutdownHandler {
// no parameters
} else if ("-ifExists".equals(arg)) {
// no parameters
} else if ("-ifNotExists".equals(arg)) {
// no parameters
} else if ("-baseDir".equals(arg)) {
i++;
} else if ("-key".equals(arg)) {
......@@ -232,6 +240,8 @@ public class Server extends Tool implements Runnable, ShutdownHandler {
// no parameters
} else if ("-webPort".equals(arg)) {
i++;
} else if ("-webAdminPassword".equals(arg)) {
i += 2;
} else {
showUsageAndThrowUnsupportedOption(arg);
}
......@@ -280,6 +290,8 @@ public class Server extends Tool implements Runnable, ShutdownHandler {
// no parameters
} else if ("-ifExists".equals(arg)) {
// no parameters
} else if ("-ifNotExists".equals(arg)) {
// no parameters
} else if ("-baseDir".equals(arg)) {
i++;
} else if ("-key".equals(arg)) {
......@@ -407,7 +419,7 @@ public class Server extends Tool implements Runnable, ShutdownHandler {
* </pre>
* Supported options are:
* -webPort, -webSSL, -webAllowOthers, -webDaemon,
* -trace, -ifExists, -baseDir, -properties.
* -trace, -ifExists, -ifNotExists, -baseDir, -properties.
* See the main method for details.
*
* @param args the argument list
......@@ -429,7 +441,7 @@ public class Server extends Tool implements Runnable, ShutdownHandler {
* </pre>
* Supported options are:
* -tcpPort, -tcpSSL, -tcpPassword, -tcpAllowOthers, -tcpDaemon,
* -trace, -ifExists, -baseDir, -key.
* -trace, -ifExists, -ifNotExists, -baseDir, -key.
* See the main method for details.
* <p>
* If no port is specified, the default port is used if possible,
......@@ -456,7 +468,7 @@ public class Server extends Tool implements Runnable, ShutdownHandler {
* </pre>
* Supported options are:
* -pgPort, -pgAllowOthers, -pgDaemon,
* -trace, -ifExists, -baseDir, -key.
* -trace, -ifExists, -ifNotExists, -baseDir, -key.
* See the main method for details.
* <p>
* If no port is specified, the default port is used if possible,
......
......@@ -78,6 +78,18 @@ public class SortedProperties extends Properties {
}
}
/**
* Get a string property value from a properties object.
*
* @param prop the properties object
* @param key the key
* @param def the default value
* @return the value if set, or the default value if not
*/
public static String getStringProperty(Properties prop, String key, String def) {
return prop.getProperty(key, def);
}
/**
* Load a properties object from a file.
*
......
......@@ -1094,7 +1094,7 @@ kill -9 `jps -l | grep "org.h2.test." | cut -d " " -f 1`
DeleteDbFiles.execute(TestBase.BASE_TEST_DIR, null, true);
FileUtils.deleteRecursive("trace.db", false);
if (networked) {
String[] args = ssl ? new String[] { "-tcpSSL" } : new String[0];
String[] args = ssl ? new String[] { "-ifNotExists", "-tcpSSL" } : new String[] { "-ifNotExists" };
server = Server.createTcpServer(args);
try {
server.start();
......
......@@ -62,7 +62,7 @@ public class TestCluster extends TestDb {
Connection conn;
Statement stat;
Server n1 = org.h2.tools.Server.createTcpServer("-baseDir", getBaseDir() + "/node1").start();
Server n1 = org.h2.tools.Server.createTcpServer("-ifNotExists", "-baseDir", getBaseDir() + "/node1").start();
int port1 = n1.getPort();
String url1 = getURL("jdbc:h2:tcp://localhost:" + port1 + "/test", false);
......@@ -72,7 +72,7 @@ public class TestCluster extends TestDb {
stat.execute("insert into t1 values(1, repeat('Hello', 50))");
conn.close();
Server n2 = org.h2.tools.Server.createTcpServer("-baseDir", getBaseDir() + "/node2").start();
Server n2 = org.h2.tools.Server.createTcpServer("-ifNotExists", "-baseDir", getBaseDir() + "/node2").start();
int port2 = n2.getPort();
String url2 = getURL("jdbc:h2:tcp://localhost:" + port2 + "/test", false);
......@@ -100,9 +100,11 @@ public class TestCluster extends TestDb {
ResultSet rs;
Server server1 = org.h2.tools.Server.createTcpServer("-baseDir", getBaseDir() + "/node1").start();
Server server1 = org.h2.tools.Server.createTcpServer("-ifNotExists", "-baseDir", getBaseDir() + "/node1")
.start();
int port1 = server1.getPort();
Server server2 = org.h2.tools.Server.createTcpServer("-baseDir", getBaseDir() + "/node2").start();
Server server2 = org.h2.tools.Server.createTcpServer("-ifNotExists", "-baseDir", getBaseDir() + "/node2")
.start();
int port2 = server2.getPort();
String url1 = getURL("jdbc:h2:tcp://localhost:" + port1 + "/test", true);
......@@ -130,7 +132,7 @@ public class TestCluster extends TestDb {
rs.next();
assertEquals(5, rs.getInt(1));
server2 = org.h2.tools.Server.createTcpServer("-tcpPort",
server2 = org.h2.tools.Server.createTcpServer("-ifNotExists", "-tcpPort",
"" + port2 , "-baseDir", getBaseDir() + "/node2").start();
CreateCluster.main("-urlSource", url1, "-urlTarget", url2,
"-user", user, "-password", password, "-serverList",
......@@ -159,9 +161,9 @@ public class TestCluster extends TestDb {
Statement stat;
ResultSet rs;
Server n1 = org.h2.tools.Server.createTcpServer("-baseDir", getBaseDir() + "/node1").start();
Server n1 = org.h2.tools.Server.createTcpServer("-ifNotExists", "-baseDir", getBaseDir() + "/node1").start();
int port1 = n1.getPort();
Server n2 = org.h2.tools.Server.createTcpServer("-baseDir", getBaseDir() + "/node2").start();
Server n2 = org.h2.tools.Server.createTcpServer("-ifNotExists", "-baseDir", getBaseDir() + "/node2").start();
int port2 = n2.getPort();
String url1 = getURL("jdbc:h2:tcp://localhost:" + port1 + "/test", true);
......@@ -204,9 +206,9 @@ public class TestCluster extends TestDb {
ResultSet rs;
Server n1 = org.h2.tools.Server.createTcpServer("-baseDir", getBaseDir() + "/node1").start();
Server n1 = org.h2.tools.Server.createTcpServer("-ifNotExists", "-baseDir", getBaseDir() + "/node1").start();
int port1 = n1.getPort();
Server n2 = org.h2.tools.Server.createTcpServer("-baseDir", getBaseDir() + "/node2").start();
Server n2 = org.h2.tools.Server.createTcpServer("-ifNotExists", "-baseDir", getBaseDir() + "/node2").start();
int port2 = n2.getPort();
String serverList = "localhost:" + port1 + ",localhost:" + port2;
String url1 = getURL("jdbc:h2:tcp://localhost:" + port1 + "/test", true);
......@@ -255,9 +257,9 @@ public class TestCluster extends TestDb {
Connection conn;
Server n1 = org.h2.tools.Server.createTcpServer("-baseDir", getBaseDir() + "/node1").start();
Server n1 = org.h2.tools.Server.createTcpServer("-ifNotExists", "-baseDir", getBaseDir() + "/node1").start();
int port1 = n1.getPort();
Server n2 = org.h2.tools.Server.createTcpServer("-baseDir", getBaseDir() + "/node2").start();
Server n2 = org.h2.tools.Server.createTcpServer("-ifNotExists", "-baseDir", getBaseDir() + "/node2").start();
int port2 = n2.getPort();
String serverList = "localhost:" + port1 + ",localhost:" + port2;
......@@ -306,7 +308,7 @@ public class TestCluster extends TestDb {
int len = 10;
// initialize the database
Server n1 = org.h2.tools.Server.createTcpServer("-baseDir", getBaseDir() + "/node1").start();
Server n1 = org.h2.tools.Server.createTcpServer("-ifNotExists", "-baseDir", getBaseDir() + "/node1").start();
int port1 = n1.getPort();
String url1 = getURL("jdbc:h2:tcp://localhost:" + port1 + "/test", false);
conn = getConnection(url1, user, password);
......@@ -317,7 +319,7 @@ public class TestCluster extends TestDb {
stat.execute("grant all on test to test");
// start the second server
Server n2 = org.h2.tools.Server.createTcpServer("-baseDir", getBaseDir() + "/node2").start();
Server n2 = org.h2.tools.Server.createTcpServer("-ifNotExists", "-baseDir", getBaseDir() + "/node2").start();
int port2 = n2.getPort();
String url2 = getURL("jdbc:h2:tcp://localhost:" + port2 + "/test", false);
......@@ -352,7 +354,7 @@ public class TestCluster extends TestDb {
connApp.setAutoCommit(true);
// re-create the cluster
n2 = org.h2.tools.Server.createTcpServer("-tcpPort", "" + port2,
n2 = org.h2.tools.Server.createTcpServer("-ifNotExists", "-tcpPort", "" + port2,
"-baseDir", getBaseDir() + "/node2").start();
CreateCluster.main("-urlSource", url1, "-urlTarget", url2,
"-user", user, "-password", password, "-serverList",
......
......@@ -172,7 +172,7 @@ public class TestWeb extends TestDb {
Server server = new Server();
server.setOut(new PrintStream(new ByteArrayOutputStream()));
server.runTool("-web", "-webPort", "8182",
"-properties", "null", "-tcp", "-tcpPort", "9101");
"-properties", "null", "-tcp", "-tcpPort", "9101", "-webAdminPassword", "123");
try {
String url = "http://localhost:8182";
WebClient client;
......@@ -180,6 +180,7 @@ public class TestWeb extends TestDb {
client = new WebClient();
result = client.get(url);
client.readSessionId(result);
result = client.get(url, "adminLogin.do?password=123");
result = client.get(url, "tools.jsp");
FileUtils.delete(getBaseDir() + "/backup.zip");
result = client.get(url, "tools.do?tool=Backup&args=-dir," +
......@@ -264,7 +265,8 @@ public class TestWeb extends TestDb {
getUser(), getPassword());
Server server = new Server();
server.setOut(new PrintStream(new ByteArrayOutputStream()));
server.runTool("-ifExists", "-web", "-webPort", "8182",
// -ifExists is the default
server.runTool("-web", "-webPort", "8182",
"-properties", "null", "-tcp", "-tcpPort", "9101");
try {
String url = "http://localhost:8182";
......@@ -288,12 +290,13 @@ public class TestWeb extends TestDb {
server.shutdown();
conn.close();
}
}
private void testWebApp() throws Exception {
Server server = new Server();
server.setOut(new PrintStream(new ByteArrayOutputStream()));
server.runTool("-web", "-webPort", "8182",
server.runTool("-ifNotExists", "-web", "-webPort", "8182",
"-properties", "null", "-tcp", "-tcpPort", "9101");
try {
String url = "http://localhost:8182";
......
......@@ -94,7 +94,7 @@ public class TestAutoReconnect extends TestDb {
"AUTO_SERVER=TRUE;OPEN_NEW=TRUE";
restart();
} else {
server = Server.createTcpServer().start();
server = Server.createTcpServer("-ifNotExists").start();
int port = server.getPort();
url = "jdbc:h2:tcp://localhost:" + port + "/" + getBaseDir() + "/" + getTestName() + ";" +
"FILE_LOCK=SOCKET;AUTO_RECONNECT=TRUE";
......
......@@ -77,7 +77,7 @@ public class TestPgServer extends TestDb {
Statement stat = conn.createStatement();
stat.execute("create table test(id int, name varchar(255))");
Server server = createPgServer("-baseDir", getBaseDir(),
"-pgPort", "5535", "-pgDaemon", "-key", "pgserver",
"-ifNotExists", "-pgPort", "5535", "-pgDaemon", "-key", "pgserver",
"mem:pgserver");
try {
Connection conn2;
......@@ -128,7 +128,7 @@ public class TestPgServer extends TestDb {
private void testPgAdapter() throws SQLException {
deleteDb("pgserver");
Server server = Server.createPgServer(
"-baseDir", getBaseDir(), "-pgPort", "5535", "-pgDaemon");
"-ifNotExists", "-baseDir", getBaseDir(), "-pgPort", "5535", "-pgDaemon");
assertEquals(5535, server.getPort());
assertEquals("Not started", server.getStatus());
server.start();
......@@ -148,7 +148,7 @@ public class TestPgServer extends TestDb {
}
Server server = createPgServer(
"-pgPort", "5535", "-pgDaemon", "-key", "pgserver", "mem:pgserver");
"-ifNotExists", "-pgPort", "5535", "-pgDaemon", "-key", "pgserver", "mem:pgserver");
ExecutorService executor = Executors.newSingleThreadExecutor();
try {
......@@ -374,7 +374,7 @@ public class TestPgServer extends TestDb {
return;
}
Server server = createPgServer(
"-pgPort", "5535", "-pgDaemon", "-key", "pgserver", "mem:pgserver");
"-ifNotExists", "-pgPort", "5535", "-pgDaemon", "-key", "pgserver", "mem:pgserver");
try {
Connection conn = DriverManager.getConnection(
"jdbc:postgresql://localhost:5535/pgserver", "sa", "sa");
......@@ -401,7 +401,7 @@ public class TestPgServer extends TestDb {
}
Server server = createPgServer(
"-pgPort", "5535", "-pgDaemon", "-key", "pgserver", "mem:pgserver");
"-ifNotExists", "-pgPort", "5535", "-pgDaemon", "-key", "pgserver", "mem:pgserver");
try {
Properties props = new Properties();
props.setProperty("user", "sa");
......@@ -476,7 +476,7 @@ public class TestPgServer extends TestDb {
}
Server server = createPgServer(
"-pgPort", "5535", "-pgDaemon", "-key", "pgserver", "mem:pgserver");
"-ifNotExists", "-pgPort", "5535", "-pgDaemon", "-key", "pgserver", "mem:pgserver");
try {
Properties props = new Properties();
props.setProperty("user", "sa");
......@@ -535,7 +535,7 @@ public class TestPgServer extends TestDb {
}
Server server = createPgServer(
"-pgPort", "5535", "-pgDaemon", "-key", "pgserver", "mem:pgserver");
"-ifNotExists", "-pgPort", "5535", "-pgDaemon", "-key", "pgserver", "mem:pgserver");
try {
Properties props = new Properties();
......
......@@ -592,7 +592,7 @@ public class TestTools extends TestDb {
result = runServer(1, new String[]{"-xy"});
assertContains(result, "Starts the H2 Console");
assertContains(result, "Feature not supported");
result = runServer(0, new String[]{"-tcp",
result = runServer(0, new String[]{"-ifNotExists", "-tcp",
"-tcpPort", "9001", "-tcpPassword", "abc"});
assertContains(result, "tcp://");
assertContains(result, ":9001");
......@@ -613,7 +613,7 @@ public class TestTools extends TestDb {
Connection conn;
try {
result = runServer(0, new String[]{"-tcp",
result = runServer(0, new String[]{"-ifNotExists", "-tcp",
"-tcpAllowOthers", "-tcpPort", "9001", "-tcpPassword", "abcdef", "-tcpSSL"});
assertContains(result, "ssl://");
assertContains(result, ":9001");
......@@ -629,7 +629,7 @@ public class TestTools extends TestDb {
getConnection("jdbc:h2:ssl://localhost:9001/mem:", "sa", "sa");
result = runServer(0, new String[]{
"-web", "-webPort", "9002", "-webAllowOthers", "-webSSL",
"-ifNotExists", "-web", "-webPort", "9002", "-webAllowOthers", "-webSSL",
"-pg", "-pgAllowOthers", "-pgPort", "9003",
"-tcp", "-tcpAllowOthers", "-tcpPort", "9006", "-tcpPassword", "abc"});
Server stop = server;
......@@ -1096,7 +1096,7 @@ public class TestTools extends TestDb {
Connection conn;
try {
deleteDb("test");
Server tcpServer = Server.createTcpServer(
Server tcpServer = Server.createTcpServer("-ifNotExists",
"-baseDir", getBaseDir(),
"-tcpAllowOthers").start();
remainingServers.add(tcpServer);
......@@ -1150,7 +1150,7 @@ public class TestTools extends TestDb {
JdbcUtils.closeSilently(conn);
// Test filesystem prefix and escape from baseDir
deleteDb("testSplit");
server = Server.createTcpServer(
server = Server.createTcpServer("-ifNotExists",
"-baseDir", getBaseDir(),
"-tcpAllowOthers").start();
final int p = server.getPort();
......
......@@ -806,4 +806,4 @@ econd irst bcef ordinality nord unnest
analyst occupation distributive josaph aor engineer sajeewa isuru randil kevin doctor businessman artist ashan
corrupts splitted disruption unintentional octets preconditions predicates subq objectweb insn opcodes
preserves masking holder unboxing avert iae transformed subtle reevaluate exclusions subclause ftbl rgr
presorted inclusion contexts aax mwd percentile cont interpolate mwa hypothetical regproc childed listagg
presorted inclusion contexts aax mwd percentile cont interpolate mwa hypothetical regproc childed listagg foreground
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论