/**
* Copyright (C) 2009-2013 FoundationDB, LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.foundationdb.server.types.mcompat.mfuncs;
import com.foundationdb.server.error.InvalidDateFormatException;
import com.foundationdb.server.types.LazyList;
import com.foundationdb.server.types.TClass;
import com.foundationdb.server.types.TExecutionContext;
import com.foundationdb.server.types.TOverloadResult;
import com.foundationdb.server.types.TScalar;
import com.foundationdb.server.types.mcompat.mtypes.MDateAndTime;
import com.foundationdb.server.types.mcompat.mtypes.MDateAndTime.StringType;
import com.foundationdb.server.types.mcompat.mtypes.MDateAndTime.ZeroFlag;
import com.foundationdb.server.types.mcompat.mtypes.MNumeric;
import com.foundationdb.server.types.mcompat.mtypes.MString;
import com.foundationdb.server.types.value.ValueSource;
import com.foundationdb.server.types.value.ValueTarget;
import com.foundationdb.server.types.texpressions.TInputSetBuilder;
import com.foundationdb.server.types.texpressions.TScalarBase;
import com.foundationdb.sql.parser.TernaryOperatorNode;
public class MTimestampDiff extends TScalarBase
{
public static TScalar[] create()
{
ArgType args[] = ArgType.values();
TScalar ret[] = new TScalar[args.length * args.length + 1];
int n = 0;
for (ArgType arg1 : args)
for (ArgType arg2 : args)
ret[n++] = new MTimestampDiff(arg1, arg2)
{
public int[] getPriorities() {return new int[] {0};}
};
// create a second group prio group, forcing all arguments be
// casted to the formal types
ret[n] = new MTimestampDiff(ArgType.TIMESTAMP, ArgType.TIMESTAMP)
{
public int[] getPriorities() {return new int[] {1};}
};
return ret;
}
private static enum ArgType
{
DATE(MDateAndTime.DATE)
{
@Override
long[] getYMD(ValueSource source, TExecutionContext context)
{
int date = source.getInt32();
long ymd[] = MDateAndTime.decodeDate(date);
if (MDateAndTime.isValidDateTime(ymd, ZeroFlag.YEAR))
return ymd;
else
{
context.warnClient(new InvalidDateFormatException("DATE",
MDateAndTime.dateToString(date)));
return null;
}
}
},
DATETIME(MDateAndTime.DATETIME)
{
@Override
long[] getYMD(ValueSource source, TExecutionContext context)
{
long datetime = source.getInt64();
long ymd[] = MDateAndTime.decodeDateTime(datetime);
if (MDateAndTime.isValidDateTime(ymd, ZeroFlag.YEAR))
return ymd;
else
{
context.warnClient(new InvalidDateFormatException("DATETIME",
MDateAndTime.dateTimeToString(datetime)));
return null;
}
}
},
TIMESTAMP(MDateAndTime.TIMESTAMP)
{
@Override
long [] getYMD(ValueSource source, TExecutionContext context)
{
return MDateAndTime.decodeTimestamp(source.getInt32(), "UTC"/*context.getCurrentTimezone()*/);
}
// override this because TIMESTAMP type doesn't need to go thru the decoding process
// just return whatever is passed in
@Override
Long getUnix(ValueSource source, TExecutionContext context)
{
return source.getInt32() * 1000L; // unix
}
},
VARCHAR(MString.VARCHAR)
{
@Override
long [] getYMD(ValueSource source, TExecutionContext context)
{
long ymd[] = new long[6];
StringType strType = MDateAndTime.parseDateOrTime(source.getString(), ymd);
if (strType == StringType.TIME_ST || !MDateAndTime.isValidType(strType)) {
context.warnClient(new InvalidDateFormatException("DATETIME", source.getString()));
return null;
}
return ymd;
}
}
;
abstract long[] getYMD(ValueSource source, TExecutionContext context);
Long getUnix(ValueSource source, TExecutionContext context)
{
long ymd[] = getYMD(source, context);
return ymd == null
? null
: MDateAndTime.getTimestamp(ymd, "UTC") * 1000L; // use UTC to do the computation
}
private ArgType(TClass type)
{
this.type = type;
}
private final TClass type;
}
private final ArgType arg1;
private final ArgType arg2;
private MTimestampDiff(ArgType arg1, ArgType arg2)
{
this.arg1 = arg1;
this.arg2 = arg2;
}
@Override
protected void buildInputSets(TInputSetBuilder builder)
{
builder.covers(MNumeric.INT, 0).covers(arg1.type, 1).covers(arg2.type, 2);
}
@Override
protected void doEvaluate(TExecutionContext context, LazyList<? extends ValueSource> inputs, ValueTarget output)
{
int unit = inputs.get(0).getInt32();
ValueSource date1 = inputs.get(1);
ValueSource date2 = inputs.get(2);
switch(unit)
{
case TernaryOperatorNode.YEAR_INTERVAL:
case TernaryOperatorNode.QUARTER_INTERVAL:
case TernaryOperatorNode.MONTH_INTERVAL:
doMonthSubtraction(arg2.getYMD(date2, context),
arg1.getYMD(date1, context),
MONTH_DIV[unit - MONTH_BASE],
output);
break;
case TernaryOperatorNode.WEEK_INTERVAL:
case TernaryOperatorNode.DAY_INTERVAL:
case TernaryOperatorNode.HOUR_INTERVAL:
case TernaryOperatorNode.MINUTE_INTERVAL:
case TernaryOperatorNode.SECOND_INTERVAL:
case TernaryOperatorNode.FRAC_SECOND_INTERVAL:
Long unix1, unix2 = 0L;
if ((unix1 = arg1.getUnix(date1, context)) == null
|| (unix2 = arg2.getUnix(date2, context)) == null)
output.putNull();
else
output.putInt64((unix2 - unix1) / MILLIS_DIV[unit - MILLIS_BASE]);
break;
default:
throw new UnsupportedOperationException("Unknown UNIT: " + unit);
}
}
@Override
public String displayName()
{
return "TIMESTAMPDIFF";
}
@Override
public TOverloadResult resultType()
{
return TOverloadResult.fixed(MNumeric.BIGINT, 21);
}
// ------------ static members --------------
private static final long[] MILLIS_DIV = new long[6];
private static final int[] MONTH_DIV = {12, 4, 1};
private static final int MILLIS_BASE = TernaryOperatorNode.WEEK_INTERVAL;
private static final int MONTH_BASE = TernaryOperatorNode.YEAR_INTERVAL;
static
{
int mul[] ={7, 24, 60, 60, 1000};
MILLIS_DIV[5] = 1;
for (int n = 4; n >= 0; --n)
MILLIS_DIV[n] = MILLIS_DIV[n + 1] * mul[n];
}
private static void doMonthSubtraction (long d1[], long d2[], long divisor, ValueTarget out)
{
if (d1 == null || d2 == null)
{
out.putNull();
return;
}
long ret = (d1[0] - d2[0]) * 12 + d1[1] - d2[1];
// adjust the day difference
if (ret > 0 && d1[2] < d2[2]) --ret;
else if (ret < 0 && d1[2] > d2[2]) ++ret;
out.putInt64(ret / divisor);
}
}