Files
fantaibao-qlexpress4/docs/custom-item.adoc
small-red-hat 2dfed7464e
Some checks failed
Reduce Adoc / reduce (push) Failing after 57s
Java Unit Test with Maven / test (push) Failing after 1m42s
first
2025-12-29 13:59:13 +08:00

335 lines
12 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

:toc:
= 自定义语法元素
本节系统梳理在 QLExpress4 在自定义语法元素方面的接口和能力。
== 总览
* 自定义函数
** 实现CustomFunction接口
** 使用 Java 函数式接口
** 通过注解扫描注册
** 通过QLExpress脚本添加
** 实现QLFunctionalVarargs
* 自定义操作符
** 实现CustomBinaryOperator
** 替换内置操作符
** 使用 Java 函数式接口
** 添加别名
** 实现QLFunctionalVarargs
* 扩展函数
** 继承ExtensionFunction
** 实现QLFunctionalVarargs
* 操作符与函数别名
== 自定义函数
=== 实现CustomFunction接口
[source,java,indent=0]
----
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;
}
}
----
[source,java,indent=0]
----
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 函数式接口
[source,java,indent=0]
----
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);
----
=== 注解方式注册
[source,java,indent=0]
----
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;
}
}
----
[source,java,indent=0]
----
Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS);
BatchAddFunctionResult addResult = express4Runner.addObjFunction(new MyFunctionUtil());
assertEquals(4, 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);
----
=== 通过QLExpress脚本添加
[source,java,indent=0]
----
public static class JoinFunction implements QLFunctionalVarargs {
@Override
public Object call(Object... params) {
return Arrays.stream(params).map(Object::toString).collect(Collectors.joining(","));
}
}
----
=== 实现QLFunctionalVarargs
[source,java,indent=0]
----
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);
----
[source,java,indent=0]
----
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并设置优先级
[source,java,indent=0]
----
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);
----
=== 替换内置操作符
[source,java,indent=0]
----
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 函数式接口
[source,java,indent=0]
----
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
[source,java,indent=0]
----
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
[source,java,indent=0]
----
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
[source,java,indent=0]
----
// 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对象可同时用作函数、操作符与扩展函数的实现便于在多处复用统一的语义与实现。该能力来源于接口的可变参数设计详见下方示例与接口定义。背景讨论参考 link:https://github.com/alibaba/QLExpress/issues/407[issue407]
[source,java,indent=0]
----
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);
----
=== 接口定义
[source,java,indent=0]
----
package com.alibaba.qlexpress4.api;
/**
* Author: TaoKan
*/
@FunctionalInterface
public interface QLFunctionalVarargs {
Object call(Object... params);
}
----
== 操作符与函数别名(亦支持关键字别名)
[source,java,indent=0]
----
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 的定义模式下,扩展函数调用时实参列表首位是接收者对象,其后为调用参数;函数与操作符不含接收者。
- 自定义操作符的优先级需根据表达式期望进行设置,避免与已有运算规则产生混淆。
- 注解方式注册仅会处理公开方法,且重复名称将注册失败;批量注册返回结果中包含成功与失败清单。