/**
* 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.error.InvalidParameterValueException;
import com.foundationdb.server.types.LazyList;
import com.foundationdb.server.types.TClass;
import com.foundationdb.server.types.TExecutionContext;
import com.foundationdb.server.types.TScalar;
import com.foundationdb.server.types.TOverloadResult;
import com.foundationdb.server.types.mcompat.mtypes.MDateAndTime;
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;
public abstract class MExtractField extends TScalarBase
{
private static int MAX_YEAR = 9999;
public static final TScalar INSTANCES[] = new TScalar[]
{
new MExtractField("YEAR", MDateAndTime.DATE, Decoder.DATE)
{
@Override
protected int getField(long[] ymd, TExecutionContext context)
{
int ret = (int) ymd[MDateAndTime.YEAR_INDEX];
return ret > MAX_YEAR ? -1 : ret; // mysql caps the output to [0, 9999]
}
},
new MExtractField("QUARTER", MDateAndTime.DATE, Decoder.DATE)
{
@Override
protected int getField(long[] ymd, TExecutionContext context)
{
if (MDateAndTime.isZeroDayMonth(ymd))
return 0;
int month = (int) ymd[MDateAndTime.MONTH_INDEX];
if (month < 4) return 1;
else if (month < 7) return 2;
else if (month < 10) return 3;
else return 4;
}
},
new MExtractField("MONTH", MDateAndTime.DATE, Decoder.DATE)
{
@Override
protected int getField(long[] ymd, TExecutionContext context)
{
return (int) ymd[MDateAndTime.MONTH_INDEX];
}
},
new MExtractField("DAYOFWEEK", MDateAndTime.DATE, Decoder.DATE)
{
@Override
protected int getField(long[] ymd, TExecutionContext context)
{
return MDateAndTime.isZeroDayMonth(ymd)
? -1
// mysql: (1 = Sunday, 2 = Monday, …, 7 = Saturday
// joda (7 = Sunday, 1 = mon, l...., 6 = Saturday
: MDateAndTime.toJodaDateTime(ymd, context.getCurrentTimezone()).getDayOfWeek()
% 7 + 1;
}
},
new MExtractField("WEEKDAY", MDateAndTime.DATE, Decoder.DATE)
{
@Override
protected int getField(long[] ymd, TExecutionContext context)
{
return MDateAndTime.isZeroDayMonth(ymd)
? -1
//mysql: (0 = Monday, 1 = Tuesday, … 6 = Sunday).
//joda: mon = 1, ..., sat = 6, sun = 7
: MDateAndTime.toJodaDateTime(ymd, context.getCurrentTimezone()).getDayOfWeek() - 1;
}
},
new MExtractField("LAST_DAY", MDateAndTime.DATE, Decoder.DATE)
{
@Override
protected int getField(long[] ymd, TExecutionContext context)
{
if (MDateAndTime.isZeroDayMonth(ymd))
return -1;
ymd[2] = MDateAndTime.getLastDay(ymd);
return MDateAndTime.encodeDate(ymd);
}
@Override
public TOverloadResult resultType() {
return TOverloadResult.fixed(MDateAndTime.DATE);
}
},
new MExtractField("DAYOFYEAR", MDateAndTime.DATE, Decoder.DATE)
{
@Override
protected int getField(long[] ymd, TExecutionContext context)
{
return MDateAndTime.isZeroDayMonth(ymd)
? -1
: MDateAndTime.toJodaDateTime(ymd, context.getCurrentTimezone()).getDayOfYear();
}
},
new MExtractField("DAY", MDateAndTime.DATE, Decoder.DATE) // day of month
{
@Override
public String[] registeredNames()
{
return new String[]{"DAYOFMONTH", "DAY"};
}
@Override
protected int getField(long[] ymd, TExecutionContext context)
{
return (int) ymd[MDateAndTime.DAY_INDEX];
}
},
new MExtractField("HOUR", MDateAndTime.TIME, Decoder.TIME)
{
@Override
protected int getField(long[] ymd, TExecutionContext context)
{
// select hour('-10:10:10') should just return 10
return Math.abs((int) ymd[MDateAndTime.HOUR_INDEX]);
}
},
new MExtractField("MINUTE", MDateAndTime.TIME, Decoder.TIME)
{
@Override
protected int getField(long[] ymd, TExecutionContext context)
{
return (int) ymd[MDateAndTime.MIN_INDEX];
}
},
new MExtractField("SECOND", MDateAndTime.TIME, Decoder.TIME)
{
@Override
protected int getField(long[] ymd, TExecutionContext context)
{
return (int) ymd[MDateAndTime.SEC_INDEX];
}
},
new TScalarBase() // DAYNAME
{
@Override
protected void buildInputSets(TInputSetBuilder builder)
{
builder.covers(MDateAndTime.DATE, 0);
}
@Override
protected void doEvaluate(TExecutionContext context, LazyList<? extends ValueSource> inputs, ValueTarget output)
{
int date = inputs.get(0).getInt32();
long ymd[] = MDateAndTime.decodeDate(date);
if(!MDateAndTime.isValidDate(ymd, MDateAndTime.ZeroFlag.YEAR))
{
output.putNull();
context.warnClient(new InvalidDateFormatException("DATE", MDateAndTime.dateToString(date)));
return;
}
String dayName = MDateAndTime.toJodaDateTime(ymd, context.getCurrentTimezone()).dayOfWeek().
getAsText(context.getCurrentLocale());
output.putString(dayName, null);
}
@Override
public String displayName()
{
return "DAYNAME";
}
@Override
public TOverloadResult resultType()
{
return TOverloadResult.fixed(MString.VARCHAR, 9);
}
},
new TScalarBase() // MONTHNAME
{
@Override
protected void buildInputSets(TInputSetBuilder builder)
{
builder.covers(MDateAndTime.DATE, 0);
}
@Override
protected void doEvaluate(TExecutionContext context, LazyList<? extends ValueSource> inputs, ValueTarget output)
{
int date = inputs.get(0).getInt32();
long ymd[] = MDateAndTime.decodeDate(date);
if (!MDateAndTime.isValidDateTime(ymd, MDateAndTime.ZeroFlag.YEAR, MDateAndTime.ZeroFlag.DAY))
{
output.putNull();
context.warnClient(new InvalidDateFormatException("DATE", MDateAndTime.dateToString(date)));
return;
}
int numericMonth = (int) MDateAndTime.decodeDate(inputs.get(0).getInt32())[MDateAndTime.MONTH_INDEX];
String month = MDateAndTime.getMonthName(numericMonth,
context.getCurrentLocale().getLanguage(),
context);
output.putString(month, null);
}
@Override
public String displayName()
{
return "MONTHNAME";
}
@Override
public TOverloadResult resultType()
{
return TOverloadResult.fixed(MString.VARCHAR, 9);
}
}
};
protected abstract int getField(long ymd[], TExecutionContext context);
static enum Decoder
{
DATE
{
@Override
long[] decode(long val)
{
long ret[] = MDateAndTime.decodeDate(val);
if (!MDateAndTime.isValidDate_Zeros(ret))
return null;
else
return ret;
}
},
DATETIME
{
@Override
long[] decode(long val)
{
long ret[] = MDateAndTime.decodeDateTime(val);
if (!MDateAndTime.isValidDateTime_Zeros(ret))
return null;
else
return ret;
}
},
TIME
{
@Override
long[] decode(long val)
{
long ret[] = MDateAndTime.decodeTime(val);
if (!MDateAndTime.isValidHrMinSec(ret, false, false))
return null;
else
return ret;
}
};
abstract long[] decode(long val);
}
private final String name;
private final TClass inputType;
private final Decoder decoder;
private MExtractField (String name, TClass inputType, Decoder decoder)
{
this.name = name;
this.inputType = inputType;
this.decoder = decoder;
}
@Override
protected void buildInputSets(TInputSetBuilder builder)
{
builder.covers(inputType, 0);
}
@Override
protected void doEvaluate(TExecutionContext context, LazyList<? extends ValueSource> inputs, ValueTarget output)
{
int val = inputs.get(0).getInt32();
long ymd[] = decoder.decode(val);
int ret;
if (ymd == null || (ret = getField(ymd, context)) < 0)
{
context.warnClient(new InvalidParameterValueException("Invalid DATETIME value: " + val));
output.putNull();
}
else
output.putInt32(ret);
}
@Override
public String displayName()
{
return name;
}
@Override
public TOverloadResult resultType()
{
return TOverloadResult.fixed(MNumeric.INT);
}
}