All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.xlrit.gears.engine.snel.SnelFunctions Maven / Gradle / Ivy

package com.xlrit.gears.engine.snel;

import java.time.OffsetDateTime;
import java.time.temporal.Temporal;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;

import com.xlrit.gears.base.function.DefaultFunctions;
import com.xlrit.gears.base.model.User;
import com.xlrit.gears.engine.meta.MetaManager;
import com.xlrit.gears.engine.meta.TypeInfo;

import static com.xlrit.gears.engine.meta.BasicTypes.*;

public class SnelFunctions {
    private SnelFunctions() {}

    private static final TypeInfo userType = null;     // TODO actual type

    public static final List asList = List.of(
        // no arguments
        static0("current_date",     DATE,     DefaultFunctions::currentDate,     "CURRENT_DATE"),
        static0("current_time",     TIME,     DefaultFunctions::currentTime,     "CURRENT_TIME"),
        static0("current_datetime", DATETIME, DefaultFunctions::currentDatetime, "CURRENT_TIMESTAMP"),
        static0("current_user",     userType, SnelFunctions::currentUserImpl,    ":current_user"),
        // 1 argument
        static1("count",            INTEGER,  arg -> DefaultFunctions.count((Collection)arg), arg -> "COUNT(" + arg.text() + ")"),
        static1("uppercase",        TEXT,     arg -> String.valueOf(arg).toUpperCase(), arg -> "UPPER(" + arg.text() + ")"),
        static1("lowercase",        TEXT,     arg -> String.valueOf(arg).toLowerCase(), arg -> "LOWER(" + arg.text() + ")"),
        static1("date_part",        DATE,     OffsetDateTime.class, DefaultFunctions::datePart, arg -> "EXTRACT(date FROM " + arg.text() + ")"),
        static1("time_part",        TIME,     OffsetDateTime.class, DefaultFunctions::timePart, arg -> "EXTRACT(time FROM " + arg.text() + ")"),
        // ? arguments
        staticN("concat",           TEXT,     SnelFunctions::concatImpl, SnelFunctions::translateConcat),
        staticN("search",           BOOLEAN,  SnelFunctions::searchImpl, params -> "SEARCH(" + joinParams(", ", params) + ")"),
        // temporal unit extractors
        temporalExtractFunction("year",   DefaultFunctions::year),
        temporalExtractFunction("month",  DefaultFunctions::month),
        temporalExtractFunction("week",   DefaultFunctions::week),
        temporalExtractFunction("day",    DefaultFunctions::day),
        temporalExtractFunction("hour",   DefaultFunctions::hour),
        temporalExtractFunction("minute", DefaultFunctions::minute),
        temporalExtractFunction("second", DefaultFunctions::second),
		// misc
		distinctFunction()
    );

	private static SnelFunction distinctFunction() {
		return new SnelFunction("distinct") {
			@Override
			public Result invoke(List args) {
				if (args.size() != 1) throw new IllegalArgumentException("Function `distinct`: requires 1 parameter");
				if (!(args.get(0) instanceof List list)) throw new IllegalArgumentException("Function `distinct`: parameter must be a list");
				TypeInfo returnType = null; // TODO same as argument type
				return Result.typed(returnType, DefaultFunctions.distinct(list));
			}

			@Override
			public Fragment translate(List args) {
				if (args.size() != 1) throw new IllegalArgumentException("Function `distinct`: requires 1 parameter");
				return new Fragment(null, -1, "DISTINCT " + args.get(0).text());
			}
		};
	}

	public static final Map asMap =
        asList.stream().collect(Collectors.toMap(SnelFunction::getName, Function.identity()));

    private static User currentUserImpl() {
        throw new UnsupportedOperationException();
    }

    @SuppressWarnings({"rawtypes", "unchecked"})
   	private static String concatImpl(List args) {
   		if (args.size() != 1 && args.size() != 2) throw new IllegalArgumentException("Function `concat`: requires 1 or 2 parameters");
   		if (!(args.get(0) instanceof List texts)) throw new IllegalArgumentException("Function `concat`: first parameter must be a collection");
   		if (args.size() == 1) return DefaultFunctions.concat(texts);
   		if (args.get(1) instanceof String sep) return DefaultFunctions.concat(texts, sep);
   		throw new IllegalArgumentException("Function `concat`: unsupported parameters: " + args);
   	}

    private static String translateConcat(List args) {
        return switch (args.size()) {
            case 1  -> "CONCAT(" + args.get(0).text() + ")";
            case 2  -> "CONCAT_WS(" + args.get(1).text() + ", " + args.get(0).text() + ")";
            default -> throw new IllegalArgumentException("Function `concat`: unsupported parameters: " + args);
        };
    }

    private static String searchImpl(List params) {
        throw new UnsupportedOperationException();
   	}

    private static String joinParams(String s, List params) {
        return params.stream()
            .map(Fragment::text)
            .collect(Collectors.joining(s));
    }

    private static SnelFunction temporalExtractFunction(String name, Function extractor) {
        return static1(
            name,
            INTEGER,
            arg -> extractor.apply((Temporal) arg),
            arg -> "EXTRACT(" + name + " from " + arg.text() + ")"
        );
    }

    private static SnelFunction static0(String name, TypeInfo type, Supplier invoker, String translated) {
        // TODO ensure there's exactly 0 arguments
        return new SnelFunction(name) {
            @Override
            public Result invoke(List args) {
                Object value = invoker.get();
                return Result.typed(type, value);
            }

            @Override
            public Fragment translate(List args) {
                return new Fragment(type, -1, translated);
            }
        };
    }

    private static  SnelFunction static1(String name, TypeInfo type, Class paramType, Function invoker, Function translator) {
        return static1(name, type, arg -> invoker.apply(paramType.cast(arg)), translator);
    }

    private static SnelFunction static1(String name, TypeInfo type, Function invoker, Function translator) {
        // TODO ensure there's exactly 1 argument
        return new SnelFunction(name) {
            @Override
            public Result invoke(List args) {
                Object value = invoker.apply(args.get(0));
                return Result.typed(type, value);
            }

            @Override
            public Fragment translate(List args) {
                String translated = translator.apply(args.get(0));
                return new Fragment(type, -1, translated);
            }
        };
    }

    private static SnelFunction staticN(String name, TypeInfo type, Function,Object> impl, Function,String> translator) {
        return new SnelFunction(name) {
            @Override
            public Result invoke(List args) {
                Object value = impl.apply(args);
                return Result.typed(type, value);
            }

            @Override
            public Fragment translate(List args) {
                String translated = translator.apply(args);
                return new Fragment(type, -1, translated);
            }
        };
    }
}