提交 158b3d44 authored 作者: Thomas Mueller's avatar Thomas Mueller

--no commit message

--no commit message
上级 26414c8e
......@@ -588,28 +588,30 @@ Example permission tags:
<p>
For many databases, opening a connection is slow, and it is a good idea to use a connection pool
to re-use connections. For H2 however opening a connection usually is fast if the database is already
open. Using a connection pool manager for H2 actually slows down the process a bit, except if
open. Using a connection pool for H2 actually slows down the process a bit, except if
file encryption is used (in this case opening a connection is about half as fast as using
a connection pool). A simple connection pool manager is included in H2. It is based on the
a connection pool). A simple connection pool is included in H2. It is based on the
<a href="http://www.source-code.biz/snippets/java/8.htm">Mini Connection Pool Manager</a>
from Christian d'Heureuse. There are other, more complex connection pools available, for example
<a href="http://jakarta.apache.org/commons/dbcp/">DBCP</a>. The build-in
connection pool manager is used as follows:
connection pool is used as follows:
<pre>
// init
import org.h2.jdbcx.*;
...
JdbcDataSource ds = new JdbcDataSource();
ds.setURL("jdbc:h2:~/test");
ds.setUser("sa");
ds.setPassword("sa");
JdbcConnectionPoolManager man = new JdbcConnectionPoolManager(ds);
JdbcConnectionPool cp = JdbcConnectionPool.create(ds);
// use
Connection conn = man.getConnection();
Connection conn = cp.getConnection();
...
conn.close();
// dispose
man.dispose();
cp.dispose();
</pre>
</p>
......
......@@ -37,11 +37,11 @@ org.h2.jdbc<br />
<a href="org/h2/jdbc/JdbcSQLException.html" target="javadoc">SQLException</a><br />
<a href="org/h2/jdbc/JdbcStatement.html" target="javadoc">Statement</a><br />
org.h2.jdbcx<br />
<a href="org/h2/jdbcx/JdbcConnectionPoolManager.html" target="javadoc">ConnectionPoolManager</a><br />
<a href="org/h2/jdbcx/JdbcDataSource.html" target="javadoc">DataSource</a><br />
<a href="org/h2/jdbcx/JdbcDataSourceFactory.html" target="javadoc">DataSourceFactory</a><br />
<a href="org/h2/jdbcx/JdbcXAConnection.html" target="javadoc">XAConnection</a><br />
<a href="org/h2/jdbcx/JdbcXid.html" target="javadoc">Xid</a><br />
<a href="org/h2/jdbcx/JdbcConnectionPool.html" target="javadoc">JdbcConnectionPool</a><br />
<a href="org/h2/jdbcx/JdbcDataSource.html" target="javadoc">JdbcDataSource</a><br />
<a href="org/h2/jdbcx/JdbcDataSourceFactory.html" target="javadoc">JdbcDataSourceFactory</a><br />
<a href="org/h2/jdbcx/JdbcXAConnection.html" target="javadoc">JdbcXAConnection</a><br />
<a href="org/h2/jdbcx/JdbcXid.html" target="javadoc">JdbcXid</a><br />
<br />
<b>Tools</b><br />
......
......@@ -9,7 +9,7 @@ Initial Developer: H2 Group
<title>H2 Documentation</title>
<link rel="stylesheet" type="text/css" href="stylesheet.css" />
</head>
<frameset cols="194,*" rows="*" frameborder="2" framespacing="4" border="4" >
<frameset cols="188,*" rows="*" frameborder="2" framespacing="4" border="4" >
<frame frameborder="0" marginheight="0" marginwidth="0" src="classes.html" name="classes" />
<frame frameborder="0" marginheight="0" marginwidth="0" src="overview.html" name="javadoc" />
</frameset>
......
......@@ -53,9 +53,7 @@ public class Driver implements java.sql.Driver {
if (!acceptsURL(url)) {
return null;
}
synchronized (this) {
return new JdbcConnection(url, info);
}
return new JdbcConnection(url, info);
} catch (Throwable e) {
throw Message.convert(e);
}
......
......@@ -299,7 +299,8 @@ public class Bnf {
continue;
}
sentence.max = System.currentTimeMillis() + MAX_PARSE_TIME;
head.getRule().addNextTokenList(query, sentence);
sentence.setQuery(query);
head.getRule().addNextTokenList(sentence);
}
return next;
}
......
......@@ -46,21 +46,19 @@ public interface Rule {
* Add the next possible token for a query.
* Used for autocomplete support.
*
* @param query the query
* @param sentence the sentence context
*/
void addNextTokenList(String query, Sentence sentence);
void addNextTokenList(Sentence sentence);
/**
* Remove a token from a sentence. Used for autocomplete support.
* If there was a match, the query in the sentence is updated
* (the matched token is removed).
*
* @param query
* the query
* @param sentence
* the sentence context
* @return null if not a match or a partial match, query.substring... if a
* full match
* @return false if not a match or a partial match, true if a full match
*/
String matchRemove(String query, Sentence sentence);
boolean matchRemove(Sentence sentence);
}
......@@ -29,6 +29,10 @@ public class RuleElement implements Rule {
topic = StringUtils.toLowerEnglish(topic);
this.type = topic.startsWith("function") ? Sentence.FUNCTION : Sentence.KEYWORD;
}
public String toString() {
return name;
}
RuleElement merge(RuleElement rule) {
return new RuleElement(name + " " + rule.name, topic);
......@@ -72,41 +76,48 @@ public class RuleElement implements Rule {
}
}
public String matchRemove(String query, Sentence sentence) {
public boolean matchRemove(Sentence sentence) {
if (sentence.stop()) {
return null;
return false;
}
String query = sentence.query;
if (query.length() == 0) {
return null;
return false;
}
if (keyword) {
String up = StringUtils.toUpperEnglish(query);
String up = sentence.queryUpper;
if (up.startsWith(name)) {
query = query.substring(name.length());
while (!"_".equals(name) && query.length() > 0 && Character.isWhitespace(query.charAt(0))) {
query = query.substring(1);
}
return query;
sentence.setQuery(query);
return true;
}
return null;
return false;
} else {
query = link.matchRemove(query, sentence);
if (query != null && name != null && !name.startsWith("@") && (link.name() == null || !link.name().startsWith("@"))) {
if (!link.matchRemove(sentence)) {
return false;
}
if (name != null && !name.startsWith("@") && (link.name() == null || !link.name().startsWith("@"))) {
query = sentence.query;
while (query.length() > 0 && Character.isWhitespace(query.charAt(0))) {
query = query.substring(1);
}
sentence.setQuery(query);
}
return query;
return true;
}
}
public void addNextTokenList(String query, Sentence sentence) {
public void addNextTokenList(Sentence sentence) {
if (sentence.stop()) {
return;
}
if (keyword) {
String query = sentence.query;
String q = query.trim();
String up = StringUtils.toUpperEnglish(q);
String up = sentence.queryUpper.trim();
if (q.length() == 0 || name.startsWith(up)) {
if (q.length() < name.length()) {
sentence.add(name, name.substring(q.length()), type);
......@@ -114,7 +125,7 @@ public class RuleElement implements Rule {
}
return;
}
link.addNextTokenList(query, sentence);
link.addNextTokenList(sentence);
}
boolean isKeyword() {
......
......@@ -8,8 +8,6 @@ package org.h2.bnf;
import java.util.HashMap;
import java.util.Random;
import org.h2.util.StringUtils;
/**
* Represents a hard coded terminal rule in a BNF object.
*/
......@@ -27,6 +25,36 @@ public class RuleFixed implements Rule {
RuleFixed(int type) {
this.type = type;
}
public String toString() {
switch(type) {
case YMD:
return "2000-01-01";
case HMS:
return "12:00";
case NANOS:
return "0";
case ANY_UNTIL_EOL:
case ANY_EXCEPT_SINGLE_QUOTE:
case ANY_EXCEPT_DOUBLE_QUOTE:
case ANY_WORD:
case ANY_UNTIL_END: {
return "XYZ";
}
case HEX_START:
return "0x";
case CONCAT:
return "||";
case AZ_UNDERLINE:
return "A";
case AF:
return "F";
case DIGIT:
return "0";
default:
throw new Error("type="+type);
}
}
public String random(Bnf config, int level) {
Random r = config.getRandom();
......@@ -75,12 +103,13 @@ public class RuleFixed implements Rule {
public void setLinks(HashMap ruleMap) {
}
public String matchRemove(String query, Sentence sentence) {
public boolean matchRemove(Sentence sentence) {
if (sentence.stop()) {
return null;
return false;
}
String query = sentence.query;
if (query.length() == 0) {
return null;
return false;
}
String s = query;
switch(type) {
......@@ -139,9 +168,9 @@ public class RuleFixed implements Rule {
}
break;
case HEX_START:
if (StringUtils.toUpperEnglish(s).startsWith("0X")) {
if (s.startsWith("0X") || s.startsWith("0x")) {
s = s.substring(2);
} else if (StringUtils.toUpperEnglish(s).startsWith("0")) {
} else if (s.startsWith("0")) {
s = s.substring(1);
}
break;
......@@ -174,16 +203,17 @@ public class RuleFixed implements Rule {
throw new Error("type=" + type);
}
if (s == query) {
return null;
return false;
}
return s;
sentence.setQuery(s);
return true;
}
public void addNextTokenList(String query, Sentence sentence) {
public void addNextTokenList(Sentence sentence) {
if (sentence.stop()) {
return;
}
// String s = matchRemove(query, iteration);
String query = sentence.query;
switch(type) {
case YMD:
if (query.length() == 0) {
......
......@@ -16,6 +16,28 @@ public class RuleList implements Rule {
private boolean or;
private ArrayList list;
private boolean mapSet;
public String toString() {
StringBuffer buff = new StringBuffer();
if (or) {
buff.append("{");
for (int i = 0; i < list.size(); i++) {
if (i > 0) {
buff.append("|");
}
buff.append(list.get(i).toString());
}
buff.append("}");
} else {
for (int i = 0; i < list.size(); i++) {
if (i > 0) {
buff.append(" ");
}
buff.append(list.get(i).toString());
}
}
return buff.toString();
}
RuleList(Rule first, Rule next, boolean or) {
list = new ArrayList();
......@@ -91,48 +113,47 @@ public class RuleList implements Rule {
}
}
public String matchRemove(String query, Sentence sentence) {
public boolean matchRemove(Sentence sentence) {
String query = sentence.query;
if (query.length() == 0) {
return null;
return false;
}
if (or) {
for (int i = 0; i < list.size(); i++) {
String s = get(i).matchRemove(query, sentence);
if (s != null) {
return s;
if (get(i).matchRemove(sentence)) {
return true;
}
}
return null;
return false;
} else {
for (int i = 0; i < list.size(); i++) {
Rule r = get(i);
query = r.matchRemove(query, sentence);
if (query == null) {
return null;
if (!r.matchRemove(sentence)) {
return false;
}
}
return query;
return true;
}
}
public void addNextTokenList(String query, Sentence sentence) {
// if (sentence.stop()) {
//
// }
public void addNextTokenList(Sentence sentence) {
String old = sentence.query;
if (or) {
for (int i = 0; i < list.size(); i++) {
get(i).addNextTokenList(query, sentence);
sentence.setQuery(old);
Rule r = get(i);
r.addNextTokenList(sentence);
}
} else {
for (int i = 0; i < list.size(); i++) {
Rule r = get(i);
r.addNextTokenList(query, sentence);
query = r.matchRemove(query, sentence);
if (query == null) {
r.addNextTokenList(sentence);
if (!r.matchRemove(sentence)) {
break;
}
}
}
sentence.setQuery(old);
}
}
......@@ -17,6 +17,10 @@ public class RuleOptional implements Rule {
RuleOptional(Rule rule, boolean repeat) {
this.rule = rule;
}
public String toString() {
return "[" + rule.toString() + "]";
}
public String name() {
return null;
......@@ -41,25 +45,25 @@ public class RuleOptional implements Rule {
}
}
public String matchRemove(String query, Sentence sentence) {
public boolean matchRemove(Sentence sentence) {
if (sentence.stop()) {
return null;
return false;
}
String query = sentence.query;
if (query.length() == 0) {
return query;
return true;
}
String s = rule.matchRemove(query, sentence);
if (s == null) {
return query;
if (!rule.matchRemove(sentence)) {
return true;
}
return s;
return true;
}
public void addNextTokenList(String query, Sentence sentence) {
public void addNextTokenList(Sentence sentence) {
if (sentence.stop()) {
return;
}
rule.addNextTokenList(query, sentence);
rule.addNextTokenList(sentence);
}
}
......@@ -17,6 +17,10 @@ public class RuleRepeat implements Rule {
RuleRepeat(Rule rule) {
this.rule = rule;
}
public String toString() {
return "...";
}
public String name() {
return rule.name();
......@@ -34,36 +38,36 @@ public class RuleRepeat implements Rule {
return rule.random(config, level);
}
public String matchRemove(String query, Sentence sentence) {
public boolean matchRemove(Sentence sentence) {
if (sentence.stop()) {
return null;
return false;
}
String query = sentence.query;
if (query.length() == 0) {
return null;
return false;
}
while (true) {
String s = rule.matchRemove(query, sentence);
if (s == null) {
return query;
} else if (s.length() == 0) {
return s;
if (!rule.matchRemove(sentence)) {
return true;
}
if (sentence.query.length() == 0) {
return true;
}
query = s;
}
}
public void addNextTokenList(String query, Sentence sentence) {
public void addNextTokenList(Sentence sentence) {
if (sentence.stop()) {
return;
}
String old = sentence.query;
while (true) {
rule.addNextTokenList(query, sentence);
String s = rule.matchRemove(query, sentence);
if (s == null || s == query) {
rule.addNextTokenList(sentence);
if (!rule.matchRemove(sentence) || old == sentence.query) {
break;
}
query = s;
}
sentence.setQuery(old);
}
}
......@@ -8,7 +8,9 @@ package org.h2.bnf;
import java.util.HashMap;
import java.util.HashSet;
import org.h2.server.web.DbSchema;
import org.h2.server.web.DbTableOrView;
import org.h2.util.StringUtils;
/**
* A query context object. It contains the list of table and alias objects.
......@@ -24,8 +26,12 @@ public class Sentence {
static final int KEYWORD = 1;
static final int FUNCTION = 2;
public String text;
public String query;
public String queryUpper;
HashMap next;
long max;
private DbSchema lastMatchedSchema;
private DbTableOrView lastMatchedTable;
private DbTableOrView lastTable;
private HashSet tables;
private HashMap aliases;
......@@ -97,4 +103,49 @@ public class Sentence {
public DbTableOrView getLastTable() {
return lastTable;
}
/**
* Get the last matched schema if the last match was a schema.
*
* @return the last schema or null
*/
public DbSchema getLastMatchedSchema() {
return lastMatchedSchema;
}
/**
* Set the last matched schema if the last match was a schema,
* or null if it was not.
*
* @param schema the last matched schema or null
*/
public void setLastMatchedSchema(DbSchema schema) {
this.lastMatchedSchema = schema;
}
/**
* Set the last matched table if the last match was a table.
*
* @param table the last matched table or null
*/
public void setLastMatchedTable(DbTableOrView table) {
this.lastMatchedTable = table;
}
/**
* Get the last matched table if the last match was a table.
*
* @return the last table or null
*/
public DbTableOrView getLastMatchedTable() {
return lastMatchedTable;
}
public void setQuery(String query) {
if (this.query != query) {
this.query = query;
this.queryUpper = StringUtils.toUpperEnglish(query);
}
}
}
......@@ -65,8 +65,9 @@ public class CommandContainer extends Command {
// TODO query time: should keep lock time separate from running time
start();
prepared.checkParameters();
prepared.trace();
return prepared.update();
int updateCount = prepared.update();
prepared.trace(startTime, updateCount);
return updateCount;
}
public LocalResult query(int maxrows) throws SQLException {
......@@ -74,8 +75,9 @@ public class CommandContainer extends Command {
// TODO query time: should keep lock time separate from running time
start();
prepared.checkParameters();
prepared.trace();
return prepared.query(maxrows);
LocalResult result = prepared.query(maxrows);
prepared.trace(startTime, result.getRowCount());
return result;
}
public boolean isReadOnly() {
......
......@@ -3012,15 +3012,7 @@ public class Parser {
if (readIf("IDENTITY") || readIf("SERIAL")) {
column = new Column(columnName, Value.LONG);
column.setOriginalSQL("IDENTITY");
long start = 1, increment = 1;
if (readIf("(")) {
start = readLong();
if (readIf(",")) {
increment = readLong();
}
read(")");
}
column.setAutoIncrement(true, start, increment);
parseAutoIncrement(column);
} else {
column = parseColumn(columnName);
}
......@@ -3063,15 +3055,7 @@ public class Parser {
readIf("NULL");
}
if (readIf("AUTO_INCREMENT") || readIf("IDENTITY")) {
long start = 1, increment = 1;
if (readIf("(")) {
start = readLong();
if (readIf(",")) {
increment = readLong();
}
read(")");
}
column.setAutoIncrement(true, start, increment);
parseAutoIncrement(column);
if (readIf("NOT")) {
read("NULL");
}
......@@ -3090,6 +3074,18 @@ public class Parser {
column.setComment(readCommentIf());
return column;
}
private void parseAutoIncrement(Column column) throws SQLException {
long start = 1, increment = 1;
if (readIf("(")) {
start = readLong();
if (readIf(",")) {
increment = readLong();
}
read(")");
}
column.setAutoIncrement(true, start, increment);
}
private String readCommentIf() throws SQLException {
if (readIf("COMMENT")) {
......@@ -4397,6 +4393,9 @@ public class Parser {
pk.setTableName(tableName);
pk.setIndexColumns(cols);
command.addConstraintCommand(pk);
if (readIf("AUTO_INCREMENT")) {
parseAutoIncrement(column);
}
} else if (readIf("UNIQUE")) {
AlterTableAddConstraint unique = new AlterTableAddConstraint(session, schema);
unique.setConstraintName(constraintName);
......
......@@ -274,11 +274,12 @@ public abstract class Prepared {
this.session = currentSession;
}
void trace() throws SQLException {
void trace(long startTime, int count) throws SQLException {
if (session.getTrace().info()) {
StringBuffer buff = new StringBuffer();
buff.append(sql);
long time = System.currentTimeMillis() - startTime;
String params;
if (parameters.size() > 0) {
StringBuffer buff = new StringBuffer(parameters.size() * 10);
buff.append(" {");
for (int i = 0; i < parameters.size(); i++) {
if (i > 0) {
......@@ -289,11 +290,12 @@ public abstract class Prepared {
Expression e = (Expression) parameters.get(i);
buff.append(e.getValue(session).getSQL());
}
buff.append("};");
buff.append("}");
params = buff.toString();
} else {
buff.append(';');
params = "";
}
session.getTrace().infoSQL(buff.toString());
session.getTrace().infoSQL(sql, params, count, time);
}
}
......
......@@ -217,7 +217,7 @@ public abstract class Query extends Prepared {
}
Value[] params = getParameterValues();
long now = session.getDatabase().getModificationDataId();
if (lastResult != null && limit == lastLimit) {
if (lastResult != null && !lastResult.isClosed() && limit == lastLimit) {
if (sameResultAsLast(session, params, lastParameters, lastEvaluated)) {
lastResult = lastResult.createShallowCopy(session);
if (lastResult != null) {
......
......@@ -168,7 +168,7 @@ public class Set extends Prepared {
}
case SetTypes.DATABASE_EVENT_LISTENER: {
session.getUser().checkAdmin();
database.setEventListener(stringValue);
database.setEventListenerClass(stringValue);
break;
}
case SetTypes.MAX_MEMORY_ROWS: {
......
......@@ -182,7 +182,7 @@ public class Database implements DataHandler {
if (listener.endsWith("'")) {
listener = listener.substring(0, listener.length() - 1);
}
setEventListener(listener);
setEventListenerClass(listener);
}
}
String log = ci.getProperty(SetTypes.LOG, null);
......@@ -396,7 +396,7 @@ public class Database implements DataHandler {
return store;
}
public boolean validateFilePasswordHash(String c, byte[] hash) throws SQLException {
public boolean validateFilePasswordHash(String c, byte[] hash) {
return ByteUtils.compareSecure(hash, filePasswordHash) && StringUtils.equals(c, cipher);
}
......@@ -1405,8 +1405,12 @@ public class Database implements DataHandler {
throw Message.getSQLException(ErrorCode.CLASS_NOT_FOUND_1, new String[] { className }, e);
}
}
public void setEventListener(DatabaseEventListener eventListener) {
this.eventListener = eventListener;
}
public void setEventListener(String className) throws SQLException {
public void setEventListenerClass(String className) throws SQLException {
if (className == null || className.length() == 0) {
eventListener = null;
} else {
......@@ -1778,4 +1782,8 @@ public class Database implements DataHandler {
return lobFileListCache;
}
public boolean isSysTableLocked() {
return meta.isLockedExclusively();
}
}
......@@ -26,7 +26,8 @@ import org.h2.util.StringUtils;
public class Engine {
private static final Engine INSTANCE = new Engine();
private static long delayWrongPassword = SysProperties.DELAY_WRONG_PASSWORD_MIN;
private static volatile long wrongPasswordDelay = SysProperties.DELAY_WRONG_PASSWORD_MIN;
private static Object wrongPasswordSync = new Object();
private final HashMap databases = new HashMap();
private Engine() {
......@@ -73,26 +74,23 @@ public class Engine {
return null;
}
if (user == null) {
try {
boolean correct = database.validateFilePasswordHash(cipher, ci.getFilePasswordHash());
if (correct) {
user = database.findUser(ci.getUserName());
if (user == null) {
correct = false;
} else {
correct = user.validateUserPasswordHash(ci.getUserPasswordHash());
if (database.validateFilePasswordHash(cipher, ci.getFilePasswordHash())) {
user = database.findUser(ci.getUserName());
if (user != null) {
if (!user.validateUserPasswordHash(ci.getUserPasswordHash())) {
user = null;
}
}
validateUserAndPassword(correct);
if (opened && !user.getAdmin()) {
// reset - because the user is not an admin, and has no
// right to listen to exceptions
database.setEventListener(null);
}
} catch (SQLException e) {
database.removeSession(null);
throw e;
}
if (opened && (user == null || !user.getAdmin())) {
// reset - because the user is not an admin, and has no
// right to listen to exceptions
database.setEventListener(null);
}
}
if (user == null) {
database.removeSession(null);
throw Message.getSQLException(ErrorCode.WRONG_USER_OR_PASSWORD);
}
checkClustering(ci, database);
Session session = database.createUserSession(user);
......@@ -100,7 +98,20 @@ public class Engine {
}
}
public synchronized Session getSession(ConnectionInfo ci) throws SQLException {
public Session getSession(ConnectionInfo ci) throws SQLException {
try {
Session session = openSession(ci);
validateUserAndPassword(true);
return session;
} catch (SQLException e) {
if (e.getErrorCode() == ErrorCode.WRONG_USER_OR_PASSWORD) {
validateUserAndPassword(false);
}
throw e;
}
}
private synchronized Session openSession(ConnectionInfo ci) throws SQLException {
boolean ifExists = ci.removeProperty("IFEXISTS", false);
boolean ignoreUnknownSetting = ci.removeProperty("IGNORE_UNKNOWN_SETTINGS", false);
String cipher = ci.removeProperty("CIPHER", null);
......@@ -179,30 +190,34 @@ public class Engine {
* @param correct if the user name or the password was correct
* @throws SQLException the exception 'wrong user or password'
*/
public static synchronized void validateUserAndPassword(boolean correct) throws SQLException {
private static void validateUserAndPassword(boolean correct) throws SQLException {
int min = SysProperties.DELAY_WRONG_PASSWORD_MIN;
if (correct) {
delayWrongPassword = min;
wrongPasswordDelay = min;
} else {
long delay = delayWrongPassword;
if (min > 0) {
// a bit more to protect against timing attacks
delay += Math.abs(RandomUtils.getSecureLong() % 100);
try {
Thread.sleep(delay);
} catch (InterruptedException e) {
// ignore
// this method is not synchronized on the Engine, so that
// successful attempts are not blocked
synchronized (wrongPasswordSync) {
long delay = wrongPasswordDelay;
int max = SysProperties.DELAY_WRONG_PASSWORD_MAX;
if (max <= 0) {
max = Integer.MAX_VALUE;
}
wrongPasswordDelay += wrongPasswordDelay;
if (wrongPasswordDelay > max || wrongPasswordDelay < 0) {
wrongPasswordDelay = max;
}
if (min > 0) {
// a bit more to protect against timing attacks
delay += Math.abs(RandomUtils.getSecureLong() % 100);
try {
Thread.sleep(delay);
} catch (InterruptedException e) {
// ignore
}
}
throw Message.getSQLException(ErrorCode.WRONG_USER_OR_PASSWORD);
}
int max = SysProperties.DELAY_WRONG_PASSWORD_MAX;
if (max <= 0) {
max = Integer.MAX_VALUE;
}
delayWrongPassword += delayWrongPassword;
if (delayWrongPassword > max || delayWrongPassword < 0) {
delayWrongPassword = max;
}
throw Message.getSQLException(ErrorCode.WRONG_USER_OR_PASSWORD);
}
}
......
......@@ -772,10 +772,16 @@ public class Session implements SessionInterface {
}
public void addTemporaryResult(LocalResult result) {
if (!result.needToClose()) {
return;
}
if (temporaryResults == null) {
temporaryResults = new HashSet();
}
temporaryResults.add(result);
if (temporaryResults.size() < 100) {
// reference at most 100 result sets to avoid memory problems
temporaryResults.add(result);
}
}
public void closeTemporaryResults() {
......@@ -784,6 +790,7 @@ public class Session implements SessionInterface {
LocalResult result = (LocalResult) it.next();
result.close();
}
temporaryResults = null;
}
}
......@@ -794,6 +801,9 @@ public class Session implements SessionInterface {
queryTimeout = max;
}
this.queryTimeout = queryTimeout;
// must reset the cancel at here,
// otherwise it is still used
this.cancelAt = 0;
}
public int getQueryTimeout() {
......
......@@ -28,7 +28,7 @@ public class SequenceValue extends Expression {
}
public Value getValue(Session session) throws SQLException {
long value = sequence.getNext();
long value = sequence.getNext(session);
session.setLastIdentity(ValueLong.get(value));
return ValueLong.get(value);
}
......
......@@ -19,82 +19,116 @@
*/
package org.h2.jdbcx;
import java.util.Stack;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.SQLException;
import javax.sql.ConnectionPoolDataSource;
import java.util.Stack;
import javax.sql.ConnectionEvent;
import javax.sql.DataSource;
import javax.sql.ConnectionEventListener;
import javax.sql.ConnectionPoolDataSource;
import javax.sql.PooledConnection;
/**
* A simple standalone JDBC connection pool manager.
* A simple standalone JDBC connection pool.
* It is based on the
* <a href="http://www.source-code.biz/snippets/java/8.htm">
* MiniConnectionPoolManager written by Christian d'Heureuse (JDK 1.5)
* </a>.
* MiniConnectionPoolManager written by Christian d'Heureuse (Java 1.5)
* </a>. It is used as follows:
* <pre>
* // init
* import org.h2.jdbcx.*;
* ...
* JdbcDataSource ds = new JdbcDataSource();
* ds.setURL("jdbc:h2:~/test");
* ds.setUser("sa");
* ds.setPassword("sa");
* JdbcConnectionPool cp = JdbcConnectionPool.create(ds);
* // use
* Connection conn = cp.getConnection();
* ...
* conn.close();
* // dispose
* cp.dispose();
* </pre>
*
* @author Christian d'Heureuse
* (<a href="http://www.source-code.biz">www.source-code.biz</a>)
* @author Thomas Mueller (ported to JDK 1.4)
* @author Thomas Mueller (ported to Java 1.4, some changes)
*/
public class JdbcConnectionPoolManager {
public class JdbcConnectionPool implements DataSource {
private ConnectionPoolDataSource dataSource;
private int maxConnections;
private int timeout;
private final ConnectionPoolDataSource dataSource;
private final Stack recycledConnections = new Stack();
private final PoolConnectionEventListener poolConnectionEventListener = new PoolConnectionEventListener();
private PrintWriter logWriter;
private Stack recycledConnections;
private int maxConnections = 10;
private int timeout = 60;
private int activeConnections;
private PoolConnectionEventListener poolConnectionEventListener;
private boolean isDisposed;
/**
* This inThrown in {@link #getConnection()} when no free connection becomes
* available within <code>timeout</code> seconds.
*/
public static class TimeoutException extends RuntimeException {
private static final long serialVersionUID = 1;
public TimeoutException() {
super("Timeout while waiting for a free database connection.");
}
}
/**
* Constructs a JdbcConnectionPoolManager object with a timeout of 60
* seconds.
* Constructs a new connection pool.
*
* @param dataSource the data source for the connections.
* @param maxConnections the maximum number of connections.
* @param dataSource the data source to create connections
* @return the connection pool
*/
public JdbcConnectionPoolManager(ConnectionPoolDataSource dataSource, int maxConnections) {
this(dataSource, maxConnections, 60);
public static JdbcConnectionPool create(ConnectionPoolDataSource dataSource) {
return new JdbcConnectionPool(dataSource);
}
/**
* Constructs a JdbcConnectionPoolManager object.
*
* @param dataSource the data source for the connections.
* @param maxConnections the maximum number of connections.
* @param timeout the maximum time in seconds to wait for a free connection.
*/
public JdbcConnectionPoolManager(ConnectionPoolDataSource dataSource, int maxConnections, int timeout) {
private JdbcConnectionPool(ConnectionPoolDataSource dataSource) {
this.dataSource = dataSource;
this.maxConnections = maxConnections;
this.timeout = timeout;
try {
logWriter = dataSource.getLogWriter();
} catch (SQLException e) {
}
}
/**
* Sets the maximum number of connections to use from now on.
* The default value is 10 connections.
*
* @param max the maximum number of connections
*/
public synchronized void setMaxConnections(int max) {
if (maxConnections < 1) {
throw new IllegalArgumentException("Invalid maxConnections value.");
}
recycledConnections = new Stack();
poolConnectionEventListener = new PoolConnectionEventListener();
this.maxConnections = max;
// notify waiting threads if the value was increased
notifyAll();
}
/**
* Gets the maximum number of connections to use.
*
* @return the max the maximum number of connections
*/
public synchronized int getMaxConnections() {
return maxConnections;
}
/**
* Gets the maximum time in seconds to wait for a free connection.
*
* @return the timeout in seconds
*/
public synchronized int getLoginTimeout() {
return timeout;
}
/**
* Sets the maximum time in seconds to wait for a free connection.
* The default timeout is 60 seconds.
*
* @param seconds the maximum timeout
*/
public synchronized void setLoginTimeout(int seconds) throws SQLException {
this.timeout = seconds;
}
/**
* Closes all unused pooled connections.
*/
......@@ -125,10 +159,12 @@ public class JdbcConnectionPoolManager {
* waits until a connection becomes available or <code>timeout</code>
* seconds elapsed. When the application is finished using the connection,
* it must close it in order to return it to the pool.
* If no connection becomes available within the given timeout, an exception
* with SQL state 08001 and vendor code 8001 is thrown.
*
* @return a new Connection object.
* @throws TimeoutException when no connection becomes available within
* <code>timeout</code> seconds.
* @throws SQLException when a new connection could not be established,
* or a timeout occured
*/
public Connection getConnection() throws SQLException {
for (int i = 0;; i++) {
......@@ -137,7 +173,7 @@ public class JdbcConnectionPoolManager {
return getConnectionNow();
}
if (i >= timeout) {
throw new TimeoutException();
throw new SQLException("Login timeout", "08001", 8001);
}
try {
wait(1000);
......@@ -161,7 +197,6 @@ public class JdbcConnectionPoolManager {
Connection conn = pc.getConnection();
activeConnections++;
pc.addConnectionEventListener(poolConnectionEventListener);
assertInnerState();
return conn;
}
......@@ -174,9 +209,20 @@ public class JdbcConnectionPoolManager {
throw new AssertionError();
}
activeConnections--;
if (activeConnections < maxConnections) {
recycledConnections.push(pc);
} else {
closeConnection(pc);
}
notifyAll();
recycledConnections.push(pc);
assertInnerState();
}
private void closeConnection(PooledConnection pc) {
try {
pc.close();
} catch (SQLException e) {
log("Error while closing database connection: " + e.toString());
}
}
private synchronized void disposeConnection(PooledConnection pc) {
......@@ -185,16 +231,11 @@ public class JdbcConnectionPoolManager {
}
activeConnections--;
notifyAll();
try {
pc.close();
} catch (SQLException e) {
log("Error while closing database connection: " + e.toString());
}
assertInnerState();
closeConnection(pc);
}
private void log(String msg) {
String s = "JdbcConnectionPoolManager: " + msg;
String s = getClass().getName() + ": " + msg;
try {
if (logWriter == null) {
System.err.println(s);
......@@ -205,15 +246,6 @@ public class JdbcConnectionPoolManager {
}
}
private void assertInnerState() {
if (activeConnections < 0) {
throw new AssertionError();
}
if (activeConnections + recycledConnections.size() > maxConnections) {
throw new AssertionError();
}
}
private class PoolConnectionEventListener implements ConnectionEventListener {
public void connectionClosed(ConnectionEvent event) {
PooledConnection pc = (PooledConnection) event.getSource();
......@@ -240,4 +272,25 @@ public class JdbcConnectionPoolManager {
return activeConnections;
}
/**
* INTERNAL
*/
public Connection getConnection(String username, String password) throws SQLException {
throw new UnsupportedOperationException();
}
/**
* INTERNAL
*/
public PrintWriter getLogWriter() throws SQLException {
return logWriter;
}
/**
* INTERNAL
*/
public void setLogWriter(PrintWriter logWriter) throws SQLException {
this.logWriter = logWriter;
}
}
......@@ -291,7 +291,7 @@ implements XADataSource, DataSource, ConnectionPoolDataSource, Serializable, Ref
//#endif
/**
* Open a new XA connection using the current URL, user name and password.
* Open a new pooled connection using the current URL, user name and password.
*
* @return the connection
*/
......@@ -303,7 +303,7 @@ implements XADataSource, DataSource, ConnectionPoolDataSource, Serializable, Ref
//#endif
/**
* Open a new XA connection using the current URL and the specified user
* Open a new pooled connection using the current URL and the specified user
* name and password.
*
* @param user the user name
......
......@@ -71,9 +71,28 @@ public class Trace {
traceSystem.write(TraceSystem.INFO, module, lineSeparator + "/**/" + java, null);
}
public void infoSQL(String sql) {
sql = StringUtils.javaEncode(sql);
traceSystem.write(TraceSystem.INFO, module, lineSeparator + "/*SQL*/" + sql, null);
public void infoSQL(String sql, String params, int count, long time) {
StringBuffer buff = new StringBuffer(sql.length() + 20);
buff.append(lineSeparator);
buff.append("/*SQL ");
if (params.length() > 0) {
buff.append("l:");
buff.append(sql.length());
}
if (count > 0) {
buff.append(" #:");
buff.append(count);
}
if (time > 0) {
buff.append(" t:");
buff.append(time);
}
buff.append("*/");
buff.append(StringUtils.javaEncode(sql));
buff.append(params);
buff.append(';');
sql = buff.toString();
traceSystem.write(TraceSystem.INFO, module, sql, null);
}
public void debug(String s) {
......
......@@ -1220,13 +1220,14 @@ If the table name is not specified, then the same table is referenced.
As this database does not support deferred checking,
RESTRICT and NO ACTION will both throw an exception if the constraint is violated.
If the referenced columns are not specified, then the primary key columns are used.
The required indexes are automatically created if required.
","
FOREIGN KEY(ID) REFERENCES TEST(ID)
"
"Other Grammar","Table Expression","
{tableName | (select)} [[AS] newTableAlias]
{[schemaName.] tableName | (select)} [[AS] newTableAlias]
[{{LEFT | RIGHT} [OUTER] | [INNER] | CROSS | NATURAL}
JOIN {tableName | viewName} [[AS] newTableAlias] [ON expression] ]
JOIN tableExpression [[AS] newTableAlias] [ON expression] ]
","
Joins a table. The join expression is not supported for cross and natural joins.
A natural join is an inner join, where the condition is automatically on the columns with the same name.
......@@ -2628,7 +2629,7 @@ Returns true if the statement was cancelled, false if the session is closed
or no statement is currently executing.
Admin rights are required to execute this command.
","
CANCEL_STATEMENT(3)
CANCEL_SESSION(3)
"
"Functions (System)","CASEWHEN Function","
......
......@@ -45,6 +45,7 @@ public class LocalResult implements ResultInterface {
private boolean isUpdateCount;
private int updateCount;
private boolean distinct;
private boolean closed;
public static LocalResult read(Session session, ResultSet rs, int maxrows) throws SQLException {
ObjectArray cols = getExpressionColumns(session, rs);
......@@ -314,11 +315,16 @@ public class LocalResult implements ResultInterface {
}
}
}
public boolean needToClose() {
return disk != null;
}
public void close() {
if (disk != null) {
disk.close();
disk = null;
closed = true;
}
}
......@@ -394,4 +400,8 @@ public class LocalResult implements ResultInterface {
return "columns: " + visibleColumnCount + " rows: " + rowCount + " pos: " + rowId;
}
public boolean isClosed() {
return closed;
}
}
......@@ -82,36 +82,44 @@ public class Sequence extends SchemaObjectBase {
return buff.toString();
}
public synchronized long getNext() throws SQLException {
public synchronized long getNext(Session session) throws SQLException {
if ((increment > 0 && value >= valueWithMargin) || (increment < 0 && value <= valueWithMargin)) {
valueWithMargin += increment * cacheSize;
flush();
flush(session);
}
long v = value;
value += increment;
return v;
}
public synchronized void flush() throws SQLException {
// can not use the session, because it must be committed immediately
// otherwise other threads can not access the sys table.
public synchronized void flush(Session session) throws SQLException {
Session sysSession = database.getSystemSession();
synchronized (sysSession) {
if (session == null || !database.isSysTableLocked()) {
// this session may not lock the sys table (except if it already has locked it)
// because it must be committed immediately
// otherwise other threads can not access the sys table.
session = sysSession;
}
synchronized (session) {
// just for this case, use the value with the margin for the script
long realValue = value;
try {
value = valueWithMargin;
database.update(sysSession, this);
database.update(session, this);
} finally {
value = realValue;
}
sysSession.commit(false);
if (session == sysSession) {
// if the system session is used,
// the transaction must be committed immediately
sysSession.commit(false);
}
}
}
public void close() throws SQLException {
valueWithMargin = value;
flush();
flush(null);
}
public int getType() {
......
......@@ -36,6 +36,7 @@ public class WebSession {
private DbContents contents = new DbContents();
private DbContextRule columnRule;
private DbContextRule newAliasRule;
private DbContextRule schemaRule;
private DbContextRule tableRule;
private DbContextRule aliasRule;
private DbContextRule columnAliasRule;
......@@ -73,6 +74,7 @@ public class WebSession {
newAliasRule = new DbContextRule(contents, DbContextRule.NEW_TABLE_ALIAS);
aliasRule = new DbContextRule(contents, DbContextRule.TABLE_ALIAS);
tableRule = new DbContextRule(contents, DbContextRule.TABLE);
schemaRule = new DbContextRule(contents, DbContextRule.SCHEMA);
columnAliasRule = new DbContextRule(contents, DbContextRule.COLUMN_ALIAS);
// bnf.updateTopic("newTableName", new String[]{"TEST"});
// String[] schemas;
......@@ -90,6 +92,7 @@ public class WebSession {
newBnf.updateTopic("tableAlias", aliasRule);
newBnf.updateTopic("columnAlias", columnAliasRule);
newBnf.updateTopic("tableName", tableRule);
newBnf.updateTopic("schemaName", schemaRule);
// bnf.updateTopic("name", new String[]{""});
newBnf.linkStatements();
bnf = newBnf;
......
......@@ -36,6 +36,8 @@ public class FileSystemMemory extends FileSystem {
}
public void rename(String oldName, String newName) throws SQLException {
oldName = normalize(oldName);
newName = normalize(newName);
FileObjectMemory f = getMemoryFile(oldName);
f.setName(newName);
synchronized (MEMORY_FILES) {
......@@ -54,18 +56,21 @@ public class FileSystemMemory extends FileSystem {
}
public boolean exists(String fileName) {
fileName = normalize(fileName);
synchronized (MEMORY_FILES) {
return MEMORY_FILES.get(fileName) != null;
}
}
public void delete(String fileName) throws SQLException {
fileName = normalize(fileName);
synchronized (MEMORY_FILES) {
MEMORY_FILES.remove(fileName);
}
}
public boolean tryDelete(String fileName) {
public boolean tryDelete(String fileName) {
fileName = normalize(fileName);
synchronized (MEMORY_FILES) {
MEMORY_FILES.remove(fileName);
}
......@@ -104,12 +109,21 @@ public class FileSystemMemory extends FileSystem {
return false;
}
public String normalize(String fileName) throws SQLException {
public String normalize(String fileName) {
fileName = fileName.replace('\\', '/');
int idx = fileName.indexOf(":/");
if (idx > 0) {
fileName = fileName.substring(0, idx + 1) + fileName.substring(idx + 2);
}
return fileName;
}
public String getParent(String fileName) {
int idx = Math.max(fileName.indexOf(':'), fileName.lastIndexOf('/'));
fileName = normalize(fileName);
int idx = fileName.lastIndexOf('/');
if (idx < 0) {
idx = fileName.indexOf(':') + 1;
}
return fileName.substring(0, idx);
}
......@@ -125,7 +139,7 @@ public class FileSystemMemory extends FileSystem {
public String getAbsolutePath(String fileName) {
// TODO relative files are not supported
return fileName;
return normalize(fileName);
}
public long getLastModified(String fileName) {
......@@ -156,6 +170,8 @@ public class FileSystemMemory extends FileSystem {
}
public boolean fileStartsWith(String fileName, String prefix) {
fileName = normalize(fileName);
prefix = normalize(prefix);
return fileName.startsWith(prefix);
}
......@@ -182,6 +198,7 @@ public class FileSystemMemory extends FileSystem {
}
private FileObjectMemory getMemoryFile(String fileName) {
fileName = normalize(fileName);
synchronized (MEMORY_FILES) {
FileObjectMemory m = (FileObjectMemory) MEMORY_FILES.get(fileName);
if (m == null) {
......
......@@ -266,7 +266,7 @@ public class Column {
if (update) {
sequence.setStartValue(now + increment);
session.setLastIdentity(ValueLong.get(now));
sequence.flush();
sequence.flush(session);
}
}
}
......
......@@ -8,6 +8,10 @@ import java.io.IOException;
import java.io.LineNumberReader;
import java.io.PrintWriter;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.StringTokenizer;
import org.h2.message.Message;
import org.h2.util.FileUtils;
......@@ -20,6 +24,31 @@ import org.h2.util.Tool;
* This is required because the find command truncates lines.
*/
public class ConvertTraceFile extends Tool {
private HashMap stats = new HashMap();
private long timeTotal;
private static class Stat implements Comparable {
String sql;
int executeCount;
long time;
long resultCountTotal;
public int compareTo(Object o) {
Stat other = (Stat) o;
if (other == this) {
return 0;
}
int c = other.time > time ? 1 : other.time < time ? -1 : 0;
if (c == 0) {
c = other.executeCount > executeCount ? 1 : other.executeCount < executeCount ? -1 : 0;
if (c == 0) {
c = sql.compareTo(other.sql);
}
}
return c;
}
}
private void showUsage() {
out.println("Converts a .trace.db file to a SQL script and Java source code.");
......@@ -107,16 +136,60 @@ public class ConvertTraceFile extends Tool {
if (line.startsWith("/**/")) {
line = " " + line.substring(4);
javaWriter.println(line);
} else if (line.startsWith("/*SQL*/")) {
line = line.substring("/*SQL*/".length());
scriptWriter.println(StringUtils.javaDecode(line));
} else if (line.startsWith("/*SQL")) {
int end = line.indexOf("*/");
String sql = line.substring(end + "*/".length());
line = line.substring("/*SQL".length(), end);
if (line.length() > 0) {
int len = sql.length();
int count = 0;
int time = 0;
line = line.trim();
if (line.length() > 0) {
StringTokenizer tk = new StringTokenizer(line, " :");
while (tk.hasMoreElements()) {
String token = tk.nextToken();
if ("l".equals(token)) {
len = Integer.parseInt(tk.nextToken());
} else if ("#".equals(token)) {
count = Integer.parseInt(tk.nextToken());
} else if ("t".equals(token)) {
time = Integer.parseInt(tk.nextToken());
}
}
}
String statement = sql.substring(0, len);
addToStats(statement, count, time);
}
scriptWriter.println(StringUtils.javaDecode(sql));
}
}
javaWriter.println(" }");
javaWriter.println("}");
reader.close();
javaWriter.close();
if (stats.size() > 0) {
scriptWriter.println("---------------------------------------------------------------");
ArrayList list = new ArrayList(stats.values());
Collections.sort(list);
int todo;
scriptWriter.println("--");
}
scriptWriter.close();
}
private void addToStats(String sql, int resultCount, int time) {
Stat stat = (Stat) stats.get(sql);
if (stat == null) {
stat = new Stat();
stat.sql = sql;
stats.put(sql, stat);
}
stat.executeCount++;
stat.resultCountTotal += resultCount;
stat.time += time;
timeTotal += time;
}
}
......@@ -159,11 +159,28 @@ java org.h2.test.TestAll timer
/*
delay on wrong password: double the time, randomized, reset on right password
1. get application here: http://krtonozka23.savana.cz/mismatch_error.zip
2. unpack and execute: java -jar executeh2script.jar
3. it will copy the prepared database from basedb into dbtest
directory and execute the crashScript.txt upon it.
4. connect to created dbtest database with h2 console and execute:
SELECT Data FROM FD_12_5001 WHERE TypeId=4 ORDER BY Ts DESC;
5. you should got something like: General error: java.lang.Error: File
ID mismatch got=1707400262 expected=41 pos=57219 true
org.h2.store.DiskFile:E:\WiMax\projects\Java\ExecuteH2Script\dbtest
\dbtest.data.db blockCount:-755423910 [50000-67] (Help)
File ID mismatch: 2008-03-21.trace.zip
test ConvertTraceFile
include in the execution times in the debug log.
(for each SQL statement ran)
SQL:checksum:1ms SELECT * FROM TEST
checksum: not including values, case insensitive
optimize where x not in (select):
SELECT c FROM color LEFT OUTER JOIN (SELECT c FROM TABLE(c
VARCHAR= ?)) p ON color.c = p.c WHERE p.c IS NULL;
better document system tables
.tar.bz2 instead of .zip
drop table test;
create table test(id int);
......@@ -172,43 +189,9 @@ inner join test t3 on t3.id=t2.id on t1.id=t2.id;
-- supported by PostgreSQL, Derby,...;
not supported by MySQL,...
wrong password should be synchronized outside:
wrong password should delay other wrong password,
but not right password
javadocs: constructors are not listed (JdbcConnectionPoolManager)
JdbcConnectionPoolManager pm =
new JdbcConnectionPoolManager();
pm.setDataSource(ds);
pm.setMaxConnection(10);
pm.setTimeout(10);
pm.start();
or:
JdbcConnectionPoolManager pm =
new JdbcConnectionPoolManager();
pm.setDataSource(ds).
setMaxConnection(10).setTimeout(10).start();
include in the execution times in the debug log.
(for each SQL statement ran)
SQL:checksum:1ms SELECT * FROM TEST
checksum: not including values, case insensitive
Derby doesn't optimize it
drop table test;
create table test(id int, version int, idx int);
@LOOP 1000 insert into test values(1, 1, ?);
@LOOP 1000 insert into test values(1, 2, ?);
@LOOP 1000 insert into test values(2, 1, ?);
create index idx_test on test(id, version, idx);
@LOOP 1000 select max(id)+1 from test;
@LOOP 1000 select max(idx)+1 from test where id=1 and version=2;
@LOOP 1000 select max(id)+1 from test;
@LOOP 1000 select max(idx)+1 from test where id=1 and version=2;
@LOOP 1000 select max(id)+1 from test;
@LOOP 1000 select max(idx)+1 from test where id=1 and version=2;
-- should be direct query
optimize where x not in (select):
SELECT c FROM color LEFT OUTER JOIN (SELECT c FROM TABLE(c
VARCHAR= ?)) p ON color.c = p.c WHERE p.c IS NULL;
Browser problems:
There has been a reported incompatibility with the
......@@ -258,14 +241,31 @@ Can sometimes not delete log file? need test case
Add where required // TODO: change in version 1.1
http://www.w3schools.com/sql/
History:
The autocomplete in the H2 Console has been improved a bit.
The tools in the H2 Console are now translatable.
Invalid inline views threw confusing SQL exceptions.
The Japanese translation of the error messages and the
H2 Console has been improved. Thanks a lot to Masahiro IKEMOTO.
Optimization for MIN() and MAX() when using MVCC.
To protect against remote brute force password attacks,
the delay after each unsuccessful login now gets double as long.
Use the system properties h2.delayWrongPasswordMin
and h2.delayWrongPasswordMax
After setting the query timeout and then resetting it, the next query
would still timeout. Fixed.
Adding a IDENTITY column to a table with data threw a lock timeout.
OutOfMemoryError could occur when using EXISTS or IN(SELECT ..).
The built-in connection pool is not called JdbcConnectionPool.
The API and documentation has been changed.
Roadmap:
Doclet (javadocs): constructors are not listed
Support direct lookup for MIN and MAX when using WHERE (see todo.txt / Direct Lookup)
*/
......
......@@ -17,11 +17,24 @@ import org.h2.test.TestBase;
public class TestSequence extends TestBase {
public void test() throws Exception {
testAlterSequenceColumn();
testAlterSequence();
testCache();
testTwo();
}
private void testAlterSequenceColumn() throws Exception {
deleteDb("sequence");
Connection conn = getConnection("sequence");
Statement stat = conn.createStatement();
stat.execute("CREATE TABLE TEST(ID INT , NAME VARCHAR(255))");
stat.execute("INSERT INTO TEST VALUES(1, 'Hello')");
stat.execute("ALTER TABLE TEST ALTER COLUMN ID INT IDENTITY");
stat.execute("ALTER TABLE test ALTER COLUMN ID RESTART WITH 3");
stat.execute("INSERT INTO TEST (name) VALUES('Other World')");
conn.close();
}
private void testAlterSequence() throws Exception {
deleteDb("sequence");
Connection conn = getConnection("sequence");
......
......@@ -44,11 +44,28 @@ public class TestCancel extends TestBase {
}
public void test() throws Exception {
testReset();
testMaxQueryTimeout();
testQueryTimeout();
testJdbcQueryTimeout();
testCancelStatement();
}
private void testReset() throws Exception {
deleteDb("cancel");
Connection conn = getConnection("cancel");
Statement stat = conn.createStatement();
stat.execute("set query_timeout 1");
try {
stat.execute("select count(*) from system_range(1, 1000000), system_range(1, 1000000)");
error();
} catch (SQLException e) {
checkNotGeneralException(e);
}
stat.execute("set query_timeout 0");
stat.execute("select count(*) from system_range(1, 1000), system_range(1, 1000)");
conn.close();
}
private void testJdbcQueryTimeout() throws Exception {
deleteDb("cancel");
......
......@@ -8,7 +8,7 @@ package org.h2.test.jdbcx;
import java.sql.Connection;
import java.sql.Statement;
import org.h2.jdbcx.JdbcConnectionPoolManager;
import org.h2.jdbcx.JdbcConnectionPool;
import org.h2.jdbcx.JdbcDataSource;
import org.h2.test.TestBase;
......@@ -25,7 +25,7 @@ public class TestConnectionPool extends TestBase {
private void testThreads() throws Exception {
final int len = getSize(4, 20);
final JdbcConnectionPoolManager man = getConnectionPool(len - 2);
final JdbcConnectionPool man = getConnectionPool(len - 2);
final boolean[] stop = new boolean[1];
class TestRunner implements Runnable {
public void run() {
......@@ -57,16 +57,18 @@ public class TestConnectionPool extends TestBase {
man.dispose();
}
JdbcConnectionPoolManager getConnectionPool(int poolSize) throws Exception {
JdbcConnectionPool getConnectionPool(int poolSize) throws Exception {
JdbcDataSource ds = new JdbcDataSource();
ds.setURL(getURL("connectionPool", true));
ds.setUser(getUser());
ds.setPassword(getPassword());
return new JdbcConnectionPoolManager(ds, poolSize, 2);
JdbcConnectionPool pool = JdbcConnectionPool.create(ds);
pool.setMaxConnections(poolSize);
return pool;
}
private void testConnect() throws Exception {
JdbcConnectionPoolManager man = getConnectionPool(3);
JdbcConnectionPool man = getConnectionPool(3);
for (int i = 0; i < 100; i++) {
Connection conn = man.getConnection();
conn.close();
......
......@@ -55,6 +55,14 @@ public class TestWeb extends TestBase {
result = client.get(url, "getHistory.do?id=4");
checkContains(result, "select * from test");
result = client.get(url, "autoCompleteList.do?query=se");
// long time = System.currentTimeMillis();
// for (int i=0; i<1000; i++) {
// if(System.currentTimeMillis()-time > 15000) {
// break;
// }
// result = client.get(url, "autoCompleteList.do?query=select * from ");
checkContains(result, "select");
checkContains(result, "set");
result = client.get(url, "tables.do");
......
Auto Upgrade
-----------------
file conversion should be done automatically when the new engine connects.
auto-upgrade application:
......@@ -12,3 +14,20 @@ install new version
ftp client
task to download new version from another HTTP / HTTPS / FTP server
multi-task
Direct Lookup
-----------------
drop table test;
create table test(id int, version int, idx int);
@LOOP 1000 insert into test values(1, 1, ?);
@LOOP 1000 insert into test values(1, 2, ?);
@LOOP 1000 insert into test values(2, 1, ?);
create index idx_test on test(id, version, idx);
@LOOP 1000 select max(id)+1 from test;
@LOOP 1000 select max(idx)+1 from test where id=1 and version=2;
@LOOP 1000 select max(id)+1 from test;
@LOOP 1000 select max(idx)+1 from test where id=1 and version=2;
@LOOP 1000 select max(id)+1 from test;
@LOOP 1000 select max(idx)+1 from test where id=1 and version=2;
-- should be direct query
......@@ -26,6 +26,7 @@ import org.h2.test.TestBase;
public class TestFileSystem extends TestBase {
public void test() throws Exception {
testDatabaseInMemFileSys();
testDatabaseInJar();
testFileSystem(baseDir + "/fs");
testFileSystem(FileSystem.MEMORY_PREFIX);
......@@ -34,6 +35,20 @@ public class TestFileSystem extends TestBase {
testFileSystem(FileSystem.MEMORY_PREFIX_LZF);
testUserHome();
}
private void testDatabaseInMemFileSys() throws Exception {
Class.forName("org.h2.Driver");
String url = "jdbc:h2:" + baseDir + "/fsMem";
Connection conn = DriverManager.getConnection(url, "sa", "sa");
conn.createStatement().execute("CREATE TABLE TEST AS SELECT * FROM DUAL");
conn.createStatement().execute("BACKUP TO '" + baseDir + "/fsMem.zip'");
conn.close();
org.h2.tools.Restore.main(new String[]{"-file", baseDir + "/fsMem.zip", "-dir", "memFS:"});
conn = DriverManager.getConnection("jdbc:h2:memFS:fsMem", "sa", "sa");
ResultSet rs = conn.createStatement().executeQuery("SELECT * FROM TEST");
rs.close();
conn.close();
}
private void testDatabaseInJar() throws Exception {
if (config.networked) {
......
......@@ -59,8 +59,9 @@ public class Doclet {
return true;
}
private static String getClass(String name) {
if (name.startsWith("Jdbc")) {
private static String getClass(ClassDoc clazz) {
String name = clazz.name();
if (clazz.qualifiedName().indexOf(".jdbc.") > 0 && name.startsWith("Jdbc")) {
return name.substring(4);
}
return name;
......@@ -71,7 +72,7 @@ public class Doclet {
String dir = destDir + "/" + packageName.replace('.', '/');
(new File(dir)).mkdirs();
String fileName = dir + "/" + clazz.name() + ".html";
String className = getClass(clazz.name());
String className = getClass(clazz);
FileWriter out = new FileWriter(fileName);
PrintWriter writer = new PrintWriter(new BufferedWriter(out));
writer.println("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" " +
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论