提交 815b32a9 authored 作者: Thomas Mueller's avatar Thomas Mueller

javadocs

上级 57b810ca
......@@ -15,6 +15,7 @@ import org.h2.expression.Expression;
import org.h2.table.Plan;
import org.h2.table.PlanItem;
import org.h2.table.TableFilter;
import org.h2.util.ObjectUtils;
import org.h2.util.Permutations;
/**
......@@ -151,20 +152,20 @@ public class Optimizer {
}
boolean generateRandom = (x & 127) == 0;
if (!generateRandom) {
System.arraycopy(best, 0, list, 0, filters.length);
ObjectUtils.arrayCopy(best, list, filters.length);
if (!shuffleTwo(list)) {
generateRandom = true;
}
}
if (generateRandom) {
switched = new BitSet();
System.arraycopy(filters, 0, best, 0, filters.length);
ObjectUtils.arrayCopy(filters, best, filters.length);
shuffleAll(best);
System.arraycopy(best, 0, list, 0, filters.length);
ObjectUtils.arrayCopy(best, list, filters.length);
}
if (testPlan(list)) {
switched = new BitSet();
System.arraycopy(list, 0, best, 0, filters.length);
ObjectUtils.arrayCopy(list, best, filters.length);
}
}
}
......
......@@ -35,6 +35,7 @@ import org.h2.table.IndexColumn;
import org.h2.table.Table;
import org.h2.table.TableFilter;
import org.h2.util.ObjectArray;
import org.h2.util.ObjectUtils;
import org.h2.util.StringUtils;
import org.h2.util.ValueHashMap;
import org.h2.value.Value;
......@@ -205,7 +206,7 @@ public class Select extends Query {
if (columnCount != distinctColumnCount) {
// remove columns so that 'distinct' can filter duplicate rows
Value[] r2 = new Value[distinctColumnCount];
System.arraycopy(row, 0, r2, 0, distinctColumnCount);
ObjectUtils.arrayCopy(row, r2, distinctColumnCount);
row = r2;
}
result.addRow(row);
......@@ -337,7 +338,7 @@ public class Select extends Query {
if (columnCount != distinctColumnCount) {
// remove columns so that 'distinct' can filter duplicate rows
Value[] r2 = new Value[distinctColumnCount];
System.arraycopy(row, 0, r2, 0, distinctColumnCount);
ObjectUtils.arrayCopy(row, r2, distinctColumnCount);
row = r2;
}
result.addRow(row);
......
......@@ -33,6 +33,10 @@ public class ConnectionInfo {
* The connection display name.
*/
String name;
/**
* The last time this connection was used.
*/
int lastAccess;
ConnectionInfo() {
......
......@@ -162,6 +162,14 @@ public class DbContents {
return defaultSchemaName;
}
/**
* Add double quotes around an identifier if required.
* For the H2 database, only keywords are quoted; for other databases,
* all identifiers are.
*
* @param identifier the identifier
* @return the quoted identifier
*/
String quoteIdentifier(String identifier) {
if (identifier == null) {
return null;
......
......@@ -237,6 +237,12 @@ public class DiskFile implements CacheWriter {
}
}
/**
* Check if a page is free, that is, if all blocks of the page are not in use.
*
* @param page the page id
* @return true if no blocks are used
*/
boolean isPageFree(int page) {
for (int i = page * BLOCKS_PER_PAGE; i < (page + 1) * BLOCKS_PER_PAGE; i++) {
if (used.get(i)) {
......@@ -492,6 +498,15 @@ public class DiskFile implements CacheWriter {
return ((long) block * BLOCK_SIZE) + OFFSET;
}
/**
* Get the record if it is stored in the file, or null if not.
*
* @param session the session
* @param pos the block id
* @param reader the record reader that can parse the data
* @param storageId the storage id
* @return the record or null
*/
Record getRecordIfStored(Session session, int pos, RecordReader reader, int storageId)
throws SQLException {
synchronized (database) {
......@@ -518,6 +533,15 @@ public class DiskFile implements CacheWriter {
}
}
/**
* Get a record from the cache or read it from the file if required.
*
* @param session the session
* @param pos the block id
* @param reader the record reader that can parse the data
* @param storageId the storage id
* @return the record
*/
Record getRecord(Session session, int pos, RecordReader reader, int storageId) throws SQLException {
synchronized (database) {
if (file == null) {
......@@ -561,6 +585,13 @@ public class DiskFile implements CacheWriter {
}
}
/**
* Allocate space in the file.
*
* @param storage the storage
* @param blockCount the number of blocks required
* @return the position of the first entry
*/
int allocate(Storage storage, int blockCount) throws SQLException {
reuseSpace();
synchronized (database) {
......@@ -666,6 +697,13 @@ public class DiskFile implements CacheWriter {
}
}
/**
* Called after a session deleted a row. This sets the last uncommitted
* delete id in the session. This is used to make sure empty space is not
* re-used before the change is committed.
*
* @param session the session
*/
void uncommittedDelete(Session session) {
if (session != null && logChanges && SysProperties.REUSE_SPACE_QUICKLY) {
int deleteId = session.getLastUncommittedDelete();
......@@ -676,6 +714,11 @@ public class DiskFile implements CacheWriter {
}
}
/**
* Free a page, that is, reset the page owner.
*
* @param page the page
*/
void freePage(int page) throws SQLException {
if (!logChanges) {
setPageOwner(page, FREE_PAGE);
......@@ -686,10 +729,22 @@ public class DiskFile implements CacheWriter {
}
}
/**
* Calculate the page number from a block number.
*
* @param pos the block number
* @return the page number
*/
int getPage(int pos) {
return pos >>> BLOCK_PAGE_PAGE_SHIFT;
}
/**
* Get the storage id of a page.
*
* @param page the page id
* @return the storage id
*/
int getPageOwner(int page) {
if (page * BLOCKS_PER_PAGE > fileBlockCount || page >= pageOwners.size()) {
return FREE_PAGE;
......@@ -736,6 +791,12 @@ public class DiskFile implements CacheWriter {
pageOwners.set(page, storageId);
}
/**
* Mark a number of blocks as used.
*
* @param pos the first block id
* @param blockCount the number of blocks
*/
void setUsed(int pos, int blockCount) {
synchronized (database) {
if (pos + blockCount > fileBlockCount) {
......@@ -812,6 +873,12 @@ public class DiskFile implements CacheWriter {
return used;
}
/**
* Update a record.
*
* @param session the session
* @param record the record
*/
void updateRecord(Session session, Record record) throws SQLException {
synchronized (database) {
record.setChanged(true);
......@@ -911,6 +978,15 @@ public class DiskFile implements CacheWriter {
}
}
/**
* Remove a record from the cache and mark it as deleted. This writes the
* old data to the transaction log.
*
* @param session the session
* @param pos the block id
* @param record the record
* @param blockCount the number of blocks
*/
void removeRecord(Session session, int pos, Record record, int blockCount) throws SQLException {
synchronized (database) {
if (logChanges) {
......@@ -922,6 +998,14 @@ public class DiskFile implements CacheWriter {
}
}
/**
* Add a record to the file. The position of the record must already be set
* before. This method will write the change to the transaction log and will
* update the cache.
*
* @param session the session
* @param record the record
*/
void addRecord(Session session, Record record) throws SQLException {
synchronized (database) {
cache.put(record);
......@@ -931,23 +1015,43 @@ public class DiskFile implements CacheWriter {
}
}
/*
* Must be synchronized externally
/**
* Get the cache. The cache must be synchronized externally.
*
* @return the cache
*/
public Cache getCache() {
return cache;
}
/**
* Free up a number of blocks.
*
* @param pos the position of the first block
* @param blockCount the number of blocks
*/
void free(int pos, int blockCount) {
synchronized (database) {
used.setRange(pos, blockCount, false);
}
}
/**
* Get the overhead for each record in bytes.
*
* @return the overhead
*/
int getRecordOverhead() {
return recordOverhead;
}
/**
* Remove all rows for this storage.
*
* @param session the session
* @param storage the storage
* @param pages the page id array
*/
void truncateStorage(Session session, Storage storage, IntArray pages) throws SQLException {
synchronized (database) {
int storageId = storage.getId();
......
......@@ -55,7 +55,7 @@ public class FileObjectMemory implements FileObject {
return false;
}
CompressItem c = (CompressItem) eldest.getKey();
compress(c.data, c.l);
compress(c.data, c.page);
return true;
}
}
......@@ -64,17 +64,25 @@ public class FileObjectMemory implements FileObject {
* Represents a compressed item.
*/
static class CompressItem {
/**
* The file data.
*/
byte[][] data;
int l;
/**
* The page to compress.
*/
int page;
public int hashCode() {
return data.hashCode() ^ l;
return data.hashCode() ^ page;
}
public boolean equals(Object o) {
if (o instanceof CompressItem) {
CompressItem c = (CompressItem) o;
return c.data == data && c.l == l;
return c.data == data && c.page == page;
}
return false;
}
......@@ -88,19 +96,19 @@ public class FileObjectMemory implements FileObject {
touch();
}
private static void compressLater(byte[][] data, int l) {
private static void compressLater(byte[][] data, int page) {
//## Java 1.4 begin ##
CompressItem c = new CompressItem();
c.data = data;
c.l = l;
c.page = page;
synchronized (LZF) {
COMPRESS_LATER.put(c, c);
}
//## Java 1.4 end ##
}
private static void expand(byte[][] data, int i) {
byte[] d = data[i];
private static void expand(byte[][] data, int page) {
byte[] d = data[page];
if (d.length == BLOCK_SIZE) {
return;
}
......@@ -108,17 +116,23 @@ public class FileObjectMemory implements FileObject {
synchronized (LZF) {
LZF.expand(d, 0, d.length, out, 0, BLOCK_SIZE);
}
data[i] = out;
data[page] = out;
}
static void compress(byte[][] data, int i) {
byte[] d = data[i];
/**
* Compress the data in a byte array.
*
* @param data the page array
* @param page which page to compress
*/
static void compress(byte[][] data, int page) {
byte[] d = data[page];
synchronized (LZF) {
int len = LZF.compress(d, BLOCK_SIZE, BUFFER, 0);
if (len <= BLOCK_SIZE) {
d = new byte[len];
System.arraycopy(BUFFER, 0, d, 0, len);
data[i] = d;
data[page] = d;
}
}
}
......@@ -138,25 +152,25 @@ public class FileObjectMemory implements FileObject {
return length;
}
public void setFileLength(long l) {
public void setFileLength(long newLength) {
touch();
if (l < length) {
pos = Math.min(pos, l);
changeLength(l);
long end = MathUtils.roundUpLong(l, BLOCK_SIZE);
if (end != l) {
int lastBlock = (int) (l >>> BLOCK_SIZE_SHIFT);
expand(data, lastBlock);
byte[] d = data[lastBlock];
for (int i = (int) (l & BLOCK_SIZE_MASK); i < BLOCK_SIZE; i++) {
if (newLength < length) {
pos = Math.min(pos, newLength);
changeLength(newLength);
long end = MathUtils.roundUpLong(newLength, BLOCK_SIZE);
if (end != newLength) {
int lastPage = (int) (newLength >>> BLOCK_SIZE_SHIFT);
expand(data, lastPage);
byte[] d = data[lastPage];
for (int i = (int) (newLength & BLOCK_SIZE_MASK); i < BLOCK_SIZE; i++) {
d[i] = 0;
}
if (compress) {
compressLater(data, lastBlock);
compressLater(data, lastPage);
}
}
} else {
changeLength(l);
changeLength(newLength);
}
}
......@@ -193,9 +207,9 @@ public class FileObjectMemory implements FileObject {
}
while (len > 0) {
int l = (int) Math.min(len, BLOCK_SIZE - (pos & BLOCK_SIZE_MASK));
int id = (int) (pos >>> BLOCK_SIZE_SHIFT);
expand(data, id);
byte[] block = data[id];
int page = (int) (pos >>> BLOCK_SIZE_SHIFT);
expand(data, page);
byte[] block = data[page];
int blockOffset = (int) (pos & BLOCK_SIZE_MASK);
if (write) {
System.arraycopy(b, off, block, blockOffset, l);
......@@ -203,7 +217,7 @@ public class FileObjectMemory implements FileObject {
System.arraycopy(block, blockOffset, b, off, l);
}
if (compress) {
compressLater(data, id);
compressLater(data, page);
}
off += l;
pos += l;
......
......@@ -427,6 +427,13 @@ public class FileSystemDatabase extends FileSystem {
return true;
}
/**
* Update a file in the file system.
*
* @param fileName the file name
* @param b the data
* @param len the number of bytes
*/
synchronized void write(String fileName, byte[] b, int len) {
try {
long id = getId(fileName, false);
......
......@@ -82,7 +82,7 @@ public class FileSystemDisk extends FileSystem {
throw Message.getSQLException(ErrorCode.FILE_RENAME_FAILED_2, new String[]{oldName, newName});
}
void trace(String method, String fileName, Object o) {
private void trace(String method, String fileName, Object o) {
if (SysProperties.TRACE_IO) {
System.out.println("FileSystem." + method + " " + fileName + " " + o);
}
......
......@@ -140,6 +140,13 @@ public class Column {
return isComputed;
}
/**
* Compute the value of this computed column.
*
* @param session the session
* @param row the row
* @return the value
*/
Value computeValue(Session session, Row row) throws SQLException {
synchronized (this) {
computeTableFilter.setSession(session);
......@@ -159,6 +166,12 @@ public class Column {
this.defaultExpression = expression;
}
/**
* Set the table and column id.
*
* @param table the table
* @param columnId the column index
*/
void setTable(Table table, int columnId) {
this.table = table;
this.columnId = columnId;
......@@ -571,8 +584,15 @@ public class Column {
return DataType.getDataType(type);
}
String getCheckConstraintSQL(Session session, String name) throws SQLException {
Expression constraint = getCheckConstraint(session, name);
/**
* Get the check constraint SQL snippet.
*
* @param session the session
* @param asColumnName the column name to use
* @return the SQL snippet
*/
String getCheckConstraintSQL(Session session, String asColumnName) throws SQLException {
Expression constraint = getCheckConstraint(session, asColumnName);
return constraint == null ? "" : constraint.getSQL();
}
......@@ -588,6 +608,14 @@ public class Column {
this.primaryKey = primaryKey;
}
/**
* Visit the default expression, the check constraint, and the sequence (if
* any).
*
* @param visitor the visitor
* @return true if every visited expression returned true, or if there are
* no expressions
*/
boolean isEverything(ExpressionVisitor visitor) {
if (visitor.getType() == ExpressionVisitor.GET_DEPENDENCIES) {
if (sequence != null) {
......
......@@ -13,6 +13,7 @@ import org.h2.engine.Session;
import org.h2.expression.Expression;
import org.h2.expression.ExpressionVisitor;
import org.h2.util.ObjectArray;
import org.h2.util.ObjectUtils;
/**
* A possible query execution plan. The time required to execute a query depends
......@@ -33,7 +34,7 @@ public class Plan {
*/
public Plan(TableFilter[] filters, int count, Expression condition) {
this.filters = new TableFilter[count];
System.arraycopy(filters, 0, this.filters, 0, count);
ObjectUtils.arrayCopy(filters, this.filters, count);
ObjectArray allCond = new ObjectArray();
ObjectArray all = new ObjectArray();
if (condition != null) {
......
......@@ -13,7 +13,12 @@ import org.h2.index.Index;
* using it.
*/
public class PlanItem {
/**
* The cost.
*/
double cost;
private Index index;
private PlanItem joinPlan;
......
......@@ -521,6 +521,9 @@ public class TableFilter implements ColumnResolver {
return buff.toString();
}
/**
* Remove all index conditions that are not used by the current index.
*/
void removeUnusableIndexConditions() {
for (int i = 0; i < indexConditions.size(); i++) {
IndexCondition cond = (IndexCondition) indexConditions.get(i);
......@@ -557,6 +560,11 @@ public class TableFilter implements ColumnResolver {
return used;
}
/**
* Set the session of this table filter.
*
* @param session
*/
void setSession(Session session) {
this.session = session;
}
......
......@@ -53,7 +53,7 @@ public class TableView extends Table {
initColumnsAndTables(session);
}
Query recompileQuery(Session session) throws SQLException {
private Query recompileQuery(Session session) throws SQLException {
Prepared p = session.prepare(querySQL);
if (!(p instanceof Query)) {
throw Message.getSyntaxError(querySQL, 0);
......@@ -310,7 +310,7 @@ public class TableView extends Table {
}
}
void setOwner(User owner) {
private void setOwner(User owner) {
this.owner = owner;
}
......
......@@ -152,6 +152,9 @@ ShutdownHandler {
stopAll();
}
/**
* Stop all servers that were started using the console.
*/
void stopAll() {
if (web != null && web.isRunning(false)) {
web.stop();
......
......@@ -298,12 +298,21 @@ public class RunScript extends Tool {
* @param fileName the script file
* @param charsetName the character set name or null for UTF-8
* @param continueOnError if execution should be continued if an error occurs
* @throws SQLException
*/
public static void execute(String url, String user, String password, String fileName, String charsetName, boolean continueOnError) throws SQLException {
new RunScript().process(url, user, password, fileName, charsetName, continueOnError);
}
/**
* Executes the SQL commands in a script file against a database.
*
* @param url the database URL
* @param user the user name
* @param password the password
* @param fileName the script file
* @param charsetName the character set name or null for UTF-8
* @param continueOnError if execution should be continued if an error occurs
*/
void process(String url, String user, String password, String fileName, String charsetName, boolean continueOnError) throws SQLException {
try {
org.h2.Driver.load();
......
......@@ -126,18 +126,25 @@ public class Script extends Tool {
}
/**
* Backs up a database to a file.
* Backs up a database to a SQL script file.
*
* @param url the database URL
* @param user the user name
* @param password the password
* @param fileName the script file
* @throws SQLException
*/
public static void execute(String url, String user, String password, String fileName) throws SQLException {
new Script().process(url, user, password, fileName);
}
/**
* Backs up a database to a SQL script file.
*
* @param url the database URL
* @param user the user name
* @param password the password
* @param fileName the script file
*/
void process(String url, String user, String password, String fileName) throws SQLException {
Connection conn = null;
Statement stat = null;
......
......@@ -64,9 +64,25 @@ public class SimpleResultSet implements ResultSet, ResultSetMetaData {
* This class holds the data of a result column.
*/
static class Column {
/**
* The column name.
*/
String name;
/**
* The SQL type.
*/
int sqlType;
/**
* The precision.
*/
int precision;
/**
* The scale.
*/
int scale;
}
......
......@@ -236,6 +236,6 @@ public class ByteUtils {
byte[] copy = new byte[len];
System.arraycopy(b, 0, copy, 0, len);
return copy;
}
}
}
......@@ -107,7 +107,6 @@ public class FileUtils {
* Try to delete a file.
*
* @param fileName the file name
* @return true if it could be deleted
*/
public static void tryDelete(String fileName) {
FileSystem.getInstance(fileName).tryDelete(fileName);
......
......@@ -184,9 +184,7 @@ public class ObjectArray {
* @param array the target array
*/
public void toArray(Object[] array) {
for (int i = 0; i < size; i++) {
array[i] = data[i];
}
ObjectUtils.arrayCopy(data, array, size);
}
/**
......
......@@ -21,6 +21,13 @@ import org.h2.message.Message;
*/
public class ObjectUtils {
/**
* The maximum number of elements to copy using a Java loop. This value was
* found by running tests using the Sun JDK 1.4 and JDK 1.6 on Windows XP.
* The biggest difference is for size smaller than 40 (more than 50% saving).
*/
private static final int MAX_JAVA_LOOP_COPY = 100;
private ObjectUtils() {
// utility class
}
......@@ -168,5 +175,24 @@ public class ObjectUtils {
throw Message.getSQLException(ErrorCode.DESERIALIZATION_FAILED_1, new String[] { e.toString() }, e);
}
}
/**
* Copy the elements of the source array to the target array.
* System.arraycopy is used for larger arrays, but for very small arrays it
* is faster to use a regular loop.
*
* @param source the source array
* @param target the target array
* @param size the number of elements to copy
*/
public static void arrayCopy(Object[] source, Object[] target, int size) {
if (size > MAX_JAVA_LOOP_COPY) {
System.arraycopy(source, 0, target, 0, size);
} else {
for (int i = 0; i < size; i++) {
target[i] = source[i];
}
}
}
}
......@@ -30,7 +30,15 @@ public class TempFileDeleter {
* Contains information about a file.
*/
static class TempFile {
/**
* The file name.
*/
String fileName;
/**
* The last modified date of this file.
*/
long lastModified;
}
......
......@@ -18,6 +18,10 @@ import org.h2.util.DateTimeUtils;
* Implementation of the DATE data type.
*/
public class ValueDate extends Value {
/**
* The precision in digits.
*/
static final int PRECISION = 8;
/**
......
......@@ -17,6 +17,10 @@ import org.h2.util.ObjectUtils;
* Implementation of the REAL data type.
*/
public class ValueFloat extends Value {
/**
* The precision in digits.
*/
static final int PRECISION = 7;
/**
......
......@@ -19,6 +19,9 @@ import org.h2.util.ObjectUtils;
*/
public class ValueShort extends Value {
/**
* The precision in digits.
*/
static final int PRECISION = 5;
/**
......
......@@ -16,6 +16,10 @@ import org.h2.util.DateTimeUtils;
* Implementation of the TIME data type.
*/
public class ValueTime extends Value {
/**
* The precision in digits.
*/
static final int PRECISION = 6;
/**
......
......@@ -21,6 +21,10 @@ import org.h2.util.MathUtils;
* Implementation of the TIMESTAMP data type.
*/
public class ValueTimestamp extends Value {
/**
* The precision in digits.
*/
static final int PRECISION = 23;
/**
......
......@@ -266,14 +266,12 @@ java org.h2.test.TestAll timer
/*
jazoon
run the performance tests as part of the unit test
measure and improve performance of ObjectArray.toArray()
jazoon
H2 Console should support Java Queries
convert test.in.sql to RunScript syntax
C:\download\Data Concurrency and Consistency.pdf
not tested:
......@@ -290,8 +288,6 @@ create an mbean for each database? server? (jconsole)
in help.csv, use complete examples for functions; add a test case
improve javadocs
option to write complete page right after checkpoint
test case for out of memory (try to corrupt the database using out of memory)
......
......@@ -365,6 +365,12 @@ public abstract class TestBase {
printlnWithTime(time, getClass().getName() + " " + s);
}
/**
* Print a message, prepended with the specified time in milliseconds.
*
* @param time the milliseconds
* @param s the message
*/
static void printlnWithTime(long time, String s) {
String t = "0000000000" + time;
t = t.substring(t.length() - 6);
......@@ -743,9 +749,10 @@ public abstract class TestBase {
* @param data the expected data
* @throws Exception if there is a mismatch
*/
void assertResultSetUnordered(ResultSet rs, String[][] data) throws Exception {
assertResultSet(false, rs, data);
}
// void assertResultSetUnordered(ResultSet rs, String[][] data)
// throws Exception {
// assertResultSet(false, rs, data);
// }
/**
* Check if a result set contains the expected data.
......@@ -755,7 +762,7 @@ public abstract class TestBase {
* @param data the expected data
* @throws Exception if there is a mismatch
*/
void assertResultSet(boolean ordered, ResultSet rs, String[][] data) throws Exception {
private void assertResultSet(boolean ordered, ResultSet rs, String[][] data) throws Exception {
int len = rs.getMetaData().getColumnCount();
int rows = data.length;
if (rows == 0) {
......@@ -815,7 +822,7 @@ public abstract class TestBase {
return true;
}
String[] getData(ResultSet rs, int len) throws SQLException {
private String[] getData(ResultSet rs, int len) throws SQLException {
String[] data = new String[len];
for (int i = 0; i < len; i++) {
data[i] = rs.getString(i + 1);
......@@ -825,7 +832,7 @@ public abstract class TestBase {
return data;
}
String formatRow(String[] row) {
private String formatRow(String[] row) {
String sb = "";
for (int i = 0; i < row.length; i++) {
sb += "{" + row[i] + "}";
......
......@@ -143,11 +143,11 @@ public class BenchC implements Bench {
trace("load done");
}
void trace(String s) {
private void trace(String s) {
action = s;
}
void trace(int i, int max) {
private void trace(int i, int max) {
db.trace(action, i, max);
}
......
......@@ -17,16 +17,39 @@ public class BenchCRandom {
private Random random = new Random(10);
/**
* Get a non-uniform random integer value between min and max.
*
* @param a the bit mask
* @param min the minimum value
* @param max the maximum value
* @return the random value
*/
int getNonUniform(int a, int min, int max) {
int c = 0;
return (((getInt(0, a) | getInt(min, max)) + c) % (max - min + 1))
+ min;
}
/**
* Get a random integer value between min and max.
*
* @param min the minimum value
* @param max the maximum value
* @return the random value
*/
int getInt(int min, int max) {
return max <= min ? min : (random.nextInt(max - min) + min);
}
/**
* Generate a boolean array with this many items set to true (randomly
* distributed).
*
* @param length the size of the array
* @param trueCount the number of true elements
* @return the boolean array
*/
boolean[] getBoolean(int length, int trueCount) {
boolean[] data = new boolean[length];
for (int i = 0, pos; i < trueCount; i++) {
......@@ -38,6 +61,13 @@ public class BenchCRandom {
return data;
}
/**
* Replace a random part of the string with another text.
*
* @param text the original text
* @param replacement the replacement
* @return the patched string
*/
String replace(String text, String replacement) {
int pos = getInt(0, text.length() - replacement.length());
StringBuffer buffer = new StringBuffer(text);
......@@ -45,6 +75,13 @@ public class BenchCRandom {
return buffer.toString();
}
/**
* Get a random number string.
*
* @param min the minimum value
* @param max the maximum value
* @return the number string
*/
String getNumberString(int min, int max) {
int len = getInt(min, max);
char[] buff = new char[len];
......@@ -54,6 +91,11 @@ public class BenchCRandom {
return new String(buff);
}
/**
* Get random address data.
*
* @return the address
*/
String[] getAddress() {
String str1 = getString(10, 20);
String str2 = getString(10, 20);
......@@ -63,10 +105,23 @@ public class BenchCRandom {
return new String[] { str1, str2, city, state, zip };
}
/**
* Get a random string.
*
* @param min the minimum size
* @param max the maximum size
* @return the string
*/
String getString(int min, int max) {
return getString(getInt(min, max));
}
/**
* Get a random string.
*
* @param len the size
* @return the string
*/
String getString(int len) {
char[] buff = new char[len];
for (int i = 0; i < len; i++) {
......@@ -75,6 +130,12 @@ public class BenchCRandom {
return new String(buff);
}
/**
* Generate a random permutation if the values 0 .. length.
*
* @param length the number of elements
* @return the random permutation
*/
int[] getPermutation(int length) {
int[] data = new int[length];
for (int i = 0; i < length; i++) {
......@@ -89,11 +150,23 @@ public class BenchCRandom {
return data;
}
/**
* Create a big decimal value.
*
* @param value the value
* @param scale the scale
* @return the big decimal object
*/
BigDecimal getBigDecimal(int value, int scale) {
return new BigDecimal(new BigInteger(String.valueOf(value)), scale);
}
/**
* Generate a last name composed of three elements
*
* @param i the last name index
* @return the name
*/
String getLastname(int i) {
String[] n = { "BAR", "OUGHT", "ABLE", "PRI", "PRES", "ESE", "ANTI",
"CALLY", "ATION", "EING" };
......
......@@ -38,7 +38,10 @@ public class BenchCThread {
this.random = random;
warehouseId = random.getInt(1, bench.warehouses);
}
/**
* Process the list of operations (a 'deck') in random order.
*/
void process() throws Exception {
int[] deck = new int[] { OP_NEW_ORDER, OP_NEW_ORDER, OP_NEW_ORDER,
OP_NEW_ORDER, OP_NEW_ORDER, OP_NEW_ORDER, OP_NEW_ORDER,
......
......@@ -13,6 +13,7 @@ import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Iterator;
......@@ -49,22 +50,45 @@ class Database {
private Object serverDerby;
private boolean serverHSQLDB;
/**
* Get the database name.
*
* @return the database name
*/
String getName() {
return name;
}
/**
* Get the total measured time.
*
* @return the time
*/
int getTotalTime() {
return totalTime;
}
/**
* Get the result array.
*
* @return the result array
*/
ArrayList getResults() {
return results;
}
/**
* Get the random number generator.
*
* @return the generator
*/
Random getRandom() {
return random;
}
/**
* Start the server if the this is a remote connection.
*/
void startServer() throws Exception {
if (url.startsWith("jdbc:h2:tcp:")) {
serverH2 = Server.createTcpServer(new String[0]).start();
......@@ -90,6 +114,9 @@ class Database {
}
}
/**
* Stop the server if this is a remote connection.
*/
void stopServer() throws Exception {
if (serverH2 != null) {
serverH2.stop();
......@@ -110,6 +137,14 @@ class Database {
}
}
/**
* Parse a database configuration and create a database object from it.
*
* @param test the test application
* @param id the database id
* @param dbString the configuration string
* @return a new database object with the given settings
*/
static Database parse(TestPerformance test, int id, String dbString) {
try {
StringTokenizer tokenizer = new StringTokenizer(dbString, ",");
......@@ -133,8 +168,20 @@ class Database {
}
}
Connection getConnection() throws Exception {
Connection conn = DriverManager.getConnection(url, user, password);
/**
* Get the database connection. The connection must be opened first.
*
* @return the connection
*/
Connection getConnection() {
return conn;
}
/**
* Open a database connection.
*/
void openConnection() throws SQLException {
conn = DriverManager.getConnection(url, user, password);
if (url.startsWith("jdbc:derby:")) {
// Derby: use higher cache size
Statement stat = null;
......@@ -159,15 +206,13 @@ class Database {
JdbcUtils.closeSilently(stat);
}
}
return conn;
}
void openConnection() throws Exception {
conn = DriverManager.getConnection(url, user, password);
stat = conn.createStatement();
}
void closeConnection() throws Exception {
/**
* Close the database connection.
*/
void closeConnection() throws SQLException {
// if(!serverHSQLDB && url.startsWith("jdbc:hsqldb:")) {
// stat.execute("SHUTDOWN");
// }
......@@ -176,7 +221,12 @@ class Database {
conn = null;
}
public void setTranslations(Properties prop) {
/**
* Initialize the SQL statement translation of this database.
*
* @param prop the properties with the translations to use
*/
void setTranslations(Properties prop) {
String id = url.substring("jdbc:".length());
id = id.substring(0, id.indexOf(':'));
for (Iterator it = prop.keySet().iterator(); it.hasNext();) {
......@@ -191,12 +241,18 @@ class Database {
}
}
PreparedStatement prepare(String sql) throws Exception {
/**
* Prepare a SQL statement.
*
* @param sql the SQL statement
* @return the prepared statement
*/
PreparedStatement prepare(String sql) throws SQLException {
sql = getSQL(sql);
return conn.prepareStatement(sql);
}
public String getSQL(String sql) {
private String getSQL(String sql) {
for (int i = 0; i < replace.size(); i++) {
String[] pair = (String[]) replace.get(i);
String pattern = pair[0];
......@@ -206,11 +262,21 @@ class Database {
return sql;
}
/**
* Start the benchmark.
*
* @param bench the benchmark
* @param action the action
*/
void start(Bench bench, String action) {
this.action = bench.getName() + ": " + action;
this.startTime = System.currentTimeMillis();
}
/**
* This method is called when the test run ends. This will stop collecting
* data.
*/
void end() {
long time = System.currentTimeMillis() - startTime;
log(action, "ms", (int) time);
......@@ -219,6 +285,11 @@ class Database {
}
}
/**
* Drop a table. Errors are ignored.
*
* @param table the table name
*/
void dropTable(String table) {
try {
update("DROP TABLE " + table);
......@@ -227,13 +298,24 @@ class Database {
}
}
public void update(PreparedStatement prep, String trace) throws Exception {
/**
* Execute an SQL statement.
*
* @param prep the prepared statement
* @param trace the trace message
*/
void update(PreparedStatement prep, String trace) throws SQLException {
test.trace(trace);
prep.executeUpdate();
executedStatements++;
}
public void update(String sql) throws Exception {
/**
* Execute an SQL statement.
*
* @param sql the SQL statement
*/
void update(String sql) throws SQLException {
sql = getSQL(sql);
if (sql.trim().length() > 0) {
executedStatements++;
......@@ -243,18 +325,36 @@ class Database {
}
}
public void setAutoCommit(boolean b) throws Exception {
/**
* Enable or disable auto-commit.
*
* @param b false to disable
*/
void setAutoCommit(boolean b) throws SQLException {
conn.setAutoCommit(b);
}
public void commit() throws Exception {
/**
* Commit a transaction.
*/
void commit() throws SQLException {
conn.commit();
}
public void rollback() throws Exception {
/**
* Roll a transaction back.
*/
void rollback() throws SQLException {
conn.rollback();
}
/**
* Print trace information if trace is enabled.
*
* @param action the action
* @param i the current value
* @param max the maximum value
*/
void trace(String action, int i, int max) {
if (trace) {
long time = System.currentTimeMillis();
......@@ -267,17 +367,37 @@ class Database {
}
}
/**
* If data collection is enabled, add the currently used memory size to the
* log.
*
* @param bench the benchmark
* @param action the action
*/
void logMemory(Bench bench, String action) {
log(bench.getName() + ": " + action, "MB", TestBase.getMemoryUsed());
}
/**
* If data collection is enabled, add this information to the log.
*
* @param action the action
* @param scale the scale
* @param value the value
*/
void log(String action, String scale, int value) {
if (test.collect) {
results.add(new Object[] { action, scale, new Integer(value) });
}
}
public ResultSet query(PreparedStatement prep) throws Exception {
/**
* Execute a query.
*
* @param prep the prepared statement
* @return the result set
*/
ResultSet query(PreparedStatement prep) throws SQLException {
// long time = System.currentTimeMillis();
ResultSet rs = prep.executeQuery();
// time = System.currentTimeMillis() - time;
......@@ -288,7 +408,12 @@ class Database {
return rs;
}
public void queryReadResult(PreparedStatement prep) throws Exception {
/**
* Execute a query and read all rows.
*
* @param prep the prepared statement
*/
void queryReadResult(PreparedStatement prep) throws SQLException {
ResultSet rs = prep.executeQuery();
ResultSetMetaData meta = rs.getMetaData();
int columnCount = meta.getColumnCount();
......@@ -299,16 +424,22 @@ class Database {
}
}
/**
* Get the number of executed statements.
*
* @return the number of statements
*/
int getExecutedStatements() {
return executedStatements;
}
public int getId() {
/**
* Get the database id.
*
* @return the id
*/
int getId() {
return id;
}
public Connection getCurrentConnection() {
return conn;
}
}
......@@ -27,7 +27,14 @@ import org.h2.util.JdbcUtils;
*/
public class TestPerformance {
/**
* Whether data should be collected.
*/
boolean collect;
/**
* The flag used to enable or disable trace messages.
*/
boolean trace;
/**
......@@ -238,6 +245,11 @@ public class TestPerformance {
bench.runTest();
}
/**
* Print a message to system out if trace is enabled.
*
* @param s the message
*/
void trace(String s) {
if (trace) {
System.out.println(s);
......
......@@ -15,7 +15,11 @@ import java.io.Reader;
*/
public class Tokenizer {
/**
* This token type means no more tokens are available.
*/
static final int TYPE_EOF = -1;
private static final int TYPE_WORD = -2;
private static final int TYPE_NOTHING = -3;
private static final byte WHITESPACE = 1;
......@@ -78,6 +82,9 @@ public class Tokenizer {
return i;
}
/**
* Initialize the tokenizer.
*/
void initToken() {
buffer = new StringBuffer();
}
......@@ -91,6 +98,11 @@ public class Tokenizer {
buffer.append((char) i);
}
/**
* Read the next token and get the token type.
*
* @return the token type
*/
int nextToken() throws IOException {
byte[] ct = charTypes;
int c;
......
......@@ -179,8 +179,14 @@ public class Db {
}
}
static Error convert(Exception e) {
return new Error("Error: " + e.toString(), e);
/**
* Convert a checked exception to a runtime exception.
*
* @param e the checked exception
* @return the runtime exception
*/
static RuntimeException convert(Exception e) {
return new RuntimeException(e.toString(), e);
}
public void setAutoCommit(boolean autoCommit) {
......
......@@ -66,7 +66,12 @@ public class TestDeadlock extends TestBase {
* that execute a statement.
*/
abstract class DoIt extends Thread {
/**
* The operation to execute.
*/
abstract void execute() throws SQLException;
public void run() {
try {
execute();
......
......@@ -21,6 +21,12 @@ public class WebClient {
private String sessionId;
/**
* Open an URL and get the HTML data.
*
* @param url the HTTP URL
* @return the HTML as a string
*/
String get(String url) throws IOException {
HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
connection.setRequestMethod("GET");
......@@ -36,10 +42,15 @@ public class WebClient {
return result;
}
void readSessionId(String result) {
int idx = result.indexOf("jsessionid=");
String id = result.substring(idx + "jsessionid=".length());
for (int i = 0; i < result.length(); i++) {
/**
* Read the session ID from a URL.
*
* @param url the URL
*/
void readSessionId(String url) {
int idx = url.indexOf("jsessionid=");
String id = url.substring(idx + "jsessionid=".length());
for (int i = 0; i < url.length(); i++) {
char ch = id.charAt(i);
if (!Character.isLetterOrDigit(ch)) {
id = id.substring(0, i);
......@@ -49,6 +60,13 @@ public class WebClient {
this.sessionId = id;
}
/**
* Read the specified HTML page.
*
* @param url the base URL
* @param page the page to read
* @return the HTML page
*/
String get(String url, String page) throws IOException {
if (sessionId != null) {
if (page.indexOf('?') < 0) {
......
......@@ -23,6 +23,12 @@ class OutputCatcher extends Thread {
this.in = in;
}
/**
* Read a line from the output.
*
* @param wait the maximum number of milliseconds to wait
* @return the line
*/
String readLine(long wait) {
long start = System.currentTimeMillis();
while (true) {
......
......@@ -13,7 +13,6 @@ import java.io.InputStream;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Random;
......@@ -97,14 +96,30 @@ public abstract class TestHalt extends TestBase {
private int errorId;
private int sequenceId;
/**
* Initialize the test.
*/
abstract void testInit() throws Exception;
/**
* Check if the database is consistent after a simulated database crash.
*/
abstract void testCheckAfterCrash() throws Exception;
/**
* Wait for some time after the application has been started.
*/
abstract void testWaitAfterAppStart() throws Exception;
/**
* Start the application.
*/
abstract void appStart() throws Exception;
/**
* Run the application.
* @throws Exception
*/
abstract void appRun() throws Exception;
public void test() throws Exception {
......@@ -272,53 +287,55 @@ public abstract class TestHalt extends TestBase {
}
}
public Connection getConnectionHSQLDB() throws Exception {
File lock = new File("test.lck");
while (lock.exists()) {
lock.delete();
System.gc();
}
Class.forName("org.hsqldb.jdbcDriver");
return DriverManager.getConnection("jdbc:hsqldb:test", "sa", "");
}
// public Connection getConnectionHSQLDB() throws Exception {
// File lock = new File("test.lck");
// while (lock.exists()) {
// lock.delete();
// System.gc();
// }
// Class.forName("org.hsqldb.jdbcDriver");
// return DriverManager.getConnection("jdbc:hsqldb:test", "sa", "");
// }
public Connection getConnectionDerby() throws Exception {
File lock = new File("test3/db.lck");
while (lock.exists()) {
lock.delete();
System.gc();
}
Class.forName("org.apache.derby.jdbc.EmbeddedDriver").newInstance();
try {
return DriverManager.getConnection("jdbc:derby:test3;create=true", "sa", "sa");
} catch (SQLException e) {
Exception e2 = e;
do {
e.printStackTrace();
e = e.getNextException();
} while (e != null);
throw e2;
}
}
// public Connection getConnectionDerby() throws Exception {
// File lock = new File("test3/db.lck");
// while (lock.exists()) {
// lock.delete();
// System.gc();
// }
// Class.forName("org.apache.derby.jdbc.EmbeddedDriver").newInstance();
// try {
// return DriverManager.getConnection(
// "jdbc:derby:test3;create=true", "sa", "sa");
// } catch (SQLException e) {
// Exception e2 = e;
// do {
// e.printStackTrace();
// e = e.getNextException();
// } while (e != null);
// throw e2;
// }
// }
void disconnectHSQLDB() {
try {
conn.createStatement().execute("SHUTDOWN");
} catch (Exception e) {
// ignore
}
// super.disconnect();
}
// void disconnectHSQLDB() {
// try {
// conn.createStatement().execute("SHUTDOWN");
// } catch (Exception e) {
// // ignore
// }
// // super.disconnect();
// }
void disconnectDerby() {
// super.disconnect();
try {
Class.forName("org.apache.derby.jdbc.EmbeddedDriver");
DriverManager.getConnection("jdbc:derby:;shutdown=true", "sa", "sa");
} catch (Exception e) {
// ignore
}
}
// void disconnectDerby() {
// // super.disconnect();
// try {
// Class.forName("org.apache.derby.jdbc.EmbeddedDriver");
// DriverManager.getConnection(
// "jdbc:derby:;shutdown=true", "sa", "sa");
// } catch (Exception e) {
// // ignore
// }
// }
/**
* Create a random string with the specified length.
......
......@@ -30,7 +30,7 @@ class Column {
private boolean isPrimaryKey;
// TODO test isAutoincrement;
Column(TestSynth config) {
private Column(TestSynth config) {
this.config = config;
}
......@@ -68,7 +68,14 @@ class Column {
}
}
public static boolean isConditionType(TestSynth config, int type) {
/**
* Check if this data type supports comparisons for this database.
*
* @param config the configuration
* @param type the SQL type
* @return true if the value can be used in conditions
*/
static boolean isConditionType(TestSynth config, int type) {
switch (config.getMode()) {
case TestSynth.H2:
case TestSynth.H2_MEM:
......@@ -103,7 +110,7 @@ class Column {
}
}
String getTypeName() {
private String getTypeName() {
switch (type) {
case Types.INTEGER:
return "INT";
......@@ -152,7 +159,7 @@ class Column {
}
}
public String getCreateSQL() {
String getCreateSQL() {
String sql = name + " " + getTypeName();
if (!isNullable) {
sql += " NOT NULL";
......@@ -160,19 +167,25 @@ class Column {
return sql;
}
public String getName() {
String getName() {
return name;
}
public Value getRandomValue() {
Value getRandomValue() {
return Value.getRandom(config, type, precision, scale, isNullable);
}
public Value getRandomValueNotNull() {
return Value.getRandom(config, type, precision, scale, false);
}
public static Column getRandomColumn(TestSynth config) {
// Value getRandomValueNotNull() {
// return Value.getRandom(config, type, precision, scale, false);
// }
/**
* Generate a random column.
*
* @param config the configuration
* @return the column
*/
static Column getRandomColumn(TestSynth config) {
Column column = new Column(config);
column.name = "C_" + config.randomIdentifier();
int randomType;
......@@ -191,19 +204,24 @@ class Column {
return column;
}
public boolean isPrimaryKey() {
boolean getPrimaryKey() {
return isPrimaryKey;
}
public void setPrimaryKey(boolean b) {
void setPrimaryKey(boolean b) {
isPrimaryKey = b;
}
public void setNullable(boolean b) {
void setNullable(boolean b) {
isNullable = b;
}
public int getType() {
/**
* The the column type.
*
* @return the type
*/
int getType() {
return type;
}
......
......@@ -13,8 +13,8 @@ import java.util.ArrayList;
*/
public class DbState implements DbInterface {
boolean connected;
boolean autoCommit;
private boolean connected;
private boolean autoCommit;
private TestSynth config;
private ArrayList tables = new ArrayList();
private ArrayList indexes = new ArrayList();
......@@ -80,6 +80,11 @@ public class DbState implements DbInterface {
// nothing to do
}
/**
* Get a random table.
*
* @return the table
*/
Table randomTable() {
if (tables.size() == 0) {
return null;
......@@ -91,5 +96,9 @@ public class DbState implements DbInterface {
public void end() {
// nothing to do
}
public String toString() {
return "autocommit: " + autoCommit + " connected: " + connected;
}
}
......@@ -13,19 +13,24 @@ import java.util.ArrayList;
* Represents an expression.
*/
public class Expression {
boolean isCondition;
private String sql;
private TestSynth config;
private Command command;
private Expression(TestSynth config, Command command, boolean isCondition) {
private Expression(TestSynth config, Command command) {
this.config = config;
this.isCondition = isCondition;
this.command = command;
sql = "";
}
/**
* Create a random select list.
*
* @param config the configuration
* @param command the command
* @return the select list
*/
static String[] getRandomSelectList(TestSynth config, Command command) {
if (config.random().getBoolean(30)) {
return new String[] { "*" };
......@@ -47,16 +52,23 @@ public class Expression {
return list;
}
/**
* Generate a random condition.
*
* @param config the configuration
* @param command the command
* @return the random condition expression
*/
static Expression getRandomCondition(TestSynth config, Command command) {
Expression condition = new Expression(config, command, true);
Expression condition = new Expression(config, command);
if (config.random().getBoolean(50)) {
condition.create();
}
return condition;
}
static Expression getRandomExpression(TestSynth config, Command command) {
Expression expression = new Expression(config, command, false);
private static Expression getRandomExpression(TestSynth config, Command command) {
Expression expression = new Expression(config, command);
String alias = command.getRandomTableAlias();
Column column = command.getTable(alias).getRandomConditionColumn();
if (column == null) {
......@@ -72,12 +84,27 @@ public class Expression {
sql = v.getSQL();
}
/**
* Generate a random join condition.
*
* @param config the configuration
* @param command the command
* @param alias the alias name
* @return the join condition
*/
static Expression getRandomJoinOn(TestSynth config, Command command, String alias) {
Expression expression = new Expression(config, command, true);
Expression expression = new Expression(config, command);
expression.createJoinComparison(alias);
return expression;
}
/**
* Generate a random sort order list.
*
* @param config the configuration
* @param command the command
* @return the ORDER BY list
*/
static String getRandomOrder(TestSynth config, Command command) {
int len = config.random().getLog(6);
String sql = "";
......@@ -104,6 +131,11 @@ public class Expression {
return sql;
}
/**
* Get the SQL snippet of this expression.
*
* @return the SQL snippet
*/
String getSQL() {
return sql.trim().length() == 0 ? null : sql.trim();
}
......@@ -250,11 +282,7 @@ public class Expression {
}
}
boolean isEmpty() {
return sql == null || sql.trim().length() == 0;
}
void createExpression(String alias, Column type) {
private void createExpression(String alias, Column type) {
boolean op = is(20);
// no null values if there is an operation
boolean allowNull = !op;
......@@ -288,7 +316,7 @@ public class Expression {
}
}
void createTerm(String alias, Column type, boolean allowNull) {
private void createTerm(String alias, Column type, boolean allowNull) {
int dt = type.getType();
if (is(5) && (dt == Types.INTEGER) || (dt == Types.DECIMAL)) {
sql += " - ";
......
......@@ -10,10 +10,10 @@ package org.h2.test.synth.sql;
* Represents an index.
*/
public class Index {
Table table;
String name;
Column[] columns;
boolean unique;
private Table table;
private String name;
private Column[] columns;
private boolean unique;
Index(Table table, String name, Column[] columns, boolean unique) {
this.table = table;
......@@ -22,11 +22,11 @@ public class Index {
this.unique = unique;
}
public String getName() {
String getName() {
return name;
}
public String getCreateSQL() {
String getCreateSQL() {
String sql = "CREATE ";
if (unique) {
sql += "UNIQUE ";
......@@ -42,11 +42,11 @@ public class Index {
return sql;
}
public String getDropSQL() {
String getDropSQL() {
return "DROP INDEX " + name;
}
public Table getTable() {
Table getTable() {
return table;
}
......
......@@ -128,27 +128,27 @@ class Result implements Comparable {
}
}
public void log() {
switch (type) {
case SUCCESS:
System.out.println("> ok");
break;
case EXCEPTION:
System.out.println("> exception");
break;
case INT:
if (intValue == 0) {
System.out.println("> ok");
} else {
System.out.println("> update count: " + intValue);
}
break;
case RESULT_SET:
System.out.println("> rs " + rows.size());
break;
default:
}
System.out.println();
}
// public void log() {
// switch (type) {
// case SUCCESS:
// System.out.println("> ok");
// break;
// case EXCEPTION:
// System.out.println("> exception");
// break;
// case INT:
// if (intValue == 0) {
// System.out.println("> ok");
// } else {
// System.out.println("> update count: " + intValue);
// }
// break;
// case RESULT_SET:
// System.out.println("> rs " + rows.size());
// break;
// default:
// }
// System.out.println();
// }
}
......@@ -24,7 +24,13 @@ class Table {
this.config = config;
}
public static Table newRandomTable(TestSynth config) {
/**
* Create a new random table.
*
* @param config the configuration
* @return the table
*/
static Table newRandomTable(TestSynth config) {
Table table = new Table(config);
table.name = "T_" + config.randomIdentifier();
......@@ -52,7 +58,7 @@ class Table {
Column pk = null;
do {
pk = table.columns[config.random().getInt(len)];
} while (pk.isPrimaryKey());
} while (pk.getPrimaryKey());
table.primaryKeys[i] = pk;
pk.setPrimaryKey(true);
pk.setNullable(false);
......@@ -61,7 +67,12 @@ class Table {
return table;
}
public Index newRandomIndex() {
/**
* Create a new random index.
*
* @return the index
*/
Index newRandomIndex() {
String indexName = "I_" + config.randomIdentifier();
int len = config.random().getLog(getColumnCount() - 1) + 1;
boolean unique = config.random().getBoolean(50);
......@@ -70,11 +81,21 @@ class Table {
return index;
}
public String getDropSQL() {
/**
* Get the DROP TABLE statement for this table.
*
* @return the SQL statement
*/
String getDropSQL() {
return "DROP TABLE " + name;
}
public String getCreateSQL() {
/**
* Get the CREATE TABLE statement for this table.
*
* @return the SQL statement
*/
String getCreateSQL() {
String sql = "CREATE ";
if (temporary) {
if (globalTemporary) {
......@@ -111,7 +132,12 @@ class Table {
return sql;
}
public String getInsertSQL(Column[] c, Value[] v) {
/**
* Get the INSERT statement for this table.
*
* @return the SQL statement
*/
String getInsertSQL(Column[] c, Value[] v) {
String sql = "INSERT INTO " + name;
if (c != null) {
sql += "(";
......@@ -134,11 +160,21 @@ class Table {
return sql;
}
public String getName() {
/**
* Get the table name.
*
* @return the name
*/
String getName() {
return name;
}
public Column getRandomConditionColumn() {
/**
* Get a random column that can be used in a condition.
*
* @return the column
*/
Column getRandomConditionColumn() {
ArrayList list = new ArrayList();
for (int i = 0; i < columns.length; i++) {
if (Column.isConditionType(config, columns[i].getType())) {
......@@ -151,15 +187,21 @@ class Table {
return (Column) list.get(config.random().getInt(list.size()));
}
public Column getRandomColumn() {
Column getRandomColumn() {
return columns[config.random().getInt(columns.length)];
}
public int getColumnCount() {
int getColumnCount() {
return columns.length;
}
public Column getRandomColumnOfType(int type) {
/**
* Get a random column of the specified type.
*
* @param type the type
* @return the column or null if no such column was found
*/
Column getRandomColumnOfType(int type) {
ArrayList list = new ArrayList();
for (int i = 0; i < columns.length; i++) {
if (columns[i].getType() == type) {
......@@ -172,7 +214,13 @@ class Table {
return (Column) list.get(config.random().getInt(list.size()));
}
public Column[] getRandomColumns(int len) {
/**
* Get a number of random column from this table.
*
* @param len the column count
* @return the columns
*/
Column[] getRandomColumns(int len) {
int[] index = new int[columns.length];
for (int i = 0; i < columns.length; i++) {
index[i] = i;
......@@ -190,15 +238,25 @@ class Table {
return c;
}
public Column[] getColumns() {
Column[] getColumns() {
return columns;
}
public void addIndex(Index index) {
/**
* Add this index to the table.
*
* @param index the index to add
*/
void addIndex(Index index) {
indexes.add(index);
}
public void removeIndex(Index index) {
/**
* Remove an index from the table.
*
* @param index the index to remove
*/
void removeIndex(Index index) {
indexes.remove(index);
}
}
......@@ -21,7 +21,30 @@ public class TestSynth extends TestBase {
// TODO hsqldb: call 1||null should return 1 but returns null
// TODO hsqldb: call mod(1) should return invalid parameter count
static final int H2 = 0, H2_MEM = 1, HSQLDB = 2, MYSQL = 3, POSTGRESQL = 4;
/**
* A H2 database connection.
*/
static final int H2 = 0;
/**
* An in-memory H2 database connection.
*/
static final int H2_MEM = 1;
/**
* An HSQLDB database connection.
*/
static final int HSQLDB = 2;
/**
* A MySQL database connection.
*/
static final int MYSQL = 3;
/**
* A PostgreSQL database connection.
*/
static final int POSTGRESQL = 4;
private static final String DIR = "synth";
private DbState db = new DbState(this);
......@@ -32,14 +55,30 @@ public class TestSynth extends TestBase {
private boolean stopImmediately;
private int mode;
/**
* Check whether this database is of the specified type.
*
* @param isType the database type
* @return true if it is
*/
boolean is(int isType) {
return mode == isType;
}
/**
* Get the random number generator.
*
* @return the random number generator
*/
RandomGen random() {
return random;
}
/**
* Get a random identifier.
*
* @return the random identifier
*/
String randomIdentifier() {
int len = random.getLog(8) + 2;
while (true) {
......@@ -193,17 +232,28 @@ public class TestSynth extends TestBase {
}
}
/**
* Get a random table.
*
* @return the table
*/
Table randomTable() {
return db.randomTable();
}
/**
* Print this message if the log is enabled.
*
* @param id the id
* @param s the message
*/
void log(int id, String s) {
if (showLog && id == 0) {
System.out.println(s);
}
}
public int getMode() {
int getMode() {
return mode;
}
......
......@@ -31,6 +31,11 @@ public class Value {
this.data = data;
}
/**
* Convert the value to a SQL string.
*
* @return the SQL string
*/
String getSQL() {
if (data == null) {
return "NULL";
......@@ -127,6 +132,14 @@ public class Value {
return buff.toString();
}
/**
* Read a value from a result set.
*
* @param config the configuration
* @param rs the result set
* @param index the column index
* @return the value
*/
static Value read(TestSynth config, ResultSet rs, int index) throws SQLException {
ResultSetMetaData meta = rs.getMetaData();
Object data;
......@@ -184,6 +197,16 @@ public class Value {
return new Value(config, type, data);
}
/**
* Generate a random value.
*
* @param config the configuration
* @param type the value type
* @param precision the precision
* @param scale the scale
* @param mayBeNull if the value may be null or not
* @return the value
*/
static Value getRandom(TestSynth config, int type, int precision, int scale, boolean mayBeNull) {
Object data;
if (mayBeNull && config.random().getBoolean(20)) {
......@@ -263,48 +286,49 @@ public class Value {
return new BigDecimal(buff.toString());
}
int compareTo(Object o) {
Value v = (Value) o;
if (type != v.type) {
throw new Error("compare " + type + " " + v.type + " " + data + " " + v.data);
}
if (data == null) {
return (v.data == null) ? 0 : -1;
} else if (v.data == null) {
return 1;
}
switch (type) {
case Types.DECIMAL:
return ((BigDecimal) data).compareTo((BigDecimal) v.data);
case Types.BLOB:
case Types.VARBINARY:
case Types.BINARY:
return compareBytes((byte[]) data, (byte[]) v.data);
case Types.CLOB:
case Types.VARCHAR:
return data.toString().compareTo(v.data.toString());
case Types.DATE:
return ((Date) data).compareTo((Date) v.data);
case Types.INTEGER:
return ((Integer) data).compareTo((Integer) v.data);
default:
throw new Error("type=" + type);
}
}
// private int compareTo(Object o) {
// Value v = (Value) o;
// if (type != v.type) {
// throw new Error("compare " + type +
// " " + v.type + " " + data + " " + v.data);
// }
// if (data == null) {
// return (v.data == null) ? 0 : -1;
// } else if (v.data == null) {
// return 1;
// }
// switch (type) {
// case Types.DECIMAL:
// return ((BigDecimal) data).compareTo((BigDecimal) v.data);
// case Types.BLOB:
// case Types.VARBINARY:
// case Types.BINARY:
// return compareBytes((byte[]) data, (byte[]) v.data);
// case Types.CLOB:
// case Types.VARCHAR:
// return data.toString().compareTo(v.data.toString());
// case Types.DATE:
// return ((Date) data).compareTo((Date) v.data);
// case Types.INTEGER:
// return ((Integer) data).compareTo((Integer) v.data);
// default:
// throw new Error("type=" + type);
// }
// }
static int compareBytes(byte[] a, byte[] b) {
int al = a.length, bl = b.length;
int len = Math.min(al, bl);
for (int i = 0; i < len; i++) {
int x = a[i] & 0xff;
int y = b[i] & 0xff;
if (x == y) {
continue;
}
return x > y ? 1 : -1;
}
return al == bl ? 0 : al > bl ? 1 : -1;
}
// private static int compareBytes(byte[] a, byte[] b) {
// int al = a.length, bl = b.length;
// int len = Math.min(al, bl);
// for (int i = 0; i < len; i++) {
// int x = a[i] & 0xff;
// int y = b[i] & 0xff;
// if (x == y) {
// continue;
// }
// return x > y ? 1 : -1;
// }
// return al == bl ? 0 : al > bl ? 1 : -1;
// }
public String toString() {
return getSQL();
......
......@@ -16,25 +16,51 @@ import org.h2.test.TestBase;
*/
abstract class TestMultiThread extends Thread {
/**
* The base object.
*/
TestMulti base;
/**
* The random number generator.
*/
Random random = new Random();
TestMultiThread(TestMulti base) {
this.base = base;
}
/**
* Execute statements that need to be executed before starting the thread.
* This includes CREATE TABLE statements.
*/
abstract void first() throws SQLException;
/**
* The main operation to perform. This method is called in a loop.
*/
abstract void operation() throws SQLException;
/**
* Execute statements before entering the loop, but after starting the
* thread.
*/
abstract void begin() throws SQLException;
/**
* This method is called once after the test is stopped.
*/
abstract void end() throws SQLException;
/**
* This method is called once after all threads have been stopped.
* @throws Exception
*/
abstract void finalTest() throws Exception;
public void run() {
try {
begin();
while (!base.stop) {
operation();
}
......
......@@ -49,6 +49,9 @@ class Arg {
return quote(clazz, getValue());
}
/**
* Calculate the value if this is a statement.
*/
void execute() throws Exception {
if (stat != null) {
obj = stat.execute();
......@@ -65,7 +68,7 @@ class Arg {
return obj;
}
String quote(Class clazz, Object value) {
private String quote(Class clazz, Object value) {
if (value == null) {
return null;
} else if (clazz == String.class) {
......
......@@ -45,6 +45,13 @@ class Parser {
read();
}
/**
* Parse a Java statement.
*
* @param player the player
* @param line the statement text
* @return the statement
*/
static Statement parseStatement(Player player, String line) {
Parser p = new Parser(player, line);
p.parseStatement();
......
......@@ -111,6 +111,11 @@ public class Player {
}
}
/**
* Write trace information if trace is enabled.
*
* @param s the message to write
*/
void trace(String s) {
if (trace) {
System.out.println(s);
......@@ -131,7 +136,14 @@ public class Player {
trace("error: " + e.toString());
}
}
/**
* Get the class for the given class name.
* Only a limited set of classes is supported.
*
* @param className the class name
* @return the class
*/
static Class getClass(String className) {
for (int i = 0; i < IMPORTED_PACKAGES.length; i++) {
try {
......@@ -143,10 +155,22 @@ public class Player {
throw new Error("Class not found: " + className);
}
void assign(String objectName, Object obj) {
objects.put(objectName, obj);
/**
* Assign an object to a variable.
*
* @param variableName the variable name
* @param obj the object
*/
void assign(String variableName, Object obj) {
objects.put(variableName, obj);
}
/**
* Get an object.
*
* @param name the variable name
* @return the object
*/
Object getObject(String name) {
return objects.get(name);
}
......
......@@ -45,6 +45,11 @@ class Statement {
this.player = player;
}
/**
* Execute the statement.
*
* @return the object returned if this was a method call
*/
Object execute() throws Exception {
if (object == player) {
// there was an exception previously
......@@ -120,19 +125,38 @@ class Statement {
return returnClass;
}
/**
* This statement is an assignment.
*
* @param className the class of the variable
* @param variableName the variable name
*/
void setAssign(String className, String variableName) {
this.assignment = true;
this.assignClass = className;
this.assignVariable = variableName;
}
/**
* This statement is a static method call.
*
* @param className the class name
*/
void setStaticCall(String className) {
this.staticCall = true;
this.staticCallClass = className;
}
void setMethodCall(String objectName, Object object, String methodName) {
this.objectName = objectName;
/**
* This statement is a method call, and the result is assigned to a
* variable.
*
* @param variableName the variable name
* @param object the object
* @param methodName the method name
*/
void setMethodCall(String variableName, Object object, String methodName) {
this.objectName = variableName;
this.object = object;
this.methodName = methodName;
}
......
......@@ -39,6 +39,12 @@ public class FtpClient {
// don't allow construction
}
/**
* Open an FTP connection.
*
* @param url the FTP URL
* @return the ftp client object
*/
static FtpClient open(String url) throws IOException {
FtpClient client = new FtpClient();
client.connect(url);
......@@ -77,6 +83,12 @@ public class FtpClient {
writer.flush();
}
/**
* Login to this FTP server (USER, PASS, SYST, SITE, STRU F, TYPE I).
*
* @param userName the user name
* @param password the password
*/
void login(String userName, String password) throws IOException {
send("USER " + userName);
readCode(331);
......@@ -92,6 +104,9 @@ public class FtpClient {
readCode(200);
}
/**
* Close the connection (QUIT).
*/
void close() throws IOException {
if (socket != null) {
send("QUIT");
......@@ -100,42 +115,75 @@ public class FtpClient {
}
}
/**
* Change the working directory (CWD).
*
* @param dir the new directory
*/
void changeWorkingDirectory(String dir) throws IOException {
send("CWD " + dir);
readCode(250);
}
/**
* Change to the parent directory (CDUP).
*/
void changeDirectoryUp() throws IOException {
send("CDUP");
readCode(250);
}
/**
* Delete a file (DELE).
*
* @param fileName the name of the file to delete
*/
void delete(String fileName) throws IOException {
send("DELE " + fileName);
readCode(250);
}
/**
* Create a directory (MKD).
*
* @param dir the directory to create
*/
void makeDirectory(String dir) throws IOException {
send("MKD " + dir);
readCode(257);
}
/**
* Change the transfer mode (MODE).
*
* @param mode the mode
*/
void mode(String mode) throws IOException {
send("MODE " + mode);
readCode(200);
}
/**
* Change the modified time of a file (MDTM).
*
* @param fileName the file name
*/
void modificationTime(String fileName) throws IOException {
send("MDTM " + fileName);
readCode(213);
}
/**
* Issue a no-operation statement (NOOP).
*/
void noOperation() throws IOException {
send("NOOP");
readCode(200);
}
/**
* Print the working directory (PWD).
*/
String printWorkingDirectory() throws IOException {
send("PWD");
readCode(257);
......@@ -177,6 +225,12 @@ public class FtpClient {
outData = socketData.getOutputStream();
}
/**
* Rename a file (RNFR / RNTO).
*
* @param fromFileName the old file name
* @param toFileName the new file name
*/
void rename(String fromFileName, String toFileName) throws IOException {
send("RNFR " + fromFileName);
readCode(350);
......@@ -184,6 +238,13 @@ public class FtpClient {
readCode(250);
}
/**
* Read a file ([REST] RETR).
*
* @param fileName the file name
* @param out the output stream
* @param restartAt restart at the given position (0 if no restart is required).
*/
void retrieve(String fileName, OutputStream out, long restartAt) throws IOException {
passive();
if (restartAt > 0) {
......@@ -195,11 +256,22 @@ public class FtpClient {
readCode(226);
}
/**
* Remove a directory (RMD).
*
* @param dir the directory to remove
*/
void removeDirectory(String dir) throws IOException {
send("RMD " + dir);
readCode(250);
}
/**
* Get the size of a file (SIZE).
*
* @param fileName the file name
* @return the size
*/
long size(String fileName) throws IOException {
send("SIZE " + fileName);
readCode(250);
......@@ -207,6 +279,12 @@ public class FtpClient {
return size;
}
/**
* Store a file (STOR).
*
* @param fileName the file name
* @param in the input stream
*/
void store(String fileName, InputStream in) throws IOException {
passive();
send("STOR " + fileName);
......@@ -215,6 +293,12 @@ public class FtpClient {
readCode(226);
}
/**
* Get the directory listing (NLST).
*
* @param dir the directory
* @return the listing
*/
String nameList(String dir) throws IOException {
passive();
send("NLST " + dir);
......@@ -226,6 +310,12 @@ public class FtpClient {
return new String(data);
}
/**
* Get the directory listing (LIST).
*
* @param dir the directory
* @return the listing
*/
String list(String dir) throws IOException {
passive();
send("LIST " + dir);
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论