Unverified 提交 6db21a59 authored 作者: Evgenij Ryazanov's avatar Evgenij Ryazanov 提交者: GitHub

Merge pull request #1370 from katzyn/function

Add NEWID() as alias to RANDOM_UUID() in SQL Server compatibility mode
......@@ -28,6 +28,8 @@ import org.h2.engine.Database;
import org.h2.engine.Mode;
import org.h2.engine.Session;
import org.h2.message.DbException;
import org.h2.mode.FunctionsMSSQLServer;
import org.h2.mode.FunctionsMySQL;
import org.h2.schema.Schema;
import org.h2.schema.Sequence;
import org.h2.security.BlockCipher;
......@@ -143,7 +145,7 @@ public class Function extends Expression implements FunctionCall {
public static final int ROW_NUMBER = 300;
private static final int VAR_ARGS = -1;
protected static final int VAR_ARGS = -1;
private static final long PRECISION_UNKNOWN = -1;
private static final HashMap<String, FunctionInfo> FUNCTIONS = new HashMap<>(256);
......@@ -151,11 +153,13 @@ public class Function extends Expression implements FunctionCall {
protected Expression[] args;
private final FunctionInfo info;
protected final FunctionInfo info;
private ArrayList<Expression> varArgs;
private int dataType, scale;
private long precision = PRECISION_UNKNOWN;
private int displaySize;
protected int dataType;
protected int scale;
protected long precision = PRECISION_UNKNOWN;
protected int displaySize;
private final Database database;
static {
......@@ -239,12 +243,8 @@ public class Function extends Expression implements FunctionCall {
addFunction("LCASE", LCASE, 1, Value.STRING);
addFunction("LEFT", LEFT, 2, Value.STRING);
addFunction("LENGTH", LENGTH, 1, Value.LONG);
// alias for MSSQLServer
addFunction("LEN", LENGTH, 1, Value.LONG);
// 2 or 3 arguments
addFunction("LOCATE", LOCATE, VAR_ARGS, Value.INT);
// alias for MSSQLServer
addFunction("CHARINDEX", LOCATE, VAR_ARGS, Value.INT);
// same as LOCATE with 2 arguments
addFunction("POSITION", LOCATE, 2, Value.INT);
addFunction("INSTR", INSTR, VAR_ARGS, Value.INT);
......@@ -292,9 +292,6 @@ public class Function extends Expression implements FunctionCall {
addFunction("TO_TIMESTAMP", TO_TIMESTAMP, VAR_ARGS, Value.TIMESTAMP);
addFunction("ADD_MONTHS", ADD_MONTHS, 2, Value.TIMESTAMP);
addFunction("TO_TIMESTAMP_TZ", TO_TIMESTAMP_TZ, VAR_ARGS, Value.TIMESTAMP_TZ);
// alias for MSSQLServer
addFunctionNotDeterministic("GETDATE", CURDATE,
0, Value.DATE);
addFunctionNotDeterministic("CURRENT_TIME", CURRENT_TIME,
VAR_ARGS, Value.TIME);
addFunctionNotDeterministic("LOCALTIME", CURRENT_TIME,
......@@ -474,7 +471,13 @@ public class Function extends Expression implements FunctionCall {
addFunction("VALUES", VALUES, 1, Value.NULL, false, true, false);
}
protected Function(Database database, FunctionInfo info) {
/**
* Creates a new instance of function.
*
* @param database database
* @param info function information
*/
public Function(Database database, FunctionInfo info) {
this.database = database;
this.info = info;
if (info.parameterCount == VAR_ARGS) {
......@@ -487,15 +490,8 @@ public class Function extends Expression implements FunctionCall {
private static void addFunction(String name, int type, int parameterCount,
int returnDataType, boolean nullIfParameterIsNull, boolean deterministic,
boolean bufferResultSetToLocalTemp) {
FunctionInfo info = new FunctionInfo();
info.name = name;
info.type = type;
info.parameterCount = parameterCount;
info.returnDataType = returnDataType;
info.nullIfParameterIsNull = nullIfParameterIsNull;
info.deterministic = deterministic;
info.bufferResultSetToLocalTemp = bufferResultSetToLocalTemp;
FUNCTIONS.put(name, info);
FUNCTIONS.put(name, new FunctionInfo(name, type, parameterCount, returnDataType, nullIfParameterIsNull,
deterministic, bufferResultSetToLocalTemp));
}
private static void addFunctionNotDeterministic(String name, int type,
......@@ -528,8 +524,15 @@ public class Function extends Expression implements FunctionCall {
}
FunctionInfo info = FUNCTIONS.get(name);
if (info == null) {
switch (database.getMode().getEnum()) {
case MSSQLServer:
return FunctionsMSSQLServer.getFunction(database, name);
case MySQL:
return FunctionsMySQL.getFunction(database, name);
default:
return null;
}
}
switch (info.type) {
case TABLE:
case TABLE_DISTINCT:
......@@ -539,6 +542,16 @@ public class Function extends Expression implements FunctionCall {
}
}
/**
* Returns function information for the specified function name.
*
* @param upperName the function name in upper case
* @return the function information or {@code null}
*/
public static FunctionInfo getFunctionInfo(String upperName) {
return FUNCTIONS.get(upperName);
}
/**
* Set the parameter expression at the given index.
*
......@@ -1090,7 +1103,7 @@ public class Function extends Expression implements FunctionCall {
return table.getDiskSpaceUsed();
}
private static Value getNullOrValue(Session session, Expression[] args,
protected static Value getNullOrValue(Session session, Expression[] args,
Value[] values, int i) {
if (i >= args.length) {
return null;
......@@ -1106,7 +1119,7 @@ public class Function extends Expression implements FunctionCall {
return v;
}
private Value getValueWithArgs(Session session, Expression[] args) {
protected Value getValueWithArgs(Session session, Expression[] args) {
Value[] values = new Value[args.length];
if (info.nullIfParameterIsNull) {
for (int i = 0; i < args.length; i++) {
......
......@@ -8,41 +8,91 @@ package org.h2.expression;
/**
* This class contains information about a built-in function.
*/
class FunctionInfo {
public final class FunctionInfo {
/**
* The name of the function.
*/
String name;
public final String name;
/**
* The function type.
*/
int type;
public final int type;
/**
* The data type of the return value.
* The number of parameters.
*/
int returnDataType;
final int parameterCount;
/**
* The number of parameters.
* The data type of the return value.
*/
int parameterCount;
public final int returnDataType;
/**
* If the result of the function is NULL if any of the parameters is NULL.
*/
boolean nullIfParameterIsNull;
final boolean nullIfParameterIsNull;
/**
* If this function always returns the same value for the same parameters.
*/
boolean deterministic;
public final boolean deterministic;
/**
* Should the return value ResultSet be buffered in a local temporary file?
*/
boolean bufferResultSetToLocalTemp = true;
final boolean bufferResultSetToLocalTemp;
/**
* Creates new instance of built-in function information.
*
* @param name
* the name of the function
* @param type
* the function type
* @param parameterCount
* the number of parameters
* @param returnDataType
* the data type of the return value
* @param nullIfParameterIsNull
* if the result of the function is NULL if any of the parameters
* is NULL
* @param deterministic
* if this function always returns the same value for the same
* parameters
* @param bufferResultSetToLocalTemp
* should the return value ResultSet be buffered in a local
* temporary file?
*/
public FunctionInfo(String name, int type, int parameterCount, int returnDataType, boolean nullIfParameterIsNull,
boolean deterministic, boolean bufferResultSetToLocalTemp) {
this.name = name;
this.type = type;
this.parameterCount = parameterCount;
this.returnDataType = returnDataType;
this.nullIfParameterIsNull = nullIfParameterIsNull;
this.deterministic = deterministic;
this.bufferResultSetToLocalTemp = bufferResultSetToLocalTemp;
}
/**
* Creates a copy of built-in function information with a different name.
*
* @param source
* the source information
* @param name
* the new name
*/
public FunctionInfo(FunctionInfo source, String name) {
this.name = name;
type = source.type;
returnDataType = source.returnDataType;
parameterCount = source.parameterCount;
nullIfParameterIsNull = source.nullIfParameterIsNull;
deterministic = source.deterministic;
bufferResultSetToLocalTemp = source.bufferResultSetToLocalTemp;
}
}
/*
* Copyright 2004-2018 H2 Group. Multiple-Licensed under the MPL 2.0,
* and the EPL 1.0 (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.mode;
import java.util.HashMap;
import org.h2.engine.Database;
import org.h2.expression.Function;
import org.h2.expression.FunctionInfo;
/**
* Base class for mode-specific functions.
*/
abstract class FunctionsBase extends Function {
FunctionsBase(Database database, FunctionInfo info) {
super(database, info);
}
/**
* Copy a standard function to a mode functions with a different name.
*
* @param functions
* mode functions
* @param stdName
* the name of the standard function
* @param newName
* the name of the mode-specific function
*/
static void copyFunction(HashMap<String, FunctionInfo> functions, String stdName, String newName) {
functions.put(newName, new FunctionInfo(Function.getFunctionInfo(stdName), newName));
}
}
/*
* Copyright 2004-2018 H2 Group. Multiple-Licensed under the MPL 2.0,
* and the EPL 1.0 (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.mode;
import java.util.HashMap;
import org.h2.engine.Database;
import org.h2.expression.Function;
import org.h2.expression.FunctionInfo;
/**
* Functions for {@link org.h2.engine.Mode.ModeEnum#MSSQLServer} compatibility
* mode.
*/
public final class FunctionsMSSQLServer extends FunctionsBase {
private static final HashMap<String, FunctionInfo> FUNCTIONS = new HashMap<>();
static {
copyFunction(FUNCTIONS, "LOCATE", "CHARINDEX");
copyFunction(FUNCTIONS, "CURRENT_DATE", "GETDATE");
copyFunction(FUNCTIONS, "LENGTH", "LEN");
copyFunction(FUNCTIONS, "RANDOM_UUID", "NEWID");
}
/**
* Returns mode-specific function for a given name, or {@code null}.
*
* @param database
* the database
* @param upperName
* the upper-case name of a function
* @return the function with specified name or {@code null}
*/
public static Function getFunction(Database database, String upperName) {
FunctionInfo info = FUNCTIONS.get(upperName);
return info != null ? new Function(database, info) : null;
}
private FunctionsMSSQLServer(Database database, FunctionInfo info) {
super(database, info);
}
}
......@@ -5,14 +5,24 @@
*/
package org.h2.mode;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Locale;
import org.h2.api.ErrorCode;
import org.h2.engine.Database;
import org.h2.engine.Session;
import org.h2.expression.Expression;
import org.h2.expression.Function;
import org.h2.expression.FunctionInfo;
import org.h2.expression.ValueExpression;
import org.h2.message.DbException;
import org.h2.util.StringUtils;
import org.h2.value.DataType;
import org.h2.value.Value;
import org.h2.value.ValueInt;
import org.h2.value.ValueString;
/**
* This class implements some MySQL-specific functions.
......@@ -20,7 +30,20 @@ import org.h2.util.StringUtils;
* @author Jason Brittain
* @author Thomas Mueller
*/
public class FunctionsMySQL {
public class FunctionsMySQL extends FunctionsBase {
private static final int UNIX_TIMESTAMP = 1001, FROM_UNIXTIME = 1002, DATE = 1003;
private static final HashMap<String, FunctionInfo> FUNCTIONS = new HashMap<>();
static {
FUNCTIONS.put("UNIX_TIMESTAMP", new FunctionInfo("UNIX_TIMESTAMP", UNIX_TIMESTAMP,
VAR_ARGS, Value.INT, false, false, false));
FUNCTIONS.put("FROM_UNIXTIME", new FunctionInfo("FROM_UNIXTIME", FROM_UNIXTIME,
VAR_ARGS, Value.STRING, false, true, false));
FUNCTIONS.put("DATE", new FunctionInfo("DATE", DATE,
1, Value.DATE, false, true, false));
}
/**
* The date format of a MySQL formatted date/time.
......@@ -60,27 +83,6 @@ public class FunctionsMySQL {
"%%", "%",
};
/**
* Register the functionality in the database.
* Nothing happens if the functions are already registered.
*
* @param conn the connection
*/
public static void register(Connection conn) throws SQLException {
String[] init = {
"UNIX_TIMESTAMP", "unixTimestamp",
"FROM_UNIXTIME", "fromUnixTime",
"DATE", "date",
};
Statement stat = conn.createStatement();
for (int i = 0; i < init.length; i += 2) {
String alias = init[i], method = init[i + 1];
stat.execute(
"CREATE ALIAS IF NOT EXISTS " + alias +
" FOR \"" + FunctionsMySQL.class.getName() + "." + method + "\"");
}
}
/**
* Get the seconds since 1970-01-01 00:00:00 UTC.
* See
......@@ -140,24 +142,104 @@ public class FunctionsMySQL {
}
/**
* See
* http://dev.mysql.com/doc/refman/5.1/en/date-and-time-functions.html#function_date
* This function is dependent on the exact formatting of the MySQL date/time
* string.
* Returns mode-specific function for a given name, or {@code null}.
*
* @param dateTime The date/time String from which to extract just the date
* part.
* @return the date part of the given date/time String argument.
* @param database
* the database
* @param upperName
* the upper-case name of a function
* @return the function with specified name or {@code null}
*/
public static String date(String dateTime) {
if (dateTime == null) {
return null;
public static Function getFunction(Database database, String upperName) {
FunctionInfo info = FUNCTIONS.get(upperName);
return info != null ? new FunctionsMySQL(database, info) : null;
}
FunctionsMySQL(Database database, FunctionInfo info) {
super(database, info);
}
@Override
protected void checkParameterCount(int len) {
int min, max;
switch (info.type) {
case UNIX_TIMESTAMP:
min = 0;
max = 2;
break;
case FROM_UNIXTIME:
min = 1;
max = 2;
break;
case DATE:
min = 1;
max = 1;
break;
default:
DbException.throwInternalError("type=" + info.type);
return;
}
if (len < min || len > max) {
throw DbException.get(ErrorCode.INVALID_PARAMETER_COUNT_2, info.name, min + ".." + max);
}
}
@Override
public Expression optimize(Session session) {
boolean allConst = info.deterministic;
for (int i = 0; i < args.length; i++) {
Expression e = args[i];
if (e == null) {
continue;
}
e = e.optimize(session);
args[i] = e;
if (!e.isConstant()) {
allConst = false;
}
}
if (allConst) {
return ValueExpression.get(getValue(session));
}
dataType = info.returnDataType;
DataType dt = DataType.getDataType(dataType);
precision = dt.defaultPrecision;
scale = dt.defaultScale;
displaySize = dt.defaultDisplaySize;
return this;
}
@Override
protected Value getValueWithArgs(Session session, Expression[] args) {
Value[] values = new Value[args.length];
Value v0 = getNullOrValue(session, args, values, 0);
Value v1 = getNullOrValue(session, args, values, 1);
Value result;
switch (info.type) {
case UNIX_TIMESTAMP:
result = ValueInt.get(v0 == null ? unixTimestamp() : unixTimestamp(v0.getTimestamp()));
break;
case FROM_UNIXTIME:
result = ValueString.get(
v1 == null ? fromUnixTime(v0.getInt()) : fromUnixTime(v0.getInt(), v1.getString()));
break;
case DATE:
switch (v0.getType()) {
case Value.DATE:
result = v0;
break;
default:
v0 = v0.convertTo(Value.TIMESTAMP);
//$FALL-THROUGH$
case Value.TIMESTAMP:
case Value.TIMESTAMP_TZ:
result = v0.convertTo(Value.DATE);
}
int index = dateTime.indexOf(' ');
if (index != -1) {
return dateTime.substring(0, index);
break;
default:
throw DbException.throwInternalError("type=" + info.type);
}
return dateTime;
return result;
}
}
......@@ -307,7 +307,6 @@ public class TestCompatibility extends TestDb {
stat.execute("DROP TABLE IF EXISTS TEST");
stat.execute("CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR)");
stat.execute("INSERT INTO TEST VALUES(1, 'Hello'), (2, 'World')");
org.h2.mode.FunctionsMySQL.register(conn);
assertResult("0", stat, "SELECT UNIX_TIMESTAMP('1970-01-01 00:00:00Z')");
assertResult("1196418619", stat,
"SELECT UNIX_TIMESTAMP('2007-11-30 10:30:19Z')");
......
......@@ -2,3 +2,21 @@
-- and the EPL 1.0 (http://h2database.com/html/license.html).
-- Initial Developer: H2 Group
--
SELECT LENGTH(CAST(RANDOM_UUID() AS VARCHAR));
>> 36
SELECT RANDOM_UUID() = RANDOM_UUID();
>> FALSE
SELECT NEWID();
> exception FUNCTION_NOT_FOUND_1
SET MODE MSSQLServer;
> ok
SELECT LENGTH(CAST(NEWID() AS VARCHAR));
>> 36
SET MODE Regular;
> ok
......@@ -9,8 +9,23 @@ create memory table test(id int primary key, name varchar(255));
insert into test values(1, 'Hello');
> update count: 1
select length(null) en, len(null) en2, length('This has 17 chars') e_17, len('MSSQLServer uses the len keyword') e_32 from test;
> EN EN2 E_17 E_32
> ---- ---- ---- ----
> null null 17 32
select length(null) en, length('This has 17 chars') e_17 from test;
> EN E_17
> ---- ----
> null 17
> rows: 1
SELECT LEN(NULL);
> exception FUNCTION_NOT_FOUND_1
SET MODE MSSQLServer;
> ok
select len(null) en, len('MSSQLServer uses the len keyword') e_32 from test;
> EN E_32
> ---- ----
> null 32
> rows: 1
SET MODE Regular;
> ok
\ No newline at end of file
......@@ -20,3 +20,18 @@ select locate('World', 'Hello World') e7, locate('hi', 'abchihihi', 2) e3 from t
> -- --
> 7 4
> rows: 1
SELECT CHARINDEX('test', 'test');
> exception FUNCTION_NOT_FOUND_1
SET MODE MSSQLServer;
> ok
select charindex('World', 'Hello World') e7, charindex('hi', 'abchihihi', 2) e3 from test;
> E7 E3
> -- --
> 7 4
> rows: 1
SET MODE Regular;
> ok
......@@ -14,3 +14,15 @@ select length(curdate()) c1, length(current_date()) c2, substring(curdate(), 5,
> -- -- --
> 10 10 -
> rows: 1
SELECT GETDATE();
> exception FUNCTION_NOT_FOUND_1
SET MODE MSSQLServer;
> ok
SELECT CURRENT_DATE = GETDATE();
>> TRUE
SET MODE Regular;
> ok
\ No newline at end of file
......@@ -524,7 +524,6 @@ public class Build extends BuildBase {
exclude("temp/org/h2/jcr/*").
exclude("temp/org/h2/java/*").
exclude("temp/org/h2/jcr/*").
exclude("temp/org/h2/mode/*").
exclude("temp/org/h2/samples/*").
exclude("temp/org/h2/server/ftp/*").
exclude("temp/org/h2/test/*").
......@@ -594,7 +593,6 @@ public class Build extends BuildBase {
exclude("temp/org/h2/jcr/*").
exclude("temp/org/h2/java/*").
exclude("temp/org/h2/jcr/*").
exclude("temp/org/h2/mode/*").
exclude("temp/org/h2/samples/*").
exclude("temp/org/h2/server/ftp/*").
exclude("temp/org/h2/test/*").
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论