提交 326b51e6 authored 作者: Evgenij Ryazanov's avatar Evgenij Ryazanov

Add some arithmetic operations for INTERVAL data type

上级 eae51e9e
/*
* 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.expression;
import java.math.BigDecimal;
import java.math.BigInteger;
import org.h2.api.IntervalQualifier;
import org.h2.engine.Session;
import org.h2.message.DbException;
import org.h2.table.ColumnResolver;
import org.h2.table.TableFilter;
import org.h2.util.DateTimeUtils;
import org.h2.value.Value;
import org.h2.value.ValueInterval;
import org.h2.value.ValueNull;
/**
* A mathematical operation with intervals.
*/
public class IntervalOperation extends Expression {
public enum IntervalOpType {
/**
* Interval plus interval.
*/
INTERVAL_PLUS_INTERVAL,
/**
* Interval minus interval.
*/
INTERVAL_MINUS_INTERVAL,
/**
* Date-time plus interval.
*/
DATETIME_PLUS_INTERVAL,
/**
* Date-time minus interval.
*/
DATETIME_MINUS_INTERVAL,
/**
* Interval multiplied by numeric.
*/
INTERVAL_MULTIPLY_NUMERIC,
/**
* Interval divided by numeric.
*/
INTERVAL_DIVIDE_NUMERIC
}
private IntervalOpType opType;
private Expression left, right;
private int dataType;
public IntervalOperation(IntervalOpType opType, Expression left, Expression right) {
this.opType = opType;
this.left = left;
this.right = right;
switch (opType) {
case INTERVAL_PLUS_INTERVAL:
case INTERVAL_MINUS_INTERVAL:
dataType = Value.getHigherOrder(left.getType(), right.getType());
break;
case DATETIME_PLUS_INTERVAL:
case DATETIME_MINUS_INTERVAL:
case INTERVAL_MULTIPLY_NUMERIC:
case INTERVAL_DIVIDE_NUMERIC:
dataType = left.getType();
}
}
@Override
public String getSQL() {
return '(' + left.getSQL() + ' ' + getOperationToken() + ' ' + right.getSQL() + ')';
}
private char getOperationToken() {
switch (opType) {
case INTERVAL_PLUS_INTERVAL:
case DATETIME_PLUS_INTERVAL:
return '+';
case INTERVAL_MINUS_INTERVAL:
case DATETIME_MINUS_INTERVAL:
return '-';
case INTERVAL_MULTIPLY_NUMERIC:
return '*';
case INTERVAL_DIVIDE_NUMERIC:
return '/';
default:
throw DbException.throwInternalError("opType=" + opType);
}
}
@Override
public Value getValue(Session session) {
Value l = left.getValue(session);
Value r = right.getValue(session);
if (l == ValueNull.INSTANCE || r == ValueNull.INSTANCE) {
return ValueNull.INSTANCE;
}
switch (opType) {
case INTERVAL_PLUS_INTERVAL:
case INTERVAL_MINUS_INTERVAL: {
BigInteger a1 = DateTimeUtils.intervalToAbsolute((ValueInterval) l);
BigInteger a2 = DateTimeUtils.intervalToAbsolute((ValueInterval) r);
return DateTimeUtils.intervalFromAbsolute(
IntervalQualifier.valueOf(Value.getHigherOrder(l.getType(), r.getType()) - Value.INTERVAL_YEAR),
opType == IntervalOpType.INTERVAL_PLUS_INTERVAL ? a1.add(a2) : a1.subtract(a2));
}
case DATETIME_PLUS_INTERVAL:
case DATETIME_MINUS_INTERVAL:
// TODO
throw DbException.throwInternalError("type=" + opType);
case INTERVAL_MULTIPLY_NUMERIC:
case INTERVAL_DIVIDE_NUMERIC: {
BigDecimal a1 = new BigDecimal(DateTimeUtils.intervalToAbsolute((ValueInterval) l));
BigDecimal a2 = r.getBigDecimal();
return DateTimeUtils.intervalFromAbsolute(IntervalQualifier.valueOf(l.getType() - Value.INTERVAL_YEAR),
(opType == IntervalOpType.INTERVAL_MULTIPLY_NUMERIC ? a1.multiply(a2) : a1.divide(a2))
.toBigInteger());
}
default:
throw DbException.throwInternalError("type=" + opType);
}
}
@Override
public void mapColumns(ColumnResolver resolver, int level) {
left.mapColumns(resolver, level);
if (right != null) {
right.mapColumns(resolver, level);
}
}
@Override
public Expression optimize(Session session) {
left = left.optimize(session);
right = right.optimize(session);
if (left.isConstant() && right.isConstant()) {
return ValueExpression.get(getValue(session));
}
return this;
}
@Override
public void setEvaluatable(TableFilter tableFilter, boolean b) {
left.setEvaluatable(tableFilter, b);
right.setEvaluatable(tableFilter, b);
}
@Override
public int getType() {
return dataType;
}
@Override
public long getPrecision() {
return Math.max(left.getPrecision(), right.getPrecision());
}
@Override
public int getDisplaySize() {
return Math.max(left.getDisplaySize(), right.getDisplaySize());
}
@Override
public int getScale() {
return Math.max(left.getScale(), right.getScale());
}
@Override
public void updateAggregate(Session session) {
left.updateAggregate(session);
right.updateAggregate(session);
}
@Override
public boolean isEverything(ExpressionVisitor visitor) {
return left.isEverything(visitor) && right.isEverything(visitor);
}
@Override
public int getCost() {
return left.getCost() + 1 + right.getCost();
}
/**
* Get the left sub-expression of this operation.
*
* @return the left sub-expression
*/
public Expression getLeftSubExpression() {
return left;
}
/**
* Get the right sub-expression of this operation.
*
* @return the right sub-expression
*/
public Expression getRightSubExpression() {
return right;
}
}
......@@ -7,6 +7,7 @@ package org.h2.expression;
import org.h2.engine.Mode;
import org.h2.engine.Session;
import org.h2.expression.IntervalOperation.IntervalOpType;
import org.h2.message.DbException;
import org.h2.table.ColumnResolver;
import org.h2.table.TableFilter;
......@@ -216,6 +217,8 @@ public class Operation extends Expression {
} else {
dataType = Value.DECIMAL;
}
} else if (DataType.isIntervalType(l) || DataType.isIntervalType(r)) {
return optimizeInterval(session, l, r);
} else if (DataType.isDateTimeType(l) || DataType.isDateTimeType(r)) {
return optimizeDateTime(session, l, r);
} else {
......@@ -237,6 +240,65 @@ public class Operation extends Expression {
return this;
}
private Expression optimizeInterval(Session session, int l, int r) {
boolean lInterval = false, lNumeric = false, lDateTime = false;
if (DataType.isIntervalType(l)) {
lInterval = true;
} else if (DataType.isNumericType(l)) {
lNumeric = true;
} else if (DataType.isDateTimeType(l)) {
lDateTime = true;
} else {
throw getUnsupported(l, r);
}
boolean rInterval = false, rNumeric = false, rDateTime = false;
if (DataType.isIntervalType(r)) {
rInterval = true;
} else if (DataType.isNumericType(r)) {
rNumeric = true;
} else if (DataType.isDateTimeType(r)) {
rDateTime = true;
} else {
throw getUnsupported(l, r);
}
switch (opType) {
case PLUS:
if (lInterval && rInterval) {
if (DataType.isYearMonthIntervalType(l) == DataType.isYearMonthIntervalType(r)) {
return new IntervalOperation(IntervalOpType.INTERVAL_PLUS_INTERVAL, left, right);
}
} else if (lInterval && rDateTime) {
return new IntervalOperation(IntervalOpType.DATETIME_PLUS_INTERVAL, right, left);
} else if (lDateTime && rInterval) {
return new IntervalOperation(IntervalOpType.DATETIME_PLUS_INTERVAL, left, right);
}
break;
case MINUS:
if (lInterval && rInterval) {
if (DataType.isYearMonthIntervalType(l) == DataType.isYearMonthIntervalType(r)) {
return new IntervalOperation(IntervalOpType.INTERVAL_MINUS_INTERVAL, left, right);
}
} else if (lDateTime && rInterval) {
return new IntervalOperation(IntervalOpType.DATETIME_MINUS_INTERVAL, left, right);
}
break;
case MULTIPLY:
if (lInterval && rNumeric) {
return new IntervalOperation(IntervalOpType.INTERVAL_MULTIPLY_NUMERIC, left, right);
} else if (lNumeric && rInterval) {
return new IntervalOperation(IntervalOpType.INTERVAL_MULTIPLY_NUMERIC, right, left);
}
break;
case DIVIDE:
if (lInterval && rNumeric) {
return new IntervalOperation(IntervalOpType.INTERVAL_DIVIDE_NUMERIC, left, right);
}
break;
default:
}
throw getUnsupported(l, r);
}
private Expression optimizeDateTime(Session session, int l, int r) {
switch (opType) {
case PLUS:
......@@ -357,10 +419,12 @@ public class Operation extends Expression {
break;
default:
}
throw DbException.getUnsupportedException(
DataType.getDataType(l).name + " " +
getOperationToken() + " " +
DataType.getDataType(r).name);
throw getUnsupported(l, r);
}
private DbException getUnsupported(int l, int r) {
return DbException.getUnsupportedException(
DataType.getDataType(l).name + ' ' + getOperationToken() + ' ' + DataType.getDataType(r).name);
}
private void swap() {
......
......@@ -1340,6 +1340,16 @@ public class DataType {
return type >= Value.INTERVAL_YEAR && type <= Value.INTERVAL_MINUTE_TO_SECOND;
}
/**
* Check if the given value type is a year-month interval type.
*
* @param type the value type
* @return true if the value type is a year-month interval type
*/
public static boolean isYearMonthIntervalType(int type) {
return type == Value.INTERVAL_YEAR || type == Value.INTERVAL_MONTH || type == Value.INTERVAL_YEAR_TO_MONTH;
}
/**
* Check if the given value type is a large object (BLOB or CLOB).
*
......@@ -1350,6 +1360,16 @@ public class DataType {
return type == Value.BLOB || type == Value.CLOB;
}
/**
* Check if the given value type is a numeric type.
*
* @param type the value type
* @return true if the value type is a numeric type
*/
public static boolean isNumericType(int type) {
return type >= Value.BYTE && type <= Value.FLOAT;
}
/**
* Check if the given value type is a String (VARCHAR,...).
*
......
......@@ -432,3 +432,35 @@ SELECT CAST(INTERVAL '-1803:4.123456789' MINUTE TO SECOND AS INTERVAL HOUR TO SE
SELECT CAST(INTERVAL '10:11.123456789' MINUTE TO SECOND AS INTERVAL SECOND(3, 9));
>> INTERVAL '611.123456789' SECOND
-- Arithmetic
SELECT INTERVAL '1000' SECOND + INTERVAL '10' MINUTE;
>> INTERVAL '1600' SECOND
SELECT INTERVAL '1000' SECOND - INTERVAL '10' MINUTE;
>> INTERVAL '400' SECOND
SELECT INTERVAL '10' YEAR + INTERVAL '1' MONTH;
>> INTERVAL '121' MONTH
SELECT INTERVAL '10' YEAR - INTERVAL '1' MONTH;
>> INTERVAL '119' MONTH
SELECT INTERVAL '1000' SECOND * 2;
>> INTERVAL '2000' SECOND
SELECT 2 * INTERVAL '1000' SECOND;
>> INTERVAL '2000' SECOND
SELECT INTERVAL '1000' SECOND / 2;
>> INTERVAL '500' SECOND
SELECT INTERVAL '10' YEAR * 2;
>> INTERVAL '20' YEAR
SELECT 2 * INTERVAL '10' YEAR;
>> INTERVAL '20' YEAR
SELECT INTERVAL '10' YEAR / 2;
>> INTERVAL '5' YEAR
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论