提交 2ae9a839 authored 作者: Thomas Mueller's avatar Thomas Mueller

Functions now reside within a schema.

上级 8e3b97cb
...@@ -18,7 +18,15 @@ Change Log ...@@ -18,7 +18,15 @@ Change Log
<h1>Change Log</h1> <h1>Change Log</h1>
<h2>Next Version (unreleased)</h2> <h2>Next Version (unreleased)</h2>
<ul><li>Cluster: after a cluster node failed, the second cluster node can now be re-created <ul><li>New system property h2.functionsInSchema (default is false).
If enabled, the SCRIPT statement always includes the schema name in the CREATE ALIAS statement
(even if the schema is PUBLIC). This is not backward compatible with H2 versions 1.2.134 and older.
</li><li>Functions: it is no longer required to add a space after a comma in the parameter list.
Example: CREATE ALIAS PARSE_INT FOR "java.lang.Integer.parseInt(java.lang.String,int)"
</li><li>Functions now reside within a schema, similar to sequences.
If you do create such functions in schemas other than PUBLIC, then the database
can not be opened with older versions of H2.
</li><li>Cluster: after a cluster node failed, the second cluster node can now be re-created
and started without having to stop the first cluster node, and without having to stop and started without having to stop the first cluster node, and without having to stop
running applications. To do that, append ;AUTO_RECONNECT=TRUE to the database URL. running applications. To do that, append ;AUTO_RECONNECT=TRUE to the database URL.
</li><li>SET EXCLUSIVE now supports 0 (disable), 1 (enable), and 2 (enable and close all other connections). </li><li>SET EXCLUSIVE now supports 0 (disable), 1 (enable), and 2 (enable and close all other connections).
......
...@@ -1445,8 +1445,9 @@ The settings in the URL override the settings passed as a separate parameter. ...@@ -1445,8 +1445,9 @@ The settings in the URL override the settings passed as a separate parameter.
In addition to the built-in functions, this database supports user-defined Java functions. In addition to the built-in functions, this database supports user-defined Java functions.
In this database, Java functions can be used as stored procedures as well. In this database, Java functions can be used as stored procedures as well.
A function must be declared (registered) before it can be used. A function must be declared (registered) before it can be used.
A functions can be defined using source code, or as a reference to A function can be defined using source code, or as a reference to
a compiled class that is available in the classpath. a compiled class that is available in the classpath. By default, the
function aliases are stored in the current schema.
</p> </p>
<h3>Referencing a Compiled Method</h3> <h3>Referencing a Compiled Method</h3>
......
...@@ -25,6 +25,7 @@ See also <a href="build.html#providing_patches">Providing Patches</a>. ...@@ -25,6 +25,7 @@ See also <a href="build.html#providing_patches">Providing Patches</a>.
<h2>Version 1.3.x: Planned Changes</h2> <h2>Version 1.3.x: Planned Changes</h2>
<ul><li>Lob storage: enable the system property h2.lobInDatabase by default. <ul><li>Lob storage: enable the system property h2.lobInDatabase by default.
</li><li>Automatic ANALYZE: set the system property h2.analyzeAuto to 2000. </li><li>Automatic ANALYZE: set the system property h2.analyzeAuto to 2000.
</li><li>Enable h2.functionsInSchema.
</li></ul> </li></ul>
<h2>Priority 1</h2> <h2>Priority 1</h2>
...@@ -254,7 +255,6 @@ See also <a href="build.html#providing_patches">Providing Patches</a>. ...@@ -254,7 +255,6 @@ See also <a href="build.html#providing_patches">Providing Patches</a>.
</li><li>Native search: support "phrase search", wildcard search (* and ?), case-insensitive search, boolean operators, and grouping </li><li>Native search: support "phrase search", wildcard search (* and ?), case-insensitive search, boolean operators, and grouping
</li><li>Improve documentation of access rights </li><li>Improve documentation of access rights
</li><li>Support ENUM data type (see MySQL, PostgreSQL, MS SQL Server, maybe others) </li><li>Support ENUM data type (see MySQL, PostgreSQL, MS SQL Server, maybe others)
</li><li>Support a schema name for Java functions
</li><li>Remember the user defined data type (domain) of a column </li><li>Remember the user defined data type (domain) of a column
</li><li>Support Jackcess (MS Access databases) </li><li>Support Jackcess (MS Access databases)
</li><li>Built-in methods to write large objects (BLOB and CLOB): FILE_WRITE('test.txt', 'Hello World') </li><li>Built-in methods to write large objects (BLOB and CLOB): FILE_WRITE('test.txt', 'Hello World')
......
...@@ -313,7 +313,7 @@ public class Parser { ...@@ -313,7 +313,7 @@ public class Parser {
} else if (readIf("CREATE")) { } else if (readIf("CREATE")) {
c = parseCreate(); c = parseCreate();
} else if (readIf("CALL")) { } else if (readIf("CALL")) {
c = parserCall(); c = parseCall();
} else if (readIf("CHECKPOINT")) { } else if (readIf("CHECKPOINT")) {
c = parseCheckpoint(); c = parseCheckpoint();
} else if (readIf("COMMENT")) { } else if (readIf("COMMENT")) {
...@@ -420,7 +420,7 @@ public class Parser { ...@@ -420,7 +420,7 @@ public class Parser {
case 'v': case 'v':
case 'V': case 'V':
if (readIf("VALUES")) { if (readIf("VALUES")) {
c = parserCall(); c = parseCall();
} }
break; break;
case 'w': case 'w':
...@@ -980,6 +980,7 @@ public class Parser { ...@@ -980,6 +980,7 @@ public class Parser {
} }
} else { } else {
String tableName = readIdentifierWithSchema(null); String tableName = readIdentifierWithSchema(null);
Schema schema = getSchema();
if (readIf("(")) { if (readIf("(")) {
Schema mainSchema = database.getSchema(Constants.SCHEMA_MAIN); Schema mainSchema = database.getSchema(Constants.SCHEMA_MAIN);
if (equalsToken(tableName, RangeTable.NAME)) { if (equalsToken(tableName, RangeTable.NAME)) {
...@@ -989,7 +990,7 @@ public class Parser { ...@@ -989,7 +990,7 @@ public class Parser {
read(")"); read(")");
table = new RangeTable(mainSchema, min, max); table = new RangeTable(mainSchema, min, max);
} else { } else {
Expression func = readFunction(tableName); Expression func = readFunction(schema, tableName);
if (!(func instanceof FunctionCall)) { if (!(func instanceof FunctionCall)) {
throw getSyntaxError(); throw getSyntaxError();
} }
...@@ -1163,8 +1164,9 @@ public class Parser { ...@@ -1163,8 +1164,9 @@ public class Parser {
return command; return command;
} else if (readIf("ALIAS")) { } else if (readIf("ALIAS")) {
boolean ifExists = readIfExists(false); boolean ifExists = readIfExists(false);
DropFunctionAlias command = new DropFunctionAlias(session); String aliasName = readIdentifierWithSchema();
command.setAliasName(readUniqueIdentifier()); DropFunctionAlias command = new DropFunctionAlias(session, getSchema());
command.setAliasName(aliasName);
ifExists = readIfExists(ifExists); ifExists = readIfExists(ifExists);
command.setIfExists(ifExists); command.setIfExists(ifExists);
return command; return command;
...@@ -1934,10 +1936,15 @@ public class Parser { ...@@ -1934,10 +1936,15 @@ public class Parser {
return orderList; return orderList;
} }
private JavaFunction readJavaFunction(String name) { private JavaFunction readJavaFunction(Schema schema, String functionName) {
FunctionAlias functionAlias = database.findFunctionAlias(name); FunctionAlias functionAlias = null;
if (schema != null) {
functionAlias = schema.findFunction(functionName);
} else {
functionAlias = findFunctionAlias(session.getCurrentSchemaName(), functionName);
}
if (functionAlias == null) { if (functionAlias == null) {
throw DbException.get(ErrorCode.FUNCTION_NOT_FOUND_1, name); throw DbException.get(ErrorCode.FUNCTION_NOT_FOUND_1, functionName);
} }
Expression[] args; Expression[] args;
ArrayList<Expression> argList = New.arrayList(); ArrayList<Expression> argList = New.arrayList();
...@@ -1967,7 +1974,10 @@ public class Parser { ...@@ -1967,7 +1974,10 @@ public class Parser {
return agg; return agg;
} }
private Expression readFunction(String name) { private Expression readFunction(Schema schema, String name) {
if (schema != null) {
return readJavaFunction(schema, name);
}
int agg = Aggregate.getAggregateType(name); int agg = Aggregate.getAggregateType(name);
if (agg >= 0) { if (agg >= 0) {
return readAggregate(agg); return readAggregate(agg);
...@@ -1978,7 +1988,7 @@ public class Parser { ...@@ -1978,7 +1988,7 @@ public class Parser {
if (aggregate != null) { if (aggregate != null) {
return readJavaAggregate(aggregate); return readJavaAggregate(aggregate);
} }
return readJavaFunction(name); return readJavaFunction(null, name);
} }
switch (function.getFunctionType()) { switch (function.getFunctionType()) {
case Function.CAST: { case Function.CAST: {
...@@ -2145,7 +2155,13 @@ public class Parser { ...@@ -2145,7 +2155,13 @@ public class Parser {
return expr; return expr;
} }
String name = readColumnIdentifier(); String name = readColumnIdentifier();
if (readIf(".")) { Schema s = database.findSchema(objectName);
if (s != null && readIf("(")) {
// only if the token before the dot is a valid schema name,
// otherwise the old style Oracle outer join doesn't work:
// t.x = t2.x(+)
return readFunction(s, name);
} else if (readIf(".")) {
String schema = objectName; String schema = objectName;
objectName = name; objectName = name;
expr = readWildcardOrSequenceValue(schema, objectName); expr = readWildcardOrSequenceValue(schema, objectName);
...@@ -2153,7 +2169,15 @@ public class Parser { ...@@ -2153,7 +2169,15 @@ public class Parser {
return expr; return expr;
} }
name = readColumnIdentifier(); name = readColumnIdentifier();
if (readIf(".")) { if (readIf("(")) {
String databaseName = schema;
if (!equalsToken(database.getShortName(), databaseName)) {
throw DbException.get(ErrorCode.DATABASE_NOT_FOUND_1, databaseName);
}
schema = objectName;
objectName = name;
return readFunction(database.getSchema(schema), name);
} else if (readIf(".")) {
String databaseName = schema; String databaseName = schema;
if (!equalsToken(database.getShortName(), databaseName)) { if (!equalsToken(database.getShortName(), databaseName)) {
throw DbException.get(ErrorCode.DATABASE_NOT_FOUND_1, databaseName); throw DbException.get(ErrorCode.DATABASE_NOT_FOUND_1, databaseName);
...@@ -2239,7 +2263,7 @@ public class Parser { ...@@ -2239,7 +2263,7 @@ public class Parser {
if (currentTokenQuoted) { if (currentTokenQuoted) {
read(); read();
if (readIf("(")) { if (readIf("(")) {
r = readFunction(name); r = readFunction(null, name);
} else if (readIf(".")) { } else if (readIf(".")) {
r = readTermObjectDot(name); r = readTermObjectDot(name);
} else { } else {
...@@ -2264,7 +2288,7 @@ public class Parser { ...@@ -2264,7 +2288,7 @@ public class Parser {
r = readWhen(left); r = readWhen(left);
} }
} else if (readIf("(")) { } else if (readIf("(")) {
r = readFunction(name); r = readFunction(null, name);
} else if (equalsToken("CURRENT_USER", name)) { } else if (equalsToken("CURRENT_USER", name)) {
r = readFunctionWithoutParameters("USER"); r = readFunctionWithoutParameters("USER");
} else if (equalsToken("CURRENT", name)) { } else if (equalsToken("CURRENT", name)) {
...@@ -3655,7 +3679,7 @@ public class Parser { ...@@ -3655,7 +3679,7 @@ public class Parser {
return command; return command;
} }
private Call parserCall() { private Call parseCall() {
Call command = new Call(session); Call command = new Call(session);
currentPrepared = command; currentPrepared = command;
command.setExpression(readExpression()); command.setExpression(readExpression());
...@@ -3734,11 +3758,12 @@ public class Parser { ...@@ -3734,11 +3758,12 @@ public class Parser {
boolean ifNotExists = readIfNoExists(); boolean ifNotExists = readIfNoExists();
CreateAggregate command = new CreateAggregate(session); CreateAggregate command = new CreateAggregate(session);
command.setForce(force); command.setForce(force);
String name = readUniqueIdentifier(); String name = readIdentifierWithSchema();
if (isKeyword(name) || Function.getFunction(database, name) != null || Aggregate.getAggregateType(name) >= 0) { if (isKeyword(name) || Function.getFunction(database, name) != null || Aggregate.getAggregateType(name) >= 0) {
throw DbException.get(ErrorCode.FUNCTION_ALIAS_ALREADY_EXISTS_1, name); throw DbException.get(ErrorCode.FUNCTION_ALIAS_ALREADY_EXISTS_1, name);
} }
command.setName(name); command.setName(name);
command.setSchema(getSchema());
command.setIfNotExists(ifNotExists); command.setIfNotExists(ifNotExists);
read("FOR"); read("FOR");
command.setJavaClassMethod(readUniqueIdentifier()); command.setJavaClassMethod(readUniqueIdentifier());
...@@ -3849,13 +3874,13 @@ public class Parser { ...@@ -3849,13 +3874,13 @@ public class Parser {
private CreateFunctionAlias parseCreateFunctionAlias(boolean force) { private CreateFunctionAlias parseCreateFunctionAlias(boolean force) {
boolean ifNotExists = readIfNoExists(); boolean ifNotExists = readIfNoExists();
CreateFunctionAlias command = new CreateFunctionAlias(session); String aliasName = readIdentifierWithSchema();
command.setForce(force); if (isKeyword(aliasName) || Function.getFunction(database, aliasName) != null || Aggregate.getAggregateType(aliasName) >= 0) {
String name = readUniqueIdentifier(); throw DbException.get(ErrorCode.FUNCTION_ALIAS_ALREADY_EXISTS_1, aliasName);
if (isKeyword(name) || Function.getFunction(database, name) != null || Aggregate.getAggregateType(name) >= 0) {
throw DbException.get(ErrorCode.FUNCTION_ALIAS_ALREADY_EXISTS_1, name);
} }
command.setAliasName(name); CreateFunctionAlias command = new CreateFunctionAlias(session, getSchema());
command.setForce(force);
command.setAliasName(aliasName);
command.setIfNotExists(ifNotExists); command.setIfNotExists(ifNotExists);
command.setDeterministic(readIf("DETERMINISTIC")); command.setDeterministic(readIf("DETERMINISTIC"));
if (readIf("AS")) { if (readIf("AS")) {
...@@ -4357,24 +4382,43 @@ public class Parser { ...@@ -4357,24 +4382,43 @@ public class Parser {
throw DbException.get(ErrorCode.TABLE_OR_VIEW_NOT_FOUND_1, tableName); throw DbException.get(ErrorCode.TABLE_OR_VIEW_NOT_FOUND_1, tableName);
} }
private FunctionAlias findFunctionAlias(String schema, String aliasName) {
FunctionAlias functionAlias = database.getSchema(schema).findFunction(aliasName);
if (functionAlias != null) {
return functionAlias;
}
String[] schemaNames = session.getSchemaSearchPath();
if (schemaNames != null) {
for (String n : schemaNames) {
functionAlias = database.getSchema(n).findFunction(aliasName);
if (functionAlias != null) {
return functionAlias;
}
}
}
return null;
}
private Sequence findSequence(String schema, String sequenceName) { private Sequence findSequence(String schema, String sequenceName) {
Sequence sequence = database.getSchema(schema).findSequence(sequenceName); Sequence sequence = database.getSchema(schema).findSequence(sequenceName);
if (sequence != null) { if (sequence != null) {
return sequence; return sequence;
} }
String[] schemaNames = session.getSchemaSearchPath(); String[] schemaNames = session.getSchemaSearchPath();
for (int i = 0; schemaNames != null && i < schemaNames.length; i++) { if (schemaNames != null) {
Schema s = database.getSchema(schemaNames[i]); for (String n : schemaNames) {
sequence = s.findSequence(sequenceName); sequence = database.getSchema(n).findSequence(sequenceName);
if (sequence != null) { if (sequence != null) {
return sequence; return sequence;
}
} }
} }
return null; return null;
} }
private Sequence readSequence() { private Sequence readSequence() {
// same algorithm than readTableOrView // same algorithm as readTableOrView
String sequenceName = readIdentifierWithSchema(null); String sequenceName = readIdentifierWithSchema(null);
if (schemaName != null) { if (schemaName != null) {
return getSchema().getSequence(sequenceName); return getSchema().getSequence(sequenceName);
......
...@@ -11,6 +11,7 @@ import org.h2.engine.Database; ...@@ -11,6 +11,7 @@ import org.h2.engine.Database;
import org.h2.engine.Session; import org.h2.engine.Session;
import org.h2.engine.UserAggregate; import org.h2.engine.UserAggregate;
import org.h2.message.DbException; import org.h2.message.DbException;
import org.h2.schema.Schema;
/** /**
* This class represents the statement * This class represents the statement
...@@ -18,6 +19,7 @@ import org.h2.message.DbException; ...@@ -18,6 +19,7 @@ import org.h2.message.DbException;
*/ */
public class CreateAggregate extends DefineCommand { public class CreateAggregate extends DefineCommand {
private Schema schema;
private String name; private String name;
private String javaClassMethod; private String javaClassMethod;
private boolean ifNotExists; private boolean ifNotExists;
...@@ -31,7 +33,7 @@ public class CreateAggregate extends DefineCommand { ...@@ -31,7 +33,7 @@ public class CreateAggregate extends DefineCommand {
session.commit(true); session.commit(true);
session.getUser().checkAdmin(); session.getUser().checkAdmin();
Database db = session.getDatabase(); Database db = session.getDatabase();
if (db.findAggregate(name) != null || db.findFunctionAlias(name) != null) { if (db.findAggregate(name) != null || schema.findFunction(name) != null) {
if (!ifNotExists) { if (!ifNotExists) {
throw DbException.get(ErrorCode.FUNCTION_ALIAS_ALREADY_EXISTS_1, name); throw DbException.get(ErrorCode.FUNCTION_ALIAS_ALREADY_EXISTS_1, name);
} }
...@@ -43,6 +45,10 @@ public class CreateAggregate extends DefineCommand { ...@@ -43,6 +45,10 @@ public class CreateAggregate extends DefineCommand {
return 0; return 0;
} }
public void setSchema(Schema schema) {
this.schema = schema;
}
public void setName(String name) { public void setName(String name) {
this.name = name; this.name = name;
} }
......
...@@ -11,12 +11,14 @@ import org.h2.engine.Database; ...@@ -11,12 +11,14 @@ import org.h2.engine.Database;
import org.h2.engine.FunctionAlias; import org.h2.engine.FunctionAlias;
import org.h2.engine.Session; import org.h2.engine.Session;
import org.h2.message.DbException; import org.h2.message.DbException;
import org.h2.schema.Schema;
import org.h2.util.StringUtils;
/** /**
* This class represents the statement * This class represents the statement
* CREATE ALIAS * CREATE ALIAS
*/ */
public class CreateFunctionAlias extends DefineCommand { public class CreateFunctionAlias extends SchemaCommand {
private String aliasName; private String aliasName;
private String javaClassMethod; private String javaClassMethod;
...@@ -25,15 +27,15 @@ public class CreateFunctionAlias extends DefineCommand { ...@@ -25,15 +27,15 @@ public class CreateFunctionAlias extends DefineCommand {
private boolean force; private boolean force;
private String source; private String source;
public CreateFunctionAlias(Session session) { public CreateFunctionAlias(Session session, Schema schema) {
super(session); super(session, schema);
} }
public int update() { public int update() {
session.commit(true); session.commit(true);
session.getUser().checkAdmin(); session.getUser().checkAdmin();
Database db = session.getDatabase(); Database db = session.getDatabase();
if (db.findFunctionAlias(aliasName) != null) { if (getSchema().findFunction(aliasName) != null) {
if (!ifNotExists) { if (!ifNotExists) {
throw DbException.get(ErrorCode.FUNCTION_ALIAS_ALREADY_EXISTS_1, aliasName); throw DbException.get(ErrorCode.FUNCTION_ALIAS_ALREADY_EXISTS_1, aliasName);
} }
...@@ -41,12 +43,12 @@ public class CreateFunctionAlias extends DefineCommand { ...@@ -41,12 +43,12 @@ public class CreateFunctionAlias extends DefineCommand {
int id = getObjectId(); int id = getObjectId();
FunctionAlias functionAlias; FunctionAlias functionAlias;
if (javaClassMethod != null) { if (javaClassMethod != null) {
functionAlias = FunctionAlias.newInstance(db, id, aliasName, javaClassMethod, force); functionAlias = FunctionAlias.newInstance(getSchema(), id, aliasName, javaClassMethod, force);
} else { } else {
functionAlias = FunctionAlias.newInstanceFromSource(db, id, aliasName, source, force); functionAlias = FunctionAlias.newInstanceFromSource(getSchema(), id, aliasName, source, force);
} }
functionAlias.setDeterministic(deterministic); functionAlias.setDeterministic(deterministic);
db.addDatabaseObject(session, functionAlias); db.addSchemaObject(session, functionAlias);
} }
return 0; return 0;
} }
...@@ -55,8 +57,13 @@ public class CreateFunctionAlias extends DefineCommand { ...@@ -55,8 +57,13 @@ public class CreateFunctionAlias extends DefineCommand {
this.aliasName = name; this.aliasName = name;
} }
public void setJavaClassMethod(String string) { /**
this.javaClassMethod = string; * Set the qualified method name after removing whitespaces.
*
* @param method the qualified method name
*/
public void setJavaClassMethod(String method) {
this.javaClassMethod = StringUtils.replaceAll(method, " ", "");
} }
public void setIfNotExists(boolean ifNotExists) { public void setIfNotExists(boolean ifNotExists) {
......
...@@ -74,6 +74,7 @@ public class DropDatabase extends DefineCommand { ...@@ -74,6 +74,7 @@ public class DropDatabase extends DefineCommand {
list.addAll(db.getAllSchemaObjects(DbObject.CONSTRAINT)); list.addAll(db.getAllSchemaObjects(DbObject.CONSTRAINT));
list.addAll(db.getAllSchemaObjects(DbObject.TRIGGER)); list.addAll(db.getAllSchemaObjects(DbObject.TRIGGER));
list.addAll(db.getAllSchemaObjects(DbObject.CONSTANT)); list.addAll(db.getAllSchemaObjects(DbObject.CONSTANT));
list.addAll(db.getAllSchemaObjects(DbObject.FUNCTION_ALIAS));
for (SchemaObject obj : list) { for (SchemaObject obj : list) {
if (obj.isHidden()) { if (obj.isHidden()) {
continue; continue;
...@@ -94,7 +95,6 @@ public class DropDatabase extends DefineCommand { ...@@ -94,7 +95,6 @@ public class DropDatabase extends DefineCommand {
} }
ArrayList<DbObject> dbObjects = New.arrayList(); ArrayList<DbObject> dbObjects = New.arrayList();
dbObjects.addAll(db.getAllRights()); dbObjects.addAll(db.getAllRights());
dbObjects.addAll(db.getAllFunctionAliases());
dbObjects.addAll(db.getAllAggregates()); dbObjects.addAll(db.getAllAggregates());
dbObjects.addAll(db.getAllUserDataTypes()); dbObjects.addAll(db.getAllUserDataTypes());
for (DbObject obj : dbObjects) { for (DbObject obj : dbObjects) {
......
...@@ -11,31 +11,32 @@ import org.h2.engine.Database; ...@@ -11,31 +11,32 @@ import org.h2.engine.Database;
import org.h2.engine.FunctionAlias; import org.h2.engine.FunctionAlias;
import org.h2.engine.Session; import org.h2.engine.Session;
import org.h2.message.DbException; import org.h2.message.DbException;
import org.h2.schema.Schema;
/** /**
* This class represents the statement * This class represents the statement
* DROP ALIAS * DROP ALIAS
*/ */
public class DropFunctionAlias extends DefineCommand { public class DropFunctionAlias extends SchemaCommand {
private String aliasName; private String aliasName;
private boolean ifExists; private boolean ifExists;
public DropFunctionAlias(Session session) { public DropFunctionAlias(Session session, Schema schema) {
super(session); super(session, schema);
} }
public int update() { public int update() {
session.getUser().checkAdmin(); session.getUser().checkAdmin();
session.commit(true); session.commit(true);
Database db = session.getDatabase(); Database db = session.getDatabase();
FunctionAlias functionAlias = db.findFunctionAlias(aliasName); FunctionAlias functionAlias = getSchema().findFunction(aliasName);
if (functionAlias == null) { if (functionAlias == null) {
if (!ifExists) { if (!ifExists) {
throw DbException.get(ErrorCode.FUNCTION_ALIAS_NOT_FOUND_1, aliasName); throw DbException.get(ErrorCode.FUNCTION_ALIAS_NOT_FOUND_1, aliasName);
} }
} else { } else {
db.removeDatabaseObject(session, functionAlias); db.removeSchemaObject(session, functionAlias);
} }
return 0; return 0;
} }
......
...@@ -49,8 +49,7 @@ public class SetComment extends DefineCommand { ...@@ -49,8 +49,7 @@ public class SetComment extends DefineCommand {
object = db.getSchema(schemaName).getConstraint(objectName); object = db.getSchema(schemaName).getConstraint(objectName);
break; break;
case DbObject.FUNCTION_ALIAS: case DbObject.FUNCTION_ALIAS:
schemaName = null; object = db.getSchema(schemaName).findFunction(objectName);
object = db.findFunctionAlias(objectName);
errorCode = ErrorCode.FUNCTION_ALIAS_NOT_FOUND_1; errorCode = ErrorCode.FUNCTION_ALIAS_NOT_FOUND_1;
break; break;
case DbObject.INDEX: case DbObject.INDEX:
......
...@@ -24,7 +24,6 @@ import org.h2.engine.Comment; ...@@ -24,7 +24,6 @@ import org.h2.engine.Comment;
import org.h2.engine.Constants; import org.h2.engine.Constants;
import org.h2.engine.Database; import org.h2.engine.Database;
import org.h2.engine.DbObject; import org.h2.engine.DbObject;
import org.h2.engine.FunctionAlias;
import org.h2.engine.Right; import org.h2.engine.Right;
import org.h2.engine.Role; import org.h2.engine.Role;
import org.h2.engine.Session; import org.h2.engine.Session;
...@@ -159,11 +158,11 @@ public class ScriptCommand extends ScriptBase { ...@@ -159,11 +158,11 @@ public class ScriptCommand extends ScriptBase {
Constant constant = (Constant) obj; Constant constant = (Constant) obj;
add(constant.getCreateSQL(), false); add(constant.getCreateSQL(), false);
} }
for (FunctionAlias alias : db.getAllFunctionAliases()) { for (SchemaObject obj : db.getAllSchemaObjects(DbObject.FUNCTION_ALIAS)) {
if (drop) { if (drop) {
add(alias.getDropSQL(), false); add(obj.getDropSQL(), false);
} }
add(alias.getCreateSQL(), false); add(obj.getCreateSQL(), false);
} }
for (UserAggregate agg : db.getAllAggregates()) { for (UserAggregate agg : db.getAllAggregates()) {
if (drop) { if (drop) {
......
...@@ -260,6 +260,15 @@ public class SysProperties { ...@@ -260,6 +260,15 @@ public class SysProperties {
*/ */
public static final int ESTIMATED_FUNCTION_TABLE_ROWS = getIntSetting("h2.estimatedFunctionTableRows", 1000); public static final int ESTIMATED_FUNCTION_TABLE_ROWS = getIntSetting("h2.estimatedFunctionTableRows", 1000);
/**
* System property <code>h2.functionsInSchema</code> (default:
* false).<br />
* If set, all functions are stored in a schema. Specially, the SCRIPT statement
* will always include the schema name in the CREATE ALIAS statement.
* This is not backward compatible with H2 versions 1.2.134 and older.
*/
public static final boolean FUNCTIONS_IN_SCHEMA = getBooleanSetting("h2.functionsInSchema", false);
/** /**
* System property <code>h2.identifiersToUpper</code> (default: true).<br /> * System property <code>h2.identifiersToUpper</code> (default: true).<br />
* Unquoted identifiers in SQL statements are case insensitive and converted * Unquoted identifiers in SQL statements are case insensitive and converted
......
...@@ -94,7 +94,6 @@ public class Database implements DataHandler { ...@@ -94,7 +94,6 @@ public class Database implements DataHandler {
private final HashMap<String, Setting> settings = New.hashMap(); private final HashMap<String, Setting> settings = New.hashMap();
private final HashMap<String, Schema> schemas = New.hashMap(); private final HashMap<String, Schema> schemas = New.hashMap();
private final HashMap<String, Right> rights = New.hashMap(); private final HashMap<String, Right> rights = New.hashMap();
private final HashMap<String, FunctionAlias> functionAliases = New.hashMap();
private final HashMap<String, UserDataType> userDataTypes = New.hashMap(); private final HashMap<String, UserDataType> userDataTypes = New.hashMap();
private final HashMap<String, UserAggregate> aggregates = New.hashMap(); private final HashMap<String, UserAggregate> aggregates = New.hashMap();
private final HashMap<String, Comment> comments = New.hashMap(); private final HashMap<String, Comment> comments = New.hashMap();
...@@ -759,9 +758,6 @@ public class Database implements DataHandler { ...@@ -759,9 +758,6 @@ public class Database implements DataHandler {
case DbObject.RIGHT: case DbObject.RIGHT:
result = rights; result = rights;
break; break;
case DbObject.FUNCTION_ALIAS:
result = functionAliases;
break;
case DbObject.SCHEMA: case DbObject.SCHEMA:
result = schemas; result = schemas;
break; break;
...@@ -846,16 +842,6 @@ public class Database implements DataHandler { ...@@ -846,16 +842,6 @@ public class Database implements DataHandler {
return comments.get(key); return comments.get(key);
} }
/**
* Get the user defined function if it exists, or null if not.
*
* @param name the name of the user defined function
* @return the function or null
*/
public FunctionAlias findFunctionAlias(String name) {
return functionAliases.get(name);
}
/** /**
* Get the role if it exists, or null if not. * Get the role if it exists, or null if not.
* *
...@@ -1219,10 +1205,6 @@ public class Database implements DataHandler { ...@@ -1219,10 +1205,6 @@ public class Database implements DataHandler {
return New.arrayList(comments.values()); return New.arrayList(comments.values());
} }
public ArrayList<FunctionAlias> getAllFunctionAliases() {
return New.arrayList(functionAliases.values());
}
public int getAllowLiterals() { public int getAllowLiterals() {
if (starting) { if (starting) {
return Constants.ALLOW_LITERALS_ALL; return Constants.ALLOW_LITERALS_ALL;
......
...@@ -19,12 +19,14 @@ import org.h2.constant.SysProperties; ...@@ -19,12 +19,14 @@ import org.h2.constant.SysProperties;
import org.h2.expression.Expression; import org.h2.expression.Expression;
import org.h2.message.DbException; import org.h2.message.DbException;
import org.h2.message.Trace; import org.h2.message.Trace;
import org.h2.schema.Schema;
import org.h2.schema.SchemaObjectBase;
import org.h2.table.Table; import org.h2.table.Table;
import org.h2.util.Utils;
import org.h2.util.New; import org.h2.util.New;
import org.h2.util.SourceCompiler; import org.h2.util.SourceCompiler;
import org.h2.util.StatementBuilder; import org.h2.util.StatementBuilder;
import org.h2.util.StringUtils; import org.h2.util.StringUtils;
import org.h2.util.Utils;
import org.h2.value.DataType; import org.h2.value.DataType;
import org.h2.value.Value; import org.h2.value.Value;
import org.h2.value.ValueNull; import org.h2.value.ValueNull;
...@@ -35,7 +37,7 @@ import org.h2.value.ValueNull; ...@@ -35,7 +37,7 @@ import org.h2.value.ValueNull;
* @author Thomas Mueller * @author Thomas Mueller
* @author Gary Tong * @author Gary Tong
*/ */
public class FunctionAlias extends DbObjectBase { public class FunctionAlias extends SchemaObjectBase {
private String className; private String className;
private String methodName; private String methodName;
...@@ -43,8 +45,8 @@ public class FunctionAlias extends DbObjectBase { ...@@ -43,8 +45,8 @@ public class FunctionAlias extends DbObjectBase {
private JavaMethod[] javaMethods; private JavaMethod[] javaMethods;
private boolean deterministic; private boolean deterministic;
private FunctionAlias(Database db, int id, String name) { private FunctionAlias(Schema schema, int id, String name) {
initDbObjectBase(db, id, name, Trace.FUNCTION); initSchemaObjectBase(schema, id, name, Trace.FUNCTION);
} }
/** /**
...@@ -57,8 +59,8 @@ public class FunctionAlias extends DbObjectBase { ...@@ -57,8 +59,8 @@ public class FunctionAlias extends DbObjectBase {
* @param force create the object even if the class or method does not exist * @param force create the object even if the class or method does not exist
* @return the database object * @return the database object
*/ */
public static FunctionAlias newInstance(Database db, int id, String name, String javaClassMethod, boolean force) { public static FunctionAlias newInstance(Schema schema, int id, String name, String javaClassMethod, boolean force) {
FunctionAlias alias = new FunctionAlias(db, id, name); FunctionAlias alias = new FunctionAlias(schema, id, name);
int paren = javaClassMethod.indexOf('('); int paren = javaClassMethod.indexOf('(');
int lastDot = javaClassMethod.lastIndexOf('.', paren < 0 ? javaClassMethod.length() : paren); int lastDot = javaClassMethod.lastIndexOf('.', paren < 0 ? javaClassMethod.length() : paren);
if (lastDot < 0) { if (lastDot < 0) {
...@@ -80,8 +82,8 @@ public class FunctionAlias extends DbObjectBase { ...@@ -80,8 +82,8 @@ public class FunctionAlias extends DbObjectBase {
* @param force create the object even if the class or method does not exist * @param force create the object even if the class or method does not exist
* @return the database object * @return the database object
*/ */
public static FunctionAlias newInstanceFromSource(Database db, int id, String name, String source, boolean force) { public static FunctionAlias newInstanceFromSource(Schema schema, int id, String name, String source, boolean force) {
FunctionAlias alias = new FunctionAlias(db, id, name); FunctionAlias alias = new FunctionAlias(schema, id, name);
alias.source = source; alias.source = source;
alias.init(force); alias.init(force);
return alias; return alias;
...@@ -167,7 +169,9 @@ public class FunctionAlias extends DbObjectBase { ...@@ -167,7 +169,9 @@ public class FunctionAlias extends DbObjectBase {
StatementBuilder buff = new StatementBuilder(m.getName()); StatementBuilder buff = new StatementBuilder(m.getName());
buff.append('('); buff.append('(');
for (Class< ? > p : m.getParameterTypes()) { for (Class< ? > p : m.getParameterTypes()) {
buff.appendExceptFirst(", "); // do not use a space here, because spaces are removed
// in CreateFunctionAlias.setJavaClassMethod()
buff.appendExceptFirst(",");
if (p.isArray()) { if (p.isArray()) {
buff.append(p.getComponentType().getName()).append("[]"); buff.append(p.getComponentType().getName()).append("[]");
} else { } else {
...@@ -185,6 +189,14 @@ public class FunctionAlias extends DbObjectBase { ...@@ -185,6 +189,14 @@ public class FunctionAlias extends DbObjectBase {
return "DROP ALIAS IF EXISTS " + getSQL(); return "DROP ALIAS IF EXISTS " + getSQL();
} }
public String getSQL() {
// TODO can remove this method once FUNCTIONS_IN_SCHEMA is enabled
if (SysProperties.FUNCTIONS_IN_SCHEMA || !getSchema().getName().equals(Constants.SCHEMA_MAIN)) {
return super.getSQL();
}
return Parser.quoteIdentifier(getName());
}
public String getCreateSQL() { public String getCreateSQL() {
StringBuilder buff = new StringBuilder("CREATE FORCE ALIAS "); StringBuilder buff = new StringBuilder("CREATE FORCE ALIAS ");
buff.append(getSQL()); buff.append(getSQL());
......
...@@ -17,6 +17,7 @@ import org.h2.constraint.Constraint; ...@@ -17,6 +17,7 @@ import org.h2.constraint.Constraint;
import org.h2.engine.Database; import org.h2.engine.Database;
import org.h2.engine.DbObject; import org.h2.engine.DbObject;
import org.h2.engine.DbObjectBase; import org.h2.engine.DbObjectBase;
import org.h2.engine.FunctionAlias;
import org.h2.engine.Session; import org.h2.engine.Session;
import org.h2.engine.User; import org.h2.engine.User;
import org.h2.index.Index; import org.h2.index.Index;
...@@ -43,6 +44,7 @@ public class Schema extends DbObjectBase { ...@@ -43,6 +44,7 @@ public class Schema extends DbObjectBase {
private HashMap<String, TriggerObject> triggers = New.hashMap(); private HashMap<String, TriggerObject> triggers = New.hashMap();
private HashMap<String, Constraint> constraints = New.hashMap(); private HashMap<String, Constraint> constraints = New.hashMap();
private HashMap<String, Constant> constants = New.hashMap(); private HashMap<String, Constant> constants = New.hashMap();
private HashMap<String, FunctionAlias> functions = New.hashMap();
/** /**
* The set of returned unique names that are not yet stored. It is used to * The set of returned unique names that are not yet stored. It is used to
...@@ -121,6 +123,10 @@ public class Schema extends DbObjectBase { ...@@ -121,6 +123,10 @@ public class Schema extends DbObjectBase {
Constant obj = (Constant) constants.values().toArray()[0]; Constant obj = (Constant) constants.values().toArray()[0];
database.removeSchemaObject(session, obj); database.removeSchemaObject(session, obj);
} }
while (functions != null && functions.size() > 0) {
FunctionAlias obj = (FunctionAlias) functions.values().toArray()[0];
database.removeSchemaObject(session, obj);
}
database.removeMeta(session, getId()); database.removeMeta(session, getId());
owner = null; owner = null;
invalidate(); invalidate();
...@@ -161,6 +167,9 @@ public class Schema extends DbObjectBase { ...@@ -161,6 +167,9 @@ public class Schema extends DbObjectBase {
case DbObject.CONSTANT: case DbObject.CONSTANT:
result = constants; result = constants;
break; break;
case DbObject.FUNCTION_ALIAS:
result = functions;
break;
default: default:
throw DbException.throwInternalError("type=" + type); throw DbException.throwInternalError("type=" + type);
} }
...@@ -169,6 +178,8 @@ public class Schema extends DbObjectBase { ...@@ -169,6 +178,8 @@ public class Schema extends DbObjectBase {
/** /**
* Add an object to this schema. * Add an object to this schema.
* This method must not be called within CreateSchemaObject;
* use Database.addSchemaObject() instead
* *
* @param obj the object to add * @param obj the object to add
*/ */
...@@ -291,6 +302,17 @@ public class Schema extends DbObjectBase { ...@@ -291,6 +302,17 @@ public class Schema extends DbObjectBase {
public Constant findConstant(String constantName) { public Constant findConstant(String constantName) {
return constants.get(constantName); return constants.get(constantName);
} }
/**
* Try to find a user defined function with this name. This method returns
* null if no object with this name exists.
*
* @param functionAlias the object name
* @return the object or null
*/
public FunctionAlias findFunction(String functionAlias) {
return functions.get(functionAlias);
}
/** /**
* Release a unique object name. * Release a unique object name.
...@@ -449,7 +471,7 @@ public class Schema extends DbObjectBase { ...@@ -449,7 +471,7 @@ public class Schema extends DbObjectBase {
* Get all objects of the given type. * Get all objects of the given type.
* *
* @param type the object type * @param type the object type
* @return a (possible empty) list of all objects * @return a (possible empty) list of all objects
*/ */
public ArrayList<SchemaObject> getAll(int type) { public ArrayList<SchemaObject> getAll(int type) {
HashMap<String, SchemaObject> map = getMap(type); HashMap<String, SchemaObject> map = getMap(type);
...@@ -459,11 +481,20 @@ public class Schema extends DbObjectBase { ...@@ -459,11 +481,20 @@ public class Schema extends DbObjectBase {
/** /**
* Get all tables and views. * Get all tables and views.
* *
* @return a (possible empty) list of all objects * @return a (possible empty) list of all objects
*/ */
public ArrayList<Table> getAllTablesAndViews() { public ArrayList<Table> getAllTablesAndViews() {
return New.arrayList(tablesAndViews.values()); return New.arrayList(tablesAndViews.values());
} }
/**
* Get all functions.
*
* @return a (possible empty) list of all objects
*/
public ArrayList<FunctionAlias> getAllFunctionAliases() {
return New.arrayList(functions.values());
}
/** /**
* Remove an object from this schema. * Remove an object from this schema.
......
...@@ -1061,7 +1061,8 @@ public class MetaTable extends Table { ...@@ -1061,7 +1061,8 @@ public class MetaTable extends Table {
break; break;
} }
case FUNCTION_ALIASES: { case FUNCTION_ALIASES: {
for (FunctionAlias alias : database.getAllFunctionAliases()) { for (SchemaObject aliasAsSchemaObject : database.getAllSchemaObjects(DbObject.FUNCTION_ALIAS)) {
FunctionAlias alias = (FunctionAlias) aliasAsSchemaObject;
for (FunctionAlias.JavaMethod method : alias.getJavaMethods()) { for (FunctionAlias.JavaMethod method : alias.getJavaMethods()) {
int returnsResult = method.getDataType() == Value.NULL ? DatabaseMetaData.procedureNoResult int returnsResult = method.getDataType() == Value.NULL ? DatabaseMetaData.procedureNoResult
: DatabaseMetaData.procedureReturnsResult; : DatabaseMetaData.procedureReturnsResult;
...@@ -1069,7 +1070,7 @@ public class MetaTable extends Table { ...@@ -1069,7 +1070,7 @@ public class MetaTable extends Table {
// ALIAS_CATALOG // ALIAS_CATALOG
catalog, catalog,
// ALIAS_SCHEMA // ALIAS_SCHEMA
Constants.SCHEMA_MAIN, alias.getSchema().getName(),
// ALIAS_NAME // ALIAS_NAME
identifier(alias.getName()), identifier(alias.getName()),
// JAVA_CLASS // JAVA_CLASS
...@@ -1119,7 +1120,8 @@ public class MetaTable extends Table { ...@@ -1119,7 +1120,8 @@ public class MetaTable extends Table {
break; break;
} }
case FUNCTION_COLUMNS: { case FUNCTION_COLUMNS: {
for (FunctionAlias alias : database.getAllFunctionAliases()) { for (SchemaObject aliasAsSchemaObject : database.getAllSchemaObjects(DbObject.FUNCTION_ALIAS)) {
FunctionAlias alias = (FunctionAlias) aliasAsSchemaObject;
for (FunctionAlias.JavaMethod method : alias.getJavaMethods()) { for (FunctionAlias.JavaMethod method : alias.getJavaMethods()) {
Class< ? >[] columnList = method.getColumnClasses(); Class< ? >[] columnList = method.getColumnClasses();
for (int k = 0; k < columnList.length; k++) { for (int k = 0; k < columnList.length; k++) {
...@@ -1132,7 +1134,7 @@ public class MetaTable extends Table { ...@@ -1132,7 +1134,7 @@ public class MetaTable extends Table {
// ALIAS_CATALOG // ALIAS_CATALOG
catalog, catalog,
// ALIAS_SCHEMA // ALIAS_SCHEMA
Constants.SCHEMA_MAIN, alias.getSchema().getName(),
// ALIAS_NAME // ALIAS_NAME
identifier(alias.getName()), identifier(alias.getName()),
// JAVA_CLASS // JAVA_CLASS
......
...@@ -292,8 +292,8 @@ java org.h2.test.TestAll timer ...@@ -292,8 +292,8 @@ java org.h2.test.TestAll timer
int testing; int testing;
System.setProperty("h2.lobInDatabase", "true"); // System.setProperty("h2.lobInDatabase", "true");
System.setProperty("h2.analyzeAuto", "100"); // System.setProperty("h2.analyzeAuto", "100");
/* /*
......
...@@ -51,6 +51,8 @@ public class TestFunctions extends TestBase implements AggregateFunction { ...@@ -51,6 +51,8 @@ public class TestFunctions extends TestBase implements AggregateFunction {
testSource(); testSource();
testDynamicArgumentAndReturn(); testDynamicArgumentAndReturn();
testUUID(); testUUID();
testWhiteSpacesInParameters();
testSchemaSearchPath();
testDeterministic(); testDeterministic();
testTransactionId(); testTransactionId();
testPrecision(); testPrecision();
...@@ -90,6 +92,9 @@ public class TestFunctions extends TestBase implements AggregateFunction { ...@@ -90,6 +92,9 @@ public class TestFunctions extends TestBase implements AggregateFunction {
ResultSet rs; ResultSet rs;
stat.execute("create force alias sayHi as 'String test(String name) {\n" + stat.execute("create force alias sayHi as 'String test(String name) {\n" +
"return \"Hello \" + name;\n}'"); "return \"Hello \" + name;\n}'");
rs = stat.executeQuery("SELECT ALIAS_NAME FROM INFORMATION_SCHEMA.FUNCTION_ALIASES");
rs.next();
assertEquals("SAY" + "HI", rs.getString(1));
rs = stat.executeQuery("call sayHi('Joe')"); rs = stat.executeQuery("call sayHi('Joe')");
rs.next(); rs.next();
assertEquals("Hello Joe", rs.getString(1)); assertEquals("Hello Joe", rs.getString(1));
...@@ -157,7 +162,8 @@ public class TestFunctions extends TestBase implements AggregateFunction { ...@@ -157,7 +162,8 @@ public class TestFunctions extends TestBase implements AggregateFunction {
rs.next(); rs.next();
assertEquals(0, rs.getInt(1)); assertEquals(0, rs.getInt(1));
stat.execute("drop alias getCount"); stat.execute("drop alias getCount");
rs = stat.executeQuery("SELECT * FROM INFORMATION_SCHEMA.FUNCTION_ALIASES WHERE UPPER(ALIAS_NAME) = 'GETCOUNT'");
assertEquals(false, rs.next());
stat.execute("create alias reverse deterministic for \""+getClass().getName()+".reverse\""); stat.execute("create alias reverse deterministic for \""+getClass().getName()+".reverse\"");
rs = stat.executeQuery("select reverse(x) from system_range(700, 700)"); rs = stat.executeQuery("select reverse(x) from system_range(700, 700)");
rs.next(); rs.next();
...@@ -467,6 +473,58 @@ public class TestFunctions extends TestBase implements AggregateFunction { ...@@ -467,6 +473,58 @@ public class TestFunctions extends TestBase implements AggregateFunction {
conn.close(); conn.close();
} }
/**
* White spaces in javaMethodDescriptors are deleted during
* CreateFunctionAlias, and all further processing is normalized.
*/
private void testWhiteSpacesInParameters() throws SQLException {
deleteDb("functions");
Connection conn = getConnection("functions");
Statement stat = conn.createStatement();
// with white space
stat.execute("CREATE ALIAS PARSE_INT2 FOR \"java.lang.Integer.parseInt(java.lang.String, int)\"");
ResultSet rs;
rs = stat.executeQuery("CALL PARSE_INT2('473', 10)");
rs.next();
assertEquals(473, rs.getInt(1));
stat.execute("DROP ALIAS PARSE_INT2");
// without white space
stat.execute("CREATE ALIAS PARSE_INT2 FOR \"java.lang.Integer.parseInt(java.lang.String,int)\"");
stat.execute("DROP ALIAS PARSE_INT2");
}
private void testSchemaSearchPath() throws SQLException {
deleteDb("functions");
Connection conn = getConnection("functions");
Statement stat = conn.createStatement();
ResultSet rs;
stat.execute("CREATE SCHEMA TEST");
stat.execute("SET SCHEMA TEST");
stat.execute("CREATE ALIAS PARSE_INT2 FOR \"java.lang.Integer.parseInt(java.lang.String, int)\";");
rs = stat.executeQuery("SELECT ALIAS_NAME FROM INFORMATION_SCHEMA.FUNCTION_ALIASES WHERE ALIAS_SCHEMA ='TEST'");
rs.next();
assertEquals("PARSE_INT2", rs.getString(1));
stat.execute("DROP ALIAS PARSE_INT2");
stat.execute("SET SCHEMA PUBLIC");
stat.execute("CREATE ALIAS TEST.PARSE_INT2 FOR \"java.lang.Integer.parseInt(java.lang.String, int)\";");
stat.execute("SET SCHEMA_SEARCH_PATH PUBLIC, TEST");
rs = stat.executeQuery("CALL PARSE_INT2('-FF', 16)");
rs.next();
assertEquals(-255, rs.getInt(1));
rs = stat.executeQuery("SELECT ALIAS_NAME FROM INFORMATION_SCHEMA.FUNCTION_ALIASES WHERE ALIAS_SCHEMA ='TEST'");
rs.next();
assertEquals("PARSE_INT2", rs.getString(1));
rs = stat.executeQuery("CALL TEST.PARSE_INT2('-2147483648', 10)");
rs.next();
assertEquals(-2147483648, rs.getInt(1));
rs = stat.executeQuery("CALL FUNCTIONS.TEST.PARSE_INT2('-2147483648', 10)");
rs.next();
assertEquals(-2147483648, rs.getInt(1));
conn.close();
}
private void assertCallResult(String expected, Statement stat, String sql) throws SQLException { private void assertCallResult(String expected, Statement stat, String sql) throws SQLException {
ResultSet rs = stat.executeQuery("CALL " + sql); ResultSet rs = stat.executeQuery("CALL " + sql);
rs.next(); rs.next();
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论