/*
* Licensed to CRATE Technology GmbH ("Crate") under one or more contributor
* license agreements. See the NOTICE file distributed with this work for
* additional information regarding copyright ownership. Crate licenses
* this file to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may
* obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* However, if you have executed another commercial license agreement
* with Crate these terms will supersede the license and you may use the
* software solely pursuant to the terms of the relevant commercial agreement.
*/
package io.crate.operation.scalar;
import com.google.common.collect.ImmutableList;
import io.crate.analyze.symbol.Function;
import io.crate.analyze.symbol.Symbol;
import io.crate.analyze.symbol.ValueSymbolVisitor;
import io.crate.analyze.symbol.format.FunctionFormatSpec;
import io.crate.analyze.symbol.format.SymbolFormatter;
import io.crate.analyze.symbol.format.SymbolPrinter;
import io.crate.metadata.*;
import io.crate.data.Input;
import io.crate.sql.tree.Extract;
import io.crate.types.DataType;
import io.crate.types.DataTypes;
import org.elasticsearch.common.joda.Joda;
import org.elasticsearch.common.lucene.BytesRefs;
import org.joda.time.DateTimeField;
import org.joda.time.chrono.ISOChronology;
import java.util.Arrays;
import java.util.Locale;
public class ExtractFunctions {
private final static ImmutableList<DataType> ARGUMENT_TYPES = ImmutableList.<DataType>of(DataTypes.TIMESTAMP);
private final static String NAME_PREFIX = "extract_";
public final static FunctionInfo GENERIC_INFO = new FunctionInfo(
new FunctionIdent("_extract", ImmutableList.<DataType>of(DataTypes.STRING, DataTypes.TIMESTAMP)), DataTypes.INTEGER);
private static final String EXTRACT_CENTURY_PREFIX = "extract(century from ";
private static final String EXTRACT_YEAR_PREFIX = "extract(year from ";
private static final String EXTRACT_QUARTER_PREFIX = "extract(quarter from ";
private static final String EXTRACT_MONTH_PREFIX = "extract(month from ";
private static final String EXTRACT_WEEK_PREFIX = "extract(week from ";
private static final String EXTRACT_DAY_OF_MONTH_PREFIX = "extract(day_of_month from ";
private static final String EXTRACT_DAY_OF_WEEK_PREFIX = "extract(day_of_week from ";
private static final String EXTRACT_SECOND_PREFIX = "extract(second from ";
private static final String EXTRACT_MINUTE_PREFIX = "extract(minute from ";
private static final String EXTRACT_HOUR_PREFIX = "extract(hour from ";
private static final String EXTRACT_DAY_OF_YEAR_PREFIX = "extract(day_of_year from ";
public static void register(ScalarFunctionModule scalarFunctionModule) {
scalarFunctionModule.register(ExtractCentury.INSTANCE);
scalarFunctionModule.register(ExtractYear.INSTANCE);
scalarFunctionModule.register(ExtractQuarter.INSTANCE);
scalarFunctionModule.register(ExtractMonth.INSTANCE);
scalarFunctionModule.register(ExtractWeek.INSTANCE);
scalarFunctionModule.register(ExtractDayOfMonth.INSTANCE);
scalarFunctionModule.register(ExtractDayOfWeek.INSTANCE);
scalarFunctionModule.register(ExtractDayOfYear.INSTANCE);
scalarFunctionModule.register(ExtractHour.INSTANCE);
scalarFunctionModule.register(ExtractMinute.INSTANCE);
scalarFunctionModule.register(ExtractSecond.INSTANCE);
scalarFunctionModule.register(ExtractFunction.INSTANCE);
}
static Scalar<Number, Long> getScalar(Extract.Field field) {
switch (field) {
case CENTURY:
return ExtractCentury.INSTANCE;
case YEAR:
return ExtractYear.INSTANCE;
case QUARTER:
return ExtractQuarter.INSTANCE;
case MONTH:
return ExtractMonth.INSTANCE;
case WEEK:
return ExtractWeek.INSTANCE;
case DAY:
case DAY_OF_MONTH:
return ExtractDayOfMonth.INSTANCE;
case DAY_OF_WEEK:
case DOW:
return ExtractDayOfWeek.INSTANCE;
case DAY_OF_YEAR:
case DOY:
return ExtractDayOfYear.INSTANCE;
case HOUR:
return ExtractHour.INSTANCE;
case MINUTE:
return ExtractMinute.INSTANCE;
case SECOND:
return ExtractSecond.INSTANCE;
case TIMEZONE_HOUR:
break;
case TIMEZONE_MINUTE:
break;
}
throw new UnsupportedOperationException(String.format(Locale.ENGLISH, "Extract( %s from <expression>) is not supported", field));
}
private static FunctionInfo createFunctionInfo(Extract.Field field) {
return new FunctionInfo(
new FunctionIdent(NAME_PREFIX + field.toString(), ARGUMENT_TYPES),
DataTypes.INTEGER,
FunctionInfo.Type.SCALAR
);
}
/**
* This is a generic ExtractFunction variant: _extract(field from ts).
* Where the field doesn't yet have a value (either because it is a parameter or because of lazy evaluation)
*
* As soon as the field has an actual value it will be re-written to one of the concrete extract_ variants.
*/
private static class ExtractFunction extends Scalar<Object, Object> implements FunctionFormatSpec {
public static final FunctionImplementation INSTANCE = new ExtractFunction();
private ExtractFunction() {
}
@Override
public FunctionInfo info() {
return GENERIC_INFO;
}
@Override
public Symbol normalizeSymbol(Function symbol, TransactionContext transactionContext) {
Symbol arg1 = symbol.arguments().get(0);
if (arg1.symbolType().isValueSymbol()) {
String field = ValueSymbolVisitor.STRING.process(arg1);
Scalar<Number, Long> scalar = getScalar(Extract.Field.valueOf(field.toUpperCase(Locale.ENGLISH)));
//noinspection ArraysAsListWithZeroOrOneArgument # need mutable list as arg to function
Function function = new Function(scalar.info(), Arrays.asList(symbol.arguments().get(1)));
return scalar.normalizeSymbol(function, transactionContext);
}
return super.normalizeSymbol(symbol, transactionContext);
}
@Override
public final Object evaluate(Input[] args) {
String field = BytesRefs.toString(args[0].value());
Scalar<Number, Long> scalar = getScalar(Extract.Field.valueOf(field.toUpperCase(Locale.ENGLISH)));
return scalar.evaluate(args[1]);
}
@Override
public String beforeArgs(Function function) {
return SymbolFormatter.format("extract(%s from %s", function.arguments().toArray(new Symbol[0]));
}
@Override
public String afterArgs(Function function) {
return SymbolPrinter.Strings.PAREN_CLOSE;
}
@Override
public boolean formatArgs(Function function) {
return false;
}
}
private abstract static class GenericExtractFunction extends Scalar<Number, Long> implements FunctionFormatSpec {
public abstract int evaluate(long value);
@Override
public Integer evaluate(Input... args) {
assert args.length == 1 : "extract only takes one argument";
Object value = args[0].value();
if (value == null) {
return null;
}
assert value instanceof Long : "value of argument to extract must be of type long (timestamp)";
return evaluate((Long) value);
}
@Override
public boolean formatArgs(Function function) {
return true;
}
@Override
public String afterArgs(Function function) {
return SymbolPrinter.Strings.PAREN_CLOSE;
}
}
private static class ExtractCentury extends GenericExtractFunction {
private static final FunctionInfo INFO = createFunctionInfo(Extract.Field.CENTURY);
static final ExtractCentury INSTANCE = new ExtractCentury();
static final DateTimeField CENTURY = ISOChronology.getInstanceUTC().centuryOfEra();
private ExtractCentury() {
}
@Override
public int evaluate(long value) {
return CENTURY.get(value);
}
@Override
public FunctionInfo info() {
return INFO;
}
@Override
public String beforeArgs(Function function) {
return EXTRACT_CENTURY_PREFIX;
}
}
private static class ExtractYear extends GenericExtractFunction {
private static final FunctionInfo INFO = createFunctionInfo(Extract.Field.YEAR);
static final DateTimeField YEAR = ISOChronology.getInstanceUTC().year();
static final ExtractYear INSTANCE = new ExtractYear();
@Override
public int evaluate(long value) {
return YEAR.get(value);
}
@Override
public FunctionInfo info() {
return INFO;
}
@Override
public String beforeArgs(Function function) {
return EXTRACT_YEAR_PREFIX;
}
}
private static class ExtractQuarter extends GenericExtractFunction {
public static final ExtractQuarter INSTANCE = new ExtractQuarter();
private static final FunctionInfo INFO = createFunctionInfo(Extract.Field.QUARTER);
private static final DateTimeField QUARTER = Joda.QuarterOfYear.getField(ISOChronology.getInstanceUTC());
private ExtractQuarter() {
}
@Override
public int evaluate(long value) {
return QUARTER.get(value);
}
@Override
public FunctionInfo info() {
return INFO;
}
@Override
public String beforeArgs(Function function) {
return EXTRACT_QUARTER_PREFIX;
}
}
private static class ExtractMonth extends GenericExtractFunction {
private static final FunctionInfo INFO = createFunctionInfo(Extract.Field.MONTH);
private static final DateTimeField MONTH = ISOChronology.getInstanceUTC().monthOfYear();
public static final ExtractMonth INSTANCE = new ExtractMonth();
private ExtractMonth() {
}
@Override
public int evaluate(long value) {
return MONTH.get(value);
}
@Override
public FunctionInfo info() {
return INFO;
}
@Override
public String beforeArgs(Function function) {
return EXTRACT_MONTH_PREFIX;
}
}
private static class ExtractWeek extends GenericExtractFunction {
public static final FunctionInfo INFO = createFunctionInfo(Extract.Field.WEEK);
static final DateTimeField WEEK_OF_WEEK_YEAR = ISOChronology.getInstanceUTC().weekOfWeekyear();
public static final ExtractWeek INSTANCE = new ExtractWeek();
private ExtractWeek() {
}
@Override
public int evaluate(long value) {
return WEEK_OF_WEEK_YEAR.get(value);
}
@Override
public FunctionInfo info() {
return INFO;
}
@Override
public String beforeArgs(Function function) {
return EXTRACT_WEEK_PREFIX;
}
}
private static class ExtractDayOfMonth extends GenericExtractFunction {
private static final FunctionInfo INFO = createFunctionInfo(Extract.Field.DAY_OF_MONTH);
private static final DateTimeField DAY_OF_MONTH = ISOChronology.getInstanceUTC().dayOfMonth();
public static final ExtractDayOfMonth INSTANCE = new ExtractDayOfMonth();
private ExtractDayOfMonth() {
}
@Override
public int evaluate(long value) {
return DAY_OF_MONTH.get(value);
}
@Override
public FunctionInfo info() {
return INFO;
}
@Override
public String beforeArgs(Function function) {
return EXTRACT_DAY_OF_MONTH_PREFIX;
}
}
private static class ExtractDayOfWeek extends GenericExtractFunction {
private static final FunctionInfo INFO = createFunctionInfo(Extract.Field.DAY_OF_WEEK);
private static final DateTimeField DAY_OF_WEEK = ISOChronology.getInstanceUTC().dayOfWeek();
public static final ExtractDayOfWeek INSTANCE = new ExtractDayOfWeek();
@Override
public int evaluate(long value) {
return DAY_OF_WEEK.get(value);
}
@Override
public FunctionInfo info() {
return INFO;
}
@Override
public String beforeArgs(Function function) {
return EXTRACT_DAY_OF_WEEK_PREFIX;
}
}
private static class ExtractSecond extends GenericExtractFunction {
private static final FunctionInfo INFO = createFunctionInfo(Extract.Field.SECOND);
private static final DateTimeField SECOND_OF_MINUTE = ISOChronology.getInstanceUTC().secondOfMinute();
public static final ExtractSecond INSTANCE = new ExtractSecond();
private ExtractSecond() {
}
@Override
public int evaluate(long value) {
return SECOND_OF_MINUTE.get(value);
}
@Override
public FunctionInfo info() {
return INFO;
}
@Override
public String beforeArgs(Function function) {
return EXTRACT_SECOND_PREFIX;
}
}
private static class ExtractMinute extends GenericExtractFunction {
private static final FunctionInfo INFO = createFunctionInfo(Extract.Field.MINUTE);
private static final DateTimeField MINUTE_OF_HOUR = ISOChronology.getInstanceUTC().minuteOfHour();
public static final ExtractMinute INSTANCE = new ExtractMinute();
private ExtractMinute() {
}
@Override
public int evaluate(long value) {
return MINUTE_OF_HOUR.get(value);
}
@Override
public FunctionInfo info() {
return INFO;
}
@Override
public String beforeArgs(Function function) {
return EXTRACT_MINUTE_PREFIX;
}
}
private static class ExtractHour extends GenericExtractFunction {
private static final FunctionInfo INFO = createFunctionInfo(Extract.Field.HOUR);
private static final DateTimeField HOUR_OF_DAY = ISOChronology.getInstanceUTC().hourOfDay();
public static final ExtractHour INSTANCE = new ExtractHour();
private ExtractHour() {
}
@Override
public int evaluate(long value) {
return HOUR_OF_DAY.get(value);
}
@Override
public FunctionInfo info() {
return INFO;
}
@Override
public String beforeArgs(Function function) {
return EXTRACT_HOUR_PREFIX;
}
}
private static class ExtractDayOfYear extends GenericExtractFunction {
private static final FunctionInfo INFO = createFunctionInfo(Extract.Field.DAY_OF_YEAR);
private static final DateTimeField DAY_OF_YEAR = ISOChronology.getInstanceUTC().dayOfYear();
private static final ExtractDayOfYear INSTANCE = new ExtractDayOfYear();
private ExtractDayOfYear() {
}
@Override
public int evaluate(long value) {
return DAY_OF_YEAR.get(value);
}
@Override
public FunctionInfo info() {
return INFO;
}
@Override
public String beforeArgs(Function function) {
return EXTRACT_DAY_OF_YEAR_PREFIX;
}
}
}