Skip to content

Latest commit

 

History

History
345 lines (287 loc) · 12.2 KB

File metadata and controls

345 lines (287 loc) · 12.2 KB

自定义语法元素

本节系统梳理在 QLExpress4 在自定义语法元素方面的接口和能力。

总览

  • 自定义函数

    • 实现CustomFunction接口

    • 使用 Java 函数式接口

    • 通过注解扫描注册

    • 通过QLExpress脚本添加

    • 实现QLFunctionalVarargs

  • 自定义操作符

    • 实现CustomBinaryOperator

    • 替换内置操作符

    • 使用 Java 函数式接口

    • 添加别名

    • 实现QLFunctionalVarargs

  • 扩展函数

    • 继承ExtensionFunction

    • 实现QLFunctionalVarargs

  • 操作符与函数别名

自定义函数

实现CustomFunction接口

package com.alibaba.qlexpress4.test.function;

import com.alibaba.qlexpress4.runtime.Parameters;
import com.alibaba.qlexpress4.runtime.QContext;
import com.alibaba.qlexpress4.runtime.function.CustomFunction;

public class HelloFunction implements CustomFunction {
    @Override
    public Object call(QContext qContext, Parameters parameters)
        throws Throwable {
        String tenant = (String)qContext.attachment().get("tenant");
        return "hello," + tenant;
    }
}
Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS);
express4Runner.addFunction("hello", new HelloFunction());
String resultJack = (String)express4Runner.execute("hello()",
    Collections.emptyMap(),
    // Additional information(tenant for example) can be brought into the custom function from outside via attachments
    QLOptions.builder().attachments(Collections.singletonMap("tenant", "jack")).build()).getResult();
assertEquals("hello,jack", resultJack);
String resultLucy =
    (String)express4Runner
        .execute("hello()",
            Collections.emptyMap(),
            QLOptions.builder().attachments(Collections.singletonMap("tenant", "lucy")).build())
        .getResult();
assertEquals("hello,lucy", resultLucy);

使用 Java 函数式接口

Express4Runner runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS);
// Function<T,R>
runner.addFunction("inc", (Function<Integer, Integer>)x -> x + 1);
// Predicate<T>
runner.addFunction("isPos", (Predicate<Integer>)x -> x > 0);
// Runnable
runner.addFunction("notify", () -> {
});
// Consumer<T>
runner.addFunction("print", (Consumer<Object>)System.out::println);

Object r1 = runner.execute("inc(1)", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS).getResult();
Object r2 = runner.execute("isPos(1)", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS).getResult();
assertEquals(2, r1);
assertEquals(true, r2);

注解方式注册

public static class MyFunctionUtil {
    @QLFunction({"myAdd", "iAdd"})
    public int add(int a, int b) {
        return a + b;
    }

    @QLFunction("arr3")
    public static int[] array3(int a, int b, int c) {
        return new int[] {a, b, c};
    }

    @QLFunction("concat")
    public String concat(String a, String b) {
        return a + b;
    }

    @QLFunction({"addAll"})
    public List<Object> addAll(List<Object> list, Object... obs) {
        list.addAll(Arrays.asList(obs));
        return list;
    }
}
Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS);
BatchAddFunctionResult addResult = express4Runner.addObjFunction(new MyFunctionUtil());
assertEquals(5, addResult.getSucc().size());
Object result =
    express4Runner.execute("myAdd(1,2) + iAdd(5,6)", new HashMap<>(), QLOptions.DEFAULT_OPTIONS).getResult();
assertEquals(14, result);
express4Runner.addStaticFunction(MyFunctionUtil.class);
Object result1 =
    express4Runner.execute("arr3(5,9,10)[2]", new HashMap<>(), QLOptions.DEFAULT_OPTIONS).getResult();
assertEquals(10, result1);

Object result2 =
    express4Runner.execute("concat('aa', null)", new HashMap<>(), QLOptions.DEFAULT_OPTIONS).getResult();
assertEquals("aanull", result2);

Object result3 = express4Runner
    .execute("l = [1,2];\naddAll(l, 'aa', 'bb', 'cc')", new HashMap<>(), QLOptions.DEFAULT_OPTIONS)
    .getResult();
assertEquals(Arrays.asList(1, 2, "aa", "bb", "cc"), result3);

通过QLExpress脚本添加

public static class JoinFunction implements QLFunctionalVarargs {
    @Override
    public Object call(Object... params) {
        return Arrays.stream(params).map(Object::toString).collect(Collectors.joining(","));
    }
}

实现QLFunctionalVarargs

Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS);
express4Runner.addVarArgsFunction("join", new JoinFunction());
Object resultFunction =
    express4Runner.execute("join(1,2,3)", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS).getResult();
assertEquals("1,2,3", resultFunction);
Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS);
express4Runner.addVarArgsFunction("join", new JoinFunction());
Object resultFunction =
    express4Runner.execute("join(1,2,3)", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS).getResult();
assertEquals("1,2,3", resultFunction);

自定义操作符

实现CustomBinaryOperator并设置优先级

Express4Runner runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS);
runner.addOperator("?><", (left, right) -> left.get().toString() + right.get().toString(), QLPrecedences.ADD);
Object r = runner.execute("1 ?>< 2 * 3", new HashMap<>(), QLOptions.DEFAULT_OPTIONS).getResult();
// precedence set to ADD, so multiply first, then custom operator: "1" + "6" => "16"
assertEquals("16", r);

替换内置操作符

Express4Runner runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS);
boolean ok = runner.replaceDefaultOperator("+",
    (left, right) -> Double.parseDouble(left.get().toString()) + Double.parseDouble(right.get().toString()));
assertTrue(ok);
Object r = runner.execute("'1.2' + '2.3'", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS).getResult();
assertEquals(3.5d, r);

使用 Java 函数式接口

Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS);
express4Runner.addOperatorBiFunction("join", (left, right) -> left + "," + right);
Object resultOperator =
    express4Runner.execute("1 join 2 join 3", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS).getResult();
assertEquals("1,2,3", resultOperator);

实现QLFunctionalVarargs

Express4Runner runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS);
runner.addOperator("join", params -> params[0] + "," + params[1]);
Object r = runner.execute("1 join 2", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS).getResult();
assertEquals("1,2", r);

扩展函数

继承ExtensionFunction

ExtensionFunction helloFunction = new ExtensionFunction() {
    @Override
    public Class<?>[] getParameterTypes() {
        return new Class[0];
    }

    @Override
    public String getName() {
        return "hello";
    }

    @Override
    public Class<?> getDeclaringClass() {
        return String.class;
    }

    @Override
    public Object invoke(Object obj, Object[] args) {
        String originStr = (String)obj;
        return "Hello," + originStr;
    }
};
Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS);
express4Runner.addExtendFunction(helloFunction);
Object result =
    express4Runner.execute("'jack'.hello()", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS).getResult();
assertEquals("Hello,jack", result);

实现QLFunctionalVarargs

// simpler way to define extension function
express4Runner.addExtendFunction("add",
    Number.class,
    params -> ((Number)params[0]).intValue() + ((Number)params[1]).intValue());
QLResult resultAdd = express4Runner.execute("1.add(2)", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS);
assertEquals(3, resultAdd.getResult());

QLFunctionalVarargs:一个对象同时定义三类操作

同一个QLFunctionalVarargs对象可同时用作函数、操作符与扩展函数的实现,便于在多处复用统一的语义与实现。该能力来源于接口的可变参数设计,详见下方示例与接口定义。背景讨论参考 issue407

Express4Runner runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS);
QLFunctionalVarargs allInOne = params -> {
    // sum numbers no matter how many args
    double sum = 0d;
    for (Object p : params) {
        if (p instanceof Number) {
            sum += ((Number)p).doubleValue();
        }
    }
    return sum;
};

// as function
runner.addVarArgsFunction("sumAll", allInOne);
// as operator
runner.addOperator("+&", allInOne);
// as extension function: first arg is the receiver, followed by call arguments
runner.addExtendFunction("plusAll", Number.class, allInOne);

Map<String, Object> ctx = new HashMap<>();
Object rf = runner.execute("sumAll(1,2,3)", ctx, QLOptions.DEFAULT_OPTIONS).getResult();
Object ro = runner.execute("1 +& 4", ctx, QLOptions.DEFAULT_OPTIONS).getResult();
Object re = runner.execute("1.plusAll(5)", ctx, QLOptions.DEFAULT_OPTIONS).getResult();
assertEquals(6d, rf);
assertEquals(5d, ro);
assertEquals(6d, re);

接口定义

package com.alibaba.qlexpress4.api;

/**
 * Author: TaoKan
 */
@FunctionalInterface
public interface QLFunctionalVarargs {
    Object call(Object... params);
}

操作符与函数别名(亦支持关键字别名)

Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS);
// add custom function zero
express4Runner.addFunction("zero", (String ignore) -> 0);

// keyword alias
assertTrue(express4Runner.addAlias("如果", "if"));
assertTrue(express4Runner.addAlias("则", "then"));
assertTrue(express4Runner.addAlias("否则", "else"));
assertTrue(express4Runner.addAlias("返回", "return"));
// operator alias
assertTrue(express4Runner.addAlias("大于", ">"));
// function alias
assertTrue(express4Runner.addAlias("零", "zero"));

Map<String, Object> context = new HashMap<>();
context.put("语文", 90);
context.put("数学", 90);
context.put("英语", 90);

Object result = express4Runner
    .execute("如果 (语文 + 数学 + 英语 大于 270) 则 {返回 1;} 否则 {返回 零();}", context, QLOptions.DEFAULT_OPTIONS)
    .getResult();
assertEquals(0, result);

说明与建议

  • QLFunctionalVarargs 的定义模式下,扩展函数调用时实参列表首位是接收者对象,其后为调用参数;函数与操作符不含接收者。

  • 自定义操作符的优先级需根据表达式期望进行设置,避免与已有运算规则产生混淆。

  • 注解方式注册仅会处理公开方法,且重复名称将注册失败;批量注册返回结果中包含成功与失败清单。