diff --git a/dinky-core/src/main/java/org/dinky/executor/VariableManager.java b/dinky-core/src/main/java/org/dinky/executor/VariableManager.java
index 10f7abe70f..a783793925 100644
--- a/dinky-core/src/main/java/org/dinky/executor/VariableManager.java
+++ b/dinky-core/src/main/java/org/dinky/executor/VariableManager.java
@@ -25,6 +25,8 @@
import org.dinky.assertion.Asserts;
import org.dinky.constant.FlinkSQLConstant;
+import org.dinky.data.exception.DinkyException;
+import org.dinky.utils.StringUtil;
import org.apache.flink.table.api.DataTypes;
import org.apache.flink.table.api.Table;
@@ -34,6 +36,7 @@
import org.apache.flink.util.StringUtils;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
@@ -41,17 +44,19 @@
import java.util.regex.Matcher;
import java.util.regex.Pattern;
-import cn.hutool.core.date.DateUtil;
+import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.io.resource.ResourceUtil;
import cn.hutool.core.lang.Dict;
-import cn.hutool.core.util.IdUtil;
-import cn.hutool.core.util.RandomUtil;
import cn.hutool.extra.expression.engine.jexl.JexlEngine;
+import lombok.extern.slf4j.Slf4j;
/**
* Flink Sql Variable Manager
*
* @since 2021/6/7 22:06
*/
+@Slf4j
public final class VariableManager {
public static final String VARIABLE = "variable";
static final String SHOW_VARIABLES = "SHOW VARIABLES";
@@ -59,24 +64,43 @@ public final class VariableManager {
public static final JexlEngine ENGINE = new JexlEngine();
+ public static final Dict ENGINE_CONTEXT = Dict.create();
+
/**
- *
- * engine , key is variable name , value is class .
- * for example:
- * random -> RandomUtil -> about random operation
- * date -> DateUtil -> about date operation
- * id -> IdUtil -> to generate random uuid
- * ...
+ * load expression variable class
*/
- public static final Dict ENGINE_CONTEXT = Dict.create()
- .set("random", RandomUtil.class)
- .set("date", DateUtil.class)
- .set("id", IdUtil.class);
+ private static void loadExpressionVariableClass() {
+ List classLoaderVariableJexlClass = getClassLoaderVariableJexlClass();
+ if (CollUtil.isEmpty(classLoaderVariableJexlClass)) {
+ return;
+ }
+ classLoaderVariableJexlClass.forEach(fullClassName -> {
+ try {
+ String classSimpleName =
+ BeanUtil.getBeanDesc(Class.forName(fullClassName)).getSimpleName();
+ String snakeCaseClassName = StringUtil.toSnakeCase(true, classSimpleName);
+ ENGINE_CONTEXT.set(snakeCaseClassName, Class.forName(fullClassName));
+ log.info("load class : {}", fullClassName);
+ } catch (ClassNotFoundException e) {
+ log.error(
+ "The class [{}] that needs to be loaded may not be loaded by dinky or there is no jar file of this class under dinky's lib/plugins. Please check, and try again. {}",
+ fullClassName,
+ e.getMessage(),
+ e);
+ }
+ });
+ }
public VariableManager() {
variables = new HashMap<>();
}
+ public static List getClassLoaderVariableJexlClass() {
+ return Arrays.asList(ResourceUtil.readUtf8Str("dinky-loader/ExpressionVariableClass")
+ .replace("\r", "")
+ .split("\n"));
+ }
+
/**
* Get names of sql variables loaded.
*
@@ -140,15 +164,18 @@ public void unregisterVariable(String variableName, boolean ignoreIfNotExists) {
*/
public Object getVariable(String variableName) {
checkArgument(
- !StringUtils.isNullOrWhitespaceOnly(variableName), "sql variableName name cannot be null or empty.");
+ !StringUtils.isNullOrWhitespaceOnly(variableName),
+ "sql variable name or jexl key cannot be null or empty.");
try {
if (variables.containsKey(variableName)) {
return variables.get(variableName);
}
+ // load expression variable class
+ loadExpressionVariableClass();
// use jexl to parse variable value
return ENGINE.eval(variableName, ENGINE_CONTEXT, null);
} catch (Exception e) {
- throw new CatalogException(format("The variable of sql %s does not exist.", variableName));
+ throw new DinkyException(format("The variable name or jexl key of sql %s does not exist.", variableName));
}
}
diff --git a/dinky-core/src/main/java/org/dinky/utils/StringUtil.java b/dinky-core/src/main/java/org/dinky/utils/StringUtil.java
new file mode 100644
index 0000000000..24c36115eb
--- /dev/null
+++ b/dinky-core/src/main/java/org/dinky/utils/StringUtil.java
@@ -0,0 +1,72 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF 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.
+ *
+ */
+
+package org.dinky.utils;
+
+import cn.hutool.core.util.StrUtil;
+
+public class StringUtil {
+ /**
+ * 驼峰 -> 蛇形命名
+ *
+ * @param content 内容
+ * @param isFirstUpper 是否首字母大写
+ * @param isFirstLower 是否首字母小写
+ * @return 转换后的内容
+ */
+ public static String toSnakeCase(String content, boolean isFirstUpper, boolean isFirstLower) {
+ if (StrUtil.isEmpty(content)) {
+ return content;
+ }
+ StringBuilder sb = new StringBuilder(content.length());
+ for (int i = 0; i < content.length(); i++) {
+ char c = content.charAt(i);
+ if (isFirstUpper && i == 0) {
+ sb.append(Character.toUpperCase(c));
+ } else if (isFirstLower && i == 0) {
+ sb.append(Character.toLowerCase(c));
+ } else {
+ sb.append(c);
+ }
+ }
+ return sb.toString();
+ }
+
+ /**
+ * 蛇形命名转换 -> 小驼峰
+ *
+ * @param content 内容
+ * @param isFirstUpper 是否首字母大写
+ * @return 转换后的内容
+ */
+ public static String toSnakeCase(String content, boolean isFirstUpper) {
+ return toSnakeCase(content, isFirstUpper, false);
+ }
+
+ /**
+ * 蛇形命名转换 -> 小驼峰
+ *
+ * @param isFirstLower 是否首字母小写
+ * @param content 内容
+ * @return 转换后的内容
+ */
+ public static String toSnakeCase(boolean isFirstLower, String content) {
+ return toSnakeCase(content, false, isFirstLower);
+ }
+}
diff --git a/docs/docs/administrator_guide/register_center/global_var.md b/docs/docs/administrator_guide/register_center/global_var.md
index f26afd5c3c..3d4141d7b3 100644
--- a/docs/docs/administrator_guide/register_center/global_var.md
+++ b/docs/docs/administrator_guide/register_center/global_var.md
@@ -111,23 +111,24 @@ SHOW FRAGMENT var1;
如您升级到 `1.0.0` 及以上版本, 请修改引用 `${_CURRENT_DATE_}` `${_CURRENT_TIMESTAMP_}` 变量的相关作业
-新的表达式变量无需声明, 已由程序启动时自动加载初始化 ,直接使用即可
+新的表达式变量无需声明 ,直接使用即可,
+请注意: 表达式变量调用时请使用小驼峰命名法
:::
### 日期时间类表达式变量
-> 日期相关: `date` 为实例名用来调用方法`(必须固定)`,使用 [DateUtil](https://doc.hutool.cn/pages/DateUtil/)工具类实现,且支持此工具类的所有方法调用
+> 日期相关: `dateUtil` 为实例名用来调用方法`(必须固定)`,使用 [DateUtil](https://doc.hutool.cn/pages/DateUtil/)工具类实现,且支持此工具类的所有方法调用
```sql
-- 获取当前秒
-select '${date.currentSeconds()}';
+select '${dateUtil.currentSeconds()}';
# 获取日期 减去 10 天
-select '${date.offsetDay(date.date(), -10)}';
+select '${dateUtil.offsetDay(date.date(), -10)}';
# 获取当前日期 标准格式 yyyy-MM-dd HH:mm:ss
-select '${date.now()}';
+select '${dateUtil.now()}';
# etc .....
@@ -138,30 +139,30 @@ select '${date.now()}';
### 随机串相关表达式变量
-> `random` 为实例名用来调用方法`(必须固定)`, 使用 [RandomUtil](https://doc.hutool.cn/pages/RandomUtil/)工具类实现,且支持此工具类的所有方法调用
+> `randomUtil` 为实例名用来调用方法`(必须固定)`, 使用 [RandomUtil](https://doc.hutool.cn/pages/RandomUtil/)工具类实现,且支持此工具类的所有方法调用
```sql
# 产生一个[10, 100)的随机数
-select '${random.randomInt(10, 100)}';
+select '${randomUtil.randomInt(10, 100)}';
# 随机字符串(只包含数字和字符)
-select '${random.randomString()}';
+select '${randomUtil.randomString()}';
# 获得一个只包含数字的字符串
-select '${random.randomNumbers()}';
+select '${randomUtil.randomNumbers()}';
```
### 唯一 ID 相关表达式变量
-> id 为实例名用来调用方法`(必须固定)` , 使用 [IdUtil](https://doc.hutool.cn/pages/IdUtil/) 工具类实现
+> idUtil 为实例名用来调用方法`(必须固定)` , 使用 [IdUtil](https://doc.hutool.cn/pages/IdUtil/) 工具类实现
```sql
# 生成的UUID是带-的字符串,类似于:a5c8a5e8-df2b-4706-bea4-08d0939410e3
-select '${id.randomUUID()}';
+select '${idUtil.randomUUID()}';
# 生成的是不带-的字符串,类似于:b17f24ff026d40949c85a24f4f375d42
-select '${id.simpleUUID()}';
+select '${idUtil.simpleUUID()}';
```
:::tip 扩展
diff --git a/docs/docs/extend/function_expansion/global_var_ext.md b/docs/docs/extend/function_expansion/global_var_ext.md
index e3aee01e08..f0a9a519b9 100644
--- a/docs/docs/extend/function_expansion/global_var_ext.md
+++ b/docs/docs/extend/function_expansion/global_var_ext.md
@@ -6,49 +6,36 @@ title: 表达式变量扩展
:::tip
-本扩展文档适用于 v1.0.0 及以上版本
-
+本扩展文档适用于 v1.0.0 及以上版本,可以直接在已部署服务中直接扩展,无需重新编译部署
:::
## 介绍
> 本扩展用于扩展表达式中的变量,以满足更多场景需求
-## 前置环境
-
-> 扩展前需要启动 Dinky 项目, 本地开发环境搭建请参考 [本地调试](../../developer_guide/local_debug)
## 扩展方法
-找到 `dinky-core` 模块下的 `org.dinky.executor.VariableManager.ENGINE_CONTEXT` 变量, 并在其中添加
+找到 `部署的 dinky/dinky-loader` 目录下的 `ExpressionVariableClass` 文件, 并在其中添加
举例:
> 假设需要扩展 [`Hash 算法`](https://doc.hutool.cn/pages/HashUtil/) 相关的表达式变量
-```java
-
-import cn.hutool.core.util.HashUtil;
+只需要在 `ExpressionVariableClass` 文件中添加如下类的全限定名即可
+```text
-public final class VariableManager {
+cn.hutool.core.util.HashUtil
- public static final Dict ENGINE_CONTEXT = Dict.create()
- .set("random", RandomUtil.class)
- .set("date", DateUtil.class)
- // 只需要在此处添加即可
- // 需要注意的是, 此处的 key 必须与表达式中的变量名一致,随后便可以使用该变量调用其方法
- .set("hash", HashUtil.class)
- .set("id", IdUtil.class);
-
-
- //... 其他代码无需关心,因此省略...
-}
```
+如上示例,将会扩展表达式变量中的 `Hash 算法`,你就可以在表达式中使用 `Hash 算法` 相关的表达式了
:::tip 说明
-如您扩展完成需要生产中使用, 参考[编译](../../deploy_guide/compiler) , 打包完成 替换服务器部署的 `dinky/lib/dinky-core-1.0.0.jar` 文件, 重启 dinky 服务即可
+1. 请确保您的扩展类在 `dinky-loader` 中存在
+2. 请确保您的扩展类在 `dinky 中已被加载(类加载机制)`
+3. 请确保您的扩展类中的方法为 `public static` 修饰
-考虑到多数用户通用情况下,当然也欢迎您将此次扩展贡献给社区, 请参考 [如何贡献](../../developer_guide/contribution/how_contribute) 进行 [PR](../../developer_guide/contribution/pull_request)
+考虑到多数用户通用情况下,当然也欢迎您将此次扩展贡献给社区, 请参考 [如何贡献](../../developer_guide/contribution/how_contribute) 进行 [PR](../../developer_guide/contribution/pull_request)
:::
\ No newline at end of file