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

--no commit message

--no commit message
上级 61f89401
......@@ -40,7 +40,10 @@ Hypersonic SQL or HSQLDB. H2 is built from scratch.
<h3>Version 1.0 (Current)</h3>
<h3>Version 1.0.60 (2007-10-?)</h3><ul>
<li>Stack traces did not include the SQL statement in all cases where they could have.
<li>User defined aggregate functions are not supported.
</li><li>Server.shutdownTcpServer was blocked when first called with force=false and then force=true.
Now documentation is improved, and it is no longer blocked.
</li><li>Stack traces did not include the SQL statement in all cases where they could have.
Also, stack traces with SQL statement are now shorter.
</li><li>Linked tables: now tables in non-default schemas are supported as well
</li><li>New Italian translation from PierPaolo Ucchino. Thanks a lot!
......@@ -507,7 +510,6 @@ Hypersonic SQL or HSQLDB. H2 is built from scratch.
</li><li>Better support large transactions, large updates / deletes: use less memory
</li><li>Better support large transactions, large updates / deletes: allow tables without primary key
</li><li>Support Oracle RPAD and LPAD(string, n[, pad]) (truncate the end if longer)
</li><li>User defined aggregate functions
</li><li>Procedural language / script language (Javascript)
</li></ul>
......
......@@ -59,6 +59,7 @@ Package org.h2.tools<br />
<b>Interfaces</b><br />
Package org.h2.api<br />
<a href="org/h2/api/AggregateFunction.html" target="javadoc">AggregateFunction</a><br />
<a href="org/h2/api/DatabaseEventListener.html" target="javadoc">DatabaseEventListener</a><br />
<a href="org/h2/api/Trigger.html" target="javadoc">Trigger</a><br />
<br />
......
/*
* Copyright 2004-2007 H2 Group. Licensed under the H2 License, Version 1.0 (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.api;
import java.sql.Connection;
import java.sql.SQLException;
/**
* A user defined aggregate function needs to implement this interface.
* The class must be public and must have a public non-argument constructor.
*/
public interface AggregateFunction {
/**
* This method is called when the aggregate function is used.
* A new object is created for each invocation.
*
* @param conn a connection to the database
*/
void init(Connection conn) throws SQLException;
/**
* This method must return the SQL type of the method,
* given the SQL type of the input data.
* The method should check here if the number of parameters passed is correct,
* and if not it should throw an exception.
*
* @param inputType the SQL type of the parameters
* @return the SQL type of the result
*/
int getType(int[] inputType) throws SQLException;
/**
* This method is called once for each row.
* If the aggregate function is called with multiple parameters, those are passed as array.
*
* @param value the value(s) for this row
*/
void add(Object value) throws SQLException;
/**
* This method returns the computed aggregate value.
*/
Object getResult() throws SQLException;
}
......@@ -19,6 +19,7 @@ import org.h2.command.ddl.AlterTableRenameColumn;
import org.h2.command.ddl.AlterUser;
import org.h2.command.ddl.AlterView;
import org.h2.command.ddl.Analyze;
import org.h2.command.ddl.CreateAggregate;
import org.h2.command.ddl.CreateConstant;
import org.h2.command.ddl.CreateFunctionAlias;
import org.h2.command.ddl.CreateIndex;
......@@ -32,6 +33,7 @@ import org.h2.command.ddl.CreateUser;
import org.h2.command.ddl.CreateUserDataType;
import org.h2.command.ddl.CreateView;
import org.h2.command.ddl.DeallocateProcedure;
import org.h2.command.ddl.DropAggregate;
import org.h2.command.ddl.DropConstant;
import org.h2.command.ddl.DropDatabase;
import org.h2.command.ddl.DropFunctionAlias;
......@@ -80,6 +82,7 @@ import org.h2.engine.Right;
import org.h2.engine.Session;
import org.h2.engine.Setting;
import org.h2.engine.User;
import org.h2.engine.UserAggregate;
import org.h2.engine.UserDataType;
import org.h2.expression.Aggregate;
import org.h2.expression.Alias;
......@@ -95,6 +98,7 @@ import org.h2.expression.ExpressionColumn;
import org.h2.expression.ExpressionList;
import org.h2.expression.Function;
import org.h2.expression.FunctionCall;
import org.h2.expression.JavaAggregate;
import org.h2.expression.JavaFunction;
import org.h2.expression.Operation;
import org.h2.expression.Parameter;
......@@ -1032,6 +1036,8 @@ public class Parser {
return parseDropUserDataType();
} else if (readIf("DATATYPE")) {
return parseDropUserDataType();
} else if (readIf("AGGREGATE")) {
return parseDropAggregate();
}
throw getSyntaxError();
}
......@@ -1045,6 +1051,15 @@ public class Parser {
return command;
}
DropAggregate parseDropAggregate() throws SQLException {
boolean ifExists = readIfExists(false);
DropAggregate command = new DropAggregate(session);
command.setName(readUniqueIdentifier());
ifExists = readIfExists(ifExists);
command.setIfExists(ifExists);
return command;
}
private TableFilter readJoin(TableFilter top, Select command, boolean fromOuter) throws SQLException {
TableFilter last = top;
while (true) {
......@@ -1719,6 +1734,19 @@ public class Parser {
return func;
}
private JavaAggregate readJavaAggregate(UserAggregate aggregate) throws SQLException {
ObjectArray params = new ObjectArray();
do {
params.add(readExpression());
} while(readIf(","));
read(")");
Expression[] list = new Expression[params.size()];
params.toArray(list);
JavaAggregate agg = new JavaAggregate(aggregate, list, currentSelect);
currentSelect.setGroupQuery();
return agg;
}
private Expression readFunction(String name) throws SQLException {
int agg = Aggregate.getAggregateType(name);
if (agg >= 0) {
......@@ -1726,6 +1754,10 @@ public class Parser {
}
Function function = Function.getFunction(database, name);
if (function == null) {
UserAggregate aggregate = database.findAggregate(name);
if (aggregate != null) {
return readJavaAggregate(aggregate);
}
return readJavaFunction(name);
}
switch (function.getFunctionType()) {
......@@ -3113,6 +3145,8 @@ public class Parser {
return parseCreateUserDataType();
} else if (readIf("DATATYPE")) {
return parseCreateUserDataType();
} else if (readIf("AGGREGATE")) {
return parseCreateAggregate(force);
} else {
boolean hash = false, primaryKey = false, unique = false;
String indexName = null;
......@@ -3286,6 +3320,21 @@ public class Parser {
return command;
}
private CreateAggregate parseCreateAggregate(boolean force) throws SQLException {
boolean ifNotExists = readIfNoExists();
CreateAggregate command = new CreateAggregate(session);
command.setForce(force);
String name = readUniqueIdentifier();
if (isKeyword(name) || Function.getFunction(database, name) != null || Aggregate.getAggregateType(name) >= 0) {
throw Message.getSQLException(ErrorCode.FUNCTION_ALIAS_ALREADY_EXISTS_1, name);
}
command.setName(name);
command.setIfNotExists(ifNotExists);
read("FOR");
command.setJavaClassMethod(readUniqueIdentifier());
return command;
}
private CreateUserDataType parseCreateUserDataType() throws SQLException {
boolean ifNotExists = readIfNoExists();
CreateUserDataType command = new CreateUserDataType(session);
......@@ -3527,8 +3576,7 @@ public class Parser {
if (readIf("SET")) {
AlterUser command = new AlterUser(session);
command.setType(AlterUser.SET_PASSWORD);
User user = database.getUser(userName);
command.setUser(user);
command.setUser(database.getUser(userName));
if (readIf("PASSWORD")) {
command.setPassword(readString());
} else if (readIf("SALT")) {
......
/*
* Copyright 2004-2007 H2 Group. Licensed under the H2 License, Version 1.0 (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.command.ddl;
import java.sql.SQLException;
import org.h2.constant.ErrorCode;
import org.h2.engine.UserAggregate;
import org.h2.engine.Database;
import org.h2.engine.Session;
import org.h2.message.Message;
public class CreateAggregate extends DefineCommand {
private String name;
private String javaClassMethod;
private boolean ifNotExists;
private boolean force;
public CreateAggregate(Session session) {
super(session);
}
public int update() throws SQLException {
session.commit(true);
session.getUser().checkAdmin();
Database db = session.getDatabase();
if (db.findAggregate(name) != null || db.findFunctionAlias(name) != null) {
if (!ifNotExists) {
throw Message.getSQLException(ErrorCode.FUNCTION_ALIAS_ALREADY_EXISTS_1, name);
}
} else {
int id = getObjectId(false, true);
UserAggregate aggregate = new UserAggregate(db, id, name, javaClassMethod, force);
db.addDatabaseObject(session, aggregate);
}
return 0;
}
public void setName(String name) {
this.name = name;
}
public void setJavaClassMethod(String string) {
this.javaClassMethod = string;
}
public void setIfNotExists(boolean ifNotExists) {
this.ifNotExists = ifNotExists;
}
public void setForce(boolean force) {
this.force = force;
}
}
/*
* Copyright 2004-2007 H2 Group. Licensed under the H2 License, Version 1.0 (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.command.ddl;
import java.sql.SQLException;
import org.h2.constant.ErrorCode;
import org.h2.engine.UserAggregate;
import org.h2.engine.Database;
import org.h2.engine.Session;
import org.h2.message.Message;
public class DropAggregate extends DefineCommand {
private String name;
private boolean ifExists;
public DropAggregate(Session session) {
super(session);
}
public int update() throws SQLException {
session.getUser().checkAdmin();
session.commit(true);
Database db = session.getDatabase();
UserAggregate aggregate = db.findAggregate(name);
if (aggregate == null) {
if (!ifExists) {
throw Message.getSQLException(ErrorCode.AGGREGATE_NOT_FOUND_1, name);
}
} else {
db.removeDatabaseObject(session, aggregate);
}
return 0;
}
public void setName(String name) {
this.name = name;
}
public void setIfExists(boolean ifExists) {
this.ifExists = ifExists;
}
}
......@@ -27,6 +27,7 @@ import org.h2.engine.Role;
import org.h2.engine.Session;
import org.h2.engine.Setting;
import org.h2.engine.User;
import org.h2.engine.UserAggregate;
import org.h2.engine.UserDataType;
import org.h2.expression.ExpressionColumn;
import org.h2.index.Cursor;
......@@ -164,6 +165,14 @@ public class ScriptCommand extends ScriptBase {
}
add(alias.getCreateSQL(), false);
}
ObjectArray aggregates = db.getAllAggregates();
for (int i = 0; i < aggregates.size(); i++) {
UserAggregate agg = (UserAggregate) aggregates.get(i);
if (drop) {
add(agg.getDropSQL(), false);
}
add(agg.getCreateSQL(), false);
}
ObjectArray tables = db.getAllSchemaObjects(DbObject.TABLE_OR_VIEW);
// sort by id, so that views are after tables and views on views
// after the base views
......
......@@ -312,6 +312,7 @@ public class ErrorCode {
public static final int TRANSACTION_NOT_FOUND_1 = 90129;
public static final int METHOD_NOT_ALLOWED_FOR_PREPARED_STATEMENT = 90130;
public static final int CONCURRENT_UPDATE_1 = 90131;
public static final int AGGREGATE_NOT_FOUND_1 = 90132;
/**
* INTERNAL
......
......@@ -81,6 +81,7 @@ public class Database implements DataHandler {
private final HashMap rights = new HashMap();
private final HashMap functionAliases = new HashMap();
private final HashMap userDataTypes = new HashMap();
private final HashMap aggregates = new HashMap();
private final HashMap comments = new HashMap();
private final HashSet sessions = new HashSet();
private final BitField objectIds = new BitField();
......@@ -676,6 +677,8 @@ public class Database implements DataHandler {
return userDataTypes;
case DbObject.COMMENT:
return comments;
case DbObject.AGGREGATE:
return aggregates;
default:
throw Message.getInternalError("type=" + type);
}
......@@ -728,20 +731,26 @@ public class Database implements DataHandler {
return (FunctionAlias) functionAliases.get(name);
}
public UserAggregate findAggregate(String name) {
return (UserAggregate) aggregates.get(name);
}
public UserDataType findUserDataType(String name) {
return (UserDataType) userDataTypes.get(name);
}
public User getUser(String name) throws SQLException {
public User getUser(String name, SQLException notFound) throws SQLException {
User user = (User) users.get(name);
if (user == null) {
// TODO security: from the stack trace the attacker now knows the
// user name is ok
throw Message.getSQLException(ErrorCode.WRONG_USER_OR_PASSWORD, name);
throw notFound;
}
return user;
}
public User getUser(String name) throws SQLException {
return getUser(name, Message.getSQLException(ErrorCode.USER_NOT_FOUND_1, name));
}
public synchronized Session createSession(User user) {
Session session = new Session(this, user, ++nextSessionId);
sessions.add(session);
......@@ -971,6 +980,10 @@ public class Database implements DataHandler {
return new ObjectArray(functionAliases.values());
}
public ObjectArray getAllAggregates() {
return new ObjectArray(aggregates.values());
}
public ObjectArray getAllUserDataTypes() {
return new ObjectArray(userDataTypes.values());
}
......@@ -1261,8 +1274,12 @@ public class Database implements DataHandler {
}
}
public Class loadClass(String className) throws ClassNotFoundException {
public Class loadClass(String className) throws SQLException {
try {
return ClassUtils.loadClass(className);
} catch (ClassNotFoundException e) {
throw Message.getSQLException(ErrorCode.CLASS_NOT_FOUND_1, new String[] { className }, e);
}
}
public void setEventListener(String className) throws SQLException {
......
......@@ -23,6 +23,7 @@ public interface DbObject {
int SCHEMA = 10;
int COMMENT = 13;
int USER_DATATYPE = 12;
int AGGREGATE = 14;
void setModified();
......
......@@ -68,8 +68,11 @@ public class Engine {
if (user == null) {
try {
database.checkFilePasswordHash(cipher, ci.getFilePasswordHash());
user = database.getUser(ci.getUserName());
user.checkUserPasswordHash(ci.getUserPasswordHash());
//create the exception here so it is not possible from the stack trace
// to see if the user name was wrong or the password
SQLException wrongUserOrPassword = Message.getSQLException(ErrorCode.WRONG_USER_OR_PASSWORD);
user = database.getUser(ci.getUserName(), wrongUserOrPassword);
user.checkUserPasswordHash(ci.getUserPasswordHash(), wrongUserOrPassword);
if (opened && !user.getAdmin()) {
// reset - because the user is not an admin, and has no
// right to listen to exceptions
......
......@@ -52,13 +52,7 @@ public class FunctionAlias extends DbObjectBase {
if (javaMethod != null) {
return;
}
Class javaClass;
try {
javaClass = database.loadClass(className);
} catch (ClassNotFoundException e) {
throw Message.getSQLException(ErrorCode.CLASS_NOT_FOUND_1, new String[] { className + " (" + methodName
+ ")" }, e);
}
Class javaClass = database.loadClass(className);
Method[] methods = javaClass.getMethods();
for (int i = 0; i < methods.length; i++) {
Method m = methods[i];
......
......@@ -77,7 +77,7 @@ public class Session implements SessionInterface {
public Table findLocalTempTable(String name) {
Table t = null;
if (t == null && localTempTables != null) {
if (localTempTables != null) {
t = (Table) localTempTables.get(name);
}
return t;
......
......@@ -118,11 +118,11 @@ public class User extends RightOwner {
return buff.toString();
}
public void checkUserPasswordHash(byte[] buff) throws SQLException {
public void checkUserPasswordHash(byte[] buff, SQLException onError) throws SQLException {
SHA256 sha = new SHA256();
byte[] hash = sha.getHashWithSalt(buff, salt);
if (!ByteUtils.compareSecure(hash, passwordHash)) {
throw Message.getSQLException(ErrorCode.WRONG_USER_OR_PASSWORD);
throw onError;
}
}
......
/*
* Copyright 2004-2007 H2 Group. Licensed under the H2 License, Version 1.0 (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.engine;
import java.sql.SQLException;
import org.h2.api.AggregateFunction;
import org.h2.command.Parser;
import org.h2.message.Message;
import org.h2.message.Trace;
import org.h2.table.Table;
public class UserAggregate extends DbObjectBase {
private String className;
private Class javaClass;
public UserAggregate(Database db, int id, String name, String className, boolean force) throws SQLException {
super(db, id, name, Trace.FUNCTION);
this.className = className;
if (!force) {
getInstance();
}
}
public AggregateFunction getInstance() throws SQLException {
if (javaClass == null) {
javaClass = database.loadClass(className);
}
Object obj;
try {
obj = javaClass.newInstance();
AggregateFunction agg = (AggregateFunction) obj;
return agg;
} catch (Exception e) {
throw Message.convert(e);
}
}
public String getCreateSQLForCopy(Table table, String quotedName) {
throw Message.getInternalError();
}
public String getDropSQL() {
return "DROP AGGREGATE IF EXISTS " + getSQL();
}
public String getCreateSQL() {
StringBuffer buff = new StringBuffer();
buff.append("CREATE FORCE AGGREGATE ");
buff.append(getSQL());
buff.append(" FOR ");
buff.append(Parser.quoteIdentifier(className));
return buff.toString();
}
public int getType() {
return DbObject.AGGREGATE;
}
public synchronized void removeChildrenAndResources(Session session) throws SQLException {
className = null;
javaClass = null;
invalidate();
}
public void checkRename() throws SQLException {
throw Message.getUnsupportedException();
}
public String getJavaClassName() {
return this.className;
}
}
......@@ -39,7 +39,6 @@ import org.h2.value.ValueString;
*/
public class Aggregate extends Expression {
// TODO aggregates: make them 'pluggable'
// TODO incompatibility to hsqldb: aggregates: hsqldb uses automatic data
// type for sum if value is too big,
// h2 uses the same type as the data
......
/*
* Copyright 2004-2007 H2 Group. Licensed under the H2 License, Version 1.0 (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.expression;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.HashMap;
import org.h2.api.AggregateFunction;
import org.h2.command.Parser;
import org.h2.command.dml.Select;
import org.h2.constant.ErrorCode;
import org.h2.engine.Session;
import org.h2.engine.UserAggregate;
import org.h2.message.Message;
import org.h2.table.ColumnResolver;
import org.h2.table.TableFilter;
import org.h2.value.DataType;
import org.h2.value.Value;
import org.h2.value.ValueNull;
public class JavaAggregate extends Expression {
private final UserAggregate userAggregate;
private final Select select;
private AggregateFunction aggregate;
private Expression[] args;
private int[] argTypes;
private int dataType;
private Connection userConnection;
public JavaAggregate(UserAggregate userAggregate, Expression[] args, Select select) throws SQLException {
this.userAggregate = userAggregate;
this.args = args;
this.select = select;
}
public int getCost() {
int cost = 5;
for (int i = 0; i < args.length; i++) {
cost += args[i].getCost();
}
return cost;
}
public long getPrecision() {
return 0;
}
public String getSQL() {
StringBuffer buff = new StringBuffer();
buff.append(Parser.quoteIdentifier(userAggregate.getName()));
buff.append('(');
for (int i = 0; i < args.length; i++) {
if (i > 0) {
buff.append(", ");
}
Expression e = args[i];
buff.append(e.getSQL());
}
buff.append(')');
return buff.toString();
}
public int getScale() {
return 0;
}
public int getType() {
return dataType;
}
public boolean isEverything(ExpressionVisitor visitor) {
if (visitor.type == ExpressionVisitor.DETERMINISTIC) {
// TODO optimization: some functions are deterministic, but we don't
// know (no setting for that)
return false;
}
for (int i = 0; i < args.length; i++) {
Expression e = args[i];
if (e != null && !e.isEverything(visitor)) {
return false;
}
}
return true;
}
public void mapColumns(ColumnResolver resolver, int level) throws SQLException {
for (int i = 0; i < args.length; i++) {
args[i].mapColumns(resolver, level);
}
}
public Expression optimize(Session session) throws SQLException {
userConnection = session.createConnection(false);
argTypes = new int[args.length];
for (int i = 0; i < args.length; i++) {
Expression expr = args[i];
args[i] = expr.optimize(session);
argTypes[i] = expr.getType();
}
aggregate = getInstance();
dataType = aggregate.getType(argTypes);
return this;
}
public void setEvaluatable(TableFilter tableFilter, boolean b) {
for (int i = 0; i < args.length; i++) {
args[i].setEvaluatable(tableFilter, b);
}
}
private AggregateFunction getInstance() throws SQLException {
AggregateFunction agg = userAggregate.getInstance();
agg.init(userConnection);
return agg;
}
public Value getValue(Session session) throws SQLException {
HashMap group = select.getCurrentGroup();
if (group == null) {
throw Message.getSQLException(ErrorCode.INVALID_USE_OF_AGGREGATE_FUNCTION_1, getSQL());
}
AggregateFunction agg = (AggregateFunction) group.get(this);
if (agg == null) {
agg = getInstance();
}
Object obj = agg.getResult();
if (obj == null) {
return ValueNull.INSTANCE;
} else {
return DataType.convertToValue(session, obj, dataType);
}
}
public void updateAggregate(Session session) throws SQLException {
HashMap group = select.getCurrentGroup();
if (group == null) {
// this is a different level (the enclosing query)
return;
}
AggregateFunction agg = (AggregateFunction) group.get(this);
if (agg == null) {
agg = getInstance();
group.put(this, agg);
}
Object[] argValues = new Object[args.length];
Object arg = null;
for (int i = 0; i < args.length; i++) {
Value v = args[i].getValue(session);
v = v.convertTo(argTypes[i]);
arg = v.getObject();
argValues[i] = arg;
}
if (args.length == 1) {
agg.add(arg);
} else {
agg.add(argValues);
}
}
}
......@@ -34,6 +34,7 @@ public class Trace {
public static final String SCHEMA = "schema";
public static final String DATABASE = "database";
public static final String SESSION = "session";
public static final String AGGREGATE = "aggregate";
public Trace(TraceSystem traceSystem, String module) {
this.traceSystem = traceSystem;
......
......@@ -150,8 +150,9 @@
90127=Die Resultat-Zeilen k\u00F6nnen nicht ver\u00E4ndert werden. Die Abfrage muss alle Felder eines eindeutigen Schl\u00FCssels enthalten, und nur eine Tabelle enthalten.
90128=Kann nicht an den Anfang der Resultat-Zeilen springen. M\u00F6gliche L\u00F6sung\: conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY).
90129=Transaktion {0} nicht gefunden
90130=\#This method is not allowed for a prepared statement; use a regular statement instead.
90131=\#Concurrent update in table {0}\: another transaction has updated or deleted the same row
90130=Diese Methode ist nicht erlaubt f\u00FCr ein PreparedStatement; ben\u00FCtzen Sie ein Statement.
90131=Gleichzeitige \u00C4nderung in Tabelle {0}\: eine andere Transaktion hat den gleichen Datensatz ge\u00E4ndert oder gel\u00F6scht
90132=Aggregat-Funktion {0} nicht gefunden
HY000=Allgemeiner Fehler\: {0}
HY004=Unbekannter Datentyp\: {0}
HYC00=Dieses Feature wird unterst\u00FCtzt
......
......@@ -152,6 +152,7 @@
90129=Transaction {0} not found
90130=This method is not allowed for a prepared statement; use a regular statement instead.
90131=Concurrent update in table {0}\: another transaction has updated or deleted the same row
90132=Aggregate {0} not found
HY000=General error\: {0}
HY004=Unknown data type\: {0}
HYC00=Feature not supported
......
......@@ -268,7 +268,7 @@ ALTER TABLE TEST RENAME TO MY_DATA
ALTER USER userName ADMIN {TRUE | FALSE}
","
Switches the admin flag of a user on or off.
The user name is converted to uppercase if it is not quoted (with double quotes).
For compatibility, only unquoted or uppercase user names are allowed.
Admin rights are required to execute this command.
","
ALTER USER TOM ADMIN TRUE
......@@ -278,7 +278,7 @@ ALTER USER TOM ADMIN TRUE
ALTER USER userName RENAME TO newUserName
","
Renames a user.
The user name is converted to uppercase if it is not quoted (with double quotes).
For compatibility, only unquoted or uppercase user names are allowed.
After renaming a user the password becomes invalid and needs to be changed as well.
Admin rights are required to execute this command.
","
......@@ -290,7 +290,7 @@ ALTER USER userName SET
{PASSWORD string | SALT bytes HASH bytes}
","
Changes the password of a user.
The user name is converted to uppercase if it is not quoted (with double quotes).
For compatibility, only unquoted or uppercase user names are allowed.
The password must be in single quotes. It is case sensitive and can contain spaces.
The salt and hash values are hex strings.
Admin rights are required to execute this command.
......@@ -332,6 +332,16 @@ Admin rights are required to execute this command.
COMMENT ON TABLE TEST IS 'Table used for testing'
"
"Commands (DDL)","CREATE AGGREGATE","
CREATE AGGREGATE [IF NOT EXISTS] newAggregateName FOR className
","
Creates a new user defined aggregate function. The method name must be the full qualified class name.
The class must implement the interface org.h2.api.AggregateFunction.
Admin rights are required to execute this command.
","
CREATE AGGREGATE MEDIAN FOR ""com.acme.db.Median""
"
"Commands (DDL)","CREATE ALIAS","
CREATE ALIAS [IF NOT EXISTS] newFunctionAliasName FOR classAndMethodName
","
......@@ -460,7 +470,7 @@ CREATE USER [IF NOT EXISTS] newUserName
","
Creates a new user.
Admin rights are required to execute this command.
The user name is converted to uppercase if it is not quoted (with double quotes).
For compatibility, only unquoted or uppercase user names are allowed.
The password must be in single quotes. It is case sensitive and can contain spaces.
The salt and hash values are hex strings.
","
......@@ -477,6 +487,15 @@ Admin rights are required to execute this command.
CREATE VIEW TEST_VIEW AS SELECT * FROM TEST WHERE ID < 100
"
"Commands (DDL)","DROP AGGREGATE","
DROP AGGREGATE [IF EXISTS] aggregateName
","
Drops an existing user defined aggregate function.
Admin rights are required to execute this command.
","
CREATE AGGREGATE MEDIAN
"
"Commands (DDL)","DROP ALIAS","
DROP ALIAS [IF EXISTS] functionAliasName
","
......@@ -548,7 +567,7 @@ DROP USER [IF EXISTS] userName
Drops a user.
Admin rights are required to execute this command.
The current user cannot be dropped.
The user name is converted to uppercase if it is not quoted (with double quotes).
For compatibility, only unquoted or uppercase user names are allowed.
","
DROP USER TOM
"
......
......@@ -140,6 +140,7 @@ public class TcpServer implements Service {
c.setThread(thread);
thread.start();
}
serverSocket.close();
} catch (Exception e) {
if (!stop) {
TraceSystem.traceThrowable(e);
......
......@@ -43,10 +43,14 @@ public class TcpServerThread implements Runnable {
transfer.setSocket(socket);
}
private void log(String s) {
server.log(this + " " + s);
}
public void run() {
try {
transfer.init();
server.log("Connect");
log("Connect");
// TODO server: should support a list of allowed databases and a
// list of allowed clients
try {
......@@ -83,7 +87,7 @@ public class TcpServerThread implements Runnable {
session = engine.getSession(ci);
transfer.setSession(session);
transfer.writeInt(SessionRemote.STATUS_OK).flush();
server.log("Connected");
log("Connected");
} catch (Throwable e) {
sendError(e);
stop = true;
......@@ -95,7 +99,7 @@ public class TcpServerThread implements Runnable {
sendError(e);
}
}
server.log("Disconnect");
log("Disconnect");
} catch (Throwable e) {
server.logError(e);
} finally {
......@@ -122,7 +126,7 @@ public class TcpServerThread implements Runnable {
stop = true;
closeSession();
transfer.close();
server.log("Close");
log("Close");
} catch (Exception e) {
server.logError(e);
}
......
......@@ -512,7 +512,7 @@ public class Csv implements SimpleRowSource {
}
/**
* Get the current field reparator for writing.
* Get the current field separator for writing.
*
* @return the field separator
*/
......
......@@ -221,7 +221,11 @@ public class Server implements Runnable, ShutdownHandler {
}
/**
* Shutdown a TCP server.
* Shutdown a TCP server. If force is set to false, the server will not allow new connections,
* but not kill existing connections, instead it will stop if the last connection is closed.
* If force is set to true, existing connections are killed.
* After calling the method with force=false, it is not possible to call it again with
* force=true because new connections are not allowed.
*
* @param url example: tcp://localhost:9094
* @param password the password to use ("" for no password)
......
......@@ -13,15 +13,57 @@ import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Types;
import java.util.ArrayList;
import org.h2.api.AggregateFunction;
import org.h2.test.TestBase;
import org.h2.tools.SimpleResultSet;
public class TestFunctions extends TestBase {
Statement stat;
private Statement stat;
public void test() throws Exception {
testAggregate();
testFunctions();
}
public static class MedianString implements AggregateFunction {
private ArrayList list = new ArrayList();
public void add(Object value) throws SQLException {
list.add(value.toString());
}
public Object getResult() throws SQLException {
return list.get(list.size() / 2);
}
public int getType(int[] inputType) throws SQLException {
return Types.VARCHAR;
}
public void init(Connection conn) throws SQLException {
}
}
private void testAggregate() throws Exception {
deleteDb("functions");
Connection conn = getConnection("functions");
stat = conn.createStatement();
stat.execute("CREATE AGGREGATE MEDIAN FOR \"" + MedianString.class.getName() + "\"");
stat.execute("CREATE AGGREGATE IF NOT EXISTS MEDIAN FOR \"" + MedianString.class.getName() + "\"");
ResultSet rs = stat.executeQuery("SELECT MEDIAN(X) FROM SYSTEM_RANGE(1, 9)");
rs.next();
check("5", rs.getString(1));
stat.execute("DROP AGGREGATE MEDIAN");
stat.execute("DROP AGGREGATE IF EXISTS MEDIAN");
conn.close();
}
private void testFunctions() throws Exception {
deleteDb("functions");
Connection conn = getConnection("functions");
stat = conn.createStatement();
......
......@@ -18,6 +18,7 @@ import org.h2.test.TestBase;
public class TestLinkedTable extends TestBase {
public void test() throws Exception {
// testLinkAutoAdd();
testLinkOtherSchema();
testLinkDrop();
testLinkSchema();
......@@ -26,6 +27,22 @@ public class TestLinkedTable extends TestBase {
testLinkTwoTables();
}
// this is not a bug, it is the documented behavior
// private void testLinkAutoAdd() throws Exception {
// Class.forName("org.h2.Driver");
// Connection ca = DriverManager.getConnection("jdbc:h2:mem:one", "sa", "sa");
// Connection cb = DriverManager.getConnection("jdbc:h2:mem:two", "sa", "sa");
// Statement sa = ca.createStatement();
// Statement sb = cb.createStatement();
// sa.execute("CREATE TABLE ONE (X NUMBER)");
// sb.execute("CALL LINK_SCHEMA('GOOD', '', 'jdbc:h2:mem:one', 'sa', 'sa', 'PUBLIC'); ");
// sb.executeQuery("SELECT * FROM GOOD.ONE");
// sa.execute("CREATE TABLE TWO (X NUMBER)");
// sb.executeQuery("SELECT * FROM GOOD.TWO"); // FAILED
// ca.close();
// cb.close();
// }
private void testLinkOtherSchema() throws Exception {
Class.forName("org.h2.Driver");
Connection ca = DriverManager.getConnection("jdbc:h2:mem:one", "sa", "sa");
......
......@@ -14,9 +14,28 @@ import org.h2.test.TestBase;
public class TestRights extends TestBase {
Statement stat;
private Statement stat;
public void test() throws Exception {
// testLowerCaseUser();
testAccessRights();
}
// public void testLowerCaseUser() throws Exception {
// Documentation: For compatibility, only unquoted or uppercase user names are allowed.
// deleteDb("rights");
// Connection conn = getConnection("rights");
// stat = conn.createStatement();
// stat.execute("CREATE USER \"TEST1\" PASSWORD 'abc'");
// stat.execute("CREATE USER \"Test2\" PASSWORD 'abc'");
// conn.close();
// conn = getConnection("rights", "TEST1", "abc");
// conn.close();
// conn = getConnection("rights", "Test2", "abc");
// conn.close();
// }
public void testAccessRights() throws Exception {
if (config.memory) {
return;
}
......
......@@ -510,4 +510,5 @@ forge chr trunc gabealbert tunebackup manifest
lumber thus taking repositories ago delegated mention leaks pgsql seeded felt efficiently mill mentioned forgot leaked restarted clearing occupies randomness warn implementing abstraction
spfile svr pkey synced semicolon terminating
framework constructing architectural jmatter workgroup upgraded naked stopper skipping assumed
opensource atlassian hhh
opensource atlassian hhh establish pawel nice italiano ucchino paolo italian pier shorter
\ No newline at end of file
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论