提交 f031fa50 authored 作者: Thomas Mueller's avatar Thomas Mueller

--no commit message

--no commit message
上级 3f000aa1
......@@ -53,11 +53,12 @@ Roadmap
<li>Automatic mode: jdbc:h2:auto: (embedded mode if possible, if not use server mode). Keep the server running until all have disconnected.
</li><li>Support function overloading as in Java (multiple functions with the same name, but different parameter count or data types).
</li><li>Linked tables that point to the same database should share the connection ([SHARED CONNECTION]). Serialize access to the connection.
</li><li>Procedural language / script language (Javascript)
</li><li>Linked tables should support a schema name: CREATE LINKED TABLE ... (... [schemaName, ] originalTableString) - DB2: SET SCHEMA after connecting
</li><li>Option to shutdown all the running servers (on the same VM).
</li><li>Support OSGi: http://oscar-osgi.sourceforge.net, http://incubator.apache.org/felix/index.html
</li><li>H2 Console: new option 'Open a browser when starting the H2 Console'.
</li><li>Better space re-use in the files after deleting data: shrink the data file without closing the database (if the end of the file is empty)
</li><li>Procedural language / script language (Javascript)
</li><li>Full outer joins
</li><li>Support hints for the optimizer (which index to use, enforce the join order).
</li><li>Change LOB mechanism (less files, keep index of lob files, point to files and row, delete unused files earlier, maybe bundle files into a tar file)
......@@ -362,7 +363,6 @@ Roadmap
Also support it when using INSERT ... SELECT.
</li><li>HSQLDB compatibility: automatic data type for SUM if value is the value is too big (by default use the same type as the data).
</li><li>Improve the optimizer to select the right index for special cases: where id between 2 and 4 and booleanColumn
</li><li>H2 Console: new option 'Open a browser when starting the H2 Console'.
</li><li>Enable warning for 'Local variable declaration hides another field or variable'.
</li><li>Linked tables: make hidden columns available (Oracle: rowid and ora_rowscn columns).
</li><li>Support merge join.
......
......@@ -131,8 +131,8 @@ public class GrantRevoke extends DefineCommand {
}
private void grantRole(Role grantedRole) throws SQLException {
if (grantee.isRoleGranted(grantedRole)) {
throw Message.getSQLException(ErrorCode.ROLE_ALREADY_GRANTED_1, grantedRole.getSQL());
if (grantedRole != grantee && grantee.isRoleGranted(grantedRole)) {
return;
}
if (grantee instanceof Role) {
Role granteeRole = (Role) grantee;
......@@ -153,13 +153,10 @@ public class GrantRevoke extends DefineCommand {
Table table = (Table) tables.get(i);
Right right = grantee.getRightForTable(table);
if (right == null) {
throw Message.getSQLException(ErrorCode.RIGHT_NOT_FOUND);
continue;
}
int mask = right.getRightMask();
if ((mask & rightMask) != rightMask) {
throw Message.getSQLException(ErrorCode.RIGHT_NOT_FOUND);
}
int newRight = mask ^ rightMask;
int newRight = mask & ~rightMask;
Database db = session.getDatabase();
if (newRight == 0) {
db.removeDatabaseObject(session, right);
......@@ -173,7 +170,7 @@ public class GrantRevoke extends DefineCommand {
private void revokeRole(Role grantedRole) throws SQLException {
Right right = grantee.getRightForRole(grantedRole);
if (right == null) {
throw Message.getSQLException(ErrorCode.RIGHT_NOT_FOUND);
return;
}
Database db = session.getDatabase();
db.removeDatabaseObject(session, right);
......
......@@ -1092,25 +1092,15 @@ public class ErrorCode {
*/
public static final int ROLES_AND_RIGHT_CANNOT_BE_MIXED = 90072;
/**
* The error with code <code>90073</code> is thrown when
* trying to revoke a right that does not or no longer exist.
* Example:
* <pre>
* CREATE USER TEST_USER PASSWORD 'abc';
* REVOKE SELECT ON TEST FROM TEST_USER;
* </pre>
*/
public static final int RIGHT_NOT_FOUND = 90073;
/**
* The error with code <code>90074</code> is thrown when
* trying to grant a role that has already been granted.
* Example:
* <pre>
* CREATE ROLE TEST_ROLE;
* GRANT TEST_ROLE TO SA;
* GRANT TEST_ROLE TO SA;
* CREATE ROLE TEST_A;
* CREATE ROLE TEST_B;
* GRANT TEST_A TO TEST_B;
* GRANT TEST_B TO TEST_A;
* </pre>
*/
public static final int ROLE_ALREADY_GRANTED_1 = 90074;
......@@ -1784,7 +1774,7 @@ public class ErrorCode {
*/
public static final int CAN_ONLY_ASSIGN_TO_VARIABLE_1 = 90137;
// next is 90108
// next is 90073, 90108
private ErrorCode() {
// utility class
......
......@@ -117,6 +117,13 @@ public abstract class Constraint extends SchemaObjectBase implements Comparable
* @param session the session
*/
public abstract void checkExistingData(Session session) throws SQLException;
/**
* Get the unique index used to enforce this constraint, or null if no index is used.
*
* @return the index
*/
public abstract Index getUniqueIndex();
public void checkRename() {
// ok
......
......@@ -139,4 +139,8 @@ public class ConstraintCheck extends Constraint {
}
}
public Index getUniqueIndex() {
return null;
}
}
......@@ -666,5 +666,9 @@ public class ConstraintReferential extends Constraint {
getShortDescription());
}
}
public Index getUniqueIndex() {
return refIndex;
}
}
......@@ -143,5 +143,9 @@ public class ConstraintUnique extends Constraint {
public void checkExistingData(Session session) {
// no need to check: when creating the unique index any problems are found
}
public Index getUniqueIndex() {
return index;
}
}
......@@ -672,7 +672,7 @@ public class Database implements DataHandler {
for (int i = 0; i < storages.size(); i++) {
Storage storage = (Storage) storages.get(i);
if (storage != null && storage.getRecordReader() == null) {
storage.delete(session);
storage.truncate(session);
}
}
}
......
......@@ -10,8 +10,6 @@ import java.sql.SQLException;
import java.util.HashMap;
import java.util.Iterator;
import org.h2.constant.ErrorCode;
import org.h2.message.Message;
import org.h2.table.Table;
/**
......@@ -136,11 +134,11 @@ public abstract class RightOwner extends DbObjectBase {
*/
public void revokeRole(Session session, Role role) throws SQLException {
if (grantedRoles == null) {
throw Message.getSQLException(ErrorCode.RIGHT_NOT_FOUND);
return;
}
Right right = (Right) grantedRoles.get(role);
if (right == null) {
throw Message.getSQLException(ErrorCode.RIGHT_NOT_FOUND);
return;
}
grantedRoles.remove(role);
if (grantedRoles.size() == 0) {
......
......@@ -114,7 +114,7 @@ public class BtreeIndex extends BaseIndex implements RecordReader {
}
public void remove(Session session) throws SQLException {
storage.delete(session);
storage.truncate(session);
storage = null;
}
......
......@@ -61,7 +61,7 @@ public class ScanIndex extends BaseIndex {
public void remove(Session session) throws SQLException {
truncate(session);
if (storage != null) {
storage.delete(session);
storage.truncate(session);
}
}
......
......@@ -47,7 +47,11 @@ public class TcpServer implements Service {
// TODO better exception message if the port is already in use, maybe
// automatically use the next free port?
/**
* The default port to use for the TCP server.
*/
public static final int DEFAULT_PORT = 9092;
private static final int SHUTDOWN_NORMAL = 0;
private static final int SHUTDOWN_FORCE = 1;
......@@ -329,10 +333,17 @@ public class TcpServer implements Service {
return "H2 TCP Server";
}
public boolean getIfExists() {
boolean getIfExists() {
return ifExists;
}
/**
* Stop the TCP server with the given URL.
*
* @param url the database URL
* @param password the password
* @param force if the server should be stopped immediately
*/
public static synchronized void shutdown(String url, String password, boolean force) throws SQLException {
int port = Constants.DEFAULT_SERVER_PORT;
int idx = url.indexOf(':', "jdbc:h2:".length());
......@@ -380,7 +391,7 @@ public class TcpServer implements Service {
}
}
public void cancelStatement(String sessionId, int statementId) throws SQLException {
void cancelStatement(String sessionId, int statementId) throws SQLException {
ArrayList list = new ArrayList(running);
for (int i = 0; i < list.size(); i++) {
TcpServerThread c = (TcpServerThread) list.get(i);
......
......@@ -47,7 +47,7 @@ public class TcpServerThread implements Runnable {
private int clientVersion;
private String sessionId;
public TcpServerThread(Socket socket, TcpServer server, int id) {
TcpServerThread(Socket socket, TcpServer server, int id) {
this.server = server;
this.id = id;
transfer = new Transfer(null);
......@@ -151,7 +151,7 @@ public class TcpServerThread implements Runnable {
}
}
public void close() {
void close() {
try {
stop = true;
closeSession();
......@@ -352,15 +352,15 @@ public class TcpServerThread implements Runnable {
}
}
public void setThread(Thread thread) {
void setThread(Thread thread) {
this.thread = thread;
}
public Thread getThread() {
Thread getThread() {
return thread;
}
public void cancelStatement(String sessionId, int statementId) throws SQLException {
void cancelStatement(String sessionId, int statementId) throws SQLException {
if (StringUtils.equals(sessionId, this.sessionId)) {
Command cmd = (Command) cache.getObject(statementId, false);
cmd.cancel();
......
......@@ -30,8 +30,8 @@ public class DbContextRule implements Rule {
static final int COLUMN_ALIAS = 4, SCHEMA = 5;
private static final boolean SUGGEST_TABLE_ALIAS = false;
DbContents contents;
int type;
private DbContents contents;
private int type;
DbContextRule(DbContents contents, int type) {
this.contents = contents;
......@@ -319,7 +319,7 @@ public class DbContextRule implements Rule {
return true;
}
public String matchSchema(Sentence sentence) {
private String matchSchema(Sentence sentence) {
String query = sentence.query;
String up = sentence.queryUpper;
DbSchema[] schemas = contents.schemas;
......@@ -346,7 +346,7 @@ public class DbContextRule implements Rule {
return query;
}
public String matchTable(Sentence sentence) {
private String matchTable(Sentence sentence) {
String query = sentence.query;
String up = sentence.queryUpper;
DbSchema schema = sentence.getLastMatchedSchema();
......@@ -400,7 +400,7 @@ public class DbContextRule implements Rule {
return query.substring(alias.length());
}
public String matchTableAlias(Sentence sentence, boolean add) {
private String matchTableAlias(Sentence sentence, boolean add) {
String query = sentence.query;
String up = sentence.queryUpper;
int i = 0;
......@@ -454,7 +454,7 @@ public class DbContextRule implements Rule {
return null;
}
public String matchColumn(Sentence sentence) {
private String matchColumn(Sentence sentence) {
String query = sentence.query;
String up = sentence.queryUpper;
HashSet set = sentence.getTables();
......
......@@ -59,6 +59,11 @@ public class DbStarter implements ServletContextListener {
return value == null ? defaultValue : value;
}
/**
* Get the connection.
*
* @return the connection
*/
public Connection getConnection() {
return conn;
}
......
......@@ -15,15 +15,13 @@ import java.util.Map;
* This class is used by the H2 Console.
*/
public class PageParser {
private WebServer server;
private String page;
private int pos;
private Map settings;
private int len;
private StringBuffer result;
private PageParser(WebServer server, String page, Map settings, int pos) {
this.server = server;
private PageParser(String page, Map settings, int pos) {
this.page = page;
this.pos = pos;
this.len = page.length();
......@@ -31,9 +29,16 @@ public class PageParser {
result = new StringBuffer(len);
}
public static String parse(WebServer server, String page, Map settings) {
PageParser block = new PageParser(server, page, settings, 0);
return block.parse();
/**
* Replace the tags in the HTML page with the given settings.
*
* @param page the HTML page
* @param settings the settings
* @return the converted page
*/
public static String parse(String page, Map settings) {
PageParser block = new PageParser(page, settings, 0);
return block.replaceTags();
}
private void setError(int i) {
......@@ -43,7 +48,7 @@ public class PageParser {
}
private String parseBlockUntil(String end) throws Exception {
PageParser block = new PageParser(server, page, settings, pos);
PageParser block = new PageParser(page, settings, pos);
block.parseAll();
if (!block.readIf(end)) {
throw new Exception();
......@@ -52,7 +57,7 @@ public class PageParser {
return block.result.toString();
}
private String parse() {
private String replaceTags() {
try {
parseAll();
if (pos != len) {
......@@ -151,7 +156,7 @@ public class PageParser {
String item = p.substring(i, j).trim();
i = j;
String s = (String) get(item);
append(s);
replaceTags(s);
} else {
buff.append(c);
}
......@@ -177,9 +182,9 @@ public class PageParser {
return settings.get(item);
}
private void append(String s) {
private void replaceTags(String s) {
if (s != null) {
result.append(PageParser.parse(server, s, settings));
result.append(PageParser.parse(s, settings));
}
}
......@@ -194,7 +199,7 @@ public class PageParser {
int end = pos;
read("\"");
String s = page.substring(start, end);
return PageParser.parse(server, s, settings);
return PageParser.parse(s, settings);
}
private void skipSpaces() {
......
......@@ -434,7 +434,7 @@ public class WebServer implements Service {
return FileUtils.getFileInUserHome(Constants.SERVER_PROPERTIES_FILE);
}
Properties loadProperties() {
private Properties loadProperties() {
String fileName = getPropertiesFileName();
try {
return FileUtils.loadProperties(fileName);
......@@ -481,7 +481,7 @@ public class WebServer implements Service {
return settings;
}
void sortConnectionInfo(ArrayList list) {
private void sortConnectionInfo(ArrayList list) {
for (int i = 1, j; i < list.size(); i++) {
ConnectionInfo t = (ConnectionInfo) list.get(i);
for (j = i - 1; j >= 0 && (((ConnectionInfo) list.get(j)).lastAccess < t.lastAccess); j--) {
......
......@@ -139,7 +139,7 @@ public class WebServlet extends HttpServlet {
} else {
if (session != null && file.endsWith(".jsp")) {
String page = StringUtils.utf8Decode(bytes);
page = PageParser.parse(server, page, session.map);
page = PageParser.parse(page, session.map);
try {
bytes = StringUtils.utf8Encode(page);
} catch (SQLException e) {
......
......@@ -30,9 +30,9 @@ public class WebSession {
long lastAccess;
HashMap map = new HashMap();
Locale locale;
WebServer server;
Statement executingStatement;
ResultSet result;
private WebServer server;
private ArrayList commandHistory = new ArrayList();
......
......@@ -226,7 +226,7 @@ class WebThread extends Thread implements DatabaseEventListener {
} else {
if (session != null && file.endsWith(".jsp")) {
String page = StringUtils.utf8Decode(bytes);
page = PageParser.parse(server, page, session.map);
page = PageParser.parse(page, session.map);
try {
bytes = StringUtils.utf8Encode(page);
} catch (SQLException e) {
......
......@@ -58,22 +58,26 @@ import org.h2.util.ObjectUtils;
*/
public class DiskFile implements CacheWriter {
/**
* The number of bits to shift to divide a position to get the page number.
*/
public static final int BLOCK_PAGE_PAGE_SHIFT = 6;
/**
* The size of a page in blocks.
* Each page contains blocks from the same storage.
*/
public static final int BLOCK_PAGE_PAGE_SHIFT = 6;
public static final int BLOCKS_PER_PAGE = 1 << BLOCK_PAGE_PAGE_SHIFT;
public static final int OFFSET = FileStore.HEADER_LENGTH;
/**
* The size of a block in bytes.
* A block is the minimum row size.
*/
public static final int BLOCK_SIZE = 128;
static final int FREE_PAGE = -1;
private static final int OFFSET = FileStore.HEADER_LENGTH;
private static final int FREE_PAGE = -1;
// TODO storage: header should probably be 4 KB or so
// (to match block size of operating system)
private Database database;
......@@ -98,6 +102,16 @@ public class DiskFile implements CacheWriter {
private String mode;
private int nextDeleteId = 1;
/**
* Create a new disk file.
*
* @param database the database
* @param fileName the file name
* @param mode the file opening mode ("r", "rw", "rws", "rwd")
* @param dataFile if this is the data file
* @param logChanges if changes should be logged
* @param cacheSize the number of cache entries
*/
public DiskFile(Database database, String fileName, String mode, boolean dataFile, boolean logChanges, int cacheSize) throws SQLException {
reset();
this.database = database;
......@@ -152,10 +166,6 @@ public class DiskFile implements CacheWriter {
}
}
int getBlockCount() {
return fileBlockCount;
}
private void create() throws SQLException {
file = database.openFile(fileName, mode, false);
DataPage header = DataPage.create(database, OFFSET);
......@@ -539,7 +549,7 @@ public class DiskFile implements CacheWriter {
throw Message.getSQLException(ErrorCode.SIMULATED_POWER_OFF);
}
blockCount = getPage(blockCount + BLOCKS_PER_PAGE - 1) * BLOCKS_PER_PAGE;
int lastPage = getPage(getBlockCount());
int lastPage = getPage(fileBlockCount);
int pageCount = getPage(blockCount);
int pos = -1;
boolean found = false;
......@@ -557,7 +567,7 @@ public class DiskFile implements CacheWriter {
}
}
if (!found) {
int max = getBlockCount();
int max = fileBlockCount;
pos = MathUtils.roundUp(max, BLOCKS_PER_PAGE);
if (rowBuff instanceof DataPageText) {
if (pos > max) {
......@@ -611,7 +621,7 @@ public class DiskFile implements CacheWriter {
}
}
void reuseSpace() throws SQLException {
private void reuseSpace() throws SQLException {
if (SysProperties.REUSE_SPACE_QUICKLY) {
if (potentiallyFreePages.size() >= SysProperties.REUSE_SPACE_AFTER) {
Session[] sessions = database.getSessions(true);
......@@ -791,7 +801,7 @@ public class DiskFile implements CacheWriter {
}
}
void writeDirectDeleted(int recordId, int blockCount) throws SQLException {
private void writeDirectDeleted(int recordId, int blockCount) throws SQLException {
synchronized (database) {
go(recordId);
for (int i = 0; i < blockCount; i++) {
......@@ -801,7 +811,7 @@ public class DiskFile implements CacheWriter {
}
}
void writeDirect(Storage storage, int pos, byte[] data, int offset) throws SQLException {
private void writeDirect(Storage storage, int pos, byte[] data, int offset) throws SQLException {
synchronized (database) {
go(pos);
file.write(data, offset, BLOCK_SIZE);
......
......@@ -70,7 +70,7 @@ public class FileStoreInputStream extends InputStream {
return read == 0 ? -1 : read;
}
public int readBlock(byte[] buff, int off, int len) throws IOException {
private int readBlock(byte[] buff, int off, int len) throws IOException {
fillBuffer();
if (endOfFile) {
return -1;
......
......@@ -49,6 +49,11 @@ public abstract class Record extends CacheObject {
// nothing to do
}
/**
* Check if this record is empty.
*
* @return false
*/
public boolean isEmpty() {
return false;
}
......@@ -65,6 +70,9 @@ public abstract class Record extends CacheObject {
return sessionId;
}
/**
* This record has been committed. The session id is reset.
*/
public void commit() {
this.sessionId = 0;
}
......@@ -81,11 +89,23 @@ public abstract class Record extends CacheObject {
return storageId;
}
/**
* Set the last log file and position where this record needs to be written.
*
* @param log the log file id
* @param pos the position in the log file
*/
public void setLastLog(int log, int pos) {
lastLog = log;
lastPos = pos;
}
/**
* Set the last log file and position where this record was written.
*
* @param log the log file id
* @param pos the position in the log file
*/
public void setLogWritten(int log, int pos) {
if (log < lastLog) {
return;
......@@ -107,6 +127,11 @@ public abstract class Record extends CacheObject {
return true;
}
/**
* Check if this record has been written to the log file.
*
* @return true if it is
*/
public boolean isLogWritten() {
return lastLog == LogSystem.LOG_WRITTEN;
}
......
......@@ -34,6 +34,10 @@ import org.h2.util.MathUtils;
*/
public class Storage {
/**
* This value is used to indicate that the position is not yet known, and
* space needs to be allocated.
*/
public static final int ALLOCATE_POS = -1;
private static final int FREE_LIST_SIZE = Math.max(1024, DiskFile.BLOCKS_PER_PAGE * 4);
private DiskFile file;
......@@ -46,6 +50,14 @@ public class Storage {
private DataPage dummy;
private int pageCheckIndex;
/**
* Create a new storage object for this file.
*
* @param database the database
* @param file the file
* @param reader the reader that can parse records
* @param id the storage id
*/
public Storage(Database database, DiskFile file, RecordReader reader, int id) {
this.database = database;
this.file = file;
......@@ -54,6 +66,11 @@ public class Storage {
dummy = DataPage.create(database, 0);
}
/**
* Get the record parser for this storage.
*
* @return the record parser
*/
public RecordReader getRecordReader() {
return reader;
}
......@@ -62,10 +79,24 @@ public class Storage {
recordCount++;
}
/**
* Read a record from the file or cache.
*
* @param session the session
* @param pos the position of the record
* @return the record
*/
public Record getRecord(Session session, int pos) throws SQLException {
return file.getRecord(session, pos, reader, id);
}
/**
* Read a record if it is stored at that location.
*
* @param session the session
* @param pos the position where it is stored
* @return the record or null
*/
public Record getRecordIfStored(Session session, int pos) throws SQLException {
return file.getRecordIfStored(session, pos, reader, id);
}
......@@ -119,11 +150,24 @@ public class Storage {
}
}
/**
* Update an existing record.
*
* @param session the session
* @param record the record
*/
public void updateRecord(Session session, Record record) throws SQLException {
record.setDeleted(false);
file.updateRecord(session, record);
}
/**
* Add or update a record in the file.
*
* @param session the session
* @param record the record
* @param pos the position (use ALLOCATE_POS to add a new record)
*/
public void addRecord(Session session, Record record, int pos) throws SQLException {
record.setStorageId(id);
int size = file.getRecordOverhead() + record.getByteCount(dummy);
......@@ -142,6 +186,12 @@ public class Storage {
file.addRecord(session, record);
}
/**
* Remove a record.
*
* @param session the session
* @param pos where the record is stored
*/
public void removeRecord(Session session, int pos) throws SQLException {
checkOnePage();
Record record = getRecord(session, pos);
......@@ -168,7 +218,7 @@ public class Storage {
}
}
public int allocate(int blockCount) throws SQLException {
private int allocate(int blockCount) throws SQLException {
if (freeList.size() > 0) {
synchronized (database) {
BitField used = file.getUsed();
......@@ -202,10 +252,6 @@ public class Storage {
}
}
public void delete(Session session) throws SQLException {
truncate(session);
}
// private int allocateBest(int start, int blocks) {
// while (true) {
// int p = getLastUsedPlusOne(start, blocks);
......@@ -219,24 +265,49 @@ public class Storage {
// return start;
// }
/**
* Get the unique storage id.
*
* @return the id
*/
public int getId() {
return id;
}
/**
* Get the number of records in this storage.
*
* @return the number of records
*/
public int getRecordCount() {
return recordCount;
}
/**
* Delete all records from this storage.
*
* @param session the session
*/
public void truncate(Session session) throws SQLException {
freeList = new IntArray();
recordCount = 0;
file.truncateStorage(session, this, pages);
}
/**
* Set the record parser for this storage.
*
* @param reader the record parser
*/
public void setReader(RecordReader reader) {
this.reader = reader;
}
/**
* Write this record now.
*
* @param rec the record to write
*/
public void flushRecord(Record rec) throws SQLException {
file.writeBack(rec);
}
......@@ -245,6 +316,11 @@ public class Storage {
file.flush();
}
/**
* Get the overhead to store a record (header data) in number of bytes.
*
* @return the overhead
*/
public int getRecordOverhead() {
return file.getRecordOverhead();
}
......@@ -253,6 +329,11 @@ public class Storage {
return file;
}
/**
* Update the record count.
*
* @param recordCount the new record count
*/
public void setRecordCount(int recordCount) {
this.recordCount = recordCount;
}
......
......@@ -50,6 +50,11 @@ public class WriterThread extends Thread {
this.writeDelay = writeDelay;
}
/**
* Change the write delay
*
* @param writeDelay the new write delay
*/
public void setWriteDelay(int writeDelay) {
LogSystem log = getLog();
this.writeDelay = writeDelay;
......@@ -61,6 +66,13 @@ public class WriterThread extends Thread {
}
}
/**
* Create and start a new writer thread for the given database.
*
* @param database the database
* @param writeDelay the delay
* @return the writer thread object
*/
public static WriterThread create(Database database, int writeDelay) {
WriterThread thread = new WriterThread(database, writeDelay);
thread.setName("H2 Log Writer " + database.getShortName());
......@@ -155,11 +167,21 @@ public class WriterThread extends Thread {
databaseRef = null;
}
/**
* Stop the thread. This method is called when closing the database. Old log
* files are deleted as well.
*/
public void stopThread() throws SQLException {
stop = true;
deleteLogFileLater(null);
}
/**
* Delete the following log file later on. If there is already a file to be
* deleted, that one will be deleted immediately.
*
* @param fileName the name of the file to delete
*/
public synchronized void deleteLogFileLater(String fileName) throws SQLException {
if (oldLogFile != null) {
FileUtils.delete(oldLogFile);
......
......@@ -375,6 +375,7 @@ public class MetaTable extends Table {
"TABLE_CATALOG",
"TABLE_SCHEMA",
"TABLE_NAME",
"UNIQUE_INDEX_NAME",
"CHECK_EXPRESSION",
"COLUMN_LIST",
"REMARKS",
......@@ -1276,6 +1277,11 @@ public class MetaTable extends Table {
String checkExpression = null;
IndexColumn[] columns = null;
Table table = constraint.getTable();
Index index = constraint.getUniqueIndex();
String uniqueIndexName = null;
if (index != null) {
uniqueIndexName = index.getName();
}
String tableName = identifier(table.getName());
if (!checkIndex(session, tableName, indexFrom, indexTo)) {
continue;
......@@ -1313,6 +1319,8 @@ public class MetaTable extends Table {
identifier(table.getSchema().getName()),
// TABLE_NAME
tableName,
// UNIQUE_INDEX_NAME
uniqueIndexName,
// CHECK_EXPRESSION
checkExpression,
// COLUMN_LIST
......
......@@ -146,6 +146,11 @@ public class RandomUtils {
}
}
/**
* Get a cryptographically secure pseudo random long value.
*
* @return the random long value
*/
public static long getSecureLong() {
SecureRandom sr = getSecureRandom();
synchronized (sr) {
......@@ -153,6 +158,12 @@ public class RandomUtils {
}
}
/**
* Get a number of cryptographically secure pseudo random bytes.
*
* @param len the number of bytes
* @return the random bytes
*/
public static byte[] getSecureBytes(int len) {
if (len <= 0) {
len = 1;
......@@ -165,8 +176,14 @@ public class RandomUtils {
return buff;
}
public static int nextInt(int max) {
return random.nextInt(max);
/**
* Get a cryptographically secure pseudo random int value between 0 (including and the given value (excluding).
*
* @param lower the value returned will be lower than this value
* @return the random long value
*/
public static int nextInt(int lower) {
return random.nextInt(lower);
}
static void warn(String s, Throwable t) {
......
......@@ -234,7 +234,7 @@ public class StringUtils {
}
}
public static String utf8Decode(byte[] bytes, int offset, int length) {
private static String utf8Decode(byte[] bytes, int offset, int length) {
try {
return new String(bytes, offset, length, Constants.UTF8);
} catch (UnsupportedEncodingException e) {
......@@ -466,7 +466,7 @@ public class StringUtils {
* @param s the string
* @return the indented string
*/
public static String indent(String s) {
private static String indent(String s) {
return indent(s, 4);
}
......@@ -476,7 +476,7 @@ public class StringUtils {
* @param spaces the number of spaces
* @return the indented string
*/
public static String indent(String s, int spaces) {
private static String indent(String s, int spaces) {
StringBuffer buff = new StringBuffer(s.length() + spaces);
for (int i = 0; i < s.length();) {
for (int j = 0; j < spaces; j++) {
......
......@@ -22,6 +22,11 @@ import org.h2.tools.RunScript;
*/
public class Compact {
/**
* This method is called when executing this sample application.
*
* @param args the command line parameters
*/
public static void main(String[] args) throws Exception {
DeleteDbFiles.execute("data", "test", true);
Class.forName("org.h2.Driver");
......
......@@ -22,6 +22,11 @@ import org.h2.tools.SimpleResultSet;
*/
public class CsvSample {
/**
* This method is called when executing this sample application.
*
* @param args the command line parameters
*/
public static void main(String[] args) throws SQLException {
CsvSample.write();
CsvSample.read();
......
......@@ -19,6 +19,11 @@ import java.sql.Statement;
*/
public class FileFunctions {
/**
* This method is called when executing this sample application.
*
* @param args the command line parameters
*/
public static void main(String[] args) throws Exception {
Class.forName("org.h2.Driver");
Connection conn = DriverManager.getConnection("jdbc:h2:mem:", "sa", "");
......
......@@ -22,6 +22,11 @@ import org.h2.tools.SimpleResultSet;
*/
public class Function {
/**
* This method is called when executing this sample application.
*
* @param args the command line parameters
*/
public static void main(String[] args) throws Exception {
Class.forName("org.h2.Driver");
Connection conn = DriverManager.getConnection("jdbc:h2:mem:", "sa", "");
......
......@@ -24,6 +24,11 @@ import org.h2.tools.SimpleResultSet;
*/
public class FunctionMultiReturn {
/**
* This method is called when executing this sample application.
*
* @param args the command line parameters
*/
public static void main(String[] args) throws Exception {
Class.forName("org.h2.Driver");
Connection conn = DriverManager.getConnection("jdbc:h2:mem:", "sa", "");
......
......@@ -21,6 +21,11 @@ import org.h2.tools.RunScript;
*/
public class InitDatabaseFromJar {
/**
* This method is called when executing this sample application.
*
* @param args the command line parameters
*/
public static void main(String[] args) throws Exception {
new InitDatabaseFromJar().createScript();
new InitDatabaseFromJar().initDb();
......
......@@ -19,6 +19,11 @@ import org.h2.tools.Server;
*/
public class MixedMode {
/**
* This method is called when executing this sample application.
*
* @param args the command line parameters
*/
public static void main(String[] args) throws Exception {
// start the server, allows to access the database remotely
......
......@@ -25,6 +25,11 @@ import org.h2.util.StringUtils;
*/
public class Newsfeed {
/**
* This method is called when executing this sample application.
*
* @param args the command line parameters
*/
public static void main(String[] args) throws Exception {
String targetDir = args.length == 0 ? "." : args[0];
Class.forName("org.h2.Driver");
......
......@@ -26,6 +26,11 @@ public class SQLInjection {
private Connection conn;
private Statement stat;
/**
* This method is called when executing this sample application.
*
* @param args the command line parameters
*/
public static void main(String[] args) throws Exception {
new SQLInjection().run("org.h2.Driver",
"jdbc:h2:test", "sa", "sa");
......
......@@ -19,6 +19,11 @@ import java.util.Properties;
*/
public class SecurePassword {
/**
* This method is called when executing this sample application.
*
* @param args the command line parameters
*/
public static void main(String[] argv) throws Exception {
Class.forName("org.h2.Driver");
......
......@@ -12,6 +12,11 @@ package org.h2.samples;
*/
public class ShutdownServer {
/**
* This method is called when executing this sample application.
*
* @param args the command line parameters
*/
public static void main(String[] args) throws Exception {
org.h2.tools.Server.shutdownTcpServer("tcp://localhost:9094", "", false);
}
......
......@@ -21,6 +21,11 @@ import org.h2.api.Trigger;
*/
public class TriggerSample {
/**
* This method is called when executing this sample application.
*
* @param args the command line parameters
*/
public static void main(String[] args) throws Exception {
Class.forName("org.h2.Driver");
Connection conn = DriverManager.getConnection("jdbc:h2:mem:", "sa", "");
......
......@@ -167,12 +167,38 @@ java org.h2.test.TestAll timer
/*
grant select on test to sa;
C:\download\Data Concurrency and Consistency.pdf
detect deadlock alarm
not tested:
PreparedProcedure PREPARE <name>(column,...) AS ...
Procedure
DeallocateProcedure DEALLOCATE [PLAN] <name>
ExecuteProcedure EXECUTE <name>[([p[,...])]
> DROP TABLE IF EXISTS TEST;
> CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR(255));
> INSERT INTO TEST VALUES(1, 'Hello');
> INSERT INTO TEST VALUES(2, 'World');
> CREATE USER IF NOT EXISTS SA PASSWORD '' ADMIN;
> CREATE USER IF NOT EXISTS SERGEY PASSWORD '' ;
> CREATE ROLE IF NOT EXISTS MANAGER;
> GRANT MANAGER TO SERGEY;
> GRANT SELECT ON PUBLIC.TEST TO MANAGER;
>
> SCRIPT DROP TO 'SCRIPT_ERROR.SQL';
> RUNSCRIPT FROM 'SCRIPT_ERROR.SQL';
Ignore errors if role already granted? What do other dbs do?
Maybe add a REVOKE before the GRANT?
Concurrent update in table test: another transaction has updated or deleted the same row
when exactly does it occur in other databases (PostgreSQL, Oracle)?
create an mbean for each database? server? (jconsole)
jazoon
......@@ -230,6 +256,11 @@ Add where required // TODO: change in version 1.1
http://www.w3schools.com/sql/
History:
New column INFORMATION_SCHEMA.CONSTRAINTS.UNIQUE_INDEX_NAME
that contains the name of the unique index used to enforce this
constraint, if there is such an index.
It is now possible to grant or revoke a role to a user multiple times without
getting an exception.
Roadmap:
......
--- special grammar and test cases ---------------------------------------------------------------------------------------------
CREATE ROLE TEST_A;
> ok
GRANT TEST_A TO TEST_A;
> exception
CREATE ROLE TEST_B;
> ok
GRANT TEST_A TO TEST_B;
> ok
GRANT TEST_B TO TEST_A;
> exception
DROP ROLE TEST_A;
> ok
DROP ROLE TEST_B;
> ok
CREATE ROLE PUBLIC2;
> ok
GRANT PUBLIC2 TO SA;
> ok
GRANT PUBLIC2 TO SA;
> ok
REVOKE PUBLIC2 FROM SA;
> ok
REVOKE PUBLIC2 FROM SA;
> ok
DROP ROLE PUBLIC2;
> ok
create table test(id int primary key, lastname varchar, firstname varchar, parent int references(id));
> ok
alter table test add constraint name unique (lastname, firstname);
> ok
SELECT CONSTRAINT_NAME, UNIQUE_INDEX_NAME, COLUMN_LIST FROM INFORMATION_SCHEMA.CONSTRAINTS ;
> CONSTRAINT_NAME UNIQUE_INDEX_NAME COLUMN_LIST
> --------------- ----------------- ------------------
> CONSTRAINT_2 PRIMARY_KEY_2 ID
> CONSTRAINT_27 PRIMARY_KEY_2 PARENT
> NAME NAME_INDEX_2 LASTNAME,FIRSTNAME
> rows: 3
drop table test;
> ok
alter table information_schema.help rename to information_schema.help2;
> exception
......
......@@ -99,7 +99,7 @@ public class GenerateDoc {
byte[] bytes = IOUtils.readBytesAndClose(in, 0);
if (fileName.endsWith(".html")) {
String page = new String(bytes);
page = PageParser.parse(null, page, session);
page = PageParser.parse(page, session);
bytes = page.getBytes();
}
out.write(bytes);
......
......@@ -136,7 +136,7 @@ public class PrepareTranslation {
// remove '.jsp'
name = name.substring(0, name.length() - 4);
String template = IOUtils.readStringAndClose(new FileReader(templateDir + "/" + name + ".jsp"), -1);
String html = PageParser.parse(null, template, prop);
String html = PageParser.parse(template, prop);
html = StringUtils.replaceAll(html, "lang=\"" + MAIN_LANGUAGE + "\"", "lang=\"" + language + "\"");
for (int j = 0; j < fileNames.size(); j++) {
String n = (String) fileNames.get(j);
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论