提交 26414c8e authored 作者: Thomas Mueller's avatar Thomas Mueller

--no commit message

--no commit message
上级 07470668
...@@ -79,8 +79,8 @@ public abstract class ScriptBase extends Prepared implements DataHandler { ...@@ -79,8 +79,8 @@ public abstract class ScriptBase extends Prepared implements DataHandler {
} }
protected String getFileName() throws SQLException { protected String getFileName() throws SQLException {
if (fileName != null) { if (file != null && fileName == null) {
fileName = file == null ? null : file.getValue(session).getString(); fileName = file.getValue(session).getString();
if (fileName == null || fileName.trim().length() == 0) { if (fileName == null || fileName.trim().length() == 0) {
fileName = "script.sql"; fileName = "script.sql";
} }
......
...@@ -158,6 +158,24 @@ public class SysProperties { ...@@ -158,6 +158,24 @@ public class SysProperties {
*/ */
public static final int DEFAULT_LOCK_MODE = getIntSetting("h2.defaultLockMode", Constants.LOCK_MODE_READ_COMMITTED); public static final int DEFAULT_LOCK_MODE = getIntSetting("h2.defaultLockMode", Constants.LOCK_MODE_READ_COMMITTED);
/**
* System property <code>h2.delayWrongPasswordMin</code> (default: 200).<br />
* The minimum delay in milliseconds before an exception is thrown for using
* the wrong user name or password. This slows down brute force attacks. The
* delay is reset to this value after a successful login. Unsuccessful
* logins will double the time until DELAY_WRONG_PASSWORD_MAX.
*/
public static final int DELAY_WRONG_PASSWORD_MIN = getIntSetting("h2.delayWrongPasswordMin", 200);
/**
* System property <code>h2.delayWrongPasswordMax</code> (default: 0).<br />
* The maximum delay in milliseconds before an exception is thrown for using
* the wrong user name or password. This slows down brute force attacks. The
* delay is reset after a successful login. The value 0 means there is no
* maximum delay.
*/
public static final int DELAY_WRONG_PASSWORD_MAX = getIntSetting("h2.delayWrongPasswordMax", 0);
/** /**
* System property <code>h2.emergencySpaceInitial</code> (default: 262144).<br /> * System property <code>h2.emergencySpaceInitial</code> (default: 262144).<br />
* Size of 'reserve' file to detect disk full problems early. * Size of 'reserve' file to detect disk full problems early.
......
...@@ -191,11 +191,4 @@ public class Constants { ...@@ -191,11 +191,4 @@ public class Constants {
public static final String SCRIPT_SQL = "script.sql"; public static final String SCRIPT_SQL = "script.sql";
public static final int CACHE_MIN_RECORDS = 16; public static final int CACHE_MIN_RECORDS = 16;
/**
* The delay in milliseconds before an exception about
* a wrong user or password is thrown.
* This slows down dictionary attacks.
* An attacker can still open multiple connections.
*/
public static final long DELAY_WRONG_PASSWORD = 200;
} }
...@@ -396,15 +396,8 @@ public class Database implements DataHandler { ...@@ -396,15 +396,8 @@ public class Database implements DataHandler {
return store; return store;
} }
public void checkFilePasswordHash(String c, byte[] hash) throws SQLException { public boolean validateFilePasswordHash(String c, byte[] hash) throws SQLException {
if (!ByteUtils.compareSecure(hash, filePasswordHash) || !StringUtils.equals(c, cipher)) { return ByteUtils.compareSecure(hash, filePasswordHash) && StringUtils.equals(c, cipher);
try {
Thread.sleep(Constants.DELAY_WRONG_PASSWORD);
} catch (InterruptedException e) {
// ignore
}
throw Message.getSQLException(ErrorCode.WRONG_USER_OR_PASSWORD);
}
} }
private void openFileData() throws SQLException { private void openFileData() throws SQLException {
...@@ -789,35 +782,14 @@ public class Database implements DataHandler { ...@@ -789,35 +782,14 @@ public class Database implements DataHandler {
return (UserDataType) userDataTypes.get(name); return (UserDataType) userDataTypes.get(name);
} }
/** public User getUser(String name) throws SQLException {
* Get the user with the given name. If there is no such user, this method User user = findUser(name);
* waits a short amount of time (to make rainbow table attacks harder) and
* then throws the exception that is passed. There is only one exception
* both for wrong user and for wrong password, to make it harder to get the
* list of user names.
*
* @param name the user name
* @param notFound the exception that should be thrown if the user does not
* exist
* @throws SQLException if the user does not exist
*/
public User getUser(String name, SQLException notFound) throws SQLException {
User user = (User) users.get(name);
if (user == null) { if (user == null) {
try { throw Message.getSQLException(ErrorCode.USER_NOT_FOUND_1, name);
Thread.sleep(Constants.DELAY_WRONG_PASSWORD);
} catch (InterruptedException e) {
// ignore
}
throw notFound;
} }
return user; return user;
} }
public User getUser(String name) throws SQLException {
return getUser(name, Message.getSQLException(ErrorCode.USER_NOT_FOUND_1, name));
}
public synchronized Session createUserSession(User user) throws SQLException { public synchronized Session createUserSession(User user) throws SQLException {
if (exclusiveSession != null) { if (exclusiveSession != null) {
throw Message.getSQLException(ErrorCode.DATABASE_IS_IN_EXCLUSIVE_MODE); throw Message.getSQLException(ErrorCode.DATABASE_IS_IN_EXCLUSIVE_MODE);
......
...@@ -12,8 +12,10 @@ import org.h2.command.CommandInterface; ...@@ -12,8 +12,10 @@ import org.h2.command.CommandInterface;
import org.h2.command.Parser; import org.h2.command.Parser;
import org.h2.command.dml.SetTypes; import org.h2.command.dml.SetTypes;
import org.h2.constant.ErrorCode; import org.h2.constant.ErrorCode;
import org.h2.constant.SysProperties;
import org.h2.message.Message; import org.h2.message.Message;
import org.h2.message.Trace; import org.h2.message.Trace;
import org.h2.util.RandomUtils;
import org.h2.util.StringUtils; import org.h2.util.StringUtils;
/** /**
...@@ -22,10 +24,9 @@ import org.h2.util.StringUtils; ...@@ -22,10 +24,9 @@ import org.h2.util.StringUtils;
* This is a singleton class. * This is a singleton class.
*/ */
public class Engine { public class Engine {
// TODO use a 'engine'/'master' database to allow shut down the server,
// view & kill sessions and so on
private static final Engine INSTANCE = new Engine(); private static final Engine INSTANCE = new Engine();
private static long delayWrongPassword = SysProperties.DELAY_WRONG_PASSWORD_MIN;
private final HashMap databases = new HashMap(); private final HashMap databases = new HashMap();
private Engine() { private Engine() {
...@@ -73,12 +74,16 @@ public class Engine { ...@@ -73,12 +74,16 @@ public class Engine {
} }
if (user == null) { if (user == null) {
try { try {
database.checkFilePasswordHash(cipher, ci.getFilePasswordHash()); boolean correct = database.validateFilePasswordHash(cipher, ci.getFilePasswordHash());
// create the exception here so it is not possible from the stack trace if (correct) {
// to see if the user name was wrong or the password user = database.findUser(ci.getUserName());
SQLException wrongUserOrPassword = Message.getSQLException(ErrorCode.WRONG_USER_OR_PASSWORD); if (user == null) {
user = database.getUser(ci.getUserName(), wrongUserOrPassword); correct = false;
user.checkUserPasswordHash(ci.getUserPasswordHash(), wrongUserOrPassword); } else {
correct = user.validateUserPasswordHash(ci.getUserPasswordHash());
}
}
validateUserAndPassword(correct);
if (opened && !user.getAdmin()) { if (opened && !user.getAdmin()) {
// reset - because the user is not an admin, and has no // reset - because the user is not an admin, and has no
// right to listen to exceptions // right to listen to exceptions
...@@ -158,4 +163,47 @@ public class Engine { ...@@ -158,4 +163,47 @@ public class Engine {
databases.remove(name); databases.remove(name);
} }
/**
* This method is called after validating user name and password. If user
* name and password were correct, the sleep time is reset, otherwise this
* method waits some time (to make brute force / rainbow table attacks
* harder) and then throws a 'wrong user or password' exception. The delay
* is a bit randomized to protect against timing attacks. Also the delay
* doubles after each unsuccessful logins, to make brute force attacks harder.
*
* There is only one exception both for wrong user and for wrong password,
* to make it harder to get the list of user names. This method must only be
* called from one place, so it is not possible from the stack trace to see
* if the user name was wrong or the password.
*
* @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 {
int min = SysProperties.DELAY_WRONG_PASSWORD_MIN;
if (correct) {
delayWrongPassword = 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
}
}
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);
}
}
} }
...@@ -659,7 +659,10 @@ public class Session implements SessionInterface { ...@@ -659,7 +659,10 @@ public class Session implements SessionInterface {
return database; return database;
} }
public void unlinkAtCommit(Value v) { public void unlinkAtCommit(ValueLob v) {
if (SysProperties.CHECK && !v.isLinked()) {
throw Message.getInternalError();
}
if (unlinkMap == null) { if (unlinkMap == null) {
unlinkMap = new HashMap(); unlinkMap = new HashMap();
} }
......
...@@ -149,28 +149,15 @@ public class User extends RightOwner { ...@@ -149,28 +149,15 @@ public class User extends RightOwner {
} }
/** /**
* Check the password of this user. If the password is wrong, this method * Check the password of this user.
* waits a short amount of time (to make S attacks harder) and then throws
* the exception that is passed. There is only one exception both for wrong
* user and for wrong password, to make it harder to get the list of user
* names.
* *
* @param userPasswordHash the password data (the user password hash) * @param userPasswordHash the password data (the user password hash)
* @param onError the exception that should be thrown if the password does * @return true if the user password hash is correct
* not match
* @throws SQLException if the password does not match
*/ */
public void checkUserPasswordHash(byte[] userPasswordHash, SQLException onError) throws SQLException { public boolean validateUserPasswordHash(byte[] userPasswordHash) {
SHA256 sha = new SHA256(); SHA256 sha = new SHA256();
byte[] hash = sha.getHashWithSalt(userPasswordHash, salt); byte[] hash = sha.getHashWithSalt(userPasswordHash, salt);
if (!ByteUtils.compareSecure(hash, passwordHash)) { return ByteUtils.compareSecure(hash, passwordHash);
try {
Thread.sleep(Constants.DELAY_WRONG_PASSWORD);
} catch (InterruptedException e) {
// ignore
}
throw onError;
}
} }
/** /**
......
...@@ -169,11 +169,11 @@ public class Aggregate extends Expression { ...@@ -169,11 +169,11 @@ public class Aggregate extends Expression {
first = !first; first = !first;
} }
Cursor cursor = index.findFirstOrLast(session, first); Cursor cursor = index.findFirstOrLast(session, first);
SearchRow row = cursor.getSearchRow();
Value v; Value v;
if (cursor == null) { if (row == null) {
v = ValueNull.INSTANCE; v = ValueNull.INSTANCE;
} else { } else {
SearchRow row = cursor.getSearchRow();
v = row.getValue(index.getColumns()[0].getColumnId()); v = row.getValue(index.getColumns()[0].getColumnId());
} }
return v; return v;
......
...@@ -115,11 +115,12 @@ public abstract class BaseIndex extends SchemaObjectBase implements Index { ...@@ -115,11 +115,12 @@ public abstract class BaseIndex extends SchemaObjectBase implements Index {
public abstract boolean canGetFirstOrLast(); public abstract boolean canGetFirstOrLast();
/** /**
* Find the first (or last) value of this index. * Find the first (or last) value of this index. The cursor returned is
* positioned on the correct row, or on null if no row has been found.
* *
* @param session the session * @param session the session
* @param first true for the first value, false for the last * @param first true for the first value, false for the last
* @return a cursor or null * @return a cursor (never null)
*/ */
public abstract Cursor findFirstOrLast(Session session, boolean first) throws SQLException; public abstract Cursor findFirstOrLast(Session session, boolean first) throws SQLException;
......
...@@ -92,7 +92,9 @@ public class BtreeCursor implements Cursor { ...@@ -92,7 +92,9 @@ public class BtreeCursor implements Cursor {
} }
public boolean previous() throws SQLException { public boolean previous() throws SQLException {
if (currentSearchRow != null) {
top.page.previous(this, top.position); top.page.previous(this, top.position);
}
return currentSearchRow != null; return currentSearchRow != null;
} }
......
...@@ -345,7 +345,7 @@ public class BtreeIndex extends BaseIndex implements RecordReader { ...@@ -345,7 +345,7 @@ public class BtreeIndex extends BaseIndex implements RecordReader {
return cursor; return cursor;
} }
} }
return null; return cursor;
} else { } else {
BtreePage root = getRoot(session); BtreePage root = getRoot(session);
BtreeCursor cursor = new BtreeCursor(session, this, null); BtreeCursor cursor = new BtreeCursor(session, this, null);
...@@ -361,7 +361,7 @@ public class BtreeIndex extends BaseIndex implements RecordReader { ...@@ -361,7 +361,7 @@ public class BtreeIndex extends BaseIndex implements RecordReader {
return cursor; return cursor;
} }
} while (cursor.previous()); } while (cursor.previous());
return null; return cursor;
} }
} }
......
...@@ -26,6 +26,7 @@ public class MultiVersionCursor implements Cursor { ...@@ -26,6 +26,7 @@ public class MultiVersionCursor implements Cursor {
private boolean onBase; private boolean onBase;
private boolean end; private boolean end;
private boolean needNewDelta, needNewBase; private boolean needNewDelta, needNewBase;
private boolean reverse;
MultiVersionCursor(Session session, MultiVersionIndex index, Cursor base, Cursor delta, Object sync) throws SQLException { MultiVersionCursor(Session session, MultiVersionIndex index, Cursor base, Cursor delta, Object sync) throws SQLException {
this.session = session; this.session = session;
...@@ -36,34 +37,25 @@ public class MultiVersionCursor implements Cursor { ...@@ -36,34 +37,25 @@ public class MultiVersionCursor implements Cursor {
needNewDelta = needNewBase = true; needNewDelta = needNewBase = true;
} }
private void loadNext(boolean base) throws SQLException { void loadCurrent() throws SQLException {
synchronized (sync) { synchronized (sync) {
if (base) {
if (baseCursor.next()) {
baseRow = baseCursor.getSearchRow(); baseRow = baseCursor.getSearchRow();
} else {
baseRow = null;
}
} else {
if (deltaCursor.next()) {
deltaRow = deltaCursor.get(); deltaRow = deltaCursor.get();
} else { needNewDelta = false;
deltaRow = null; needNewBase = false;
}
}
} }
} }
private void loadPrevious(boolean base) throws SQLException { private void loadNext(boolean base) throws SQLException {
synchronized (sync) { synchronized (sync) {
if (base) { if (base) {
if (baseCursor.previous()) { if (step(baseCursor)) {
baseRow = baseCursor.getSearchRow(); baseRow = baseCursor.getSearchRow();
} else { } else {
baseRow = null; baseRow = null;
} }
} else { } else {
if (deltaCursor.previous()) { if (step(deltaCursor)) {
deltaRow = deltaCursor.get(); deltaRow = deltaCursor.get();
} else { } else {
deltaRow = null; deltaRow = null;
...@@ -72,10 +64,14 @@ public class MultiVersionCursor implements Cursor { ...@@ -72,10 +64,14 @@ public class MultiVersionCursor implements Cursor {
} }
} }
private boolean step(Cursor cursor) throws SQLException {
return reverse ? cursor.previous() : cursor.next();
}
public Row get() throws SQLException { public Row get() throws SQLException {
synchronized (sync) { synchronized (sync) {
if (SysProperties.CHECK && end) { if (end) {
throw Message.getInternalError(); return null;
} }
return onBase ? baseCursor.get() : deltaCursor.get(); return onBase ? baseCursor.get() : deltaCursor.get();
} }
...@@ -92,8 +88,8 @@ public class MultiVersionCursor implements Cursor { ...@@ -92,8 +88,8 @@ public class MultiVersionCursor implements Cursor {
public SearchRow getSearchRow() throws SQLException { public SearchRow getSearchRow() throws SQLException {
synchronized (sync) { synchronized (sync) {
if (SysProperties.CHECK && end) { if (end) {
throw Message.getInternalError(); return null;
} }
return onBase ? baseCursor.getSearchRow() : deltaCursor.getSearchRow(); return onBase ? baseCursor.getSearchRow() : deltaCursor.getSearchRow();
} }
...@@ -186,8 +182,13 @@ public class MultiVersionCursor implements Cursor { ...@@ -186,8 +182,13 @@ public class MultiVersionCursor implements Cursor {
} }
} }
public boolean previous() { public boolean previous() throws SQLException {
throw Message.getInternalError(); reverse = true;
try {
return next();
} finally {
reverse = false;
}
} }
} }
...@@ -31,6 +31,7 @@ public class MultiVersionIndex implements Index { ...@@ -31,6 +31,7 @@ public class MultiVersionIndex implements Index {
private final TreeIndex delta; private final TreeIndex delta;
private final TableData table; private final TableData table;
private final Object sync; private final Object sync;
private final Column firstColumn;
public MultiVersionIndex(Index base, TableData table) throws SQLException { public MultiVersionIndex(Index base, TableData table) throws SQLException {
this.base = base; this.base = base;
...@@ -38,6 +39,7 @@ public class MultiVersionIndex implements Index { ...@@ -38,6 +39,7 @@ public class MultiVersionIndex implements Index {
IndexType deltaIndexType = IndexType.createNonUnique(false); IndexType deltaIndexType = IndexType.createNonUnique(false);
this.delta = new TreeIndex(table, -1, "DELTA", base.getIndexColumns(), deltaIndexType); this.delta = new TreeIndex(table, -1, "DELTA", base.getIndexColumns(), deltaIndexType);
this.sync = base.getDatabase(); this.sync = base.getDatabase();
this.firstColumn = base.getColumns()[0];
} }
public void add(Session session, Row row) throws SQLException { public void add(Session session, Row row) throws SQLException {
...@@ -76,42 +78,39 @@ public class MultiVersionIndex implements Index { ...@@ -76,42 +78,39 @@ public class MultiVersionIndex implements Index {
} }
public boolean canGetFirstOrLast() { public boolean canGetFirstOrLast() {
// TODO in many cases possible, but more complicated return base.canGetFirstOrLast() && delta.canGetFirstOrLast();
return false;
} }
public Cursor findFirstOrLast(Session session, boolean first) throws SQLException { public Cursor findFirstOrLast(Session session, boolean first) throws SQLException {
int test; if (first) {
throw Message.getUnsupportedException(); // TODO optimization: this loops through NULL elements
Cursor cursor = find(session, null, null);
// if (first) { while (cursor.next()) {
// // TODO optimization: this loops through NULL elements SearchRow row = cursor.getSearchRow();
// Cursor cursor = find(session, null, false, null); Value v = row.getValue(firstColumn.getColumnId());
// while (cursor.next()) { if (v != ValueNull.INSTANCE) {
// SearchRow row = cursor.getSearchRow(); return cursor;
// Value v = row.getValue(columnIds[0]); }
// if (v != ValueNull.INSTANCE) { }
// return cursor; return cursor;
// } } else {
// } Cursor baseCursor = base.findFirstOrLast(session, false);
// return null; Cursor deltaCursor = delta.findFirstOrLast(session, false);
// } else { MultiVersionCursor cursor = new MultiVersionCursor(session, this, baseCursor, deltaCursor, sync);
// BtreePage root = getRoot(session); cursor.loadCurrent();
// BtreeCursor cursor = new BtreeCursor(session, this, null); // TODO optimization: this loops through NULL elements
// root.last(cursor); while (cursor.previous()) {
// // TODO optimization: this loops through NULL elements SearchRow row = cursor.getSearchRow();
// do { if (row == null) {
// SearchRow row = cursor.getSearchRow(); break;
// if (row == null) { }
// break; Value v = row.getValue(firstColumn.getColumnId());
// } if (v != ValueNull.INSTANCE) {
// Value v = row.getValue(columnIds[0]); return cursor;
// if (v != ValueNull.INSTANCE) { }
// return cursor; }
// } return cursor;
// } while (cursor.previous()); }
// return null;
// }
} }
public double getCost(Session session, int[] masks) throws SQLException { public double getCost(Session session, int[] masks) throws SQLException {
......
...@@ -166,7 +166,7 @@ public class ScanIndex extends BaseIndex { ...@@ -166,7 +166,7 @@ public class ScanIndex extends BaseIndex {
for (int i = 0; i < row.getColumnCount(); i++) { for (int i = 0; i < row.getColumnCount(); i++) {
Value v = row.getValue(i); Value v = row.getValue(i);
if (v.isLinked()) { if (v.isLinked()) {
session.unlinkAtCommit(v); session.unlinkAtCommit((ValueLob) v);
} }
} }
} }
......
...@@ -382,7 +382,7 @@ public class TreeIndex extends BaseIndex { ...@@ -382,7 +382,7 @@ public class TreeIndex extends BaseIndex {
return cursor; return cursor;
} }
} }
return null; return cursor;
} else { } else {
TreeNode x = root, n; TreeNode x = root, n;
while (x != null) { while (x != null) {
...@@ -392,10 +392,10 @@ public class TreeIndex extends BaseIndex { ...@@ -392,10 +392,10 @@ public class TreeIndex extends BaseIndex {
} }
x = n; x = n;
} }
TreeCursor cursor = new TreeCursor(this, x, null, null);
if (x == null) { if (x == null) {
return null; return cursor;
} }
TreeCursor cursor = new TreeCursor(this, x, null, null);
// TODO optimization: this loops through NULL elements // TODO optimization: this loops through NULL elements
do { do {
SearchRow row = cursor.getSearchRow(); SearchRow row = cursor.getSearchRow();
...@@ -407,7 +407,7 @@ public class TreeIndex extends BaseIndex { ...@@ -407,7 +407,7 @@ public class TreeIndex extends BaseIndex {
return cursor; return cursor;
} }
} while (cursor.previous()); } while (cursor.previous());
return null; return cursor;
} }
} }
......
...@@ -157,7 +157,7 @@ public class RowList { ...@@ -157,7 +157,7 @@ public class RowList {
// the table id is 0 if it was linked when writing // the table id is 0 if it was linked when writing
// a temporary entry // a temporary entry
if (lob.getTableId() == 0) { if (lob.getTableId() == 0) {
session.unlinkAtCommit(v); session.unlinkAtCommit(lob);
} }
} }
values[i] = v; values[i] = v;
......
...@@ -159,15 +159,42 @@ java org.h2.test.TestAll timer ...@@ -159,15 +159,42 @@ java org.h2.test.TestAll timer
/* /*
test # in h2 console (other languages) delay on wrong password: double the time, randomized, reset on right password
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;
drop table test;
create table test(id int);
select * from test t1 inner join test t2
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. include in the execution times in the debug log.
(for each SQL statement ran) (for each SQL statement ran)
SQL:checksum:1ms SELECT * FROM TEST SQL:checksum:1ms SELECT * FROM TEST
checksum: not including values, case insensitive checksum: not including values, case insensitive
make everything translatable
Derby doesn't optimize it Derby doesn't optimize it
drop table test; drop table test;
create table test(id int, version int, idx int); create table test(id int, version int, idx int);
...@@ -183,10 +210,6 @@ create index idx_test on test(id, version, idx); ...@@ -183,10 +210,6 @@ create index idx_test on test(id, version, idx);
@LOOP 1000 select max(idx)+1 from test where id=1 and version=2; @LOOP 1000 select max(idx)+1 from test where id=1 and version=2;
-- should be direct query -- should be direct query
Fix ScriptBase.getFileName()
Fix Shell.java 159 (close PreparedStatement)
Browser problems: Browser problems:
There has been a reported incompatibility with the There has been a reported incompatibility with the
RealPlayer Browser Record Plugin 1.0 when using Firefox 2.0 and Vista RealPlayer Browser Record Plugin 1.0 when using Firefox 2.0 and Vista
...@@ -240,6 +263,7 @@ The tools in the H2 Console are now translatable. ...@@ -240,6 +263,7 @@ The tools in the H2 Console are now translatable.
Invalid inline views threw confusing SQL exceptions. Invalid inline views threw confusing SQL exceptions.
The Japanese translation of the error messages and the The Japanese translation of the error messages and the
H2 Console has been improved. Thanks a lot to Masahiro IKEMOTO. H2 Console has been improved. Thanks a lot to Masahiro IKEMOTO.
Optimization for MIN() and MAX() when using MVCC.
Roadmap: Roadmap:
...@@ -441,6 +465,7 @@ Roadmap: ...@@ -441,6 +465,7 @@ Roadmap:
* Run all tests with the current settings. * Run all tests with the current settings.
*/ */
private void test() throws Exception { private void test() throws Exception {
System.out.println(); System.out.println();
System.out.println("Test big:"+big+" net:"+networked+" cipher:"+cipher+" memory:"+memory+" log:"+logMode+" diskResult:"+diskResult + " mvcc:" + mvcc + " deleteIndex:" + deleteIndex); System.out.println("Test big:"+big+" net:"+networked+" cipher:"+cipher+" memory:"+memory+" log:"+logMode+" diskResult:"+diskResult + " mvcc:" + mvcc + " deleteIndex:" + deleteIndex);
beforeTest(); beforeTest();
......
...@@ -488,4 +488,5 @@ greenwich sqli informix pointbase fbj pervasive jtds ifx syb mimer sybase ...@@ -488,4 +488,5 @@ greenwich sqli informix pointbase fbj pervasive jtds ifx syb mimer sybase
frontbase intersys maxwidth belonging learning mono typical toggle winexe frontbase intersys maxwidth belonging learning mono typical toggle winexe
hider ikvmc invert recycle filtering lesser recycled assertion runner teradata hider ikvmc invert recycle filtering lesser recycled assertion runner teradata
christian lgpl elapsed ncr disposed heureuse tera years retrieves unlocked christian lgpl elapsed ncr disposed heureuse tera years retrieves unlocked
selecting vista everywhere locations zones fragment svg thought selecting vista everywhere locations zones fragment svg thought constructors
\ No newline at end of file doubles validating
\ No newline at end of file
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论