From 28bc668f1448378cf9819d53068117c8a7ec8155 Mon Sep 17 00:00:00 2001 From: Lichao Date: Sat, 9 Mar 2024 18:06:38 +0800 Subject: [PATCH 01/87] feat: init Signed-off-by: Lichao --- .idea/vcs.xml | 10 ------- .../dinky/controller/MonitorController.java | 5 +++- .../trans/parse/AddJarSqlParseStrategy.java | 2 +- .../java/org/dinky/flink/DinkyExecutor.java | 5 ++++ .../org/dinky/flink/LocalExecutorService.java | 18 +++++++++++++ .../dinky/flink/ServerExecutorService.java | 10 +++++++ .../org/dinky/interceptor/CdcSourceTests.java | 3 ++- .../interceptor/FlinkCDCPipelineTest.java | 3 ++- dinky-executor-server/pom.xml | 26 +++++++++++++++++++ .../org/dinky/ServerExecutorServiceImpl.java | 10 +++++++ pom.xml | 1 + 11 files changed, 79 insertions(+), 14 deletions(-) create mode 100644 dinky-core/src/main/java/org/dinky/flink/DinkyExecutor.java create mode 100644 dinky-core/src/main/java/org/dinky/flink/LocalExecutorService.java create mode 100644 dinky-core/src/main/java/org/dinky/flink/ServerExecutorService.java create mode 100644 dinky-executor-server/pom.xml create mode 100644 dinky-executor-server/src/main/java/org/dinky/ServerExecutorServiceImpl.java diff --git a/.idea/vcs.xml b/.idea/vcs.xml index a6b68d70dc..830674470f 100644 --- a/.idea/vcs.xml +++ b/.idea/vcs.xml @@ -1,15 +1,5 @@ - - - diff --git a/dinky-admin/src/main/java/org/dinky/controller/MonitorController.java b/dinky-admin/src/main/java/org/dinky/controller/MonitorController.java index 49c597012c..1e3a38dbab 100644 --- a/dinky-admin/src/main/java/org/dinky/controller/MonitorController.java +++ b/dinky-admin/src/main/java/org/dinky/controller/MonitorController.java @@ -54,6 +54,8 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import javax.servlet.http.HttpSession; + @Slf4j @RestController @Api(tags = "Monitor Controller") @@ -126,7 +128,8 @@ public Result> getMetricsLayoutByName(@RequestParam String layoutN @GetMapping("/getJvmInfo") @ApiOperation("Get Jvm Data Display") - public SseEmitter getJvmInfo() { + public SseEmitter getJvmInfo(HttpSession session) { + String seesionID = session.getId(); return monitorService.sendJvmInfo(); } diff --git a/dinky-client/dinky-client-base/src/main/java/org/dinky/trans/parse/AddJarSqlParseStrategy.java b/dinky-client/dinky-client-base/src/main/java/org/dinky/trans/parse/AddJarSqlParseStrategy.java index 5e9b6a52ab..32d37ce390 100644 --- a/dinky-client/dinky-client-base/src/main/java/org/dinky/trans/parse/AddJarSqlParseStrategy.java +++ b/dinky-client/dinky-client-base/src/main/java/org/dinky/trans/parse/AddJarSqlParseStrategy.java @@ -42,7 +42,7 @@ */ public class AddJarSqlParseStrategy extends AbstractRegexParseStrategy { - private static final String ADD_JAR = "(add\\s+customjar)\\s+'(.*.jar)'"; + private static final String ADD_JAR = "(add\\s+(customjar|file))\\s+'(.*.jar)'"; private static final Pattern ADD_JAR_PATTERN = Pattern.compile(ADD_JAR, Pattern.CASE_INSENSITIVE); public static final AddJarSqlParseStrategy INSTANCE = new AddJarSqlParseStrategy(); diff --git a/dinky-core/src/main/java/org/dinky/flink/DinkyExecutor.java b/dinky-core/src/main/java/org/dinky/flink/DinkyExecutor.java new file mode 100644 index 0000000000..2c02ca1536 --- /dev/null +++ b/dinky-core/src/main/java/org/dinky/flink/DinkyExecutor.java @@ -0,0 +1,5 @@ +package org.dinky.flink; + +public interface DinkyExecutor { + void init(); +} diff --git a/dinky-core/src/main/java/org/dinky/flink/LocalExecutorService.java b/dinky-core/src/main/java/org/dinky/flink/LocalExecutorService.java new file mode 100644 index 0000000000..215a587784 --- /dev/null +++ b/dinky-core/src/main/java/org/dinky/flink/LocalExecutorService.java @@ -0,0 +1,18 @@ +package org.dinky.flink; + +import org.dinky.classloader.DinkyClassLoader; + +import java.lang.ref.WeakReference; + +public class LocalExecutorService implements DinkyExecutor { + private final WeakReference dinkyClassLoader; + + public LocalExecutorService() { + dinkyClassLoader = new WeakReference<>(DinkyClassLoader.build()); + } + + @Override + public void init() { + + } +} diff --git a/dinky-core/src/main/java/org/dinky/flink/ServerExecutorService.java b/dinky-core/src/main/java/org/dinky/flink/ServerExecutorService.java new file mode 100644 index 0000000000..fdbd306641 --- /dev/null +++ b/dinky-core/src/main/java/org/dinky/flink/ServerExecutorService.java @@ -0,0 +1,10 @@ +package org.dinky.flink; + +import org.dinky.executor.ExecutorConfig; + +import java.rmi.Remote; + +public interface ServerExecutorService extends Remote, DinkyExecutor { + + void init(ExecutorConfig executorConfig); +} diff --git a/dinky-core/src/test/java/org/dinky/interceptor/CdcSourceTests.java b/dinky-core/src/test/java/org/dinky/interceptor/CdcSourceTests.java index 0a782c7192..cbbf0fcd76 100644 --- a/dinky-core/src/test/java/org/dinky/interceptor/CdcSourceTests.java +++ b/dinky-core/src/test/java/org/dinky/interceptor/CdcSourceTests.java @@ -24,6 +24,7 @@ import org.dinky.executor.ExecutorConfig; import org.dinky.executor.ExecutorFactory; +import org.dinky.executor.LocalStreamExecutor; import org.junit.Ignore; import org.junit.Test; @@ -50,7 +51,7 @@ public void printTest() throws Exception { .toString(); ExecutorConfig executorConfig = ExecutorConfig.DEFAULT; - Executor executor = ExecutorFactory.buildLocalExecutor(executorConfig, DinkyClassLoader.build()); + Executor executor = new LocalStreamExecutor(executorConfig, DinkyClassLoader.build()); executor.executeSql(statement); executor.execute(""); } diff --git a/dinky-core/src/test/java/org/dinky/interceptor/FlinkCDCPipelineTest.java b/dinky-core/src/test/java/org/dinky/interceptor/FlinkCDCPipelineTest.java index 7e314b3a5b..95fffc0118 100644 --- a/dinky-core/src/test/java/org/dinky/interceptor/FlinkCDCPipelineTest.java +++ b/dinky-core/src/test/java/org/dinky/interceptor/FlinkCDCPipelineTest.java @@ -24,6 +24,7 @@ import org.dinky.executor.ExecutorConfig; import org.dinky.executor.ExecutorFactory; +import org.dinky.executor.LocalStreamExecutor; import org.junit.Ignore; import org.junit.Test; @@ -60,7 +61,7 @@ public void mysqlTest() throws Exception { .toString(); ExecutorConfig executorConfig = ExecutorConfig.DEFAULT; - Executor executor = ExecutorFactory.buildLocalExecutor(executorConfig, DinkyClassLoader.build()); + Executor executor = new LocalStreamExecutor(executorConfig, DinkyClassLoader.build()); executor.executeSql(statement); executor.execute(""); } diff --git a/dinky-executor-server/pom.xml b/dinky-executor-server/pom.xml new file mode 100644 index 0000000000..672d8f555c --- /dev/null +++ b/dinky-executor-server/pom.xml @@ -0,0 +1,26 @@ + + + 4.0.0 + + org.dinky + dinky + 1.0.0 + + + dinky-executor-server + + + 8 + 8 + UTF-8 + + + + org.dinky + dinky-core + + + + \ No newline at end of file diff --git a/dinky-executor-server/src/main/java/org/dinky/ServerExecutorServiceImpl.java b/dinky-executor-server/src/main/java/org/dinky/ServerExecutorServiceImpl.java new file mode 100644 index 0000000000..41a41c580a --- /dev/null +++ b/dinky-executor-server/src/main/java/org/dinky/ServerExecutorServiceImpl.java @@ -0,0 +1,10 @@ +package org.dinky; + +import java.rmi.RemoteException; +import java.rmi.server.UnicastRemoteObject; +import org.dinky.flink.ServerExecutorService; + +public class ServerExecutorServiceImpl extends UnicastRemoteObject implements ServerExecutorService { + public ServerExecutorServiceImpl() throws RemoteException { + } +} diff --git a/pom.xml b/pom.xml index a7ec0fd284..3f725fb4e2 100644 --- a/pom.xml +++ b/pom.xml @@ -46,6 +46,7 @@ dinky-app dinky-admin dinky-assembly + From 8891fa81e586c50b25c55bd115e4da064cb500b8 Mon Sep 17 00:00:00 2001 From: leechor Date: Sat, 9 Mar 2024 10:09:56 +0000 Subject: [PATCH 02/87] Spotless Apply --- .../dinky/controller/MonitorController.java | 4 +-- .../java/org/dinky/flink/DinkyExecutor.java | 19 ++++++++++++++ .../org/dinky/flink/LocalExecutorService.java | 25 ++++++++++++++++--- .../dinky/flink/ServerExecutorService.java | 19 ++++++++++++++ .../org/dinky/interceptor/CdcSourceTests.java | 3 +-- .../interceptor/FlinkCDCPipelineTest.java | 3 +-- pom.xml | 2 +- 7 files changed, 64 insertions(+), 11 deletions(-) diff --git a/dinky-admin/src/main/java/org/dinky/controller/MonitorController.java b/dinky-admin/src/main/java/org/dinky/controller/MonitorController.java index 1e3a38dbab..2306af4de9 100644 --- a/dinky-admin/src/main/java/org/dinky/controller/MonitorController.java +++ b/dinky-admin/src/main/java/org/dinky/controller/MonitorController.java @@ -34,6 +34,8 @@ import java.util.Arrays; import java.util.List; +import javax.servlet.http.HttpSession; + import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -54,8 +56,6 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import javax.servlet.http.HttpSession; - @Slf4j @RestController @Api(tags = "Monitor Controller") diff --git a/dinky-core/src/main/java/org/dinky/flink/DinkyExecutor.java b/dinky-core/src/main/java/org/dinky/flink/DinkyExecutor.java index 2c02ca1536..d6564ff863 100644 --- a/dinky-core/src/main/java/org/dinky/flink/DinkyExecutor.java +++ b/dinky-core/src/main/java/org/dinky/flink/DinkyExecutor.java @@ -1,3 +1,22 @@ +/* + * + * 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.flink; public interface DinkyExecutor { diff --git a/dinky-core/src/main/java/org/dinky/flink/LocalExecutorService.java b/dinky-core/src/main/java/org/dinky/flink/LocalExecutorService.java index 215a587784..9e21a4aaf5 100644 --- a/dinky-core/src/main/java/org/dinky/flink/LocalExecutorService.java +++ b/dinky-core/src/main/java/org/dinky/flink/LocalExecutorService.java @@ -1,3 +1,22 @@ +/* + * + * 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.flink; import org.dinky.classloader.DinkyClassLoader; @@ -8,11 +27,9 @@ public class LocalExecutorService implements DinkyExecutor { private final WeakReference dinkyClassLoader; public LocalExecutorService() { - dinkyClassLoader = new WeakReference<>(DinkyClassLoader.build()); + dinkyClassLoader = new WeakReference<>(DinkyClassLoader.build()); } @Override - public void init() { - - } + public void init() {} } diff --git a/dinky-core/src/main/java/org/dinky/flink/ServerExecutorService.java b/dinky-core/src/main/java/org/dinky/flink/ServerExecutorService.java index fdbd306641..dd56496cdf 100644 --- a/dinky-core/src/main/java/org/dinky/flink/ServerExecutorService.java +++ b/dinky-core/src/main/java/org/dinky/flink/ServerExecutorService.java @@ -1,3 +1,22 @@ +/* + * + * 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.flink; import org.dinky.executor.ExecutorConfig; diff --git a/dinky-core/src/test/java/org/dinky/interceptor/CdcSourceTests.java b/dinky-core/src/test/java/org/dinky/interceptor/CdcSourceTests.java index cbbf0fcd76..0cfa1029b3 100644 --- a/dinky-core/src/test/java/org/dinky/interceptor/CdcSourceTests.java +++ b/dinky-core/src/test/java/org/dinky/interceptor/CdcSourceTests.java @@ -22,9 +22,8 @@ import org.dinky.classloader.DinkyClassLoader; import org.dinky.executor.Executor; import org.dinky.executor.ExecutorConfig; -import org.dinky.executor.ExecutorFactory; - import org.dinky.executor.LocalStreamExecutor; + import org.junit.Ignore; import org.junit.Test; diff --git a/dinky-core/src/test/java/org/dinky/interceptor/FlinkCDCPipelineTest.java b/dinky-core/src/test/java/org/dinky/interceptor/FlinkCDCPipelineTest.java index 95fffc0118..cd96292a02 100644 --- a/dinky-core/src/test/java/org/dinky/interceptor/FlinkCDCPipelineTest.java +++ b/dinky-core/src/test/java/org/dinky/interceptor/FlinkCDCPipelineTest.java @@ -22,9 +22,8 @@ import org.dinky.classloader.DinkyClassLoader; import org.dinky.executor.Executor; import org.dinky.executor.ExecutorConfig; -import org.dinky.executor.ExecutorFactory; - import org.dinky.executor.LocalStreamExecutor; + import org.junit.Ignore; import org.junit.Test; diff --git a/pom.xml b/pom.xml index 3f725fb4e2..8881e08854 100644 --- a/pom.xml +++ b/pom.xml @@ -46,7 +46,7 @@ dinky-app dinky-admin dinky-assembly - + From 37017b096b8e9ac78296de6648a199eeed73333b Mon Sep 17 00:00:00 2001 From: Lichao Date: Mon, 11 Mar 2024 09:33:09 +0800 Subject: [PATCH 03/87] fix: --- .../src/main/java/org/dinky/flink/DinkyExecutor.java | 4 +++- .../main/java/org/dinky/flink/LocalExecutorService.java | 4 +++- .../main/java/org/dinky/flink/ServerExecutorService.java | 1 - dinky-executor-server/src/main/java/org/dinky/Main.java | 7 +++++++ .../src/main/java/org/dinky/ServerExecutorServiceImpl.java | 7 +++++++ 5 files changed, 20 insertions(+), 3 deletions(-) create mode 100644 dinky-executor-server/src/main/java/org/dinky/Main.java diff --git a/dinky-core/src/main/java/org/dinky/flink/DinkyExecutor.java b/dinky-core/src/main/java/org/dinky/flink/DinkyExecutor.java index 2c02ca1536..58679309d0 100644 --- a/dinky-core/src/main/java/org/dinky/flink/DinkyExecutor.java +++ b/dinky-core/src/main/java/org/dinky/flink/DinkyExecutor.java @@ -1,5 +1,7 @@ package org.dinky.flink; +import org.dinky.executor.ExecutorConfig; + public interface DinkyExecutor { - void init(); + void init(ExecutorConfig executorConfig); } diff --git a/dinky-core/src/main/java/org/dinky/flink/LocalExecutorService.java b/dinky-core/src/main/java/org/dinky/flink/LocalExecutorService.java index 215a587784..4eb160e33d 100644 --- a/dinky-core/src/main/java/org/dinky/flink/LocalExecutorService.java +++ b/dinky-core/src/main/java/org/dinky/flink/LocalExecutorService.java @@ -1,6 +1,7 @@ package org.dinky.flink; import org.dinky.classloader.DinkyClassLoader; +import org.dinky.executor.ExecutorConfig; import java.lang.ref.WeakReference; @@ -11,8 +12,9 @@ public LocalExecutorService() { dinkyClassLoader = new WeakReference<>(DinkyClassLoader.build()); } + @Override - public void init() { + public void init(ExecutorConfig executorConfig) { } } diff --git a/dinky-core/src/main/java/org/dinky/flink/ServerExecutorService.java b/dinky-core/src/main/java/org/dinky/flink/ServerExecutorService.java index fdbd306641..a4c0e438f9 100644 --- a/dinky-core/src/main/java/org/dinky/flink/ServerExecutorService.java +++ b/dinky-core/src/main/java/org/dinky/flink/ServerExecutorService.java @@ -6,5 +6,4 @@ public interface ServerExecutorService extends Remote, DinkyExecutor { - void init(ExecutorConfig executorConfig); } diff --git a/dinky-executor-server/src/main/java/org/dinky/Main.java b/dinky-executor-server/src/main/java/org/dinky/Main.java new file mode 100644 index 0000000000..74c0e03205 --- /dev/null +++ b/dinky-executor-server/src/main/java/org/dinky/Main.java @@ -0,0 +1,7 @@ +package org.dinky; + +public class Main { + public static void main(String[] args) { + System.out.println("Hello world!"); + } +} \ No newline at end of file diff --git a/dinky-executor-server/src/main/java/org/dinky/ServerExecutorServiceImpl.java b/dinky-executor-server/src/main/java/org/dinky/ServerExecutorServiceImpl.java index 41a41c580a..91ed7ae4f2 100644 --- a/dinky-executor-server/src/main/java/org/dinky/ServerExecutorServiceImpl.java +++ b/dinky-executor-server/src/main/java/org/dinky/ServerExecutorServiceImpl.java @@ -2,9 +2,16 @@ import java.rmi.RemoteException; import java.rmi.server.UnicastRemoteObject; + +import org.dinky.executor.ExecutorConfig; import org.dinky.flink.ServerExecutorService; public class ServerExecutorServiceImpl extends UnicastRemoteObject implements ServerExecutorService { public ServerExecutorServiceImpl() throws RemoteException { } + + @Override + public void init(ExecutorConfig executorConfig) { + + } } From 0a0cb846c858761be9ac4111a57e100379bd2772 Mon Sep 17 00:00:00 2001 From: leechor Date: Mon, 11 Mar 2024 01:36:42 +0000 Subject: [PATCH 04/87] Spotless Apply --- .../org/dinky/flink/LocalExecutorService.java | 26 +++++++++++++++---- .../dinky/flink/ServerExecutorService.java | 6 +---- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/dinky-core/src/main/java/org/dinky/flink/LocalExecutorService.java b/dinky-core/src/main/java/org/dinky/flink/LocalExecutorService.java index 4eb160e33d..4f5a495b61 100644 --- a/dinky-core/src/main/java/org/dinky/flink/LocalExecutorService.java +++ b/dinky-core/src/main/java/org/dinky/flink/LocalExecutorService.java @@ -1,3 +1,22 @@ +/* + * + * 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.flink; import org.dinky.classloader.DinkyClassLoader; @@ -9,12 +28,9 @@ public class LocalExecutorService implements DinkyExecutor { private final WeakReference dinkyClassLoader; public LocalExecutorService() { - dinkyClassLoader = new WeakReference<>(DinkyClassLoader.build()); + dinkyClassLoader = new WeakReference<>(DinkyClassLoader.build()); } - @Override - public void init(ExecutorConfig executorConfig) { - - } + public void init(ExecutorConfig executorConfig) {} } diff --git a/dinky-core/src/main/java/org/dinky/flink/ServerExecutorService.java b/dinky-core/src/main/java/org/dinky/flink/ServerExecutorService.java index 97db32b5d2..e4915abcc8 100644 --- a/dinky-core/src/main/java/org/dinky/flink/ServerExecutorService.java +++ b/dinky-core/src/main/java/org/dinky/flink/ServerExecutorService.java @@ -19,10 +19,6 @@ package org.dinky.flink; -import org.dinky.executor.ExecutorConfig; - import java.rmi.Remote; -public interface ServerExecutorService extends Remote, DinkyExecutor { - -} +public interface ServerExecutorService extends Remote, DinkyExecutor {} From 825500b61d3f049790fecbbe5c589ab1b562bd4f Mon Sep 17 00:00:00 2001 From: Lichao Date: Mon, 11 Mar 2024 20:29:06 +0800 Subject: [PATCH 05/87] refactor: move DinkyLoader to Executor.java --- .../dinky/controller/MonitorController.java | 5 +--- .../trans/parse/AddJarSqlParseStrategy.java | 2 +- .../org/dinky/executor/AppBatchExecutor.java | 4 +-- .../org/dinky/executor/AppStreamExecutor.java | 4 +-- .../java/org/dinky/executor/Executor.java | 14 +++++----- .../org/dinky/executor/ExecutorFactory.java | 26 +++++++++---------- .../dinky/executor/LocalBatchExecutor.java | 4 +-- .../dinky/executor/LocalStreamExecutor.java | 4 +-- .../dinky/executor/RemoteBatchExecutor.java | 4 +-- .../dinky/executor/RemoteStreamExecutor.java | 4 +-- .../main/java/org/dinky/job/JobManager.java | 5 ++-- 11 files changed, 36 insertions(+), 40 deletions(-) diff --git a/dinky-admin/src/main/java/org/dinky/controller/MonitorController.java b/dinky-admin/src/main/java/org/dinky/controller/MonitorController.java index 2306af4de9..49c597012c 100644 --- a/dinky-admin/src/main/java/org/dinky/controller/MonitorController.java +++ b/dinky-admin/src/main/java/org/dinky/controller/MonitorController.java @@ -34,8 +34,6 @@ import java.util.Arrays; import java.util.List; -import javax.servlet.http.HttpSession; - import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -128,8 +126,7 @@ public Result> getMetricsLayoutByName(@RequestParam String layoutN @GetMapping("/getJvmInfo") @ApiOperation("Get Jvm Data Display") - public SseEmitter getJvmInfo(HttpSession session) { - String seesionID = session.getId(); + public SseEmitter getJvmInfo() { return monitorService.sendJvmInfo(); } diff --git a/dinky-client/dinky-client-base/src/main/java/org/dinky/trans/parse/AddJarSqlParseStrategy.java b/dinky-client/dinky-client-base/src/main/java/org/dinky/trans/parse/AddJarSqlParseStrategy.java index 32d37ce390..5e9b6a52ab 100644 --- a/dinky-client/dinky-client-base/src/main/java/org/dinky/trans/parse/AddJarSqlParseStrategy.java +++ b/dinky-client/dinky-client-base/src/main/java/org/dinky/trans/parse/AddJarSqlParseStrategy.java @@ -42,7 +42,7 @@ */ public class AddJarSqlParseStrategy extends AbstractRegexParseStrategy { - private static final String ADD_JAR = "(add\\s+(customjar|file))\\s+'(.*.jar)'"; + private static final String ADD_JAR = "(add\\s+customjar)\\s+'(.*.jar)'"; private static final Pattern ADD_JAR_PATTERN = Pattern.compile(ADD_JAR, Pattern.CASE_INSENSITIVE); public static final AddJarSqlParseStrategy INSTANCE = new AddJarSqlParseStrategy(); diff --git a/dinky-core/src/main/java/org/dinky/executor/AppBatchExecutor.java b/dinky-core/src/main/java/org/dinky/executor/AppBatchExecutor.java index 414b4fd961..e97dd1d0b7 100644 --- a/dinky-core/src/main/java/org/dinky/executor/AppBatchExecutor.java +++ b/dinky-core/src/main/java/org/dinky/executor/AppBatchExecutor.java @@ -31,7 +31,7 @@ */ public class AppBatchExecutor extends Executor { - public AppBatchExecutor(ExecutorConfig executorConfig, DinkyClassLoader classLoader) { + public AppBatchExecutor(ExecutorConfig executorConfig) { this.executorConfig = executorConfig; if (executorConfig.isValidConfig()) { Configuration configuration = Configuration.fromMap(executorConfig.getConfig()); @@ -39,7 +39,7 @@ public AppBatchExecutor(ExecutorConfig executorConfig, DinkyClassLoader classLoa } else { this.environment = StreamExecutionEnvironment.getExecutionEnvironment(); } - init(classLoader); + init(); } @Override diff --git a/dinky-core/src/main/java/org/dinky/executor/AppStreamExecutor.java b/dinky-core/src/main/java/org/dinky/executor/AppStreamExecutor.java index d7843e61db..485cd64fb3 100644 --- a/dinky-core/src/main/java/org/dinky/executor/AppStreamExecutor.java +++ b/dinky-core/src/main/java/org/dinky/executor/AppStreamExecutor.java @@ -31,7 +31,7 @@ */ public class AppStreamExecutor extends Executor { - public AppStreamExecutor(ExecutorConfig executorConfig, DinkyClassLoader classLoader) { + public AppStreamExecutor(ExecutorConfig executorConfig) { this.executorConfig = executorConfig; if (executorConfig.isValidConfig()) { Configuration configuration = Configuration.fromMap(executorConfig.getConfig()); @@ -39,7 +39,7 @@ public AppStreamExecutor(ExecutorConfig executorConfig, DinkyClassLoader classLo } else { this.environment = StreamExecutionEnvironment.getExecutionEnvironment(); } - init(classLoader); + init(); } @Override diff --git a/dinky-core/src/main/java/org/dinky/executor/Executor.java b/dinky-core/src/main/java/org/dinky/executor/Executor.java index edd6928773..5fe5a4ad40 100644 --- a/dinky-core/src/main/java/org/dinky/executor/Executor.java +++ b/dinky-core/src/main/java/org/dinky/executor/Executor.java @@ -46,6 +46,7 @@ import org.apache.flink.table.api.TableResult; import java.io.File; +import java.lang.ref.WeakReference; import java.net.URL; import java.util.Arrays; import java.util.HashMap; @@ -83,7 +84,7 @@ public abstract class Executor { // The config of Dinky executor. protected ExecutorConfig executorConfig; - protected DinkyClassLoader dinkyClassLoader; + protected WeakReference dinkyClassLoader = new WeakReference<>(DinkyClassLoader.build()); // Flink configuration, such as set rest.port = 8086 protected Map setConfig = new HashMap<>(); @@ -93,7 +94,7 @@ public abstract class Executor { // return dinkyClassLoader public DinkyClassLoader getDinkyClassLoader() { - return dinkyClassLoader; + return dinkyClassLoader.get(); } public VariableManager getVariableManager() { @@ -150,14 +151,13 @@ private void initClassloader(DinkyClassLoader classLoader) { } } - protected void init(DinkyClassLoader classLoader) { - initClassloader(classLoader); - this.dinkyClassLoader = classLoader; + protected void init() { + initClassloader(getDinkyClassLoader()); if (executorConfig.isValidParallelism()) { environment.setParallelism(executorConfig.getParallelism()); } - tableEnvironment = createCustomTableEnvironment(classLoader); + tableEnvironment = createCustomTableEnvironment(getDinkyClassLoader()); CustomTableEnvironmentContext.set(tableEnvironment); Configuration configuration = tableEnvironment.getConfig().getConfiguration(); @@ -210,7 +210,7 @@ public TableResult executeSql(String statement) { public void initUDF(String... udfFilePath) { List jarFiles = DinkyClassLoader.getJarFiles(udfFilePath, null); - dinkyClassLoader.addURLs(jarFiles); + getDinkyClassLoader().addURLs(jarFiles); } public void initPyUDF(String executable, String... udfPyFilePath) { diff --git a/dinky-core/src/main/java/org/dinky/executor/ExecutorFactory.java b/dinky-core/src/main/java/org/dinky/executor/ExecutorFactory.java index 237b462996..80de699666 100644 --- a/dinky-core/src/main/java/org/dinky/executor/ExecutorFactory.java +++ b/dinky-core/src/main/java/org/dinky/executor/ExecutorFactory.java @@ -34,38 +34,38 @@ public final class ExecutorFactory { private ExecutorFactory() {} public static Executor getDefaultExecutor() { - return new LocalStreamExecutor(ExecutorConfig.DEFAULT, new WeakReference<>(DinkyClassLoader.build()).get()); + return new LocalStreamExecutor(ExecutorConfig.DEFAULT); } - public static Executor buildExecutor(ExecutorConfig executorConfig, DinkyClassLoader classLoader) { + public static Executor buildExecutor(ExecutorConfig executorConfig) { if (executorConfig.isRemote()) { - return buildRemoteExecutor(executorConfig, classLoader); + return buildRemoteExecutor(executorConfig); } else { - return buildLocalExecutor(executorConfig, classLoader); + return buildLocalExecutor(executorConfig); } } - public static Executor buildLocalExecutor(ExecutorConfig executorConfig, DinkyClassLoader classLoader) { + public static Executor buildLocalExecutor(ExecutorConfig executorConfig) { if (executorConfig.isUseBatchModel()) { - return new LocalBatchExecutor(executorConfig, classLoader); + return new LocalBatchExecutor(executorConfig); } else { - return new LocalStreamExecutor(executorConfig, classLoader); + return new LocalStreamExecutor(executorConfig); } } - public static Executor buildAppStreamExecutor(ExecutorConfig executorConfig, DinkyClassLoader classLoader) { + public static Executor buildAppStreamExecutor(ExecutorConfig executorConfig) { if (executorConfig.isUseBatchModel()) { - return new AppBatchExecutor(executorConfig, classLoader); + return new AppBatchExecutor(executorConfig); } else { - return new AppStreamExecutor(executorConfig, classLoader); + return new AppStreamExecutor(executorConfig); } } - public static Executor buildRemoteExecutor(ExecutorConfig executorConfig, DinkyClassLoader classLoader) { + public static Executor buildRemoteExecutor(ExecutorConfig executorConfig) { if (executorConfig.isUseBatchModel()) { - return new RemoteBatchExecutor(executorConfig, classLoader); + return new RemoteBatchExecutor(executorConfig); } else { - return new RemoteStreamExecutor(executorConfig, classLoader); + return new RemoteStreamExecutor(executorConfig); } } } diff --git a/dinky-core/src/main/java/org/dinky/executor/LocalBatchExecutor.java b/dinky-core/src/main/java/org/dinky/executor/LocalBatchExecutor.java index 3a56e8c413..549e8febff 100644 --- a/dinky-core/src/main/java/org/dinky/executor/LocalBatchExecutor.java +++ b/dinky-core/src/main/java/org/dinky/executor/LocalBatchExecutor.java @@ -38,7 +38,7 @@ */ public class LocalBatchExecutor extends Executor { - public LocalBatchExecutor(ExecutorConfig executorConfig, DinkyClassLoader classLoader) { + public LocalBatchExecutor(ExecutorConfig executorConfig) { this.executorConfig = executorConfig; if (executorConfig.isValidJarFiles()) { executorConfig @@ -58,7 +58,7 @@ public LocalBatchExecutor(ExecutorConfig executorConfig, DinkyClassLoader classL } else { this.environment = StreamExecutionEnvironment.createLocalEnvironment(); } - init(classLoader); + init(); } @Override diff --git a/dinky-core/src/main/java/org/dinky/executor/LocalStreamExecutor.java b/dinky-core/src/main/java/org/dinky/executor/LocalStreamExecutor.java index 6948ac944a..bc7041ccb1 100644 --- a/dinky-core/src/main/java/org/dinky/executor/LocalStreamExecutor.java +++ b/dinky-core/src/main/java/org/dinky/executor/LocalStreamExecutor.java @@ -41,7 +41,7 @@ */ public class LocalStreamExecutor extends Executor { - public LocalStreamExecutor(ExecutorConfig executorConfig, DinkyClassLoader classLoader) { + public LocalStreamExecutor(ExecutorConfig executorConfig) { this.executorConfig = executorConfig; if (executorConfig.isValidJarFiles()) { executorConfig @@ -64,7 +64,7 @@ public LocalStreamExecutor(ExecutorConfig executorConfig, DinkyClassLoader class } else { this.environment = StreamExecutionEnvironment.createLocalEnvironment(); } - init(classLoader); + init(); } @Override diff --git a/dinky-core/src/main/java/org/dinky/executor/RemoteBatchExecutor.java b/dinky-core/src/main/java/org/dinky/executor/RemoteBatchExecutor.java index 2562932846..612f7e4c2c 100644 --- a/dinky-core/src/main/java/org/dinky/executor/RemoteBatchExecutor.java +++ b/dinky-core/src/main/java/org/dinky/executor/RemoteBatchExecutor.java @@ -31,7 +31,7 @@ */ public class RemoteBatchExecutor extends Executor { - public RemoteBatchExecutor(ExecutorConfig executorConfig, DinkyClassLoader classLoader) { + public RemoteBatchExecutor(ExecutorConfig executorConfig) { this.executorConfig = executorConfig; if (executorConfig.isValidConfig()) { Configuration configuration = Configuration.fromMap(executorConfig.getConfig()); @@ -41,7 +41,7 @@ public RemoteBatchExecutor(ExecutorConfig executorConfig, DinkyClassLoader class this.environment = StreamExecutionEnvironment.createRemoteEnvironment( executorConfig.getHost(), executorConfig.getPort(), executorConfig.getJarFiles()); } - init(classLoader); + init(); } @Override diff --git a/dinky-core/src/main/java/org/dinky/executor/RemoteStreamExecutor.java b/dinky-core/src/main/java/org/dinky/executor/RemoteStreamExecutor.java index 0a298af9b9..c7a99af0e3 100644 --- a/dinky-core/src/main/java/org/dinky/executor/RemoteStreamExecutor.java +++ b/dinky-core/src/main/java/org/dinky/executor/RemoteStreamExecutor.java @@ -31,7 +31,7 @@ */ public class RemoteStreamExecutor extends Executor { - public RemoteStreamExecutor(ExecutorConfig executorConfig, DinkyClassLoader classLoader) { + public RemoteStreamExecutor(ExecutorConfig executorConfig) { this.executorConfig = executorConfig; if (executorConfig.isValidConfig()) { Configuration configuration = Configuration.fromMap(executorConfig.getConfig()); @@ -41,7 +41,7 @@ public RemoteStreamExecutor(ExecutorConfig executorConfig, DinkyClassLoader clas this.environment = StreamExecutionEnvironment.createRemoteEnvironment( executorConfig.getHost(), executorConfig.getPort(), executorConfig.getJarFiles()); } - init(classLoader); + init(); } @Override diff --git a/dinky-core/src/main/java/org/dinky/job/JobManager.java b/dinky-core/src/main/java/org/dinky/job/JobManager.java index 83dba4a242..27df0cee6f 100644 --- a/dinky-core/src/main/java/org/dinky/job/JobManager.java +++ b/dinky-core/src/main/java/org/dinky/job/JobManager.java @@ -111,7 +111,6 @@ public class JobManager { private JobParam jobParam = null; private String currentSql = ""; - private final WeakReference dinkyClassLoader = new WeakReference<>(DinkyClassLoader.build()); private Job job; public JobManager() {} @@ -170,7 +169,7 @@ public boolean isUseGateway() { // return dinkyclassloader public DinkyClassLoader getDinkyClassLoader() { - return dinkyClassLoader.get(); + return executor.getDinkyClassLoader(); } // return udfPathContextHolder @@ -216,7 +215,7 @@ public void init() { useRestAPI = SystemConfiguration.getInstances().isUseRestAPI(); executorConfig = config.getExecutorSetting(); executorConfig.setPlan(isPlanMode); - executor = ExecutorFactory.buildExecutor(executorConfig, getDinkyClassLoader()); + executor = ExecutorFactory.buildExecutor(executorConfig); } private boolean ready() { From 0409a314b1772e77312bf1d9df1348e582fc4017 Mon Sep 17 00:00:00 2001 From: Lichao Date: Mon, 11 Mar 2024 20:55:58 +0800 Subject: [PATCH 06/87] refactor: add Executor interface --- .../org/dinky/app/flinksql/Submitter.java | 3 +- .../org/dinky/executor/AbstractExecutor.java | 333 ++++++++++++++++++ .../org/dinky/executor/AppBatchExecutor.java | 4 +- .../org/dinky/executor/AppStreamExecutor.java | 4 +- .../java/org/dinky/executor/Executor.java | 292 ++------------- .../org/dinky/executor/ExecutorFactory.java | 4 - .../dinky/executor/LocalBatchExecutor.java | 4 +- .../dinky/executor/LocalStreamExecutor.java | 3 +- .../dinky/executor/RemoteBatchExecutor.java | 4 +- .../dinky/executor/RemoteStreamExecutor.java | 4 +- .../main/java/org/dinky/job/JobManager.java | 1 - .../org/dinky/interceptor/CdcSourceTests.java | 3 +- .../interceptor/FlinkCDCPipelineTest.java | 3 +- 13 files changed, 375 insertions(+), 287 deletions(-) create mode 100644 dinky-core/src/main/java/org/dinky/executor/AbstractExecutor.java diff --git a/dinky-app/dinky-app-base/src/main/java/org/dinky/app/flinksql/Submitter.java b/dinky-app/dinky-app-base/src/main/java/org/dinky/app/flinksql/Submitter.java index 42bf641425..c8bcf7dc01 100644 --- a/dinky-app/dinky-app-base/src/main/java/org/dinky/app/flinksql/Submitter.java +++ b/dinky-app/dinky-app-base/src/main/java/org/dinky/app/flinksql/Submitter.java @@ -123,8 +123,7 @@ public static void submit(AppParamConfig config) throws SQLException { // .config(JsonUtils.toMap(appTask.getConfigJson())) .build(); - executor = ExecutorFactory.buildAppStreamExecutor( - executorConfig, new WeakReference<>(DinkyClassLoader.build()).get()); + executor = ExecutorFactory.buildAppStreamExecutor(executorConfig); // 加载第三方jar //TODO 这里有问题,需要修一修 loadDep(appTask.getType(), config.getTaskId(), executorConfig); diff --git a/dinky-core/src/main/java/org/dinky/executor/AbstractExecutor.java b/dinky-core/src/main/java/org/dinky/executor/AbstractExecutor.java new file mode 100644 index 0000000000..7eee29914a --- /dev/null +++ b/dinky-core/src/main/java/org/dinky/executor/AbstractExecutor.java @@ -0,0 +1,333 @@ +/* + * + * 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.executor; + +import org.dinky.assertion.Asserts; +import org.dinky.classloader.DinkyClassLoader; +import org.dinky.context.CustomTableEnvironmentContext; +import org.dinky.data.model.LineageRel; +import org.dinky.data.result.SqlExplainResult; +import org.dinky.interceptor.FlinkInterceptor; +import org.dinky.interceptor.FlinkInterceptorResult; +import org.dinky.utils.KerberosUtil; + +import org.apache.flink.api.common.ExecutionConfig; +import org.apache.flink.api.common.JobExecutionResult; +import org.apache.flink.configuration.Configuration; +import org.apache.flink.configuration.PipelineOptions; +import org.apache.flink.core.execution.JobClient; +import org.apache.flink.python.PythonOptions; +import org.apache.flink.runtime.jobgraph.JobGraph; +import org.apache.flink.runtime.jobgraph.jsonplan.JsonPlanGenerator; +import org.apache.flink.runtime.rest.messages.JobPlanInfo; +import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; +import org.apache.flink.streaming.api.graph.JSONGenerator; +import org.apache.flink.streaming.api.graph.StreamGraph; +import org.apache.flink.table.api.ExplainDetail; +import org.apache.flink.table.api.StatementSet; +import org.apache.flink.table.api.TableConfig; +import org.apache.flink.table.api.TableResult; + +import java.io.File; +import java.lang.ref.WeakReference; +import java.net.URL; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ReflectUtil; +import cn.hutool.core.util.URLUtil; +import lombok.extern.slf4j.Slf4j; + +/** + * Executor + * + * @since 2021/11/17 + */ +@Slf4j +public abstract class AbstractExecutor implements Executor{ + + private static final Logger logger = LoggerFactory.getLogger(AbstractExecutor.class); + + // Flink stream execution environment, batch model also use it. + protected StreamExecutionEnvironment environment; + + // Dinky table environment. + protected CustomTableEnvironment tableEnvironment; + + // The config of Dinky executor. + protected ExecutorConfig executorConfig; + + protected WeakReference dinkyClassLoader = new WeakReference<>(DinkyClassLoader.build()); + + // Flink configuration, such as set rest.port = 8086 + protected Map setConfig = new HashMap<>(); + + // Dinky variable manager + protected VariableManager variableManager = new VariableManager(); + + // return dinkyClassLoader + @Override + public DinkyClassLoader getDinkyClassLoader() { + return dinkyClassLoader.get(); + } + + @Override + public VariableManager getVariableManager() { + return variableManager; + } + + @Override + public boolean isUseSqlFragment() { + return executorConfig.isUseSqlFragment(); + } + + @Override + public ExecutionConfig getExecutionConfig() { + return environment.getConfig(); + } + + @Override + public StreamExecutionEnvironment getStreamExecutionEnvironment() { + return environment; + } + + @Override + public void setStreamExecutionEnvironment(StreamExecutionEnvironment environment) { + this.environment = environment; + } + + @Override + public CustomTableEnvironment getCustomTableEnvironment() { + return tableEnvironment; + } + + @Override + public ExecutorConfig getExecutorConfig() { + return executorConfig; + } + + @Override + public Map getSetConfig() { + return setConfig; + } + + @Override + public TableConfig getTableConfig() { + return tableEnvironment.getConfig(); + } + + @Override + public String getTimeZone() { + return getTableConfig().getLocalTimeZone().getId(); + } + + private void initClassloader(DinkyClassLoader classLoader) { + if (classLoader != null) { + try { + StreamExecutionEnvironment env = this.environment; + // Fix the Classloader in the env above to appClassLoader, causing ckp to fail to compile + ReflectUtil.setFieldValue(env, "userClassloader", classLoader); + env.configure(env.getConfiguration(), classLoader); + } catch (Throwable e) { + log.warn( + "The version of flink does not have a Classloader field and the classloader cannot be set.", e); + } + } + } + + protected void init() { + initClassloader(getDinkyClassLoader()); + if (executorConfig.isValidParallelism()) { + environment.setParallelism(executorConfig.getParallelism()); + } + + tableEnvironment = createCustomTableEnvironment(getDinkyClassLoader()); + CustomTableEnvironmentContext.set(tableEnvironment); + + Configuration configuration = tableEnvironment.getConfig().getConfiguration(); + if (executorConfig.isValidJobName()) { + configuration.setString(PipelineOptions.NAME.key(), executorConfig.getJobName()); + setConfig.put(PipelineOptions.NAME.key(), executorConfig.getJobName()); + } + if (executorConfig.isValidConfig()) { + for (Map.Entry entry : executorConfig.getConfig().entrySet()) { + configuration.setString(entry.getKey(), entry.getValue()); + } + } + if (executorConfig.isValidVariables()) { + variableManager.registerVariable(executorConfig.getVariables()); + } + } + + abstract CustomTableEnvironment createCustomTableEnvironment(ClassLoader classLoader); + + @Override + public String pretreatStatement(String statement) { + return FlinkInterceptor.pretreatStatement(this, statement); + } + + private FlinkInterceptorResult pretreatExecute(String statement) { + return FlinkInterceptor.build(this, statement); + } + + @Override + public JobExecutionResult execute(String jobName) throws Exception { + return environment.execute(jobName); + } + + @Override + public JobClient executeAsync(String jobName) throws Exception { + return environment.executeAsync(jobName); + } + + @Override + public TableResult executeSql(String statement) { + statement = pretreatStatement(statement); + FlinkInterceptorResult flinkInterceptorResult = pretreatExecute(statement); + if (Asserts.isNotNull(flinkInterceptorResult.getTableResult())) { + return flinkInterceptorResult.getTableResult(); + } + + if (flinkInterceptorResult.isNoExecute()) { + return CustomTableResultImpl.TABLE_RESULT_OK; + } + + KerberosUtil.authenticate(setConfig); + return tableEnvironment.executeSql(statement); + } + + @Override + public void initUDF(String... udfFilePath) { + List jarFiles = DinkyClassLoader.getJarFiles(udfFilePath, null); + getDinkyClassLoader().addURLs(jarFiles); + } + + @Override + public void initPyUDF(String executable, String... udfPyFilePath) { + if (udfPyFilePath == null || udfPyFilePath.length == 0) { + return; + } + + Configuration configuration = tableEnvironment.getConfig().getConfiguration(); + configuration.setString(PythonOptions.PYTHON_FILES, String.join(",", udfPyFilePath)); + configuration.setString(PythonOptions.PYTHON_CLIENT_EXECUTABLE, executable); + } + + private void addJar(String... jarPath) { + Configuration configuration = tableEnvironment.getRootConfiguration(); + List jars = configuration.get(PipelineOptions.JARS); + if (jars == null) { + tableEnvironment.addConfiguration(PipelineOptions.JARS, CollUtil.newArrayList(jarPath)); + } else { + CollUtil.addAll(jars, jarPath); + } + } + + @Override + public void addJar(File... jarPath) { + addJar(Arrays.stream(jarPath).map(URLUtil::getURL).map(URL::toString).toArray(String[]::new)); + } + + @Override + public SqlExplainResult explainSqlRecord(String statement, ExplainDetail... extraDetails) { + statement = pretreatStatement(statement); + if (Asserts.isNotNullString(statement) && !pretreatExecute(statement).isNoExecute()) { + return tableEnvironment.explainSqlRecord(statement, extraDetails); + } + return null; + } + + @Override + public ObjectNode getStreamGraph(List statements) { + StreamGraph streamGraph = tableEnvironment.getStreamGraphFromInserts(statements); + return getStreamGraphJsonNode(streamGraph); + } + + private ObjectNode getStreamGraphJsonNode(StreamGraph streamGraph) { + JSONGenerator jsonGenerator = new JSONGenerator(streamGraph); + String json = jsonGenerator.getJSON(); + ObjectMapper mapper = new ObjectMapper(); + ObjectNode objectNode = mapper.createObjectNode(); + try { + objectNode = (ObjectNode) mapper.readTree(json); + } catch (JsonProcessingException e) { + logger.error("Get stream graph json node error.", e); + } + + return objectNode; + } + + @Override + public StreamGraph getStreamGraph() { + return environment.getStreamGraph(); + } + + @Override + public ObjectNode getStreamGraphFromDataStream(List statements) { + statements.forEach(this::executeSql); + return getStreamGraphJsonNode(getStreamGraph()); + } + + @Override + public JobPlanInfo getJobPlanInfo(List statements) { + return tableEnvironment.getJobPlanInfo(statements); + } + + @Override + public JobPlanInfo getJobPlanInfoFromDataStream(List statements) { + statements.forEach(this::executeSql); + StreamGraph streamGraph = getStreamGraph(); + return new JobPlanInfo(JsonPlanGenerator.generatePlan(streamGraph.getJobGraph())); + } + + @Override + public JobGraph getJobGraphFromInserts(List statements) { + return tableEnvironment.getJobGraphFromInserts(statements); + } + + @Override + public TableResult executeStatementSet(List statements) { + StatementSet statementSet = tableEnvironment.createStatementSet(); + statements.forEach(statementSet::addInsertSql); + return statementSet.execute(); + } + + @Override + public String explainStatementSet(List statements) { + StatementSet statementSet = tableEnvironment.createStatementSet(); + statements.forEach(statementSet::addInsertSql); + return statementSet.explain(); + } + + @Override + public List getLineage(String statement) { + return tableEnvironment.getLineage(statement); + } +} diff --git a/dinky-core/src/main/java/org/dinky/executor/AppBatchExecutor.java b/dinky-core/src/main/java/org/dinky/executor/AppBatchExecutor.java index e97dd1d0b7..ea32c78134 100644 --- a/dinky-core/src/main/java/org/dinky/executor/AppBatchExecutor.java +++ b/dinky-core/src/main/java/org/dinky/executor/AppBatchExecutor.java @@ -19,8 +19,6 @@ package org.dinky.executor; -import org.dinky.classloader.DinkyClassLoader; - import org.apache.flink.configuration.Configuration; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; @@ -29,7 +27,7 @@ * * @since 2022/2/7 22:14 */ -public class AppBatchExecutor extends Executor { +public class AppBatchExecutor extends AbstractExecutor { public AppBatchExecutor(ExecutorConfig executorConfig) { this.executorConfig = executorConfig; diff --git a/dinky-core/src/main/java/org/dinky/executor/AppStreamExecutor.java b/dinky-core/src/main/java/org/dinky/executor/AppStreamExecutor.java index 485cd64fb3..573f7eb4fe 100644 --- a/dinky-core/src/main/java/org/dinky/executor/AppStreamExecutor.java +++ b/dinky-core/src/main/java/org/dinky/executor/AppStreamExecutor.java @@ -19,8 +19,6 @@ package org.dinky.executor; -import org.dinky.classloader.DinkyClassLoader; - import org.apache.flink.configuration.Configuration; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; @@ -29,7 +27,7 @@ * * @since 2021/11/18 */ -public class AppStreamExecutor extends Executor { +public class AppStreamExecutor extends AbstractExecutor { public AppStreamExecutor(ExecutorConfig executorConfig) { this.executorConfig = executorConfig; diff --git a/dinky-core/src/main/java/org/dinky/executor/Executor.java b/dinky-core/src/main/java/org/dinky/executor/Executor.java index 5fe5a4ad40..90f8a126ea 100644 --- a/dinky-core/src/main/java/org/dinky/executor/Executor.java +++ b/dinky-core/src/main/java/org/dinky/executor/Executor.java @@ -1,305 +1,79 @@ -/* - * - * 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.executor; -import org.dinky.assertion.Asserts; -import org.dinky.classloader.DinkyClassLoader; -import org.dinky.context.CustomTableEnvironmentContext; -import org.dinky.data.model.LineageRel; -import org.dinky.data.result.SqlExplainResult; -import org.dinky.interceptor.FlinkInterceptor; -import org.dinky.interceptor.FlinkInterceptorResult; -import org.dinky.utils.KerberosUtil; - +import com.fasterxml.jackson.databind.node.ObjectNode; import org.apache.flink.api.common.ExecutionConfig; import org.apache.flink.api.common.JobExecutionResult; -import org.apache.flink.configuration.Configuration; -import org.apache.flink.configuration.PipelineOptions; import org.apache.flink.core.execution.JobClient; -import org.apache.flink.python.PythonOptions; import org.apache.flink.runtime.jobgraph.JobGraph; -import org.apache.flink.runtime.jobgraph.jsonplan.JsonPlanGenerator; import org.apache.flink.runtime.rest.messages.JobPlanInfo; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; -import org.apache.flink.streaming.api.graph.JSONGenerator; import org.apache.flink.streaming.api.graph.StreamGraph; import org.apache.flink.table.api.ExplainDetail; -import org.apache.flink.table.api.StatementSet; import org.apache.flink.table.api.TableConfig; import org.apache.flink.table.api.TableResult; +import org.dinky.classloader.DinkyClassLoader; +import org.dinky.data.model.LineageRel; +import org.dinky.data.result.SqlExplainResult; import java.io.File; -import java.lang.ref.WeakReference; -import java.net.URL; -import java.util.Arrays; -import java.util.HashMap; import java.util.List; import java.util.Map; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.node.ObjectNode; - -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.util.ReflectUtil; -import cn.hutool.core.util.URLUtil; -import lombok.extern.slf4j.Slf4j; - -/** - * Executor - * - * @since 2021/11/17 - */ -@Slf4j -public abstract class Executor { - - private static final Logger logger = LoggerFactory.getLogger(Executor.class); - - // Flink stream execution environment, batch model also use it. - protected StreamExecutionEnvironment environment; - - // Dinky table environment. - protected CustomTableEnvironment tableEnvironment; - - // The config of Dinky executor. - protected ExecutorConfig executorConfig; - - protected WeakReference dinkyClassLoader = new WeakReference<>(DinkyClassLoader.build()); - - // Flink configuration, such as set rest.port = 8086 - protected Map setConfig = new HashMap<>(); - - // Dinky variable manager - protected VariableManager variableManager = new VariableManager(); - +public interface Executor { // return dinkyClassLoader - public DinkyClassLoader getDinkyClassLoader() { - return dinkyClassLoader.get(); - } - - public VariableManager getVariableManager() { - return variableManager; - } - - public boolean isUseSqlFragment() { - return executorConfig.isUseSqlFragment(); - } - - public ExecutionConfig getExecutionConfig() { - return environment.getConfig(); - } - - public StreamExecutionEnvironment getStreamExecutionEnvironment() { - return environment; - } - - public void setStreamExecutionEnvironment(StreamExecutionEnvironment environment) { - this.environment = environment; - } - - public CustomTableEnvironment getCustomTableEnvironment() { - return tableEnvironment; - } - - public ExecutorConfig getExecutorConfig() { - return executorConfig; - } - - public Map getSetConfig() { - return setConfig; - } - - public TableConfig getTableConfig() { - return tableEnvironment.getConfig(); - } - - public String getTimeZone() { - return getTableConfig().getLocalTimeZone().getId(); - } - - private void initClassloader(DinkyClassLoader classLoader) { - if (classLoader != null) { - try { - StreamExecutionEnvironment env = this.environment; - // Fix the Classloader in the env above to appClassLoader, causing ckp to fail to compile - ReflectUtil.setFieldValue(env, "userClassloader", classLoader); - env.configure(env.getConfiguration(), classLoader); - } catch (Throwable e) { - log.warn( - "The version of flink does not have a Classloader field and the classloader cannot be set.", e); - } - } - } - - protected void init() { - initClassloader(getDinkyClassLoader()); - if (executorConfig.isValidParallelism()) { - environment.setParallelism(executorConfig.getParallelism()); - } + DinkyClassLoader getDinkyClassLoader(); - tableEnvironment = createCustomTableEnvironment(getDinkyClassLoader()); - CustomTableEnvironmentContext.set(tableEnvironment); + VariableManager getVariableManager(); - Configuration configuration = tableEnvironment.getConfig().getConfiguration(); - if (executorConfig.isValidJobName()) { - configuration.setString(PipelineOptions.NAME.key(), executorConfig.getJobName()); - setConfig.put(PipelineOptions.NAME.key(), executorConfig.getJobName()); - } - if (executorConfig.isValidConfig()) { - for (Map.Entry entry : executorConfig.getConfig().entrySet()) { - configuration.setString(entry.getKey(), entry.getValue()); - } - } - if (executorConfig.isValidVariables()) { - variableManager.registerVariable(executorConfig.getVariables()); - } - } + boolean isUseSqlFragment(); - abstract CustomTableEnvironment createCustomTableEnvironment(ClassLoader classLoader); + ExecutionConfig getExecutionConfig(); - public String pretreatStatement(String statement) { - return FlinkInterceptor.pretreatStatement(this, statement); - } + StreamExecutionEnvironment getStreamExecutionEnvironment(); - private FlinkInterceptorResult pretreatExecute(String statement) { - return FlinkInterceptor.build(this, statement); - } + void setStreamExecutionEnvironment(StreamExecutionEnvironment environment); - public JobExecutionResult execute(String jobName) throws Exception { - return environment.execute(jobName); - } + CustomTableEnvironment getCustomTableEnvironment(); - public JobClient executeAsync(String jobName) throws Exception { - return environment.executeAsync(jobName); - } + ExecutorConfig getExecutorConfig(); - public TableResult executeSql(String statement) { - statement = pretreatStatement(statement); - FlinkInterceptorResult flinkInterceptorResult = pretreatExecute(statement); - if (Asserts.isNotNull(flinkInterceptorResult.getTableResult())) { - return flinkInterceptorResult.getTableResult(); - } + Map getSetConfig(); - if (flinkInterceptorResult.isNoExecute()) { - return CustomTableResultImpl.TABLE_RESULT_OK; - } + TableConfig getTableConfig(); - KerberosUtil.authenticate(setConfig); - return tableEnvironment.executeSql(statement); - } + String getTimeZone(); - public void initUDF(String... udfFilePath) { - List jarFiles = DinkyClassLoader.getJarFiles(udfFilePath, null); - getDinkyClassLoader().addURLs(jarFiles); - } + String pretreatStatement(String statement); - public void initPyUDF(String executable, String... udfPyFilePath) { - if (udfPyFilePath == null || udfPyFilePath.length == 0) { - return; - } + JobExecutionResult execute(String jobName) throws Exception; - Configuration configuration = tableEnvironment.getConfig().getConfiguration(); - configuration.setString(PythonOptions.PYTHON_FILES, String.join(",", udfPyFilePath)); - configuration.setString(PythonOptions.PYTHON_CLIENT_EXECUTABLE, executable); - } + JobClient executeAsync(String jobName) throws Exception; - private void addJar(String... jarPath) { - Configuration configuration = tableEnvironment.getRootConfiguration(); - List jars = configuration.get(PipelineOptions.JARS); - if (jars == null) { - tableEnvironment.addConfiguration(PipelineOptions.JARS, CollUtil.newArrayList(jarPath)); - } else { - CollUtil.addAll(jars, jarPath); - } - } + TableResult executeSql(String statement); - public void addJar(File... jarPath) { - addJar(Arrays.stream(jarPath).map(URLUtil::getURL).map(URL::toString).toArray(String[]::new)); - } + void initUDF(String... udfFilePath); - public SqlExplainResult explainSqlRecord(String statement, ExplainDetail... extraDetails) { - statement = pretreatStatement(statement); - if (Asserts.isNotNullString(statement) && !pretreatExecute(statement).isNoExecute()) { - return tableEnvironment.explainSqlRecord(statement, extraDetails); - } - return null; - } + void initPyUDF(String executable, String... udfPyFilePath); - public ObjectNode getStreamGraph(List statements) { - StreamGraph streamGraph = tableEnvironment.getStreamGraphFromInserts(statements); - return getStreamGraphJsonNode(streamGraph); - } + void addJar(File... jarPath); - private ObjectNode getStreamGraphJsonNode(StreamGraph streamGraph) { - JSONGenerator jsonGenerator = new JSONGenerator(streamGraph); - String json = jsonGenerator.getJSON(); - ObjectMapper mapper = new ObjectMapper(); - ObjectNode objectNode = mapper.createObjectNode(); - try { - objectNode = (ObjectNode) mapper.readTree(json); - } catch (JsonProcessingException e) { - logger.error("Get stream graph json node error.", e); - } + SqlExplainResult explainSqlRecord(String statement, ExplainDetail... extraDetails); - return objectNode; - } + ObjectNode getStreamGraph(List statements); - public StreamGraph getStreamGraph() { - return environment.getStreamGraph(); - } + StreamGraph getStreamGraph(); - public ObjectNode getStreamGraphFromDataStream(List statements) { - statements.forEach(this::executeSql); - return getStreamGraphJsonNode(getStreamGraph()); - } + ObjectNode getStreamGraphFromDataStream(List statements); - public JobPlanInfo getJobPlanInfo(List statements) { - return tableEnvironment.getJobPlanInfo(statements); - } + JobPlanInfo getJobPlanInfo(List statements); - public JobPlanInfo getJobPlanInfoFromDataStream(List statements) { - statements.forEach(this::executeSql); - StreamGraph streamGraph = getStreamGraph(); - return new JobPlanInfo(JsonPlanGenerator.generatePlan(streamGraph.getJobGraph())); - } + JobPlanInfo getJobPlanInfoFromDataStream(List statements); - public JobGraph getJobGraphFromInserts(List statements) { - return tableEnvironment.getJobGraphFromInserts(statements); - } + JobGraph getJobGraphFromInserts(List statements); - public TableResult executeStatementSet(List statements) { - StatementSet statementSet = tableEnvironment.createStatementSet(); - statements.forEach(statementSet::addInsertSql); - return statementSet.execute(); - } + TableResult executeStatementSet(List statements); - public String explainStatementSet(List statements) { - StatementSet statementSet = tableEnvironment.createStatementSet(); - statements.forEach(statementSet::addInsertSql); - return statementSet.explain(); - } + String explainStatementSet(List statements); - public List getLineage(String statement) { - return tableEnvironment.getLineage(statement); - } + List getLineage(String statement); } diff --git a/dinky-core/src/main/java/org/dinky/executor/ExecutorFactory.java b/dinky-core/src/main/java/org/dinky/executor/ExecutorFactory.java index 80de699666..a6d58d85fd 100644 --- a/dinky-core/src/main/java/org/dinky/executor/ExecutorFactory.java +++ b/dinky-core/src/main/java/org/dinky/executor/ExecutorFactory.java @@ -19,10 +19,6 @@ package org.dinky.executor; -import org.dinky.classloader.DinkyClassLoader; - -import java.lang.ref.WeakReference; - /** * ExecutorFactory * diff --git a/dinky-core/src/main/java/org/dinky/executor/LocalBatchExecutor.java b/dinky-core/src/main/java/org/dinky/executor/LocalBatchExecutor.java index 549e8febff..ed658fb80d 100644 --- a/dinky-core/src/main/java/org/dinky/executor/LocalBatchExecutor.java +++ b/dinky-core/src/main/java/org/dinky/executor/LocalBatchExecutor.java @@ -19,8 +19,6 @@ package org.dinky.executor; -import org.dinky.classloader.DinkyClassLoader; - import org.apache.flink.configuration.Configuration; import org.apache.flink.configuration.PipelineOptions; import org.apache.flink.configuration.RestOptions; @@ -36,7 +34,7 @@ * * @since 2022/2/4 0:04 */ -public class LocalBatchExecutor extends Executor { +public class LocalBatchExecutor extends AbstractExecutor { public LocalBatchExecutor(ExecutorConfig executorConfig) { this.executorConfig = executorConfig; diff --git a/dinky-core/src/main/java/org/dinky/executor/LocalStreamExecutor.java b/dinky-core/src/main/java/org/dinky/executor/LocalStreamExecutor.java index bc7041ccb1..edc1c2eb0c 100644 --- a/dinky-core/src/main/java/org/dinky/executor/LocalStreamExecutor.java +++ b/dinky-core/src/main/java/org/dinky/executor/LocalStreamExecutor.java @@ -20,7 +20,6 @@ package org.dinky.executor; import org.dinky.assertion.Asserts; -import org.dinky.classloader.DinkyClassLoader; import org.apache.flink.configuration.Configuration; import org.apache.flink.configuration.PipelineOptions; @@ -39,7 +38,7 @@ * * @since 2021/5/25 13:48 */ -public class LocalStreamExecutor extends Executor { +public class LocalStreamExecutor extends AbstractExecutor { public LocalStreamExecutor(ExecutorConfig executorConfig) { this.executorConfig = executorConfig; diff --git a/dinky-core/src/main/java/org/dinky/executor/RemoteBatchExecutor.java b/dinky-core/src/main/java/org/dinky/executor/RemoteBatchExecutor.java index 612f7e4c2c..300c53ab8c 100644 --- a/dinky-core/src/main/java/org/dinky/executor/RemoteBatchExecutor.java +++ b/dinky-core/src/main/java/org/dinky/executor/RemoteBatchExecutor.java @@ -19,8 +19,6 @@ package org.dinky.executor; -import org.dinky.classloader.DinkyClassLoader; - import org.apache.flink.configuration.Configuration; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; @@ -29,7 +27,7 @@ * * @since 2022/2/7 22:10 */ -public class RemoteBatchExecutor extends Executor { +public class RemoteBatchExecutor extends AbstractExecutor { public RemoteBatchExecutor(ExecutorConfig executorConfig) { this.executorConfig = executorConfig; diff --git a/dinky-core/src/main/java/org/dinky/executor/RemoteStreamExecutor.java b/dinky-core/src/main/java/org/dinky/executor/RemoteStreamExecutor.java index c7a99af0e3..7edc6e205f 100644 --- a/dinky-core/src/main/java/org/dinky/executor/RemoteStreamExecutor.java +++ b/dinky-core/src/main/java/org/dinky/executor/RemoteStreamExecutor.java @@ -19,8 +19,6 @@ package org.dinky.executor; -import org.dinky.classloader.DinkyClassLoader; - import org.apache.flink.configuration.Configuration; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; @@ -29,7 +27,7 @@ * * @since 2021/5/25 14:05 */ -public class RemoteStreamExecutor extends Executor { +public class RemoteStreamExecutor extends AbstractExecutor { public RemoteStreamExecutor(ExecutorConfig executorConfig) { this.executorConfig = executorConfig; diff --git a/dinky-core/src/main/java/org/dinky/job/JobManager.java b/dinky-core/src/main/java/org/dinky/job/JobManager.java index 27df0cee6f..92048ff094 100644 --- a/dinky-core/src/main/java/org/dinky/job/JobManager.java +++ b/dinky-core/src/main/java/org/dinky/job/JobManager.java @@ -81,7 +81,6 @@ import java.io.File; import java.io.IOException; -import java.lang.ref.WeakReference; import java.net.URL; import java.time.LocalDateTime; import java.util.ArrayList; diff --git a/dinky-core/src/test/java/org/dinky/interceptor/CdcSourceTests.java b/dinky-core/src/test/java/org/dinky/interceptor/CdcSourceTests.java index 0cfa1029b3..4212440131 100644 --- a/dinky-core/src/test/java/org/dinky/interceptor/CdcSourceTests.java +++ b/dinky-core/src/test/java/org/dinky/interceptor/CdcSourceTests.java @@ -19,7 +19,6 @@ package org.dinky.interceptor; -import org.dinky.classloader.DinkyClassLoader; import org.dinky.executor.Executor; import org.dinky.executor.ExecutorConfig; import org.dinky.executor.LocalStreamExecutor; @@ -50,7 +49,7 @@ public void printTest() throws Exception { .toString(); ExecutorConfig executorConfig = ExecutorConfig.DEFAULT; - Executor executor = new LocalStreamExecutor(executorConfig, DinkyClassLoader.build()); + Executor executor = new LocalStreamExecutor(executorConfig); executor.executeSql(statement); executor.execute(""); } diff --git a/dinky-core/src/test/java/org/dinky/interceptor/FlinkCDCPipelineTest.java b/dinky-core/src/test/java/org/dinky/interceptor/FlinkCDCPipelineTest.java index cd96292a02..8a474c8d5d 100644 --- a/dinky-core/src/test/java/org/dinky/interceptor/FlinkCDCPipelineTest.java +++ b/dinky-core/src/test/java/org/dinky/interceptor/FlinkCDCPipelineTest.java @@ -19,7 +19,6 @@ package org.dinky.interceptor; -import org.dinky.classloader.DinkyClassLoader; import org.dinky.executor.Executor; import org.dinky.executor.ExecutorConfig; import org.dinky.executor.LocalStreamExecutor; @@ -60,7 +59,7 @@ public void mysqlTest() throws Exception { .toString(); ExecutorConfig executorConfig = ExecutorConfig.DEFAULT; - Executor executor = new LocalStreamExecutor(executorConfig, DinkyClassLoader.build()); + Executor executor = new LocalStreamExecutor(executorConfig); executor.executeSql(statement); executor.execute(""); } From 6ad4759f78136b2292ef2956b624697cdb8baf95 Mon Sep 17 00:00:00 2001 From: Lichao Date: Mon, 11 Mar 2024 21:16:07 +0800 Subject: [PATCH 07/87] refactor: JobManager executor --- .../src/main/java/org/dinky/job/JobManager.java | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/dinky-core/src/main/java/org/dinky/job/JobManager.java b/dinky-core/src/main/java/org/dinky/job/JobManager.java index 92048ff094..d5370399ef 100644 --- a/dinky-core/src/main/java/org/dinky/job/JobManager.java +++ b/dinky-core/src/main/java/org/dinky/job/JobManager.java @@ -233,7 +233,7 @@ public boolean close() { CustomTableEnvironmentContext.clear(); RowLevelPermissionsContext.clear(); try { - getExecutor().getDinkyClassLoader().close(); + executor.getDinkyClassLoader().close(); } catch (IOException e) { throw new RuntimeException(e); } @@ -373,13 +373,16 @@ public IResult executeDDL(String statement) { SqlType operationType = Operations.getOperationType(newStatement); if (SqlType.INSERT == operationType || SqlType.SELECT == operationType) { continue; - } else if (operationType.equals(SqlType.ADD) || operationType.equals(SqlType.ADD_JAR)) { + } + + if (operationType.equals(SqlType.ADD) || operationType.equals(SqlType.ADD_JAR)) { Set allFilePath = AddJarSqlParseStrategy.getAllFilePath(item); - getExecutor().getDinkyClassLoader().addURLs(allFilePath); + executor.getDinkyClassLoader().addURLs(allFilePath); } else if (operationType.equals(SqlType.ADD_FILE)) { Set allFilePath = AddFileSqlParseStrategy.getAllFilePath(item); - getExecutor().getDinkyClassLoader().addURLs(allFilePath); + executor.getDinkyClassLoader().addURLs(allFilePath); } + LocalDateTime startTime = LocalDateTime.now(); TableResult tableResult = executor.executeSql(newStatement); result = ResultBuilder.build( From 069ed9ee555bfaf8fd45157cded12aba1ad87a40 Mon Sep 17 00:00:00 2001 From: Lichao Date: Mon, 11 Mar 2024 21:16:24 +0800 Subject: [PATCH 08/87] feat: patch ServerExecutorServiceImpl --- .../src/main/java/org/dinky/ServerExecutorServiceImpl.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/dinky-executor-server/src/main/java/org/dinky/ServerExecutorServiceImpl.java b/dinky-executor-server/src/main/java/org/dinky/ServerExecutorServiceImpl.java index 91ed7ae4f2..d02500e356 100644 --- a/dinky-executor-server/src/main/java/org/dinky/ServerExecutorServiceImpl.java +++ b/dinky-executor-server/src/main/java/org/dinky/ServerExecutorServiceImpl.java @@ -3,15 +3,21 @@ import java.rmi.RemoteException; import java.rmi.server.UnicastRemoteObject; +import org.dinky.executor.Executor; import org.dinky.executor.ExecutorConfig; +import org.dinky.executor.ExecutorFactory; import org.dinky.flink.ServerExecutorService; public class ServerExecutorServiceImpl extends UnicastRemoteObject implements ServerExecutorService { + private Executor executor; + public ServerExecutorServiceImpl() throws RemoteException { } @Override public void init(ExecutorConfig executorConfig) { + executor = ExecutorFactory.buildExecutor(executorConfig); } + } From 0ccf5e432e7bab4fea03772549678b5719de2d6d Mon Sep 17 00:00:00 2001 From: leechor Date: Mon, 11 Mar 2024 13:20:33 +0000 Subject: [PATCH 09/87] Spotless Apply --- .../org/dinky/app/flinksql/Submitter.java | 1 - .../org/dinky/executor/AbstractExecutor.java | 2 +- .../java/org/dinky/executor/Executor.java | 29 ++++++++++++++++--- 3 files changed, 26 insertions(+), 6 deletions(-) diff --git a/dinky-app/dinky-app-base/src/main/java/org/dinky/app/flinksql/Submitter.java b/dinky-app/dinky-app-base/src/main/java/org/dinky/app/flinksql/Submitter.java index c8bcf7dc01..2c5373714f 100644 --- a/dinky-app/dinky-app-base/src/main/java/org/dinky/app/flinksql/Submitter.java +++ b/dinky-app/dinky-app-base/src/main/java/org/dinky/app/flinksql/Submitter.java @@ -58,7 +58,6 @@ import java.io.File; import java.io.IOException; -import java.lang.ref.WeakReference; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.URL; diff --git a/dinky-core/src/main/java/org/dinky/executor/AbstractExecutor.java b/dinky-core/src/main/java/org/dinky/executor/AbstractExecutor.java index 7eee29914a..4e1c9b23f9 100644 --- a/dinky-core/src/main/java/org/dinky/executor/AbstractExecutor.java +++ b/dinky-core/src/main/java/org/dinky/executor/AbstractExecutor.java @@ -71,7 +71,7 @@ * @since 2021/11/17 */ @Slf4j -public abstract class AbstractExecutor implements Executor{ +public abstract class AbstractExecutor implements Executor { private static final Logger logger = LoggerFactory.getLogger(AbstractExecutor.class); diff --git a/dinky-core/src/main/java/org/dinky/executor/Executor.java b/dinky-core/src/main/java/org/dinky/executor/Executor.java index 90f8a126ea..4edefd12de 100644 --- a/dinky-core/src/main/java/org/dinky/executor/Executor.java +++ b/dinky-core/src/main/java/org/dinky/executor/Executor.java @@ -1,6 +1,28 @@ +/* + * + * 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.executor; -import com.fasterxml.jackson.databind.node.ObjectNode; +import org.dinky.classloader.DinkyClassLoader; +import org.dinky.data.model.LineageRel; +import org.dinky.data.result.SqlExplainResult; + import org.apache.flink.api.common.ExecutionConfig; import org.apache.flink.api.common.JobExecutionResult; import org.apache.flink.core.execution.JobClient; @@ -11,14 +33,13 @@ import org.apache.flink.table.api.ExplainDetail; import org.apache.flink.table.api.TableConfig; import org.apache.flink.table.api.TableResult; -import org.dinky.classloader.DinkyClassLoader; -import org.dinky.data.model.LineageRel; -import org.dinky.data.result.SqlExplainResult; import java.io.File; import java.util.List; import java.util.Map; +import com.fasterxml.jackson.databind.node.ObjectNode; + public interface Executor { // return dinkyClassLoader DinkyClassLoader getDinkyClassLoader(); From 57aabe22496fd3c986d0bf843e8d8bad97578ce1 Mon Sep 17 00:00:00 2001 From: licho Date: Wed, 13 Mar 2024 01:00:11 +0800 Subject: [PATCH 10/87] storage --- .../dinky/trans/dml/ExecuteJarOperation.java | 5 + .../org/dinky/executor/ExecutorContext.java | 7 + .../main/java/org/dinky/job/JobManager.java | 144 +++++++++--------- pom.xml | 2 +- 4 files changed, 85 insertions(+), 73 deletions(-) create mode 100644 dinky-core/src/main/java/org/dinky/executor/ExecutorContext.java diff --git a/dinky-client/dinky-client-base/src/main/java/org/dinky/trans/dml/ExecuteJarOperation.java b/dinky-client/dinky-client-base/src/main/java/org/dinky/trans/dml/ExecuteJarOperation.java index d745144553..a71c27ce79 100644 --- a/dinky-client/dinky-client-base/src/main/java/org/dinky/trans/dml/ExecuteJarOperation.java +++ b/dinky-client/dinky-client-base/src/main/java/org/dinky/trans/dml/ExecuteJarOperation.java @@ -74,6 +74,11 @@ public StreamGraph getStreamGraph(CustomTableEnvironment tEnv, List classpa return getStreamGraph(submitParam, tEnv, classpaths); } + public static StreamGraph getStreamGraph(CustomTableEnvironment tEnv, List classpaths, String statement) { + JarSubmitParam submitParam = JarSubmitParam.build(statement); + return getStreamGraph(submitParam, tEnv, classpaths); + } + public static StreamGraph getStreamGraph( JarSubmitParam submitParam, CustomTableEnvironment tEnv, List classpaths) { SavepointRestoreSettings savepointRestoreSettings = StrUtil.isBlank(submitParam.getSavepointPath()) diff --git a/dinky-core/src/main/java/org/dinky/executor/ExecutorContext.java b/dinky-core/src/main/java/org/dinky/executor/ExecutorContext.java new file mode 100644 index 0000000000..0ab68b6f4b --- /dev/null +++ b/dinky-core/src/main/java/org/dinky/executor/ExecutorContext.java @@ -0,0 +1,7 @@ +package org.dinky.executor; + +public class ExecutorContext { + Executor executor; + + +} diff --git a/dinky-core/src/main/java/org/dinky/job/JobManager.java b/dinky-core/src/main/java/org/dinky/job/JobManager.java index d5370399ef..16e0a2cfe5 100644 --- a/dinky-core/src/main/java/org/dinky/job/JobManager.java +++ b/dinky-core/src/main/java/org/dinky/job/JobManager.java @@ -114,78 +114,6 @@ public class JobManager { public JobManager() {} - public JobParam getJobParam() { - return jobParam; - } - - public void setJobParam(JobParam jobParam) { - this.jobParam = jobParam; - } - - public JobConfig getConfig() { - return config; - } - - public void setConfig(JobConfig config) { - this.config = config; - } - - public GatewayType getRunMode() { - return runMode; - } - - public void setCurrentSql(String currentSql) { - this.currentSql = currentSql; - } - - public Executor getExecutor() { - return executor; - } - - public void setExecutor(Executor executor) { - this.executor = executor; - } - - public void setPlanMode(boolean planMode) { - isPlanMode = planMode; - } - - public boolean isPlanMode() { - return isPlanMode; - } - - public boolean isUseStatementSet() { - return useStatementSet; - } - - public boolean isUseRestAPI() { - return useRestAPI; - } - - public boolean isUseGateway() { - return useGateway; - } - - // return dinkyclassloader - public DinkyClassLoader getDinkyClassLoader() { - return executor.getDinkyClassLoader(); - } - - // return udfPathContextHolder - public FlinkUdfPathContextHolder getUdfPathContextHolder() { - return getDinkyClassLoader().getUdfPathContextHolder(); - } - - // return job - public Job getJob() { - return job; - } - - // set job - public void setJob(Job job) { - this.job = job; - } - private JobManager(JobConfig config) { this.config = config; } @@ -520,4 +448,76 @@ public List getAllFileSet() { : Arrays.asList(URLUtils.getURLs( getUdfPathContextHolder().getAllFileSet().toArray(new File[0]))); } + + public JobParam getJobParam() { + return jobParam; + } + + public void setJobParam(JobParam jobParam) { + this.jobParam = jobParam; + } + + public JobConfig getConfig() { + return config; + } + + public void setConfig(JobConfig config) { + this.config = config; + } + + public GatewayType getRunMode() { + return runMode; + } + + public void setCurrentSql(String currentSql) { + this.currentSql = currentSql; + } + + public Executor getExecutor() { + return executor; + } + + public void setExecutor(Executor executor) { + this.executor = executor; + } + + public void setPlanMode(boolean planMode) { + isPlanMode = planMode; + } + + public boolean isPlanMode() { + return isPlanMode; + } + + public boolean isUseStatementSet() { + return useStatementSet; + } + + public boolean isUseRestAPI() { + return useRestAPI; + } + + public boolean isUseGateway() { + return useGateway; + } + + // return dinkyclassloader + public DinkyClassLoader getDinkyClassLoader() { + return executor.getDinkyClassLoader(); + } + + // return udfPathContextHolder + public FlinkUdfPathContextHolder getUdfPathContextHolder() { + return getDinkyClassLoader().getUdfPathContextHolder(); + } + + // return job + public Job getJob() { + return job; + } + + // set job + public void setJob(Job job) { + this.job = job; + } } diff --git a/pom.xml b/pom.xml index 8881e08854..26973d4d61 100644 --- a/pom.xml +++ b/pom.xml @@ -46,7 +46,7 @@ dinky-app dinky-admin dinky-assembly - + dinky-executor-server From b74582addf9a0cbe57c6773ac6854c9ebee628c5 Mon Sep 17 00:00:00 2001 From: licho Date: Wed, 13 Mar 2024 01:13:29 +0800 Subject: [PATCH 11/87] test: gpg --- .../java/org/dinky/job/builder/JobJarStreamGraphBuilder.java | 1 + 1 file changed, 1 insertion(+) diff --git a/dinky-core/src/main/java/org/dinky/job/builder/JobJarStreamGraphBuilder.java b/dinky-core/src/main/java/org/dinky/job/builder/JobJarStreamGraphBuilder.java index 4cd6653076..e43cd91177 100644 --- a/dinky-core/src/main/java/org/dinky/job/builder/JobJarStreamGraphBuilder.java +++ b/dinky-core/src/main/java/org/dinky/job/builder/JobJarStreamGraphBuilder.java @@ -99,6 +99,7 @@ public List getUris(String statement) { break; } } + return uriList; } } From 13ebc401aff3b1719f6e9129a504f0ab81c155c2 Mon Sep 17 00:00:00 2001 From: licho Date: Thu, 14 Mar 2024 08:22:56 +0800 Subject: [PATCH 12/87] storage: refactor move --- .../org/dinky/executor/AbstractExecutor.java | 20 +++++++ .../java/org/dinky/executor/Executor.java | 6 +++ .../java/org/dinky/explainer/Explainer.java | 10 ++-- .../dinky/interceptor/FlinkInterceptor.java | 9 +--- .../main/java/org/dinky/job/JobManager.java | 54 +++++++------------ .../job/builder/JobJarStreamGraphBuilder.java | 10 ++-- .../org/dinky/job/builder/JobUDFBuilder.java | 8 +-- dinky-executor-server/pom.xml | 2 +- 8 files changed, 61 insertions(+), 58 deletions(-) diff --git a/dinky-core/src/main/java/org/dinky/executor/AbstractExecutor.java b/dinky-core/src/main/java/org/dinky/executor/AbstractExecutor.java index 4e1c9b23f9..2cba45c5ea 100644 --- a/dinky-core/src/main/java/org/dinky/executor/AbstractExecutor.java +++ b/dinky-core/src/main/java/org/dinky/executor/AbstractExecutor.java @@ -22,10 +22,13 @@ import org.dinky.assertion.Asserts; import org.dinky.classloader.DinkyClassLoader; import org.dinky.context.CustomTableEnvironmentContext; +import org.dinky.context.FlinkUdfPathContextHolder; import org.dinky.data.model.LineageRel; import org.dinky.data.result.SqlExplainResult; import org.dinky.interceptor.FlinkInterceptor; import org.dinky.interceptor.FlinkInterceptorResult; +import org.dinky.job.builder.JobJarStreamGraphBuilder; +import org.dinky.utils.JsonUtils; import org.dinky.utils.KerberosUtil; import org.apache.flink.api.common.ExecutionConfig; @@ -49,10 +52,12 @@ import java.lang.ref.WeakReference; import java.net.URL; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import org.dinky.utils.URLUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -270,6 +275,21 @@ public ObjectNode getStreamGraph(List statements) { return getStreamGraphJsonNode(streamGraph); } + + + @Override + public List getAllFileSet() { + return CollUtil.isEmpty(getUdfPathContextHolder().getAllFileSet()) + ? Collections.emptyList() + : Arrays.asList(URLUtils.getURLs( + getUdfPathContextHolder().getAllFileSet().toArray(new File[0]))); + } + + @Override + public FlinkUdfPathContextHolder getUdfPathContextHolder() { + return getDinkyClassLoader().getUdfPathContextHolder(); + } + private ObjectNode getStreamGraphJsonNode(StreamGraph streamGraph) { JSONGenerator jsonGenerator = new JSONGenerator(streamGraph); String json = jsonGenerator.getJSON(); diff --git a/dinky-core/src/main/java/org/dinky/executor/Executor.java b/dinky-core/src/main/java/org/dinky/executor/Executor.java index 4edefd12de..faf20ca3d9 100644 --- a/dinky-core/src/main/java/org/dinky/executor/Executor.java +++ b/dinky-core/src/main/java/org/dinky/executor/Executor.java @@ -20,6 +20,7 @@ package org.dinky.executor; import org.dinky.classloader.DinkyClassLoader; +import org.dinky.context.FlinkUdfPathContextHolder; import org.dinky.data.model.LineageRel; import org.dinky.data.result.SqlExplainResult; @@ -35,6 +36,7 @@ import org.apache.flink.table.api.TableResult; import java.io.File; +import java.net.URL; import java.util.List; import java.util.Map; @@ -82,6 +84,10 @@ public interface Executor { ObjectNode getStreamGraph(List statements); + List getAllFileSet(); + + FlinkUdfPathContextHolder getUdfPathContextHolder(); + StreamGraph getStreamGraph(); ObjectNode getStreamGraphFromDataStream(List statements); diff --git a/dinky-core/src/main/java/org/dinky/explainer/Explainer.java b/dinky-core/src/main/java/org/dinky/explainer/Explainer.java index 1a7b7c0ded..d124ca7a20 100644 --- a/dinky-core/src/main/java/org/dinky/explainer/Explainer.java +++ b/dinky-core/src/main/java/org/dinky/explainer/Explainer.java @@ -122,16 +122,16 @@ public JobParam pretreatStatements(String[] statements) { customSetOperation.execute(this.executor.getCustomTableEnvironment()); } else if (operationType.equals(SqlType.ADD)) { AddJarSqlParseStrategy.getAllFilePath(statement) - .forEach(t -> jobManager.getUdfPathContextHolder().addOtherPlugins(t)); + .forEach(t -> executor.getUdfPathContextHolder().addOtherPlugins(t)); (executor.getDinkyClassLoader()) .addURLs(URLUtils.getURLs( - jobManager.getUdfPathContextHolder().getOtherPluginsFiles())); + executor.getUdfPathContextHolder().getOtherPluginsFiles())); } else if (operationType.equals(SqlType.ADD_FILE)) { AddFileSqlParseStrategy.getAllFilePath(statement) - .forEach(t -> jobManager.getUdfPathContextHolder().addFile(t)); + .forEach(t -> executor.getUdfPathContextHolder().addFile(t)); (executor.getDinkyClassLoader()) .addURLs(URLUtils.getURLs( - jobManager.getUdfPathContextHolder().getFiles())); + executor.getUdfPathContextHolder().getFiles())); } else if (operationType.equals(SqlType.ADD_JAR)) { Configuration combinationConfig = getCombinationConfig(); FileSystem.initialize(combinationConfig, null); @@ -299,7 +299,7 @@ public ExplainResult explainSql(String statement) { sqlExplainResult = new SqlExplainResult(); } else if (ExecuteJarParseStrategy.INSTANCE.match(item.getValue())) { - List allFileByAdd = jobManager.getAllFileSet(); + List allFileByAdd = executor.getAllFileSet(); StreamGraph streamGraph = new ExecuteJarOperation(item.getValue()) .explain(executor.getCustomTableEnvironment(), allFileByAdd); sqlExplainResult.setExplain(streamGraph.getStreamingPlanAsJSON()); diff --git a/dinky-core/src/main/java/org/dinky/interceptor/FlinkInterceptor.java b/dinky-core/src/main/java/org/dinky/interceptor/FlinkInterceptor.java index 5d8ddb7e74..03dc320b76 100644 --- a/dinky-core/src/main/java/org/dinky/interceptor/FlinkInterceptor.java +++ b/dinky-core/src/main/java/org/dinky/interceptor/FlinkInterceptor.java @@ -25,8 +25,6 @@ import org.dinky.trans.Operations; import org.dinky.utils.SqlUtil; -import org.apache.flink.table.api.TableResult; - /** * FlinkInterceptor * @@ -46,13 +44,10 @@ public static String pretreatStatement(Executor executor, String statement) { // return false to continue with executeSql public static FlinkInterceptorResult build(Executor executor, String statement) { - boolean noExecute = false; - TableResult tableResult = null; Operation operation = Operations.buildOperation(statement); if (Asserts.isNotNull(operation)) { - tableResult = operation.execute(executor); - noExecute = operation.noExecute(); + return FlinkInterceptorResult.build(operation.noExecute(), operation.execute(executor)); } - return FlinkInterceptorResult.build(noExecute, tableResult); + return FlinkInterceptorResult.build(false, null); } } diff --git a/dinky-core/src/main/java/org/dinky/job/JobManager.java b/dinky-core/src/main/java/org/dinky/job/JobManager.java index 16e0a2cfe5..98992410aa 100644 --- a/dinky-core/src/main/java/org/dinky/job/JobManager.java +++ b/dinky-core/src/main/java/org/dinky/job/JobManager.java @@ -19,11 +19,26 @@ package org.dinky.job; +import cn.hutool.core.text.StrFormatter; +import com.fasterxml.jackson.databind.node.ObjectNode; +import lombok.extern.slf4j.Slf4j; +import org.apache.flink.configuration.Configuration; +import org.apache.flink.configuration.CoreOptions; +import org.apache.flink.configuration.DeploymentOptions; +import org.apache.flink.configuration.PipelineOptions; +import org.apache.flink.core.execution.JobClient; +import org.apache.flink.runtime.jobgraph.JobGraph; +import org.apache.flink.runtime.jobgraph.SavepointConfigOptions; +import org.apache.flink.runtime.jobgraph.SavepointRestoreSettings; +import org.apache.flink.runtime.jobgraph.jsonplan.JsonPlanGenerator; +import org.apache.flink.streaming.api.environment.ExecutionCheckpointingOptions; +import org.apache.flink.streaming.api.graph.StreamGraph; +import org.apache.flink.table.api.TableResult; +import org.apache.flink.yarn.configuration.YarnConfigOptions; import org.dinky.api.FlinkAPI; import org.dinky.assertion.Asserts; import org.dinky.classloader.DinkyClassLoader; import org.dinky.context.CustomTableEnvironmentContext; -import org.dinky.context.FlinkUdfPathContextHolder; import org.dinky.context.RowLevelPermissionsContext; import org.dinky.data.annotations.ProcessStep; import org.dinky.data.enums.GatewayType; @@ -65,37 +80,15 @@ import org.dinky.utils.SqlUtil; import org.dinky.utils.URLUtils; -import org.apache.flink.configuration.Configuration; -import org.apache.flink.configuration.CoreOptions; -import org.apache.flink.configuration.DeploymentOptions; -import org.apache.flink.configuration.PipelineOptions; -import org.apache.flink.core.execution.JobClient; -import org.apache.flink.runtime.jobgraph.JobGraph; -import org.apache.flink.runtime.jobgraph.SavepointConfigOptions; -import org.apache.flink.runtime.jobgraph.SavepointRestoreSettings; -import org.apache.flink.runtime.jobgraph.jsonplan.JsonPlanGenerator; -import org.apache.flink.streaming.api.environment.ExecutionCheckpointingOptions; -import org.apache.flink.streaming.api.graph.StreamGraph; -import org.apache.flink.table.api.TableResult; -import org.apache.flink.yarn.configuration.YarnConfigOptions; - import java.io.File; import java.io.IOException; -import java.net.URL; import java.time.LocalDateTime; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; -import com.fasterxml.jackson.databind.node.ObjectNode; - -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.text.StrFormatter; -import lombok.extern.slf4j.Slf4j; - @Slf4j public class JobManager { private JobHandler handler; @@ -208,7 +201,8 @@ public JobResult executeJarSql(String statement) throws Exception { GatewayResult gatewayResult; config.addGatewayConfig(configuration); if (runMode.isApplicationMode()) { - gatewayResult = Gateway.build(config.getGatewayConfig()).submitJar(getUdfPathContextHolder()); + gatewayResult = + Gateway.build(config.getGatewayConfig()).submitJar(executor.getUdfPathContextHolder()); } else { streamGraph.setJobName(config.getJobName()); JobGraph jobGraph = streamGraph.getJobGraph(); @@ -442,13 +436,6 @@ public String exportSql(String sql) { return sb.toString(); } - public List getAllFileSet() { - return CollUtil.isEmpty(getUdfPathContextHolder().getAllFileSet()) - ? Collections.emptyList() - : Arrays.asList(URLUtils.getURLs( - getUdfPathContextHolder().getAllFileSet().toArray(new File[0]))); - } - public JobParam getJobParam() { return jobParam; } @@ -506,11 +493,6 @@ public DinkyClassLoader getDinkyClassLoader() { return executor.getDinkyClassLoader(); } - // return udfPathContextHolder - public FlinkUdfPathContextHolder getUdfPathContextHolder() { - return getDinkyClassLoader().getUdfPathContextHolder(); - } - // return job public Job getJob() { return job; diff --git a/dinky-core/src/main/java/org/dinky/job/builder/JobJarStreamGraphBuilder.java b/dinky-core/src/main/java/org/dinky/job/builder/JobJarStreamGraphBuilder.java index e43cd91177..806013dd8f 100644 --- a/dinky-core/src/main/java/org/dinky/job/builder/JobJarStreamGraphBuilder.java +++ b/dinky-core/src/main/java/org/dinky/job/builder/JobJarStreamGraphBuilder.java @@ -76,16 +76,16 @@ public StreamGraph getJarStreamGraph(String statement, DinkyClassLoader dinkyCla customSetOperation.execute(this.executor.getCustomTableEnvironment()); } else if (operationType.equals(SqlType.ADD)) { Set files = AddJarSqlParseStrategy.getAllFilePath(sqlStatement); - files.forEach(executor::addJar); - files.forEach(jobManager.getUdfPathContextHolder()::addOtherPlugins); + executor.addJar(files.toArray(new File[0])); + files.forEach(executor.getUdfPathContextHolder()::addOtherPlugins); } else if (operationType.equals(SqlType.ADD_FILE)) { Set files = AddFileSqlParseStrategy.getAllFilePath(sqlStatement); - files.forEach(executor::addJar); - files.forEach(jobManager.getUdfPathContextHolder()::addFile); + executor.addJar(files.toArray(new File[0])); + files.forEach(executor.getUdfPathContextHolder()::addFile); } } Assert.notNull(executeJarOperation, () -> new DinkyException("Not found execute jar operation.")); - List urLs = jobManager.getAllFileSet(); + List urLs = executor.getAllFileSet(); return executeJarOperation.explain(executor.getCustomTableEnvironment(), urLs); } diff --git a/dinky-core/src/main/java/org/dinky/job/builder/JobUDFBuilder.java b/dinky-core/src/main/java/org/dinky/job/builder/JobUDFBuilder.java index ee1a1dca5b..d3ad5454d5 100644 --- a/dinky-core/src/main/java/org/dinky/job/builder/JobUDFBuilder.java +++ b/dinky-core/src/main/java/org/dinky/job/builder/JobUDFBuilder.java @@ -68,7 +68,7 @@ public void run() throws Exception { } // 1. Obtain the path of the jar package and inject it into the remote environment List jarFiles = - new ArrayList<>(jobManager.getUdfPathContextHolder().getAllFileSet()); + new ArrayList<>(executor.getUdfPathContextHolder().getAllFileSet()); String[] jarPaths = CollUtil.removeNull(jarFiles).stream() .map(File::getAbsolutePath) @@ -87,12 +87,12 @@ public void run() throws Exception { if (ArrayUtil.isNotEmpty(pyPaths)) { for (String pyPath : pyPaths) { if (StrUtil.isNotBlank(pyPath)) { - jobManager.getUdfPathContextHolder().addPyUdfPath(new File(pyPath)); + executor.getUdfPathContextHolder().addPyUdfPath(new File(pyPath)); } } } - Set pyUdfFile = jobManager.getUdfPathContextHolder().getPyUdfFile(); + Set pyUdfFile = executor.getUdfPathContextHolder().getPyUdfFile(); executor.initPyUDF( SystemConfiguration.getInstances().getPythonHome(), pyUdfFile.stream().map(File::getAbsolutePath).toArray(String[]::new)); @@ -103,7 +103,7 @@ public void run() throws Exception { try { List jarList = CollUtil.newArrayList(URLUtils.getURLs(jarFiles)); // 3.Write the required files for UDF - UDFUtil.writeManifest(taskId, jarList, jobManager.getUdfPathContextHolder()); + UDFUtil.writeManifest(taskId, jarList, executor.getUdfPathContextHolder()); UDFUtil.addConfigurationClsAndJars( jobManager.getExecutor().getCustomTableEnvironment(), jarList, diff --git a/dinky-executor-server/pom.xml b/dinky-executor-server/pom.xml index 672d8f555c..7fcff0c5f2 100644 --- a/dinky-executor-server/pom.xml +++ b/dinky-executor-server/pom.xml @@ -6,7 +6,7 @@ org.dinky dinky - 1.0.0 + ${revision} dinky-executor-server From df752e5edbac9d8d2f276f84a4b4b20c01cd8221 Mon Sep 17 00:00:00 2001 From: leechor Date: Thu, 14 Mar 2024 00:26:17 +0000 Subject: [PATCH 13/87] Spotless Apply --- .../org/dinky/executor/AbstractExecutor.java | 8 ++--- .../org/dinky/executor/ExecutorContext.java | 21 +++++++++-- .../main/java/org/dinky/job/JobManager.java | 35 ++++++++++--------- .../org/dinky/job/builder/JobUDFBuilder.java | 3 +- dinky-executor-server/pom.xml | 5 ++- .../src/main/java/org/dinky/Main.java | 21 ++++++++++- .../org/dinky/ServerExecutorServiceImpl.java | 30 ++++++++++++---- 7 files changed, 86 insertions(+), 37 deletions(-) diff --git a/dinky-core/src/main/java/org/dinky/executor/AbstractExecutor.java b/dinky-core/src/main/java/org/dinky/executor/AbstractExecutor.java index 2cba45c5ea..3afcc5e807 100644 --- a/dinky-core/src/main/java/org/dinky/executor/AbstractExecutor.java +++ b/dinky-core/src/main/java/org/dinky/executor/AbstractExecutor.java @@ -27,9 +27,8 @@ import org.dinky.data.result.SqlExplainResult; import org.dinky.interceptor.FlinkInterceptor; import org.dinky.interceptor.FlinkInterceptorResult; -import org.dinky.job.builder.JobJarStreamGraphBuilder; -import org.dinky.utils.JsonUtils; import org.dinky.utils.KerberosUtil; +import org.dinky.utils.URLUtils; import org.apache.flink.api.common.ExecutionConfig; import org.apache.flink.api.common.JobExecutionResult; @@ -57,7 +56,6 @@ import java.util.List; import java.util.Map; -import org.dinky.utils.URLUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -275,14 +273,12 @@ public ObjectNode getStreamGraph(List statements) { return getStreamGraphJsonNode(streamGraph); } - - @Override public List getAllFileSet() { return CollUtil.isEmpty(getUdfPathContextHolder().getAllFileSet()) ? Collections.emptyList() : Arrays.asList(URLUtils.getURLs( - getUdfPathContextHolder().getAllFileSet().toArray(new File[0]))); + getUdfPathContextHolder().getAllFileSet().toArray(new File[0]))); } @Override diff --git a/dinky-core/src/main/java/org/dinky/executor/ExecutorContext.java b/dinky-core/src/main/java/org/dinky/executor/ExecutorContext.java index 0ab68b6f4b..00682fe64c 100644 --- a/dinky-core/src/main/java/org/dinky/executor/ExecutorContext.java +++ b/dinky-core/src/main/java/org/dinky/executor/ExecutorContext.java @@ -1,7 +1,24 @@ +/* + * + * 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.executor; public class ExecutorContext { Executor executor; - - } diff --git a/dinky-core/src/main/java/org/dinky/job/JobManager.java b/dinky-core/src/main/java/org/dinky/job/JobManager.java index 98992410aa..a42698ae8a 100644 --- a/dinky-core/src/main/java/org/dinky/job/JobManager.java +++ b/dinky-core/src/main/java/org/dinky/job/JobManager.java @@ -19,22 +19,6 @@ package org.dinky.job; -import cn.hutool.core.text.StrFormatter; -import com.fasterxml.jackson.databind.node.ObjectNode; -import lombok.extern.slf4j.Slf4j; -import org.apache.flink.configuration.Configuration; -import org.apache.flink.configuration.CoreOptions; -import org.apache.flink.configuration.DeploymentOptions; -import org.apache.flink.configuration.PipelineOptions; -import org.apache.flink.core.execution.JobClient; -import org.apache.flink.runtime.jobgraph.JobGraph; -import org.apache.flink.runtime.jobgraph.SavepointConfigOptions; -import org.apache.flink.runtime.jobgraph.SavepointRestoreSettings; -import org.apache.flink.runtime.jobgraph.jsonplan.JsonPlanGenerator; -import org.apache.flink.streaming.api.environment.ExecutionCheckpointingOptions; -import org.apache.flink.streaming.api.graph.StreamGraph; -import org.apache.flink.table.api.TableResult; -import org.apache.flink.yarn.configuration.YarnConfigOptions; import org.dinky.api.FlinkAPI; import org.dinky.assertion.Asserts; import org.dinky.classloader.DinkyClassLoader; @@ -80,6 +64,20 @@ import org.dinky.utils.SqlUtil; import org.dinky.utils.URLUtils; +import org.apache.flink.configuration.Configuration; +import org.apache.flink.configuration.CoreOptions; +import org.apache.flink.configuration.DeploymentOptions; +import org.apache.flink.configuration.PipelineOptions; +import org.apache.flink.core.execution.JobClient; +import org.apache.flink.runtime.jobgraph.JobGraph; +import org.apache.flink.runtime.jobgraph.SavepointConfigOptions; +import org.apache.flink.runtime.jobgraph.SavepointRestoreSettings; +import org.apache.flink.runtime.jobgraph.jsonplan.JsonPlanGenerator; +import org.apache.flink.streaming.api.environment.ExecutionCheckpointingOptions; +import org.apache.flink.streaming.api.graph.StreamGraph; +import org.apache.flink.table.api.TableResult; +import org.apache.flink.yarn.configuration.YarnConfigOptions; + import java.io.File; import java.io.IOException; import java.time.LocalDateTime; @@ -89,6 +87,11 @@ import java.util.Map; import java.util.Set; +import com.fasterxml.jackson.databind.node.ObjectNode; + +import cn.hutool.core.text.StrFormatter; +import lombok.extern.slf4j.Slf4j; + @Slf4j public class JobManager { private JobHandler handler; diff --git a/dinky-core/src/main/java/org/dinky/job/builder/JobUDFBuilder.java b/dinky-core/src/main/java/org/dinky/job/builder/JobUDFBuilder.java index d3ad5454d5..43585ad3fd 100644 --- a/dinky-core/src/main/java/org/dinky/job/builder/JobUDFBuilder.java +++ b/dinky-core/src/main/java/org/dinky/job/builder/JobUDFBuilder.java @@ -67,8 +67,7 @@ public void run() throws Exception { taskId = -RandomUtil.randomInt(0, 1000); } // 1. Obtain the path of the jar package and inject it into the remote environment - List jarFiles = - new ArrayList<>(executor.getUdfPathContextHolder().getAllFileSet()); + List jarFiles = new ArrayList<>(executor.getUdfPathContextHolder().getAllFileSet()); String[] jarPaths = CollUtil.removeNull(jarFiles).stream() .map(File::getAbsolutePath) diff --git a/dinky-executor-server/pom.xml b/dinky-executor-server/pom.xml index 7fcff0c5f2..debf7d6e3c 100644 --- a/dinky-executor-server/pom.xml +++ b/dinky-executor-server/pom.xml @@ -1,6 +1,5 @@ - 4.0.0 @@ -23,4 +22,4 @@ - \ No newline at end of file + diff --git a/dinky-executor-server/src/main/java/org/dinky/Main.java b/dinky-executor-server/src/main/java/org/dinky/Main.java index 74c0e03205..4b30599758 100644 --- a/dinky-executor-server/src/main/java/org/dinky/Main.java +++ b/dinky-executor-server/src/main/java/org/dinky/Main.java @@ -1,7 +1,26 @@ +/* + * + * 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; public class Main { public static void main(String[] args) { System.out.println("Hello world!"); } -} \ No newline at end of file +} diff --git a/dinky-executor-server/src/main/java/org/dinky/ServerExecutorServiceImpl.java b/dinky-executor-server/src/main/java/org/dinky/ServerExecutorServiceImpl.java index d02500e356..eb992dc980 100644 --- a/dinky-executor-server/src/main/java/org/dinky/ServerExecutorServiceImpl.java +++ b/dinky-executor-server/src/main/java/org/dinky/ServerExecutorServiceImpl.java @@ -1,23 +1,39 @@ -package org.dinky; +/* + * + * 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. + * + */ -import java.rmi.RemoteException; -import java.rmi.server.UnicastRemoteObject; +package org.dinky; import org.dinky.executor.Executor; import org.dinky.executor.ExecutorConfig; import org.dinky.executor.ExecutorFactory; import org.dinky.flink.ServerExecutorService; +import java.rmi.RemoteException; +import java.rmi.server.UnicastRemoteObject; + public class ServerExecutorServiceImpl extends UnicastRemoteObject implements ServerExecutorService { private Executor executor; - public ServerExecutorServiceImpl() throws RemoteException { - } + public ServerExecutorServiceImpl() throws RemoteException {} @Override public void init(ExecutorConfig executorConfig) { executor = ExecutorFactory.buildExecutor(executorConfig); - } - } From b1ac89fb36a7c544e8bf48f790989290d708795f Mon Sep 17 00:00:00 2001 From: licho Date: Thu, 14 Mar 2024 22:17:51 +0800 Subject: [PATCH 14/87] refactor: --- .../src/main/java/org/dinky/job/JobManager.java | 16 +++++----------- .../src/main/java/org/dinky/job/JobParam.java | 16 ---------------- 2 files changed, 5 insertions(+), 27 deletions(-) diff --git a/dinky-core/src/main/java/org/dinky/job/JobManager.java b/dinky-core/src/main/java/org/dinky/job/JobManager.java index a42698ae8a..ec62dc6f3e 100644 --- a/dinky-core/src/main/java/org/dinky/job/JobManager.java +++ b/dinky-core/src/main/java/org/dinky/job/JobManager.java @@ -141,7 +141,8 @@ public void init() { executor = ExecutorFactory.buildExecutor(executorConfig); } - private boolean ready() { + private boolean ready(String statement) { + job = Job.build(runMode, config, executorConfig, executor, statement, useGateway); return handler.init(job); } @@ -172,8 +173,7 @@ public ObjectNode getJarStreamGraphJson(String statement) { @ProcessStep(type = ProcessStepType.SUBMIT_EXECUTE) public JobResult executeJarSql(String statement) throws Exception { - job = Job.build(runMode, config, executorConfig, executor, statement, useGateway); - ready(); + ready(statement); JobJarStreamGraphBuilder jobJarStreamGraphBuilder = JobJarStreamGraphBuilder.build(this); StreamGraph streamGraph = jobJarStreamGraphBuilder.getJarStreamGraph(statement, getDinkyClassLoader()); Configuration configuration = @@ -188,12 +188,7 @@ public JobResult executeJarSql(String statement) throws Exception { JobClient jobClient = executor.getStreamExecutionEnvironment().executeAsync(streamGraph); if (Asserts.isNotNull(jobClient)) { job.setJobId(jobClient.getJobID().toHexString()); - job.setJids(new ArrayList() { - - { - add(job.getJobId()); - } - }); + job.setJids(Collections.singletonList(job.getJobId())); job.setStatus(Job.JobStatus.SUCCESS); success(); } else { @@ -249,8 +244,7 @@ public JobResult executeJarSql(String statement) throws Exception { @ProcessStep(type = ProcessStepType.SUBMIT_EXECUTE) public JobResult executeSql(String statement) throws Exception { - job = Job.build(runMode, config, executorConfig, executor, statement, useGateway); - ready(); + ready(statement); DinkyClassLoaderUtil.initClassLoader(config, getDinkyClassLoader()); jobParam = diff --git a/dinky-core/src/main/java/org/dinky/job/JobParam.java b/dinky-core/src/main/java/org/dinky/job/JobParam.java index 5e1db98ee0..01d56df699 100644 --- a/dinky-core/src/main/java/org/dinky/job/JobParam.java +++ b/dinky-core/src/main/java/org/dinky/job/JobParam.java @@ -41,22 +41,6 @@ public JobParam(List udfList) { this.udfList = udfList; } - public JobParam(List ddl, List trans) { - this.ddl = ddl; - this.trans = trans; - } - - public JobParam( - List statements, - List ddl, - List trans, - List execute) { - this.statements = statements; - this.ddl = ddl; - this.trans = trans; - this.execute = execute; - } - public JobParam( List statements, List ddl, From 359ad2ab3a467220cc150e1d7c080321809445a8 Mon Sep 17 00:00:00 2001 From: licho Date: Thu, 14 Mar 2024 23:17:49 +0800 Subject: [PATCH 15/87] refactor: Explainer remove flink dependency --- .../org/dinky/executor/AbstractExecutor.java | 91 +++++++++++++++---- .../java/org/dinky/executor/Executor.java | 9 ++ .../java/org/dinky/explainer/Explainer.java | 44 +-------- .../main/java/org/dinky/job/JobManager.java | 44 +++++---- 4 files changed, 106 insertions(+), 82 deletions(-) diff --git a/dinky-core/src/main/java/org/dinky/executor/AbstractExecutor.java b/dinky-core/src/main/java/org/dinky/executor/AbstractExecutor.java index 3afcc5e807..60cca7cf31 100644 --- a/dinky-core/src/main/java/org/dinky/executor/AbstractExecutor.java +++ b/dinky-core/src/main/java/org/dinky/executor/AbstractExecutor.java @@ -19,6 +19,7 @@ package org.dinky.executor; +import org.apache.flink.core.fs.FileSystem; import org.dinky.assertion.Asserts; import org.dinky.classloader.DinkyClassLoader; import org.dinky.context.CustomTableEnvironmentContext; @@ -27,8 +28,10 @@ import org.dinky.data.result.SqlExplainResult; import org.dinky.interceptor.FlinkInterceptor; import org.dinky.interceptor.FlinkInterceptorResult; +import org.dinky.job.JobParam; +import org.dinky.job.StatementParam; +import org.dinky.trans.dml.ExecuteJarOperation; import org.dinky.utils.KerberosUtil; -import org.dinky.utils.URLUtils; import org.apache.flink.api.common.ExecutionConfig; import org.apache.flink.api.common.JobExecutionResult; @@ -55,7 +58,9 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; +import org.dinky.utils.URLUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -210,22 +215,6 @@ public JobClient executeAsync(String jobName) throws Exception { return environment.executeAsync(jobName); } - @Override - public TableResult executeSql(String statement) { - statement = pretreatStatement(statement); - FlinkInterceptorResult flinkInterceptorResult = pretreatExecute(statement); - if (Asserts.isNotNull(flinkInterceptorResult.getTableResult())) { - return flinkInterceptorResult.getTableResult(); - } - - if (flinkInterceptorResult.isNoExecute()) { - return CustomTableResultImpl.TABLE_RESULT_OK; - } - - KerberosUtil.authenticate(setConfig); - return tableEnvironment.executeSql(statement); - } - @Override public void initUDF(String... udfFilePath) { List jarFiles = DinkyClassLoader.getJarFiles(udfFilePath, null); @@ -258,6 +247,22 @@ public void addJar(File... jarPath) { addJar(Arrays.stream(jarPath).map(URLUtil::getURL).map(URL::toString).toArray(String[]::new)); } + @Override + public TableResult executeSql(String statement) { + statement = pretreatStatement(statement); + FlinkInterceptorResult flinkInterceptorResult = pretreatExecute(statement); + if (Asserts.isNotNull(flinkInterceptorResult.getTableResult())) { + return flinkInterceptorResult.getTableResult(); + } + + if (flinkInterceptorResult.isNoExecute()) { + return CustomTableResultImpl.TABLE_RESULT_OK; + } + + KerberosUtil.authenticate(setConfig); + return tableEnvironment.executeSql(statement); + } + @Override public SqlExplainResult explainSqlRecord(String statement, ExplainDetail... extraDetails) { statement = pretreatStatement(statement); @@ -267,18 +272,30 @@ public SqlExplainResult explainSqlRecord(String statement, ExplainDetail... extr return null; } + + @Override + public String getJarStreamingPlanStringJson(String parameter) { + List allFileByAdd = getAllFileSet(); + StreamGraph streamGraph = new ExecuteJarOperation(parameter) + .explain(getCustomTableEnvironment(), allFileByAdd); + return streamGraph.getStreamingPlanAsJSON(); + } + + @Override public ObjectNode getStreamGraph(List statements) { StreamGraph streamGraph = tableEnvironment.getStreamGraphFromInserts(statements); return getStreamGraphJsonNode(streamGraph); } + + @Override public List getAllFileSet() { return CollUtil.isEmpty(getUdfPathContextHolder().getAllFileSet()) ? Collections.emptyList() : Arrays.asList(URLUtils.getURLs( - getUdfPathContextHolder().getAllFileSet().toArray(new File[0]))); + getUdfPathContextHolder().getAllFileSet().toArray(new File[0]))); } @Override @@ -342,8 +359,46 @@ public String explainStatementSet(List statements) { return statementSet.explain(); } + @Override + public JobPlanInfo getJobPlanInfo(JobParam jobParam) { + jobParam.getDdl().forEach(statementParam -> executeSql(statementParam.getValue())); + + if (!jobParam.getTrans().isEmpty()) { + return getJobPlanInfo(jobParam.getTransStatement()); + } + + if (!jobParam.getExecute().isEmpty()) { + List dataStreamPlans = + jobParam.getExecute().stream().map(StatementParam::getValue).collect(Collectors.toList()); + return getJobPlanInfoFromDataStream(dataStreamPlans); + } + throw new RuntimeException("Creating job plan fails because this job doesn't contain an insert statement."); + } + + @Override + public String getJobPlanJson(JobParam jobParam){ + return getJobPlanInfo(jobParam).getJsonPlan(); + } + @Override public List getLineage(String statement) { return tableEnvironment.getLineage(statement); } + + @Override + public void initializeFileSystem(){ + Configuration combinationConfig = getCombinationConfig(); + FileSystem.initialize(combinationConfig, null); + } + + + private Configuration getCombinationConfig() { + CustomTableEnvironment cte = getCustomTableEnvironment(); + Configuration rootConfig = cte.getRootConfiguration(); + Configuration config = cte.getConfig().getConfiguration(); + Configuration combinationConfig = new Configuration(); + combinationConfig.addAll(rootConfig); + combinationConfig.addAll(config); + return combinationConfig; + } } diff --git a/dinky-core/src/main/java/org/dinky/executor/Executor.java b/dinky-core/src/main/java/org/dinky/executor/Executor.java index faf20ca3d9..7b847dabfd 100644 --- a/dinky-core/src/main/java/org/dinky/executor/Executor.java +++ b/dinky-core/src/main/java/org/dinky/executor/Executor.java @@ -41,6 +41,7 @@ import java.util.Map; import com.fasterxml.jackson.databind.node.ObjectNode; +import org.dinky.job.JobParam; public interface Executor { // return dinkyClassLoader @@ -82,6 +83,8 @@ public interface Executor { SqlExplainResult explainSqlRecord(String statement, ExplainDetail... extraDetails); + String getJarStreamingPlanStringJson(String parameter); + ObjectNode getStreamGraph(List statements); List getAllFileSet(); @@ -102,5 +105,11 @@ public interface Executor { String explainStatementSet(List statements); + JobPlanInfo getJobPlanInfo(JobParam jobParam); + + String getJobPlanJson(JobParam jobParam); + + void initializeFileSystem(); + List getLineage(String statement); } diff --git a/dinky-core/src/main/java/org/dinky/explainer/Explainer.java b/dinky-core/src/main/java/org/dinky/explainer/Explainer.java index d124ca7a20..8e5970b044 100644 --- a/dinky-core/src/main/java/org/dinky/explainer/Explainer.java +++ b/dinky-core/src/main/java/org/dinky/explainer/Explainer.java @@ -24,7 +24,6 @@ import org.dinky.data.model.LineageRel; import org.dinky.data.result.ExplainResult; import org.dinky.data.result.SqlExplainResult; -import org.dinky.executor.CustomTableEnvironment; import org.dinky.executor.Executor; import org.dinky.explainer.print_table.PrintStatementExplainer; import org.dinky.function.data.model.UDF; @@ -38,7 +37,6 @@ import org.dinky.parser.SqlType; import org.dinky.trans.Operations; import org.dinky.trans.ddl.CustomSetOperation; -import org.dinky.trans.dml.ExecuteJarOperation; import org.dinky.trans.parse.AddFileSqlParseStrategy; import org.dinky.trans.parse.AddJarSqlParseStrategy; import org.dinky.trans.parse.ExecuteJarParseStrategy; @@ -49,12 +47,6 @@ import org.dinky.utils.SqlUtil; import org.dinky.utils.URLUtils; -import org.apache.flink.configuration.Configuration; -import org.apache.flink.core.fs.FileSystem; -import org.apache.flink.runtime.rest.messages.JobPlanInfo; -import org.apache.flink.streaming.api.graph.StreamGraph; - -import java.net.URL; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; @@ -133,8 +125,7 @@ public JobParam pretreatStatements(String[] statements) { .addURLs(URLUtils.getURLs( executor.getUdfPathContextHolder().getFiles())); } else if (operationType.equals(SqlType.ADD_JAR)) { - Configuration combinationConfig = getCombinationConfig(); - FileSystem.initialize(combinationConfig, null); + executor.initializeFileSystem(); ddl.add(new StatementParam(statement, operationType)); statementList.add(statement); } else if (operationType.equals(SqlType.INSERT) @@ -172,16 +163,6 @@ public JobParam pretreatStatements(String[] statements) { return new JobParam(statementList, ddl, trans, execute, CollUtil.removeNull(udfList)); } - private Configuration getCombinationConfig() { - CustomTableEnvironment cte = executor.getCustomTableEnvironment(); - Configuration rootConfig = cte.getRootConfiguration(); - Configuration config = cte.getConfig().getConfiguration(); - Configuration combinationConfig = new Configuration(); - combinationConfig.addAll(rootConfig); - combinationConfig.addAll(config); - return combinationConfig; - } - public List parseUDFFromStatements(String[] statements) { List udfList = new ArrayList<>(); for (String statement : statements) { @@ -290,6 +271,7 @@ public ExplainResult explainSql(String statement) { } } } + for (StatementParam item : jobParam.getExecute()) { SqlExplainResult.Builder resultBuilder = SqlExplainResult.Builder.newBuilder(); @@ -298,11 +280,7 @@ public ExplainResult explainSql(String statement) { if (Asserts.isNull(sqlExplainResult)) { sqlExplainResult = new SqlExplainResult(); } else if (ExecuteJarParseStrategy.INSTANCE.match(item.getValue())) { - - List allFileByAdd = executor.getAllFileSet(); - StreamGraph streamGraph = new ExecuteJarOperation(item.getValue()) - .explain(executor.getCustomTableEnvironment(), allFileByAdd); - sqlExplainResult.setExplain(streamGraph.getStreamingPlanAsJSON()); + sqlExplainResult.setExplain(executor.getJarStreamingPlanStringJson(item.getValue())); } else { executor.executeSql(item.getValue()); } @@ -351,22 +329,6 @@ public ObjectNode getStreamGraph(String statement) { return mapper.createObjectNode(); } - public JobPlanInfo getJobPlanInfo(String statement) { - JobParam jobParam = pretreatStatements(SqlUtil.getStatements(statement)); - jobParam.getDdl().forEach(statementParam -> executor.executeSql(statementParam.getValue())); - - if (!jobParam.getTrans().isEmpty()) { - return executor.getJobPlanInfo(jobParam.getTransStatement()); - } - - if (!jobParam.getExecute().isEmpty()) { - List dataStreamPlans = - jobParam.getExecute().stream().map(StatementParam::getValue).collect(Collectors.toList()); - return executor.getJobPlanInfoFromDataStream(dataStreamPlans); - } - throw new RuntimeException("Creating job plan fails because this job doesn't contain an insert statement."); - } - public List getLineage(String statement) { JobConfig jobConfig = JobConfig.builder() .type(GatewayType.LOCAL.getLongValue()) diff --git a/dinky-core/src/main/java/org/dinky/job/JobManager.java b/dinky-core/src/main/java/org/dinky/job/JobManager.java index ec62dc6f3e..65fd8dab88 100644 --- a/dinky-core/src/main/java/org/dinky/job/JobManager.java +++ b/dinky-core/src/main/java/org/dinky/job/JobManager.java @@ -19,6 +19,23 @@ package org.dinky.job; +import cn.hutool.core.text.StrFormatter; +import com.fasterxml.jackson.databind.node.ObjectNode; +import lombok.extern.slf4j.Slf4j; +import org.apache.flink.configuration.Configuration; +import org.apache.flink.configuration.CoreOptions; +import org.apache.flink.configuration.DeploymentOptions; +import org.apache.flink.configuration.PipelineOptions; +import org.apache.flink.core.execution.JobClient; +import org.apache.flink.runtime.jobgraph.JobGraph; +import org.apache.flink.runtime.jobgraph.SavepointConfigOptions; +import org.apache.flink.runtime.jobgraph.SavepointRestoreSettings; +import org.apache.flink.runtime.jobgraph.jsonplan.JsonPlanGenerator; +import org.apache.flink.runtime.rest.messages.JobPlanInfo; +import org.apache.flink.streaming.api.environment.ExecutionCheckpointingOptions; +import org.apache.flink.streaming.api.graph.StreamGraph; +import org.apache.flink.table.api.TableResult; +import org.apache.flink.yarn.configuration.YarnConfigOptions; import org.dinky.api.FlinkAPI; import org.dinky.assertion.Asserts; import org.dinky.classloader.DinkyClassLoader; @@ -64,20 +81,6 @@ import org.dinky.utils.SqlUtil; import org.dinky.utils.URLUtils; -import org.apache.flink.configuration.Configuration; -import org.apache.flink.configuration.CoreOptions; -import org.apache.flink.configuration.DeploymentOptions; -import org.apache.flink.configuration.PipelineOptions; -import org.apache.flink.core.execution.JobClient; -import org.apache.flink.runtime.jobgraph.JobGraph; -import org.apache.flink.runtime.jobgraph.SavepointConfigOptions; -import org.apache.flink.runtime.jobgraph.SavepointRestoreSettings; -import org.apache.flink.runtime.jobgraph.jsonplan.JsonPlanGenerator; -import org.apache.flink.streaming.api.environment.ExecutionCheckpointingOptions; -import org.apache.flink.streaming.api.graph.StreamGraph; -import org.apache.flink.table.api.TableResult; -import org.apache.flink.yarn.configuration.YarnConfigOptions; - import java.io.File; import java.io.IOException; import java.time.LocalDateTime; @@ -87,11 +90,6 @@ import java.util.Map; import java.util.Set; -import com.fasterxml.jackson.databind.node.ObjectNode; - -import cn.hutool.core.text.StrFormatter; -import lombok.extern.slf4j.Slf4j; - @Slf4j public class JobManager { private JobHandler handler; @@ -333,10 +331,10 @@ public ObjectNode getStreamGraph(String statement) { } public String getJobPlanJson(String statement) { - return Explainer.build(executor, useStatementSet, this) - .initialize(config, statement) - .getJobPlanInfo(statement) - .getJsonPlan(); + Explainer explainer = Explainer.build(executor, useStatementSet, this) + .initialize(config, statement); + JobParam jobParam = explainer.pretreatStatements(SqlUtil.getStatements(statement)); + return executor.getJobPlanJson(jobParam); } public boolean cancelNormal(String jobId) { From 59febc876221379199d94cfde89ef4c04bc404a0 Mon Sep 17 00:00:00 2001 From: licho Date: Fri, 15 Mar 2024 00:12:01 +0800 Subject: [PATCH 16/87] refactor: getLineage --- .../java/org/dinky/explainer/Explainer.java | 35 +++---------------- .../explainer/lineage/LineageBuilder.java | 16 ++++++++- .../main/java/org/dinky/job/JobManager.java | 10 ------ 3 files changed, 20 insertions(+), 41 deletions(-) diff --git a/dinky-core/src/main/java/org/dinky/explainer/Explainer.java b/dinky-core/src/main/java/org/dinky/explainer/Explainer.java index 8e5970b044..89480019d2 100644 --- a/dinky-core/src/main/java/org/dinky/explainer/Explainer.java +++ b/dinky-core/src/main/java/org/dinky/explainer/Explainer.java @@ -20,7 +20,6 @@ package org.dinky.explainer; import org.dinky.assertion.Asserts; -import org.dinky.data.enums.GatewayType; import org.dinky.data.model.LineageRel; import org.dinky.data.result.ExplainResult; import org.dinky.data.result.SqlExplainResult; @@ -85,10 +84,10 @@ public static Explainer build(Executor executor, boolean useStatementSet, JobMan } public Explainer initialize(JobConfig config, String statement) { - DinkyClassLoaderUtil.initClassLoader(config, jobManager.getDinkyClassLoader()); + DinkyClassLoaderUtil.initClassLoader(config, executor.getDinkyClassLoader()); String[] statements = SqlUtil.getStatements(SqlUtil.removeNote(statement)); - List udfs = parseUDFFromStatements(statements); - jobManager.setJobParam(new JobParam(udfs)); + JobParam jobParam = pretreatStatements(statements); + jobManager.setJobParam(jobParam); try { JobUDFBuilder.build(jobManager).run(); } catch (Exception e) { @@ -152,7 +151,7 @@ public JobParam pretreatStatements(String[] statements) { PrintStatementExplainer.getCreateStatement(tableName, host, port), SqlType.CTAS)); } } else { - UDF udf = UDFUtil.toUDF(statement, jobManager.getDinkyClassLoader()); + UDF udf = UDFUtil.toUDF(statement, executor.getDinkyClassLoader()); if (Asserts.isNotNull(udf)) { udfList.add(udf); } @@ -163,20 +162,6 @@ public JobParam pretreatStatements(String[] statements) { return new JobParam(statementList, ddl, trans, execute, CollUtil.removeNull(udfList)); } - public List parseUDFFromStatements(String[] statements) { - List udfList = new ArrayList<>(); - for (String statement : statements) { - if (statement.isEmpty()) { - continue; - } - UDF udf = UDFUtil.toUDF(statement, jobManager.getDinkyClassLoader()); - if (Asserts.isNotNull(udf)) { - udfList.add(udf); - } - } - return udfList; - } - public ExplainResult explainSql(String statement) { log.info("Start explain FlinkSQL..."); JobParam jobParam = pretreatStatements(SqlUtil.getStatements(statement)); @@ -330,17 +315,7 @@ public ObjectNode getStreamGraph(String statement) { } public List getLineage(String statement) { - JobConfig jobConfig = JobConfig.builder() - .type(GatewayType.LOCAL.getLongValue()) - .useRemote(false) - .fragment(true) - .statementSet(useStatementSet) - .parallelism(1) - .configJson(executor.getTableConfig().getConfiguration().toMap()) - .build(); - jobManager.setConfig(jobConfig); - jobManager.setExecutor(executor); - this.initialize(jobConfig, statement); + initialize(jobManager.getConfig(), statement); List lineageRelList = new ArrayList<>(); for (String item : SqlUtil.getStatements(statement)) { diff --git a/dinky-core/src/main/java/org/dinky/explainer/lineage/LineageBuilder.java b/dinky-core/src/main/java/org/dinky/explainer/lineage/LineageBuilder.java index d856a2c1ea..6a399d0d87 100644 --- a/dinky-core/src/main/java/org/dinky/explainer/lineage/LineageBuilder.java +++ b/dinky-core/src/main/java/org/dinky/explainer/lineage/LineageBuilder.java @@ -19,9 +19,12 @@ package org.dinky.explainer.lineage; +import org.dinky.data.enums.GatewayType; import org.dinky.data.model.LineageRel; +import org.dinky.executor.Executor; import org.dinky.executor.ExecutorFactory; import org.dinky.explainer.Explainer; +import org.dinky.job.JobConfig; import org.dinky.job.JobManager; import java.util.ArrayList; @@ -37,7 +40,18 @@ public class LineageBuilder { public static LineageResult getColumnLineageByLogicalPlan(String statement) { - Explainer explainer = new Explainer(ExecutorFactory.getDefaultExecutor(), false, new JobManager()); + Executor executor = ExecutorFactory.getDefaultExecutor(); + JobConfig jobConfig = JobConfig.builder() + .type(GatewayType.LOCAL.getLongValue()) + .useRemote(false) + .fragment(true) + .statementSet(false) + .parallelism(1) + .configJson(executor.getTableConfig().getConfiguration().toMap()) + .build(); + JobManager jobManager = JobManager.build(jobConfig); + jobManager.setExecutor(executor); + Explainer explainer = new Explainer(executor, false, jobManager); List lineageRelList = explainer.getLineage(statement); List relations = new ArrayList<>(); Map tableMap = new HashMap<>(); diff --git a/dinky-core/src/main/java/org/dinky/job/JobManager.java b/dinky-core/src/main/java/org/dinky/job/JobManager.java index 65fd8dab88..9fd80355b1 100644 --- a/dinky-core/src/main/java/org/dinky/job/JobManager.java +++ b/dinky-core/src/main/java/org/dinky/job/JobManager.java @@ -31,7 +31,6 @@ import org.apache.flink.runtime.jobgraph.SavepointConfigOptions; import org.apache.flink.runtime.jobgraph.SavepointRestoreSettings; import org.apache.flink.runtime.jobgraph.jsonplan.JsonPlanGenerator; -import org.apache.flink.runtime.rest.messages.JobPlanInfo; import org.apache.flink.streaming.api.environment.ExecutionCheckpointingOptions; import org.apache.flink.streaming.api.graph.StreamGraph; import org.apache.flink.table.api.TableResult; @@ -84,7 +83,6 @@ import java.io.File; import java.io.IOException; import java.time.LocalDateTime; -import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; @@ -467,18 +465,10 @@ public void setPlanMode(boolean planMode) { isPlanMode = planMode; } - public boolean isPlanMode() { - return isPlanMode; - } - public boolean isUseStatementSet() { return useStatementSet; } - public boolean isUseRestAPI() { - return useRestAPI; - } - public boolean isUseGateway() { return useGateway; } From 9a46ed073bf292de46edc029c602b2f48993c4fb Mon Sep 17 00:00:00 2001 From: licho Date: Sat, 16 Mar 2024 09:47:01 +0800 Subject: [PATCH 17/87] refactor: JobBuilder --- .../dinky/service/impl/TaskServiceImpl.java | 2 +- .../org/dinky/job/ExecuteSqlException.java | 7 ++ .../main/java/org/dinky/job/JobBuilder.java | 24 +--- .../main/java/org/dinky/job/JobConfig.java | 2 +- .../main/java/org/dinky/job/JobManager.java | 108 +++++------------- .../org/dinky/job/builder/JobDDLBuilder.java | 22 +++- .../dinky/job/builder/JobExecuteBuilder.java | 42 +++++-- .../job/builder/JobJarStreamGraphBuilder.java | 17 ++- .../dinky/job/builder/JobTransBuilder.java | 84 +++++++++----- .../org/dinky/job/builder/JobUDFBuilder.java | 29 +++-- 10 files changed, 173 insertions(+), 164 deletions(-) create mode 100644 dinky-core/src/main/java/org/dinky/job/ExecuteSqlException.java diff --git a/dinky-admin/src/main/java/org/dinky/service/impl/TaskServiceImpl.java b/dinky-admin/src/main/java/org/dinky/service/impl/TaskServiceImpl.java index bd145e58b9..ff59b4f46b 100644 --- a/dinky-admin/src/main/java/org/dinky/service/impl/TaskServiceImpl.java +++ b/dinky-admin/src/main/java/org/dinky/service/impl/TaskServiceImpl.java @@ -483,7 +483,7 @@ public ObjectNode getJobPlan(TaskDTO task) { @Override public ObjectNode getStreamGraph(TaskDTO taskDTO) { JobConfig config = taskDTO.getJobConfig(); - JobManager jobManager = JobManager.buildPlanMode(config); + JobManager jobManager = JobManager.buildPlanMode(config, true); ObjectNode streamGraph = jobManager.getStreamGraph(taskDTO.getStatement()); RunTimeUtil.recovery(jobManager); return streamGraph; diff --git a/dinky-core/src/main/java/org/dinky/job/ExecuteSqlException.java b/dinky-core/src/main/java/org/dinky/job/ExecuteSqlException.java new file mode 100644 index 0000000000..34ce721b6f --- /dev/null +++ b/dinky-core/src/main/java/org/dinky/job/ExecuteSqlException.java @@ -0,0 +1,7 @@ +package org.dinky.job; + +public class ExecuteSqlException extends Exception { + public ExecuteSqlException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/dinky-core/src/main/java/org/dinky/job/JobBuilder.java b/dinky-core/src/main/java/org/dinky/job/JobBuilder.java index c31f33bd55..6cb3dbe852 100644 --- a/dinky-core/src/main/java/org/dinky/job/JobBuilder.java +++ b/dinky-core/src/main/java/org/dinky/job/JobBuilder.java @@ -22,27 +22,7 @@ import org.dinky.data.enums.GatewayType; import org.dinky.executor.Executor; -public abstract class JobBuilder { +public interface JobBuilder { - protected JobManager jobManager; - protected JobConfig config; - protected JobParam jobParam; - protected GatewayType runMode; - protected Executor executor; - protected boolean useStatementSet; - protected boolean useGateway; - protected Job job; - - public JobBuilder(JobManager jobManager) { - this.jobManager = jobManager; - this.config = jobManager.getConfig(); - this.jobParam = jobManager.getJobParam(); - this.runMode = jobManager.getRunMode(); - this.executor = jobManager.getExecutor(); - this.useStatementSet = jobManager.isUseStatementSet(); - this.useGateway = jobManager.isUseGateway(); - this.job = jobManager.getJob(); - } - - public abstract void run() throws Exception; + void run() throws Exception; } diff --git a/dinky-core/src/main/java/org/dinky/job/JobConfig.java b/dinky-core/src/main/java/org/dinky/job/JobConfig.java index f5fd35a499..e01ba358c4 100644 --- a/dinky-core/src/main/java/org/dinky/job/JobConfig.java +++ b/dinky-core/src/main/java/org/dinky/job/JobConfig.java @@ -203,7 +203,7 @@ public void setAddress(String address) { } } - public ExecutorConfig getExecutorSetting() { + public ExecutorConfig createExecutorSetting() { return ExecutorConfig.build( type, address, diff --git a/dinky-core/src/main/java/org/dinky/job/JobManager.java b/dinky-core/src/main/java/org/dinky/job/JobManager.java index 9fd80355b1..2ec018a332 100644 --- a/dinky-core/src/main/java/org/dinky/job/JobManager.java +++ b/dinky-core/src/main/java/org/dinky/job/JobManager.java @@ -21,6 +21,8 @@ import cn.hutool.core.text.StrFormatter; import com.fasterxml.jackson.databind.node.ObjectNode; +import lombok.Getter; +import lombok.Setter; import lombok.extern.slf4j.Slf4j; import org.apache.flink.configuration.Configuration; import org.apache.flink.configuration.CoreOptions; @@ -89,57 +91,52 @@ import java.util.Set; @Slf4j +@Setter +@Getter public class JobManager { private JobHandler handler; private ExecutorConfig executorConfig; private JobConfig config; private Executor executor; private boolean useGateway = false; - private boolean isPlanMode = false; private boolean useStatementSet = false; private boolean useRestAPI = false; private GatewayType runMode = GatewayType.LOCAL; private JobParam jobParam = null; - private String currentSql = ""; - private Job job; - - public JobManager() {} - private JobManager(JobConfig config) { - this.config = config; - } + private Job job; - public static JobManager build(JobConfig config) { - JobManager manager = new JobManager(config); - manager.init(); - return manager; + public JobManager() { } - public static JobManager buildPlanMode(JobConfig config) { - JobManager manager = new JobManager(config); - manager.setPlanMode(true); - manager.init(); - log.info("Build Flink plan mode success."); - return manager; - } + private JobManager(JobConfig config, boolean isPlanMode) { + this.config = config; - public void init() { if (!isPlanMode) { runMode = GatewayType.get(config.getType()); useGateway = GatewayType.isDeployCluster(config.getType()); handler = JobHandler.build(); } + useStatementSet = config.isStatementSet(); useRestAPI = SystemConfiguration.getInstances().isUseRestAPI(); - executorConfig = config.getExecutorSetting(); + executorConfig = config.createExecutorSetting(); executorConfig.setPlan(isPlanMode); executor = ExecutorFactory.buildExecutor(executorConfig); } - private boolean ready(String statement) { + public static JobManager build(JobConfig config) { + return buildPlanMode(config, false); + } + + public static JobManager buildPlanMode(JobConfig config, boolean isPlanMode) { + return new JobManager(config, isPlanMode); + } + + private void prepare(String statement) { job = Job.build(runMode, config, executorConfig, executor, statement, useGateway); - return handler.init(job); + handler.init(job); } private boolean success() { @@ -169,7 +166,7 @@ public ObjectNode getJarStreamGraphJson(String statement) { @ProcessStep(type = ProcessStepType.SUBMIT_EXECUTE) public JobResult executeJarSql(String statement) throws Exception { - ready(statement); + prepare(statement); JobJarStreamGraphBuilder jobJarStreamGraphBuilder = JobJarStreamGraphBuilder.build(this); StreamGraph streamGraph = jobJarStreamGraphBuilder.getJarStreamGraph(statement, getDinkyClassLoader()); Configuration configuration = @@ -240,7 +237,7 @@ public JobResult executeJarSql(String statement) throws Exception { @ProcessStep(type = ProcessStepType.SUBMIT_EXECUTE) public JobResult executeSql(String statement) throws Exception { - ready(statement); + prepare(statement); DinkyClassLoaderUtil.initClassLoader(config, getDinkyClassLoader()); jobParam = @@ -262,9 +259,9 @@ public JobResult executeSql(String statement) throws Exception { job.setStatus(Job.JobStatus.SUCCESS); success(); } - } catch (Exception e) { + } catch (ExecuteSqlException e) { String error = StrFormatter.format( - "Exception in executing FlinkSQL:\n{}\n{}", SqlUtil.addLineNumber(currentSql), e.getMessage()); + "Exception in executing FlinkSQL:\n{}\n{}", SqlUtil.addLineNumber(e.getMessage()), e.getMessage()); job.setEndTime(LocalDateTime.now()); job.setStatus(Job.JobStatus.FAILED); job.setError(error); @@ -410,12 +407,12 @@ public String exportSql(String sql) { + YarnConfigOptions.PROVIDED_LIB_DIRS.key() + " = " + Collections.singletonList( - config.getGatewayConfig().getClusterConfig().getFlinkLibPath()) + config.getGatewayConfig().getClusterConfig().getFlinkLibPath()) + ";\r\n"); } if (Asserts.isNotNull(config.getGatewayConfig()) && Asserts.isNotNullString( - config.getGatewayConfig().getFlinkConfig().getJobName())) { + config.getGatewayConfig().getFlinkConfig().getJobName())) { sb.append("set " + YarnConfigOptions.APPLICATION_NAME.key() + " = " @@ -429,62 +426,9 @@ public String exportSql(String sql) { return sb.toString(); } - public JobParam getJobParam() { - return jobParam; - } - - public void setJobParam(JobParam jobParam) { - this.jobParam = jobParam; - } - - public JobConfig getConfig() { - return config; - } - - public void setConfig(JobConfig config) { - this.config = config; - } - - public GatewayType getRunMode() { - return runMode; - } - - public void setCurrentSql(String currentSql) { - this.currentSql = currentSql; - } - - public Executor getExecutor() { - return executor; - } - - public void setExecutor(Executor executor) { - this.executor = executor; - } - - public void setPlanMode(boolean planMode) { - isPlanMode = planMode; - } - - public boolean isUseStatementSet() { - return useStatementSet; - } - - public boolean isUseGateway() { - return useGateway; - } - // return dinkyclassloader public DinkyClassLoader getDinkyClassLoader() { return executor.getDinkyClassLoader(); } - // return job - public Job getJob() { - return job; - } - - // set job - public void setJob(Job job) { - this.job = job; - } } diff --git a/dinky-core/src/main/java/org/dinky/job/builder/JobDDLBuilder.java b/dinky-core/src/main/java/org/dinky/job/builder/JobDDLBuilder.java index 64869061ec..37bae513fc 100644 --- a/dinky-core/src/main/java/org/dinky/job/builder/JobDDLBuilder.java +++ b/dinky-core/src/main/java/org/dinky/job/builder/JobDDLBuilder.java @@ -19,8 +19,11 @@ package org.dinky.job.builder; +import org.dinky.executor.Executor; +import org.dinky.job.ExecuteSqlException; import org.dinky.job.JobBuilder; import org.dinky.job.JobManager; +import org.dinky.job.JobParam; import org.dinky.job.StatementParam; import lombok.extern.slf4j.Slf4j; @@ -30,21 +33,28 @@ * */ @Slf4j -public class JobDDLBuilder extends JobBuilder { +public class JobDDLBuilder implements JobBuilder { - public JobDDLBuilder(JobManager jobManager) { - super(jobManager); + private final JobParam jobParam; + private final Executor executor; + + public JobDDLBuilder(JobParam jobParam, Executor executor) { + this.jobParam = jobParam; + this.executor = executor; } public static JobDDLBuilder build(JobManager jobManager) { - return new JobDDLBuilder(jobManager); + return new JobDDLBuilder(jobManager.getJobParam(), jobManager.getExecutor()); } @Override public void run() throws Exception { for (StatementParam item : jobParam.getDdl()) { - jobManager.setCurrentSql(item.getValue()); - executor.executeSql(item.getValue()); + try { + executor.executeSql(item.getValue()); + }catch (Exception ex) { + throw new ExecuteSqlException(item.getValue(), ex); + } } } } diff --git a/dinky-core/src/main/java/org/dinky/job/builder/JobExecuteBuilder.java b/dinky-core/src/main/java/org/dinky/job/builder/JobExecuteBuilder.java index 930abf2a69..ca066b6856 100644 --- a/dinky-core/src/main/java/org/dinky/job/builder/JobExecuteBuilder.java +++ b/dinky-core/src/main/java/org/dinky/job/builder/JobExecuteBuilder.java @@ -20,14 +20,19 @@ package org.dinky.job.builder; import org.dinky.assertion.Asserts; +import org.dinky.data.enums.GatewayType; import org.dinky.data.result.IResult; import org.dinky.data.result.InsertResult; import org.dinky.data.result.ResultBuilder; +import org.dinky.data.result.RunResult; +import org.dinky.executor.Executor; import org.dinky.gateway.Gateway; import org.dinky.gateway.result.GatewayResult; import org.dinky.job.Job; import org.dinky.job.JobBuilder; +import org.dinky.job.JobConfig; import org.dinky.job.JobManager; +import org.dinky.job.JobParam; import org.dinky.job.StatementParam; import org.dinky.parser.SqlType; import org.dinky.utils.URLUtils; @@ -38,19 +43,37 @@ import org.apache.flink.streaming.api.graph.StreamGraph; import java.util.ArrayList; +import java.util.Collections; /** * JobExecuteBuilder - * */ -public class JobExecuteBuilder extends JobBuilder { +public class JobExecuteBuilder implements JobBuilder { + + private final JobParam jobParam; + private final boolean useGateway; + private final Executor executor; + private final boolean useStatementSet; + private final JobConfig config; + private final GatewayType runMode; + private final Job job; - public JobExecuteBuilder(JobManager jobManager) { - super(jobManager); + public JobExecuteBuilder(JobParam jobParam, boolean useGateway, Executor executor, boolean useStatementSet, + JobConfig config, GatewayType runMode, Job job) { + this.jobParam = jobParam; + this.useGateway = useGateway; + this.executor = executor; + this.useStatementSet = useStatementSet; + this.config = config; + this.runMode = runMode; + this.job = job; } public static JobExecuteBuilder build(JobManager jobManager) { - return new JobExecuteBuilder(jobManager); + return new JobExecuteBuilder(jobManager.getJobParam(), + jobManager.isUseGateway(), + jobManager.getExecutor(), jobManager.isUseStatementSet(), jobManager.getConfig(), + jobManager.getRunMode(), jobManager.getJob()); } @Override @@ -96,16 +119,13 @@ public void run() throws Exception { break; } } + JobClient jobClient = executor.executeAsync(config.getJobName()); if (Asserts.isNotNull(jobClient)) { job.setJobId(jobClient.getJobID().toHexString()); - job.setJids(new ArrayList() { - - { - add(job.getJobId()); - } - }); + job.setJids(Collections.singletonList(job.getJobId())); } + if (config.isUseResult()) { IResult result = ResultBuilder.build( SqlType.EXECUTE, diff --git a/dinky-core/src/main/java/org/dinky/job/builder/JobJarStreamGraphBuilder.java b/dinky-core/src/main/java/org/dinky/job/builder/JobJarStreamGraphBuilder.java index 806013dd8f..7ca8587475 100644 --- a/dinky-core/src/main/java/org/dinky/job/builder/JobJarStreamGraphBuilder.java +++ b/dinky-core/src/main/java/org/dinky/job/builder/JobJarStreamGraphBuilder.java @@ -21,7 +21,9 @@ import org.dinky.classloader.DinkyClassLoader; import org.dinky.data.exception.DinkyException; +import org.dinky.executor.Executor; import org.dinky.job.JobBuilder; +import org.dinky.job.JobConfig; import org.dinky.job.JobManager; import org.dinky.parser.SqlType; import org.dinky.trans.Operations; @@ -47,18 +49,23 @@ /** * JobJarStreamGraphBuilder */ -public class JobJarStreamGraphBuilder extends JobBuilder { +public class JobJarStreamGraphBuilder implements JobBuilder { - public JobJarStreamGraphBuilder(JobManager jobManager) { - super(jobManager); + private final JobConfig config; + private final Executor executor; + + public JobJarStreamGraphBuilder(JobConfig config, Executor executor) { + this.config = config; + this.executor = executor; } public static JobJarStreamGraphBuilder build(JobManager jobManager) { - return new JobJarStreamGraphBuilder(jobManager); + return new JobJarStreamGraphBuilder(jobManager.getConfig(), jobManager.getExecutor()); } @Override - public void run() throws Exception {} + public void run() throws Exception { + } public StreamGraph getJarStreamGraph(String statement, DinkyClassLoader dinkyClassLoader) { DinkyClassLoaderUtil.initClassLoader(config, dinkyClassLoader); diff --git a/dinky-core/src/main/java/org/dinky/job/builder/JobTransBuilder.java b/dinky-core/src/main/java/org/dinky/job/builder/JobTransBuilder.java index e983e85719..6c6c03f99a 100644 --- a/dinky-core/src/main/java/org/dinky/job/builder/JobTransBuilder.java +++ b/dinky-core/src/main/java/org/dinky/job/builder/JobTransBuilder.java @@ -25,15 +25,18 @@ import org.dinky.data.result.IResult; import org.dinky.data.result.InsertResult; import org.dinky.data.result.ResultBuilder; +import org.dinky.data.result.RunResult; import org.dinky.executor.Executor; import org.dinky.gateway.Gateway; import org.dinky.gateway.result.GatewayResult; import org.dinky.interceptor.FlinkInterceptor; import org.dinky.interceptor.FlinkInterceptorResult; +import org.dinky.job.ExecuteSqlException; import org.dinky.job.Job; import org.dinky.job.JobBuilder; import org.dinky.job.JobConfig; import org.dinky.job.JobManager; +import org.dinky.job.JobParam; import org.dinky.job.StatementParam; import org.dinky.parser.SqlType; import org.dinky.utils.URLUtils; @@ -48,33 +51,62 @@ /** * JobTransBuilder - * */ -public class JobTransBuilder extends JobBuilder { - - public JobTransBuilder(JobManager jobManager) { - super(jobManager); +public class JobTransBuilder implements JobBuilder { + + private String currentSql; + private final JobParam jobParam; + private final boolean useStatementSet; + private final boolean useGateway; + private final JobConfig config; + private final Executor executor; + private final GatewayType runMode; + private final Job job; + + public JobTransBuilder(JobParam jobParam, boolean useStatementSet, boolean useGateway, JobConfig config, + Executor executor, GatewayType runMode, Job job) { + this.jobParam = jobParam; + this.useStatementSet = useStatementSet; + this.useGateway = useGateway; + this.config = config; + this.executor = executor; + this.runMode = runMode; + this.job = job; } + public static JobTransBuilder build(JobManager jobManager) { - return new JobTransBuilder(jobManager); + + return new JobTransBuilder( + jobManager.getJobParam(), + jobManager.isUseStatementSet(), + jobManager.isUseGateway(), + jobManager.getConfig(), + jobManager.getExecutor(), + jobManager.getRunMode(), + jobManager.getJob() + ); } @Override public void run() throws Exception { - if (jobParam.getTrans().isEmpty()) { - return; - } + try { + if (jobParam.getTrans().isEmpty()) { + return; + } - if (useStatementSet) { - handleStatementSet(); - return; - } + if (useStatementSet) { + handleStatementSet(); + return; + } - handleNonStatementSet(); + handleNonStatementSet(); + } catch (Exception ex) { + throw new ExecuteSqlException(currentSql, ex); + } } - private void handleStatementSet() throws Exception { + private void handleStatementSet() { List inserts = collectInserts(); if (useGateway) { @@ -84,7 +116,7 @@ private void handleStatementSet() throws Exception { processWithoutGateway(inserts); } - private void handleNonStatementSet() throws Exception { + private void handleNonStatementSet() { if (useGateway) { processSingleInsertWithGateway(); return; @@ -104,36 +136,36 @@ private List collectInserts() { return inserts; } - private void processWithGateway(List inserts) throws Exception { - jobManager.setCurrentSql(String.join(FlinkSQLConstant.SEPARATOR, inserts)); + private void processWithGateway(List inserts) { + currentSql = String.join(FlinkSQLConstant.SEPARATOR, inserts); GatewayResult gatewayResult = submitByGateway(inserts); setJobResultFromGatewayResult(gatewayResult); } - private void processWithoutGateway(List inserts) throws Exception { + private void processWithoutGateway(List inserts) { if (!inserts.isEmpty()) { - jobManager.setCurrentSql(String.join(FlinkSQLConstant.SEPARATOR, inserts)); + currentSql = String.join(FlinkSQLConstant.SEPARATOR, inserts); TableResult tableResult = executor.executeStatementSet(inserts); updateJobWithTableResult(tableResult); } } - private void processSingleInsertWithGateway() throws Exception { + private void processSingleInsertWithGateway() { List singleInsert = collectInserts(); processWithGateway(singleInsert); } - private void processFirstStatement() throws Exception { + private void processFirstStatement() { if (jobParam.getTrans().isEmpty()) { return; } // Only process the first statement when not using statement set StatementParam item = jobParam.getTrans().get(0); - jobManager.setCurrentSql(item.getValue()); + currentSql = item.getValue(); processSingleStatement(item); } - private void processSingleStatement(StatementParam item) throws Exception { + private void processSingleStatement(StatementParam item) { FlinkInterceptorResult flinkInterceptorResult = FlinkInterceptor.build(executor, item.getValue()); if (Asserts.isNotNull(flinkInterceptorResult.getTableResult())) { updateJobWithTableResult(flinkInterceptorResult.getTableResult(), item.getType()); @@ -178,10 +210,6 @@ private void updateJobWithTableResult(TableResult tableResult, SqlType sqlType) } private GatewayResult submitByGateway(List inserts) { - JobConfig config = jobManager.getConfig(); - GatewayType runMode = jobManager.getRunMode(); - Executor executor = jobManager.getExecutor(); - GatewayResult gatewayResult = null; // Use gateway need to build gateway config, include flink configuration. diff --git a/dinky-core/src/main/java/org/dinky/job/builder/JobUDFBuilder.java b/dinky-core/src/main/java/org/dinky/job/builder/JobUDFBuilder.java index 43585ad3fd..376c775be3 100644 --- a/dinky-core/src/main/java/org/dinky/job/builder/JobUDFBuilder.java +++ b/dinky-core/src/main/java/org/dinky/job/builder/JobUDFBuilder.java @@ -23,12 +23,17 @@ import static org.dinky.function.util.UDFUtil.SESSION; import static org.dinky.function.util.UDFUtil.YARN; +import io.debezium.config.CommonConnectorConfig; import org.dinky.assertion.Asserts; +import org.dinky.data.enums.GatewayType; import org.dinky.data.model.SystemConfiguration; +import org.dinky.executor.Executor; import org.dinky.function.data.model.UDF; import org.dinky.function.util.UDFUtil; import org.dinky.job.JobBuilder; +import org.dinky.job.JobConfig; import org.dinky.job.JobManager; +import org.dinky.job.JobParam; import org.dinky.utils.URLUtils; import java.io.File; @@ -45,23 +50,31 @@ /** * JobUDFBuilder - * */ @Slf4j -public class JobUDFBuilder extends JobBuilder { - - public JobUDFBuilder(JobManager jobManager) { - super(jobManager); +public class JobUDFBuilder implements JobBuilder { + + private final JobParam jobParam; + private final Executor executor; + private final JobConfig config; + private final GatewayType runMode; + + public JobUDFBuilder(JobParam jobParam, Executor executor, JobConfig config, GatewayType runMode) { + this.jobParam = jobParam; + this.executor = executor; + this.config = config; + this.runMode = runMode; } public static JobUDFBuilder build(JobManager jobManager) { - return new JobUDFBuilder(jobManager); + return new JobUDFBuilder(jobManager.getJobParam(), jobManager.getExecutor(), jobManager.getConfig(), + jobManager.getRunMode()); } @Override public void run() throws Exception { Asserts.checkNotNull(jobParam, "No executable statement."); - List udfList = jobManager.getJobParam().getUdfList(); + List udfList = jobParam.getUdfList(); Integer taskId = config.getTaskId(); if (taskId == null) { taskId = -RandomUtil.randomInt(0, 1000); @@ -104,7 +117,7 @@ public void run() throws Exception { // 3.Write the required files for UDF UDFUtil.writeManifest(taskId, jarList, executor.getUdfPathContextHolder()); UDFUtil.addConfigurationClsAndJars( - jobManager.getExecutor().getCustomTableEnvironment(), + executor.getCustomTableEnvironment(), jarList, CollUtil.newArrayList(URLUtils.getURLs(jarFiles))); } catch (Exception e) { From 6a7e9a8bcf2007983f8579a3ac901624a4a9e243 Mon Sep 17 00:00:00 2001 From: licho Date: Sat, 16 Mar 2024 10:07:09 +0800 Subject: [PATCH 18/87] refactor: JobManager --- .../main/java/org/dinky/job/JobContext.java | 22 +++++++++++++++++++ .../main/java/org/dinky/job/JobManager.java | 12 ++-------- .../job/builder/JobJarStreamGraphBuilder.java | 15 ++++++++++++- 3 files changed, 38 insertions(+), 11 deletions(-) create mode 100644 dinky-core/src/main/java/org/dinky/job/JobContext.java diff --git a/dinky-core/src/main/java/org/dinky/job/JobContext.java b/dinky-core/src/main/java/org/dinky/job/JobContext.java new file mode 100644 index 0000000000..8beba3a53a --- /dev/null +++ b/dinky-core/src/main/java/org/dinky/job/JobContext.java @@ -0,0 +1,22 @@ +package org.dinky.job; + +import lombok.Data; +import org.dinky.data.enums.GatewayType; +import org.dinky.executor.Executor; +import org.dinky.executor.ExecutorConfig; + +@Data +public class JobContext { + private JobHandler handler; + private ExecutorConfig executorConfig; + private JobConfig config; + private Executor executor; + private boolean useGateway = false; + private boolean isPlanMode = false; + private boolean useStatementSet = false; + private boolean useRestAPI = false; + private GatewayType runMode = GatewayType.LOCAL; + + private JobParam jobParam = null; + private Job job; +} diff --git a/dinky-core/src/main/java/org/dinky/job/JobManager.java b/dinky-core/src/main/java/org/dinky/job/JobManager.java index 2ec018a332..5ea950df94 100644 --- a/dinky-core/src/main/java/org/dinky/job/JobManager.java +++ b/dinky-core/src/main/java/org/dinky/job/JobManager.java @@ -24,14 +24,12 @@ import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; -import org.apache.flink.configuration.Configuration; import org.apache.flink.configuration.CoreOptions; import org.apache.flink.configuration.DeploymentOptions; import org.apache.flink.configuration.PipelineOptions; import org.apache.flink.core.execution.JobClient; import org.apache.flink.runtime.jobgraph.JobGraph; import org.apache.flink.runtime.jobgraph.SavepointConfigOptions; -import org.apache.flink.runtime.jobgraph.SavepointRestoreSettings; import org.apache.flink.runtime.jobgraph.jsonplan.JsonPlanGenerator; import org.apache.flink.streaming.api.environment.ExecutionCheckpointingOptions; import org.apache.flink.streaming.api.graph.StreamGraph; @@ -169,13 +167,7 @@ public JobResult executeJarSql(String statement) throws Exception { prepare(statement); JobJarStreamGraphBuilder jobJarStreamGraphBuilder = JobJarStreamGraphBuilder.build(this); StreamGraph streamGraph = jobJarStreamGraphBuilder.getJarStreamGraph(statement, getDinkyClassLoader()); - Configuration configuration = - executor.getCustomTableEnvironment().getConfig().getConfiguration(); - if (Asserts.isNotNullString(config.getSavePointPath())) { - streamGraph.setSavepointRestoreSettings(SavepointRestoreSettings.forPath( - config.getSavePointPath(), - configuration.get(SavepointConfigOptions.SAVEPOINT_IGNORE_UNCLAIMED_STATE))); - } + try { if (!useGateway) { JobClient jobClient = executor.getStreamExecutionEnvironment().executeAsync(streamGraph); @@ -190,7 +182,7 @@ public JobResult executeJarSql(String statement) throws Exception { } } else { GatewayResult gatewayResult; - config.addGatewayConfig(configuration); + config.addGatewayConfig(executor.getCustomTableEnvironment().getConfig().getConfiguration()); if (runMode.isApplicationMode()) { gatewayResult = Gateway.build(config.getGatewayConfig()).submitJar(executor.getUdfPathContextHolder()); diff --git a/dinky-core/src/main/java/org/dinky/job/builder/JobJarStreamGraphBuilder.java b/dinky-core/src/main/java/org/dinky/job/builder/JobJarStreamGraphBuilder.java index 7ca8587475..cfb5c837cf 100644 --- a/dinky-core/src/main/java/org/dinky/job/builder/JobJarStreamGraphBuilder.java +++ b/dinky-core/src/main/java/org/dinky/job/builder/JobJarStreamGraphBuilder.java @@ -19,6 +19,10 @@ package org.dinky.job.builder; +import org.apache.flink.configuration.Configuration; +import org.apache.flink.runtime.jobgraph.SavepointConfigOptions; +import org.apache.flink.runtime.jobgraph.SavepointRestoreSettings; +import org.dinky.assertion.Asserts; import org.dinky.classloader.DinkyClassLoader; import org.dinky.data.exception.DinkyException; import org.dinky.executor.Executor; @@ -93,7 +97,16 @@ public StreamGraph getJarStreamGraph(String statement, DinkyClassLoader dinkyCla } Assert.notNull(executeJarOperation, () -> new DinkyException("Not found execute jar operation.")); List urLs = executor.getAllFileSet(); - return executeJarOperation.explain(executor.getCustomTableEnvironment(), urLs); + StreamGraph streamGraph = executeJarOperation.explain(executor.getCustomTableEnvironment(), urLs); + Configuration configuration = + executor.getCustomTableEnvironment().getConfig().getConfiguration(); + if (Asserts.isNotNullString(config.getSavePointPath())) { + streamGraph.setSavepointRestoreSettings(SavepointRestoreSettings.forPath( + config.getSavePointPath(), + configuration.get(SavepointConfigOptions.SAVEPOINT_IGNORE_UNCLAIMED_STATE))); + } + return streamGraph; + } public List getUris(String statement) { From ef4b1519964cdfbf3f86345f118ee9f385d4b6a8 Mon Sep 17 00:00:00 2001 From: leechor Date: Sat, 16 Mar 2024 02:21:44 +0000 Subject: [PATCH 19/87] Spotless Apply --- .../org/dinky/executor/AbstractExecutor.java | 18 +++---- .../java/org/dinky/executor/Executor.java | 2 +- .../org/dinky/job/ExecuteSqlException.java | 21 +++++++- .../main/java/org/dinky/job/JobBuilder.java | 3 -- .../main/java/org/dinky/job/JobContext.java | 22 ++++++++- .../main/java/org/dinky/job/JobManager.java | 49 ++++++++++--------- .../org/dinky/job/builder/JobDDLBuilder.java | 2 +- .../dinky/job/builder/JobExecuteBuilder.java | 22 ++++++--- .../job/builder/JobJarStreamGraphBuilder.java | 10 ++-- .../dinky/job/builder/JobTransBuilder.java | 15 +++--- .../org/dinky/job/builder/JobUDFBuilder.java | 9 ++-- 11 files changed, 105 insertions(+), 68 deletions(-) diff --git a/dinky-core/src/main/java/org/dinky/executor/AbstractExecutor.java b/dinky-core/src/main/java/org/dinky/executor/AbstractExecutor.java index 60cca7cf31..89c62b3507 100644 --- a/dinky-core/src/main/java/org/dinky/executor/AbstractExecutor.java +++ b/dinky-core/src/main/java/org/dinky/executor/AbstractExecutor.java @@ -19,7 +19,6 @@ package org.dinky.executor; -import org.apache.flink.core.fs.FileSystem; import org.dinky.assertion.Asserts; import org.dinky.classloader.DinkyClassLoader; import org.dinky.context.CustomTableEnvironmentContext; @@ -32,12 +31,14 @@ import org.dinky.job.StatementParam; import org.dinky.trans.dml.ExecuteJarOperation; import org.dinky.utils.KerberosUtil; +import org.dinky.utils.URLUtils; import org.apache.flink.api.common.ExecutionConfig; import org.apache.flink.api.common.JobExecutionResult; import org.apache.flink.configuration.Configuration; import org.apache.flink.configuration.PipelineOptions; import org.apache.flink.core.execution.JobClient; +import org.apache.flink.core.fs.FileSystem; import org.apache.flink.python.PythonOptions; import org.apache.flink.runtime.jobgraph.JobGraph; import org.apache.flink.runtime.jobgraph.jsonplan.JsonPlanGenerator; @@ -60,7 +61,6 @@ import java.util.Map; import java.util.stream.Collectors; -import org.dinky.utils.URLUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -272,30 +272,25 @@ public SqlExplainResult explainSqlRecord(String statement, ExplainDetail... extr return null; } - @Override public String getJarStreamingPlanStringJson(String parameter) { List allFileByAdd = getAllFileSet(); - StreamGraph streamGraph = new ExecuteJarOperation(parameter) - .explain(getCustomTableEnvironment(), allFileByAdd); + StreamGraph streamGraph = new ExecuteJarOperation(parameter).explain(getCustomTableEnvironment(), allFileByAdd); return streamGraph.getStreamingPlanAsJSON(); } - @Override public ObjectNode getStreamGraph(List statements) { StreamGraph streamGraph = tableEnvironment.getStreamGraphFromInserts(statements); return getStreamGraphJsonNode(streamGraph); } - - @Override public List getAllFileSet() { return CollUtil.isEmpty(getUdfPathContextHolder().getAllFileSet()) ? Collections.emptyList() : Arrays.asList(URLUtils.getURLs( - getUdfPathContextHolder().getAllFileSet().toArray(new File[0]))); + getUdfPathContextHolder().getAllFileSet().toArray(new File[0]))); } @Override @@ -376,7 +371,7 @@ public JobPlanInfo getJobPlanInfo(JobParam jobParam) { } @Override - public String getJobPlanJson(JobParam jobParam){ + public String getJobPlanJson(JobParam jobParam) { return getJobPlanInfo(jobParam).getJsonPlan(); } @@ -386,12 +381,11 @@ public List getLineage(String statement) { } @Override - public void initializeFileSystem(){ + public void initializeFileSystem() { Configuration combinationConfig = getCombinationConfig(); FileSystem.initialize(combinationConfig, null); } - private Configuration getCombinationConfig() { CustomTableEnvironment cte = getCustomTableEnvironment(); Configuration rootConfig = cte.getRootConfiguration(); diff --git a/dinky-core/src/main/java/org/dinky/executor/Executor.java b/dinky-core/src/main/java/org/dinky/executor/Executor.java index 7b847dabfd..d376a8b803 100644 --- a/dinky-core/src/main/java/org/dinky/executor/Executor.java +++ b/dinky-core/src/main/java/org/dinky/executor/Executor.java @@ -23,6 +23,7 @@ import org.dinky.context.FlinkUdfPathContextHolder; import org.dinky.data.model.LineageRel; import org.dinky.data.result.SqlExplainResult; +import org.dinky.job.JobParam; import org.apache.flink.api.common.ExecutionConfig; import org.apache.flink.api.common.JobExecutionResult; @@ -41,7 +42,6 @@ import java.util.Map; import com.fasterxml.jackson.databind.node.ObjectNode; -import org.dinky.job.JobParam; public interface Executor { // return dinkyClassLoader diff --git a/dinky-core/src/main/java/org/dinky/job/ExecuteSqlException.java b/dinky-core/src/main/java/org/dinky/job/ExecuteSqlException.java index 34ce721b6f..58b111e334 100644 --- a/dinky-core/src/main/java/org/dinky/job/ExecuteSqlException.java +++ b/dinky-core/src/main/java/org/dinky/job/ExecuteSqlException.java @@ -1,6 +1,25 @@ +/* + * + * 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.job; -public class ExecuteSqlException extends Exception { +public class ExecuteSqlException extends Exception { public ExecuteSqlException(String message, Throwable cause) { super(message, cause); } diff --git a/dinky-core/src/main/java/org/dinky/job/JobBuilder.java b/dinky-core/src/main/java/org/dinky/job/JobBuilder.java index 6cb3dbe852..864b5c8b6e 100644 --- a/dinky-core/src/main/java/org/dinky/job/JobBuilder.java +++ b/dinky-core/src/main/java/org/dinky/job/JobBuilder.java @@ -19,9 +19,6 @@ package org.dinky.job; -import org.dinky.data.enums.GatewayType; -import org.dinky.executor.Executor; - public interface JobBuilder { void run() throws Exception; diff --git a/dinky-core/src/main/java/org/dinky/job/JobContext.java b/dinky-core/src/main/java/org/dinky/job/JobContext.java index 8beba3a53a..a66bf88176 100644 --- a/dinky-core/src/main/java/org/dinky/job/JobContext.java +++ b/dinky-core/src/main/java/org/dinky/job/JobContext.java @@ -1,10 +1,30 @@ +/* + * + * 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.job; -import lombok.Data; import org.dinky.data.enums.GatewayType; import org.dinky.executor.Executor; import org.dinky.executor.ExecutorConfig; +import lombok.Data; + @Data public class JobContext { private JobHandler handler; diff --git a/dinky-core/src/main/java/org/dinky/job/JobManager.java b/dinky-core/src/main/java/org/dinky/job/JobManager.java index 5ea950df94..1591d87851 100644 --- a/dinky-core/src/main/java/org/dinky/job/JobManager.java +++ b/dinky-core/src/main/java/org/dinky/job/JobManager.java @@ -19,22 +19,6 @@ package org.dinky.job; -import cn.hutool.core.text.StrFormatter; -import com.fasterxml.jackson.databind.node.ObjectNode; -import lombok.Getter; -import lombok.Setter; -import lombok.extern.slf4j.Slf4j; -import org.apache.flink.configuration.CoreOptions; -import org.apache.flink.configuration.DeploymentOptions; -import org.apache.flink.configuration.PipelineOptions; -import org.apache.flink.core.execution.JobClient; -import org.apache.flink.runtime.jobgraph.JobGraph; -import org.apache.flink.runtime.jobgraph.SavepointConfigOptions; -import org.apache.flink.runtime.jobgraph.jsonplan.JsonPlanGenerator; -import org.apache.flink.streaming.api.environment.ExecutionCheckpointingOptions; -import org.apache.flink.streaming.api.graph.StreamGraph; -import org.apache.flink.table.api.TableResult; -import org.apache.flink.yarn.configuration.YarnConfigOptions; import org.dinky.api.FlinkAPI; import org.dinky.assertion.Asserts; import org.dinky.classloader.DinkyClassLoader; @@ -80,6 +64,18 @@ import org.dinky.utils.SqlUtil; import org.dinky.utils.URLUtils; +import org.apache.flink.configuration.CoreOptions; +import org.apache.flink.configuration.DeploymentOptions; +import org.apache.flink.configuration.PipelineOptions; +import org.apache.flink.core.execution.JobClient; +import org.apache.flink.runtime.jobgraph.JobGraph; +import org.apache.flink.runtime.jobgraph.SavepointConfigOptions; +import org.apache.flink.runtime.jobgraph.jsonplan.JsonPlanGenerator; +import org.apache.flink.streaming.api.environment.ExecutionCheckpointingOptions; +import org.apache.flink.streaming.api.graph.StreamGraph; +import org.apache.flink.table.api.TableResult; +import org.apache.flink.yarn.configuration.YarnConfigOptions; + import java.io.File; import java.io.IOException; import java.time.LocalDateTime; @@ -88,6 +84,13 @@ import java.util.Map; import java.util.Set; +import com.fasterxml.jackson.databind.node.ObjectNode; + +import cn.hutool.core.text.StrFormatter; +import lombok.Getter; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; + @Slf4j @Setter @Getter @@ -105,8 +108,7 @@ public class JobManager { private Job job; - public JobManager() { - } + public JobManager() {} private JobManager(JobConfig config, boolean isPlanMode) { this.config = config; @@ -182,7 +184,8 @@ public JobResult executeJarSql(String statement) throws Exception { } } else { GatewayResult gatewayResult; - config.addGatewayConfig(executor.getCustomTableEnvironment().getConfig().getConfiguration()); + config.addGatewayConfig( + executor.getCustomTableEnvironment().getConfig().getConfiguration()); if (runMode.isApplicationMode()) { gatewayResult = Gateway.build(config.getGatewayConfig()).submitJar(executor.getUdfPathContextHolder()); @@ -318,8 +321,7 @@ public ObjectNode getStreamGraph(String statement) { } public String getJobPlanJson(String statement) { - Explainer explainer = Explainer.build(executor, useStatementSet, this) - .initialize(config, statement); + Explainer explainer = Explainer.build(executor, useStatementSet, this).initialize(config, statement); JobParam jobParam = explainer.pretreatStatements(SqlUtil.getStatements(statement)); return executor.getJobPlanJson(jobParam); } @@ -399,12 +401,12 @@ public String exportSql(String sql) { + YarnConfigOptions.PROVIDED_LIB_DIRS.key() + " = " + Collections.singletonList( - config.getGatewayConfig().getClusterConfig().getFlinkLibPath()) + config.getGatewayConfig().getClusterConfig().getFlinkLibPath()) + ";\r\n"); } if (Asserts.isNotNull(config.getGatewayConfig()) && Asserts.isNotNullString( - config.getGatewayConfig().getFlinkConfig().getJobName())) { + config.getGatewayConfig().getFlinkConfig().getJobName())) { sb.append("set " + YarnConfigOptions.APPLICATION_NAME.key() + " = " @@ -422,5 +424,4 @@ public String exportSql(String sql) { public DinkyClassLoader getDinkyClassLoader() { return executor.getDinkyClassLoader(); } - } diff --git a/dinky-core/src/main/java/org/dinky/job/builder/JobDDLBuilder.java b/dinky-core/src/main/java/org/dinky/job/builder/JobDDLBuilder.java index 37bae513fc..6cfccfe2f2 100644 --- a/dinky-core/src/main/java/org/dinky/job/builder/JobDDLBuilder.java +++ b/dinky-core/src/main/java/org/dinky/job/builder/JobDDLBuilder.java @@ -52,7 +52,7 @@ public void run() throws Exception { for (StatementParam item : jobParam.getDdl()) { try { executor.executeSql(item.getValue()); - }catch (Exception ex) { + } catch (Exception ex) { throw new ExecuteSqlException(item.getValue(), ex); } } diff --git a/dinky-core/src/main/java/org/dinky/job/builder/JobExecuteBuilder.java b/dinky-core/src/main/java/org/dinky/job/builder/JobExecuteBuilder.java index ca066b6856..3000614df3 100644 --- a/dinky-core/src/main/java/org/dinky/job/builder/JobExecuteBuilder.java +++ b/dinky-core/src/main/java/org/dinky/job/builder/JobExecuteBuilder.java @@ -24,7 +24,6 @@ import org.dinky.data.result.IResult; import org.dinky.data.result.InsertResult; import org.dinky.data.result.ResultBuilder; -import org.dinky.data.result.RunResult; import org.dinky.executor.Executor; import org.dinky.gateway.Gateway; import org.dinky.gateway.result.GatewayResult; @@ -42,7 +41,6 @@ import org.apache.flink.runtime.jobgraph.SavepointRestoreSettings; import org.apache.flink.streaming.api.graph.StreamGraph; -import java.util.ArrayList; import java.util.Collections; /** @@ -58,8 +56,14 @@ public class JobExecuteBuilder implements JobBuilder { private final GatewayType runMode; private final Job job; - public JobExecuteBuilder(JobParam jobParam, boolean useGateway, Executor executor, boolean useStatementSet, - JobConfig config, GatewayType runMode, Job job) { + public JobExecuteBuilder( + JobParam jobParam, + boolean useGateway, + Executor executor, + boolean useStatementSet, + JobConfig config, + GatewayType runMode, + Job job) { this.jobParam = jobParam; this.useGateway = useGateway; this.executor = executor; @@ -70,10 +74,14 @@ public JobExecuteBuilder(JobParam jobParam, boolean useGateway, Executor executo } public static JobExecuteBuilder build(JobManager jobManager) { - return new JobExecuteBuilder(jobManager.getJobParam(), + return new JobExecuteBuilder( + jobManager.getJobParam(), jobManager.isUseGateway(), - jobManager.getExecutor(), jobManager.isUseStatementSet(), jobManager.getConfig(), - jobManager.getRunMode(), jobManager.getJob()); + jobManager.getExecutor(), + jobManager.isUseStatementSet(), + jobManager.getConfig(), + jobManager.getRunMode(), + jobManager.getJob()); } @Override diff --git a/dinky-core/src/main/java/org/dinky/job/builder/JobJarStreamGraphBuilder.java b/dinky-core/src/main/java/org/dinky/job/builder/JobJarStreamGraphBuilder.java index cfb5c837cf..92938d47c2 100644 --- a/dinky-core/src/main/java/org/dinky/job/builder/JobJarStreamGraphBuilder.java +++ b/dinky-core/src/main/java/org/dinky/job/builder/JobJarStreamGraphBuilder.java @@ -19,9 +19,6 @@ package org.dinky.job.builder; -import org.apache.flink.configuration.Configuration; -import org.apache.flink.runtime.jobgraph.SavepointConfigOptions; -import org.apache.flink.runtime.jobgraph.SavepointRestoreSettings; import org.dinky.assertion.Asserts; import org.dinky.classloader.DinkyClassLoader; import org.dinky.data.exception.DinkyException; @@ -40,6 +37,9 @@ import org.dinky.utils.DinkyClassLoaderUtil; import org.dinky.utils.SqlUtil; +import org.apache.flink.configuration.Configuration; +import org.apache.flink.runtime.jobgraph.SavepointConfigOptions; +import org.apache.flink.runtime.jobgraph.SavepointRestoreSettings; import org.apache.flink.streaming.api.graph.StreamGraph; import java.io.File; @@ -68,8 +68,7 @@ public static JobJarStreamGraphBuilder build(JobManager jobManager) { } @Override - public void run() throws Exception { - } + public void run() throws Exception {} public StreamGraph getJarStreamGraph(String statement, DinkyClassLoader dinkyClassLoader) { DinkyClassLoaderUtil.initClassLoader(config, dinkyClassLoader); @@ -106,7 +105,6 @@ public StreamGraph getJarStreamGraph(String statement, DinkyClassLoader dinkyCla configuration.get(SavepointConfigOptions.SAVEPOINT_IGNORE_UNCLAIMED_STATE))); } return streamGraph; - } public List getUris(String statement) { diff --git a/dinky-core/src/main/java/org/dinky/job/builder/JobTransBuilder.java b/dinky-core/src/main/java/org/dinky/job/builder/JobTransBuilder.java index 6c6c03f99a..c25e095fae 100644 --- a/dinky-core/src/main/java/org/dinky/job/builder/JobTransBuilder.java +++ b/dinky-core/src/main/java/org/dinky/job/builder/JobTransBuilder.java @@ -25,7 +25,6 @@ import org.dinky.data.result.IResult; import org.dinky.data.result.InsertResult; import org.dinky.data.result.ResultBuilder; -import org.dinky.data.result.RunResult; import org.dinky.executor.Executor; import org.dinky.gateway.Gateway; import org.dinky.gateway.result.GatewayResult; @@ -63,8 +62,14 @@ public class JobTransBuilder implements JobBuilder { private final GatewayType runMode; private final Job job; - public JobTransBuilder(JobParam jobParam, boolean useStatementSet, boolean useGateway, JobConfig config, - Executor executor, GatewayType runMode, Job job) { + public JobTransBuilder( + JobParam jobParam, + boolean useStatementSet, + boolean useGateway, + JobConfig config, + Executor executor, + GatewayType runMode, + Job job) { this.jobParam = jobParam; this.useStatementSet = useStatementSet; this.useGateway = useGateway; @@ -74,7 +79,6 @@ public JobTransBuilder(JobParam jobParam, boolean useStatementSet, boolean useGa this.job = job; } - public static JobTransBuilder build(JobManager jobManager) { return new JobTransBuilder( @@ -84,8 +88,7 @@ public static JobTransBuilder build(JobManager jobManager) { jobManager.getConfig(), jobManager.getExecutor(), jobManager.getRunMode(), - jobManager.getJob() - ); + jobManager.getJob()); } @Override diff --git a/dinky-core/src/main/java/org/dinky/job/builder/JobUDFBuilder.java b/dinky-core/src/main/java/org/dinky/job/builder/JobUDFBuilder.java index 376c775be3..8866c1e8f8 100644 --- a/dinky-core/src/main/java/org/dinky/job/builder/JobUDFBuilder.java +++ b/dinky-core/src/main/java/org/dinky/job/builder/JobUDFBuilder.java @@ -23,7 +23,6 @@ import static org.dinky.function.util.UDFUtil.SESSION; import static org.dinky.function.util.UDFUtil.YARN; -import io.debezium.config.CommonConnectorConfig; import org.dinky.assertion.Asserts; import org.dinky.data.enums.GatewayType; import org.dinky.data.model.SystemConfiguration; @@ -67,8 +66,8 @@ public JobUDFBuilder(JobParam jobParam, Executor executor, JobConfig config, Gat } public static JobUDFBuilder build(JobManager jobManager) { - return new JobUDFBuilder(jobManager.getJobParam(), jobManager.getExecutor(), jobManager.getConfig(), - jobManager.getRunMode()); + return new JobUDFBuilder( + jobManager.getJobParam(), jobManager.getExecutor(), jobManager.getConfig(), jobManager.getRunMode()); } @Override @@ -117,9 +116,7 @@ public void run() throws Exception { // 3.Write the required files for UDF UDFUtil.writeManifest(taskId, jarList, executor.getUdfPathContextHolder()); UDFUtil.addConfigurationClsAndJars( - executor.getCustomTableEnvironment(), - jarList, - CollUtil.newArrayList(URLUtils.getURLs(jarFiles))); + executor.getCustomTableEnvironment(), jarList, CollUtil.newArrayList(URLUtils.getURLs(jarFiles))); } catch (Exception e) { throw new RuntimeException("add configuration failed: ", e); } From b84fb0ad44582643fcec4777c9433e4702028095 Mon Sep 17 00:00:00 2001 From: licho Date: Sun, 17 Mar 2024 23:39:21 +0800 Subject: [PATCH 20/87] feat: creae JobManagerHandler --- .../impl/ClusterConfigurationServiceImpl.java | 7 +- .../impl/ClusterInstanceServiceImpl.java | 15 +- .../dinky/service/impl/TaskServiceImpl.java | 2 +- .../java/org/dinky/explainer/Explainer.java | 8 +- .../main/java/org/dinky/job/JobManager.java | 363 +------------ .../java/org/dinky/job/JobManagerHandler.java | 475 ++++++++++++++++++ .../org/dinky/job/builder/JobDDLBuilder.java | 4 +- .../dinky/job/builder/JobExecuteBuilder.java | 26 +- .../job/builder/JobJarStreamGraphBuilder.java | 7 +- .../dinky/job/builder/JobTransBuilder.java | 19 +- .../org/dinky/job/builder/JobUDFBuilder.java | 13 +- ...ceImpl.java => JobManagerServiceImpl.java} | 7 +- 12 files changed, 554 insertions(+), 392 deletions(-) create mode 100644 dinky-core/src/main/java/org/dinky/job/JobManagerHandler.java rename dinky-executor-server/src/main/java/org/dinky/{ServerExecutorServiceImpl.java => JobManagerServiceImpl.java} (80%) diff --git a/dinky-admin/src/main/java/org/dinky/service/impl/ClusterConfigurationServiceImpl.java b/dinky-admin/src/main/java/org/dinky/service/impl/ClusterConfigurationServiceImpl.java index c814bb44f7..66dbfe7054 100644 --- a/dinky-admin/src/main/java/org/dinky/service/impl/ClusterConfigurationServiceImpl.java +++ b/dinky-admin/src/main/java/org/dinky/service/impl/ClusterConfigurationServiceImpl.java @@ -26,6 +26,7 @@ import org.dinky.data.exception.BusException; import org.dinky.data.model.ClusterConfiguration; import org.dinky.data.model.Task; +import org.dinky.gateway.Gateway; import org.dinky.gateway.config.GatewayConfig; import org.dinky.gateway.model.FlinkClusterConfig; import org.dinky.gateway.result.TestResult; @@ -82,7 +83,11 @@ public FlinkClusterConfig getFlinkClusterCfg(Integer id) { @Override public TestResult testGateway(ClusterConfigurationDTO config) { config.getConfig().setType(GatewayType.get(config.getType())); - return JobManager.testGateway(GatewayConfig.build(config.getConfig())); + return testGateway(GatewayConfig.build(config.getConfig())); + } + + public static TestResult testGateway(GatewayConfig gatewayConfig) { + return Gateway.build(gatewayConfig).test(); } /** diff --git a/dinky-admin/src/main/java/org/dinky/service/impl/ClusterInstanceServiceImpl.java b/dinky-admin/src/main/java/org/dinky/service/impl/ClusterInstanceServiceImpl.java index a23df8d312..0c45866329 100644 --- a/dinky-admin/src/main/java/org/dinky/service/impl/ClusterInstanceServiceImpl.java +++ b/dinky-admin/src/main/java/org/dinky/service/impl/ClusterInstanceServiceImpl.java @@ -31,6 +31,8 @@ import org.dinky.data.model.ClusterConfiguration; import org.dinky.data.model.ClusterInstance; import org.dinky.data.model.Task; +import org.dinky.function.util.UDFUtil; +import org.dinky.gateway.Gateway; import org.dinky.gateway.config.GatewayConfig; import org.dinky.gateway.exception.GatewayException; import org.dinky.gateway.model.FlinkClusterConfig; @@ -221,10 +223,15 @@ public void killCluster(Integer id) { FlinkClusterConfig flinkClusterConfig = clusterConfigurationService.getFlinkClusterCfg(clusterConfigurationId); GatewayConfig gatewayConfig = GatewayConfig.build(flinkClusterConfig); - JobManager.killCluster(gatewayConfig, clusterInstance.getName()); + killCluster(gatewayConfig, clusterInstance.getName()); } } + public static void killCluster(GatewayConfig gatewayConfig, String appId) { + gatewayConfig.getClusterConfig().setAppId(appId); + Gateway.build(gatewayConfig).killCluster(); + } + @Override public ClusterInstance deploySessionCluster(Integer id) { ClusterConfiguration clusterCfg = clusterConfigurationService.getClusterConfigById(id); @@ -234,7 +241,7 @@ public ClusterInstance deploySessionCluster(Integer id) { GatewayConfig gatewayConfig = GatewayConfig.build(FlinkClusterConfig.create(clusterCfg.getType(), clusterCfg.getConfigJson())); gatewayConfig.setType(gatewayConfig.getType().getSessionType()); - GatewayResult gatewayResult = JobManager.deploySessionCluster(gatewayConfig); + GatewayResult gatewayResult = deploySessionCluster(gatewayConfig); if (gatewayResult.isSuccess()) { Asserts.checkNullString(gatewayResult.getWebURL(), "Unable to obtain Web URL."); return registersCluster(ClusterInstanceDTO.builder() @@ -250,6 +257,10 @@ public ClusterInstance deploySessionCluster(Integer id) { throw new DinkyException("Deploy session cluster error: " + gatewayResult.getError()); } + public static GatewayResult deploySessionCluster(GatewayConfig gatewayConfig) { + return Gateway.build(gatewayConfig).deployCluster(UDFUtil.createFlinkUdfPathContextHolder()); + } + /** * @param searchKeyWord * @return diff --git a/dinky-admin/src/main/java/org/dinky/service/impl/TaskServiceImpl.java b/dinky-admin/src/main/java/org/dinky/service/impl/TaskServiceImpl.java index ff59b4f46b..407c25c8dc 100644 --- a/dinky-admin/src/main/java/org/dinky/service/impl/TaskServiceImpl.java +++ b/dinky-admin/src/main/java/org/dinky/service/impl/TaskServiceImpl.java @@ -483,7 +483,7 @@ public ObjectNode getJobPlan(TaskDTO task) { @Override public ObjectNode getStreamGraph(TaskDTO taskDTO) { JobConfig config = taskDTO.getJobConfig(); - JobManager jobManager = JobManager.buildPlanMode(config, true); + JobManager jobManager = JobManager.build(config, true); ObjectNode streamGraph = jobManager.getStreamGraph(taskDTO.getStatement()); RunTimeUtil.recovery(jobManager); return streamGraph; diff --git a/dinky-core/src/main/java/org/dinky/explainer/Explainer.java b/dinky-core/src/main/java/org/dinky/explainer/Explainer.java index 89480019d2..d5e194178b 100644 --- a/dinky-core/src/main/java/org/dinky/explainer/Explainer.java +++ b/dinky-core/src/main/java/org/dinky/explainer/Explainer.java @@ -29,7 +29,7 @@ import org.dinky.function.util.UDFUtil; import org.dinky.interceptor.FlinkInterceptor; import org.dinky.job.JobConfig; -import org.dinky.job.JobManager; +import org.dinky.job.JobManagerHandler; import org.dinky.job.JobParam; import org.dinky.job.StatementParam; import org.dinky.job.builder.JobUDFBuilder; @@ -71,15 +71,15 @@ public class Explainer { private Executor executor; private boolean useStatementSet; private ObjectMapper mapper = new ObjectMapper(); - private JobManager jobManager; + private JobManagerHandler jobManager; - public Explainer(Executor executor, boolean useStatementSet, JobManager jobManager) { + public Explainer(Executor executor, boolean useStatementSet, JobManagerHandler jobManager) { this.executor = executor; this.useStatementSet = useStatementSet; this.jobManager = jobManager; } - public static Explainer build(Executor executor, boolean useStatementSet, JobManager jobManager) { + public static Explainer build(Executor executor, boolean useStatementSet, JobManagerHandler jobManager) { return new Explainer(executor, useStatementSet, jobManager); } diff --git a/dinky-core/src/main/java/org/dinky/job/JobManager.java b/dinky-core/src/main/java/org/dinky/job/JobManager.java index 1591d87851..1b3f3af54a 100644 --- a/dinky-core/src/main/java/org/dinky/job/JobManager.java +++ b/dinky-core/src/main/java/org/dinky/job/JobManager.java @@ -19,289 +19,60 @@ package org.dinky.job; -import org.dinky.api.FlinkAPI; -import org.dinky.assertion.Asserts; -import org.dinky.classloader.DinkyClassLoader; -import org.dinky.context.CustomTableEnvironmentContext; -import org.dinky.context.RowLevelPermissionsContext; +import com.fasterxml.jackson.databind.node.ObjectNode; +import lombok.Getter; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; import org.dinky.data.annotations.ProcessStep; -import org.dinky.data.enums.GatewayType; import org.dinky.data.enums.ProcessStepType; -import org.dinky.data.exception.BusException; -import org.dinky.data.model.SystemConfiguration; -import org.dinky.data.result.ErrorResult; import org.dinky.data.result.ExplainResult; import org.dinky.data.result.IResult; -import org.dinky.data.result.InsertResult; -import org.dinky.data.result.ResultBuilder; import org.dinky.data.result.ResultPool; import org.dinky.data.result.SelectResult; -import org.dinky.executor.Executor; -import org.dinky.executor.ExecutorConfig; -import org.dinky.executor.ExecutorFactory; -import org.dinky.explainer.Explainer; -import org.dinky.function.util.UDFUtil; -import org.dinky.gateway.Gateway; -import org.dinky.gateway.config.FlinkConfig; -import org.dinky.gateway.config.GatewayConfig; -import org.dinky.gateway.enums.ActionType; import org.dinky.gateway.enums.SavePointType; -import org.dinky.gateway.result.GatewayResult; import org.dinky.gateway.result.SavePointResult; -import org.dinky.gateway.result.TestResult; -import org.dinky.job.builder.JobDDLBuilder; -import org.dinky.job.builder.JobExecuteBuilder; -import org.dinky.job.builder.JobJarStreamGraphBuilder; -import org.dinky.job.builder.JobTransBuilder; -import org.dinky.job.builder.JobUDFBuilder; -import org.dinky.parser.SqlType; -import org.dinky.trans.Operations; -import org.dinky.trans.parse.AddFileSqlParseStrategy; -import org.dinky.trans.parse.AddJarSqlParseStrategy; -import org.dinky.utils.DinkyClassLoaderUtil; -import org.dinky.utils.JsonUtils; -import org.dinky.utils.LogUtil; -import org.dinky.utils.SqlUtil; -import org.dinky.utils.URLUtils; - -import org.apache.flink.configuration.CoreOptions; -import org.apache.flink.configuration.DeploymentOptions; -import org.apache.flink.configuration.PipelineOptions; -import org.apache.flink.core.execution.JobClient; -import org.apache.flink.runtime.jobgraph.JobGraph; -import org.apache.flink.runtime.jobgraph.SavepointConfigOptions; -import org.apache.flink.runtime.jobgraph.jsonplan.JsonPlanGenerator; -import org.apache.flink.streaming.api.environment.ExecutionCheckpointingOptions; -import org.apache.flink.streaming.api.graph.StreamGraph; -import org.apache.flink.table.api.TableResult; -import org.apache.flink.yarn.configuration.YarnConfigOptions; - -import java.io.File; -import java.io.IOException; -import java.time.LocalDateTime; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import com.fasterxml.jackson.databind.node.ObjectNode; - -import cn.hutool.core.text.StrFormatter; -import lombok.Getter; -import lombok.Setter; -import lombok.extern.slf4j.Slf4j; @Slf4j @Setter @Getter public class JobManager { - private JobHandler handler; - private ExecutorConfig executorConfig; - private JobConfig config; - private Executor executor; - private boolean useGateway = false; - private boolean useStatementSet = false; - private boolean useRestAPI = false; - private GatewayType runMode = GatewayType.LOCAL; - - private JobParam jobParam = null; - - private Job job; + JobManagerHandler jobManagerHandler; - public JobManager() {} + public JobManager() { + } private JobManager(JobConfig config, boolean isPlanMode) { - this.config = config; - - if (!isPlanMode) { - runMode = GatewayType.get(config.getType()); - useGateway = GatewayType.isDeployCluster(config.getType()); - handler = JobHandler.build(); - } - - useStatementSet = config.isStatementSet(); - useRestAPI = SystemConfiguration.getInstances().isUseRestAPI(); - executorConfig = config.createExecutorSetting(); - executorConfig.setPlan(isPlanMode); - executor = ExecutorFactory.buildExecutor(executorConfig); + jobManagerHandler = JobManagerHandler.build(config, isPlanMode); } public static JobManager build(JobConfig config) { - return buildPlanMode(config, false); + return build(config, false); } - public static JobManager buildPlanMode(JobConfig config, boolean isPlanMode) { + public static JobManager build(JobConfig config, boolean isPlanMode) { return new JobManager(config, isPlanMode); } - private void prepare(String statement) { - job = Job.build(runMode, config, executorConfig, executor, statement, useGateway); - handler.init(job); - } - - private boolean success() { - return handler.success(); - } - - private boolean failed() { - return handler.failed(); - } - public boolean close() { - CustomTableEnvironmentContext.clear(); - RowLevelPermissionsContext.clear(); - try { - executor.getDinkyClassLoader().close(); - } catch (IOException e) { - throw new RuntimeException(e); - } - return true; + return jobManagerHandler.close(); } public ObjectNode getJarStreamGraphJson(String statement) { - StreamGraph streamGraph = - JobJarStreamGraphBuilder.build(this).getJarStreamGraph(statement, getDinkyClassLoader()); - return JsonUtils.parseObject(JsonPlanGenerator.generatePlan(streamGraph.getJobGraph())); + return jobManagerHandler.getJarStreamGraphJson(statement); } @ProcessStep(type = ProcessStepType.SUBMIT_EXECUTE) public JobResult executeJarSql(String statement) throws Exception { - prepare(statement); - JobJarStreamGraphBuilder jobJarStreamGraphBuilder = JobJarStreamGraphBuilder.build(this); - StreamGraph streamGraph = jobJarStreamGraphBuilder.getJarStreamGraph(statement, getDinkyClassLoader()); - - try { - if (!useGateway) { - JobClient jobClient = executor.getStreamExecutionEnvironment().executeAsync(streamGraph); - if (Asserts.isNotNull(jobClient)) { - job.setJobId(jobClient.getJobID().toHexString()); - job.setJids(Collections.singletonList(job.getJobId())); - job.setStatus(Job.JobStatus.SUCCESS); - success(); - } else { - job.setStatus(Job.JobStatus.FAILED); - failed(); - } - } else { - GatewayResult gatewayResult; - config.addGatewayConfig( - executor.getCustomTableEnvironment().getConfig().getConfiguration()); - if (runMode.isApplicationMode()) { - gatewayResult = - Gateway.build(config.getGatewayConfig()).submitJar(executor.getUdfPathContextHolder()); - } else { - streamGraph.setJobName(config.getJobName()); - JobGraph jobGraph = streamGraph.getJobGraph(); - GatewayConfig gatewayConfig = config.getGatewayConfig(); - List uriList = jobJarStreamGraphBuilder.getUris(statement); - String[] jarPaths = uriList.stream() - .map(URLUtils::toFile) - .map(File::getAbsolutePath) - .toArray(String[]::new); - gatewayConfig.setJarPaths(jarPaths); - gatewayResult = Gateway.build(gatewayConfig).submitJobGraph(jobGraph); - } - job.setResult(InsertResult.success(gatewayResult.getId())); - job.setJobId(gatewayResult.getId()); - job.setJids(gatewayResult.getJids()); - job.setJobManagerAddress(URLUtils.formatAddress(gatewayResult.getWebURL())); - - if (gatewayResult.isSuccess()) { - job.setStatus(Job.JobStatus.SUCCESS); - success(); - } else { - job.setStatus(Job.JobStatus.FAILED); - job.setError(gatewayResult.getError()); - log.error(gatewayResult.getError()); - failed(); - } - } - } catch (Exception e) { - String error = - LogUtil.getError("Exception in executing FlinkJarSQL:\n" + SqlUtil.addLineNumber(statement), e); - job.setEndTime(LocalDateTime.now()); - job.setStatus(Job.JobStatus.FAILED); - job.setError(error); - failed(); - throw new Exception(error, e); - } finally { - close(); - } - return job.getJobResult(); + return jobManagerHandler.executeJarSql(statement); } @ProcessStep(type = ProcessStepType.SUBMIT_EXECUTE) public JobResult executeSql(String statement) throws Exception { - prepare(statement); - - DinkyClassLoaderUtil.initClassLoader(config, getDinkyClassLoader()); - jobParam = - Explainer.build(executor, useStatementSet, this).pretreatStatements(SqlUtil.getStatements(statement)); - try { - // step 1: init udf - JobUDFBuilder.build(this).run(); - // step 2: execute ddl - JobDDLBuilder.build(this).run(); - // step 3: execute insert/select/show/desc/CTAS... - JobTransBuilder.build(this).run(); - // step 4: execute custom data stream task - JobExecuteBuilder.build(this).run(); - // finished - job.setEndTime(LocalDateTime.now()); - if (job.isFailed()) { - failed(); - } else { - job.setStatus(Job.JobStatus.SUCCESS); - success(); - } - } catch (ExecuteSqlException e) { - String error = StrFormatter.format( - "Exception in executing FlinkSQL:\n{}\n{}", SqlUtil.addLineNumber(e.getMessage()), e.getMessage()); - job.setEndTime(LocalDateTime.now()); - job.setStatus(Job.JobStatus.FAILED); - job.setError(error); - failed(); - throw new Exception(error, e); - } finally { - close(); - } - return job.getJobResult(); + return jobManagerHandler.executeSql(statement); } public IResult executeDDL(String statement) { - String[] statements = SqlUtil.getStatements(statement); - try { - IResult result = null; - for (String item : statements) { - String newStatement = executor.pretreatStatement(item); - if (newStatement.trim().isEmpty()) { - continue; - } - SqlType operationType = Operations.getOperationType(newStatement); - if (SqlType.INSERT == operationType || SqlType.SELECT == operationType) { - continue; - } - - if (operationType.equals(SqlType.ADD) || operationType.equals(SqlType.ADD_JAR)) { - Set allFilePath = AddJarSqlParseStrategy.getAllFilePath(item); - executor.getDinkyClassLoader().addURLs(allFilePath); - } else if (operationType.equals(SqlType.ADD_FILE)) { - Set allFilePath = AddFileSqlParseStrategy.getAllFilePath(item); - executor.getDinkyClassLoader().addURLs(allFilePath); - } - - LocalDateTime startTime = LocalDateTime.now(); - TableResult tableResult = executor.executeSql(newStatement); - result = ResultBuilder.build( - operationType, null, config.getMaxRowNum(), false, false, executor.getTimeZone()) - .getResult(tableResult); - result.setStartTime(startTime); - } - return result; - } catch (Exception e) { - log.error("executeDDL failed:", e); - } - return new ErrorResult(); + return jobManagerHandler.executeDDL(statement); } public static SelectResult getJobData(String jobId) { @@ -309,119 +80,27 @@ public static SelectResult getJobData(String jobId) { } public ExplainResult explainSql(String statement) { - return Explainer.build(executor, useStatementSet, this) - .initialize(config, statement) - .explainSql(statement); + return jobManagerHandler.explainSql(statement); } public ObjectNode getStreamGraph(String statement) { - return Explainer.build(executor, useStatementSet, this) - .initialize(config, statement) - .getStreamGraph(statement); + return jobManagerHandler.getStreamGraph(statement); } public String getJobPlanJson(String statement) { - Explainer explainer = Explainer.build(executor, useStatementSet, this).initialize(config, statement); - JobParam jobParam = explainer.pretreatStatements(SqlUtil.getStatements(statement)); - return executor.getJobPlanJson(jobParam); + return jobManagerHandler.getJobPlanJson(statement); } public boolean cancelNormal(String jobId) { - try { - return FlinkAPI.build(config.getAddress()).stop(jobId); - } catch (Exception e) { - log.error("stop flink job failed:", e); - throw new BusException(e.getMessage()); - } + return jobManagerHandler.cancelNormal(jobId); } public SavePointResult savepoint(String jobId, SavePointType savePointType, String savePoint) { - if (useGateway && !useRestAPI) { - config.getGatewayConfig() - .setFlinkConfig( - FlinkConfig.build(jobId, ActionType.SAVEPOINT.getValue(), savePointType.getValue(), null)); - return Gateway.build(config.getGatewayConfig()).savepointJob(savePoint); - } else { - return FlinkAPI.build(config.getAddress()).savepoints(jobId, savePointType, config.getConfigJson()); - } - } - - public static void killCluster(GatewayConfig gatewayConfig, String appId) { - gatewayConfig.getClusterConfig().setAppId(appId); - Gateway.build(gatewayConfig).killCluster(); - } - - public static GatewayResult deploySessionCluster(GatewayConfig gatewayConfig) { - return Gateway.build(gatewayConfig).deployCluster(UDFUtil.createFlinkUdfPathContextHolder()); - } - - public static TestResult testGateway(GatewayConfig gatewayConfig) { - return Gateway.build(gatewayConfig).test(); + return jobManagerHandler.savepoint(jobId, savePointType, savePoint); } public String exportSql(String sql) { - String statement = executor.pretreatStatement(sql); - StringBuilder sb = new StringBuilder(); - if (Asserts.isNotNullString(config.getJobName())) { - sb.append("set " + PipelineOptions.NAME.key() + " = " + config.getJobName() + ";\r\n"); - } - if (Asserts.isNotNull(config.getParallelism())) { - sb.append("set " + CoreOptions.DEFAULT_PARALLELISM.key() + " = " + config.getParallelism() + ";\r\n"); - } - if (Asserts.isNotNull(config.getCheckpoint())) { - sb.append("set " - + ExecutionCheckpointingOptions.CHECKPOINTING_INTERVAL.key() - + " = " - + config.getCheckpoint() - + ";\r\n"); - } - if (Asserts.isNotNullString(config.getSavePointPath())) { - sb.append("set " + SavepointConfigOptions.SAVEPOINT_PATH + " = " + config.getSavePointPath() + ";\r\n"); - } - if (Asserts.isNotNull(config.getGatewayConfig()) - && Asserts.isNotNull(config.getGatewayConfig().getFlinkConfig().getConfiguration())) { - for (Map.Entry entry : config.getGatewayConfig() - .getFlinkConfig() - .getConfiguration() - .entrySet()) { - sb.append("set " + entry.getKey() + " = " + entry.getValue() + ";\r\n"); - } - } - - switch (GatewayType.get(config.getType())) { - case YARN_PER_JOB: - case YARN_APPLICATION: - sb.append("set " - + DeploymentOptions.TARGET.key() - + " = " - + GatewayType.get(config.getType()).getLongValue() - + ";\r\n"); - if (Asserts.isNotNull(config.getGatewayConfig())) { - sb.append("set " - + YarnConfigOptions.PROVIDED_LIB_DIRS.key() - + " = " - + Collections.singletonList( - config.getGatewayConfig().getClusterConfig().getFlinkLibPath()) - + ";\r\n"); - } - if (Asserts.isNotNull(config.getGatewayConfig()) - && Asserts.isNotNullString( - config.getGatewayConfig().getFlinkConfig().getJobName())) { - sb.append("set " - + YarnConfigOptions.APPLICATION_NAME.key() - + " = " - + config.getGatewayConfig().getFlinkConfig().getJobName() - + ";\r\n"); - } - break; - default: - } - sb.append(statement); - return sb.toString(); + return jobManagerHandler.exportSql(sql); } - // return dinkyclassloader - public DinkyClassLoader getDinkyClassLoader() { - return executor.getDinkyClassLoader(); - } } diff --git a/dinky-core/src/main/java/org/dinky/job/JobManagerHandler.java b/dinky-core/src/main/java/org/dinky/job/JobManagerHandler.java new file mode 100644 index 0000000000..aed846726f --- /dev/null +++ b/dinky-core/src/main/java/org/dinky/job/JobManagerHandler.java @@ -0,0 +1,475 @@ +package org.dinky.job; + +import cn.hutool.core.text.StrFormatter; +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.apache.flink.configuration.CoreOptions; +import org.apache.flink.configuration.DeploymentOptions; +import org.apache.flink.configuration.PipelineOptions; +import org.apache.flink.core.execution.JobClient; +import org.apache.flink.runtime.jobgraph.JobGraph; +import org.apache.flink.runtime.jobgraph.SavepointConfigOptions; +import org.apache.flink.runtime.jobgraph.jsonplan.JsonPlanGenerator; +import org.apache.flink.streaming.api.environment.ExecutionCheckpointingOptions; +import org.apache.flink.streaming.api.graph.StreamGraph; +import org.apache.flink.table.api.TableResult; +import org.apache.flink.yarn.configuration.YarnConfigOptions; +import org.dinky.api.FlinkAPI; +import org.dinky.assertion.Asserts; +import org.dinky.context.CustomTableEnvironmentContext; +import org.dinky.context.RowLevelPermissionsContext; +import org.dinky.data.annotations.ProcessStep; +import org.dinky.data.enums.GatewayType; +import org.dinky.data.enums.ProcessStepType; +import org.dinky.data.exception.BusException; +import org.dinky.data.model.SystemConfiguration; +import org.dinky.data.result.ErrorResult; +import org.dinky.data.result.ExplainResult; +import org.dinky.data.result.IResult; +import org.dinky.data.result.InsertResult; +import org.dinky.data.result.ResultBuilder; +import org.dinky.data.result.ResultPool; +import org.dinky.data.result.SelectResult; +import org.dinky.executor.Executor; +import org.dinky.executor.ExecutorConfig; +import org.dinky.executor.ExecutorFactory; +import org.dinky.explainer.Explainer; +import org.dinky.function.util.UDFUtil; +import org.dinky.gateway.Gateway; +import org.dinky.gateway.config.FlinkConfig; +import org.dinky.gateway.config.GatewayConfig; +import org.dinky.gateway.enums.ActionType; +import org.dinky.gateway.enums.SavePointType; +import org.dinky.gateway.result.GatewayResult; +import org.dinky.gateway.result.SavePointResult; +import org.dinky.gateway.result.TestResult; +import org.dinky.job.builder.JobDDLBuilder; +import org.dinky.job.builder.JobExecuteBuilder; +import org.dinky.job.builder.JobJarStreamGraphBuilder; +import org.dinky.job.builder.JobTransBuilder; +import org.dinky.job.builder.JobUDFBuilder; +import org.dinky.parser.SqlType; +import org.dinky.trans.Operations; +import org.dinky.trans.parse.AddFileSqlParseStrategy; +import org.dinky.trans.parse.AddJarSqlParseStrategy; +import org.dinky.utils.DinkyClassLoaderUtil; +import org.dinky.utils.JsonUtils; +import org.dinky.utils.LogUtil; +import org.dinky.utils.SqlUtil; +import org.dinky.utils.URLUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.IOException; +import java.time.LocalDateTime; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; + +public class JobManagerHandler { + Logger log = LoggerFactory.getLogger(JobManagerHandler.class); + private JobHandler handler; + private ExecutorConfig executorConfig; + private JobConfig config; + + private Executor executor; + private boolean useGateway = false; + private boolean useStatementSet = false; + private boolean useRestAPI = false; + private GatewayType runMode = GatewayType.LOCAL; + + private JobParam jobParam = null; + + private Job job; + + public JobManagerHandler() { + } + + private JobManagerHandler(JobConfig config, boolean isPlanMode) { + this.config = config; + + if (!isPlanMode) { + runMode = GatewayType.get(config.getType()); + useGateway = GatewayType.isDeployCluster(config.getType()); + handler = JobHandler.build(); + } + + useStatementSet = config.isStatementSet(); + useRestAPI = SystemConfiguration.getInstances().isUseRestAPI(); + executorConfig = config.createExecutorSetting(); + executorConfig.setPlan(isPlanMode); + executor = ExecutorFactory.buildExecutor(executorConfig); + } + + public static JobManagerHandler build(JobConfig config) { + return build(config, false); + } + + public static JobManagerHandler build(JobConfig config, boolean isPlanMode) { + return new JobManagerHandler(config, isPlanMode); + } + + private void prepare(String statement) { + job = Job.build(runMode, config, executorConfig, executor, statement, useGateway); + handler.init(job); + } + + private boolean success() { + return handler.success(); + } + + private boolean failed() { + return handler.failed(); + } + + public boolean close() { + CustomTableEnvironmentContext.clear(); + RowLevelPermissionsContext.clear(); + try { + executor.getDinkyClassLoader().close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + return true; + } + + public ObjectNode getJarStreamGraphJson(String statement) { + StreamGraph streamGraph = + JobJarStreamGraphBuilder.build(this).getJarStreamGraph(statement); + return JsonUtils.parseObject(JsonPlanGenerator.generatePlan(streamGraph.getJobGraph())); + } + + @ProcessStep(type = ProcessStepType.SUBMIT_EXECUTE) + public JobResult executeJarSql(String statement) throws Exception { + prepare(statement); + JobJarStreamGraphBuilder jobJarStreamGraphBuilder = JobJarStreamGraphBuilder.build(this); + StreamGraph streamGraph = jobJarStreamGraphBuilder.getJarStreamGraph(statement); + + try { + if (!useGateway) { + JobClient jobClient = executor.getStreamExecutionEnvironment().executeAsync(streamGraph); + if (Asserts.isNotNull(jobClient)) { + job.setJobId(jobClient.getJobID().toHexString()); + job.setJids(Collections.singletonList(job.getJobId())); + job.setStatus(Job.JobStatus.SUCCESS); + success(); + } else { + job.setStatus(Job.JobStatus.FAILED); + failed(); + } + } else { + GatewayResult gatewayResult; + config.addGatewayConfig(executor.getCustomTableEnvironment().getConfig().getConfiguration()); + if (runMode.isApplicationMode()) { + gatewayResult = + Gateway.build(config.getGatewayConfig()).submitJar(executor.getUdfPathContextHolder()); + } else { + streamGraph.setJobName(config.getJobName()); + JobGraph jobGraph = streamGraph.getJobGraph(); + GatewayConfig gatewayConfig = config.getGatewayConfig(); + List uriList = jobJarStreamGraphBuilder.getUris(statement); + String[] jarPaths = uriList.stream() + .map(URLUtils::toFile) + .map(File::getAbsolutePath) + .toArray(String[]::new); + gatewayConfig.setJarPaths(jarPaths); + gatewayResult = Gateway.build(gatewayConfig).submitJobGraph(jobGraph); + } + job.setResult(InsertResult.success(gatewayResult.getId())); + job.setJobId(gatewayResult.getId()); + job.setJids(gatewayResult.getJids()); + job.setJobManagerAddress(URLUtils.formatAddress(gatewayResult.getWebURL())); + + if (gatewayResult.isSuccess()) { + job.setStatus(Job.JobStatus.SUCCESS); + success(); + } else { + job.setStatus(Job.JobStatus.FAILED); + job.setError(gatewayResult.getError()); + log.error(gatewayResult.getError()); + failed(); + } + } + } catch (Exception e) { + String error = + LogUtil.getError("Exception in executing FlinkJarSQL:\n" + SqlUtil.addLineNumber(statement), e); + job.setEndTime(LocalDateTime.now()); + job.setStatus(Job.JobStatus.FAILED); + job.setError(error); + failed(); + throw new Exception(error, e); + } finally { + close(); + } + return job.getJobResult(); + } + + @ProcessStep(type = ProcessStepType.SUBMIT_EXECUTE) + public JobResult executeSql(String statement) throws Exception { + prepare(statement); + + DinkyClassLoaderUtil.initClassLoader(config, executor.getDinkyClassLoader()); + jobParam = + Explainer.build(executor, useStatementSet, this).pretreatStatements(SqlUtil.getStatements(statement)); + try { + // step 1: init udf + JobUDFBuilder.build(this).run(); + // step 2: execute ddl + JobDDLBuilder.build(this).run(); + // step 3: execute insert/select/show/desc/CTAS... + JobTransBuilder.build(this).run(); + // step 4: execute custom data stream task + JobExecuteBuilder.build(this).run(); + // finished + job.setEndTime(LocalDateTime.now()); + if (job.isFailed()) { + failed(); + } else { + job.setStatus(Job.JobStatus.SUCCESS); + success(); + } + } catch (ExecuteSqlException e) { + String error = StrFormatter.format( + "Exception in executing FlinkSQL:\n{}\n{}", SqlUtil.addLineNumber(e.getMessage()), e.getMessage()); + job.setEndTime(LocalDateTime.now()); + job.setStatus(Job.JobStatus.FAILED); + job.setError(error); + failed(); + throw new Exception(error, e); + } finally { + close(); + } + return job.getJobResult(); + } + + public IResult executeDDL(String statement) { + String[] statements = SqlUtil.getStatements(statement); + try { + IResult result = null; + for (String item : statements) { + String newStatement = executor.pretreatStatement(item); + if (newStatement.trim().isEmpty()) { + continue; + } + SqlType operationType = Operations.getOperationType(newStatement); + if (SqlType.INSERT == operationType || SqlType.SELECT == operationType) { + continue; + } + + if (operationType.equals(SqlType.ADD) || operationType.equals(SqlType.ADD_JAR)) { + Set allFilePath = AddJarSqlParseStrategy.getAllFilePath(item); + executor.getDinkyClassLoader().addURLs(allFilePath); + } else if (operationType.equals(SqlType.ADD_FILE)) { + Set allFilePath = AddFileSqlParseStrategy.getAllFilePath(item); + executor.getDinkyClassLoader().addURLs(allFilePath); + } + + LocalDateTime startTime = LocalDateTime.now(); + TableResult tableResult = executor.executeSql(newStatement); + result = ResultBuilder.build( + operationType, null, config.getMaxRowNum(), false, false, executor.getTimeZone()) + .getResult(tableResult); + result.setStartTime(startTime); + } + return result; + } catch (Exception e) { + log.error("executeDDL failed:", e); + } + return new ErrorResult(); + } + + public ExplainResult explainSql(String statement) { + return Explainer.build(executor, useStatementSet, this) + .initialize(config, statement) + .explainSql(statement); + } + + public ObjectNode getStreamGraph(String statement) { + return Explainer.build(executor, useStatementSet, this) + .initialize(config, statement) + .getStreamGraph(statement); + } + + public String getJobPlanJson(String statement) { + Explainer explainer = Explainer.build(executor, useStatementSet, this) + .initialize(config, statement); + JobParam jobParam = explainer.pretreatStatements(SqlUtil.getStatements(statement)); + return executor.getJobPlanJson(jobParam); + } + + public boolean cancelNormal(String jobId) { + try { + return FlinkAPI.build(config.getAddress()).stop(jobId); + } catch (Exception e) { + log.error("stop flink job failed:", e); + throw new BusException(e.getMessage()); + } + } + + public SavePointResult savepoint(String jobId, SavePointType savePointType, String savePoint) { + if (useGateway && !useRestAPI) { + config.getGatewayConfig() + .setFlinkConfig( + FlinkConfig.build(jobId, ActionType.SAVEPOINT.getValue(), savePointType.getValue(), null)); + return Gateway.build(config.getGatewayConfig()).savepointJob(savePoint); + } else { + return FlinkAPI.build(config.getAddress()).savepoints(jobId, savePointType, config.getConfigJson()); + } + } + + public static TestResult testGateway(GatewayConfig gatewayConfig) { + return Gateway.build(gatewayConfig).test(); + } + + public String exportSql(String sql) { + String statement = executor.pretreatStatement(sql); + StringBuilder sb = new StringBuilder(); + if (Asserts.isNotNullString(config.getJobName())) { + sb.append("set " + PipelineOptions.NAME.key() + " = " + config.getJobName() + ";\r\n"); + } + if (Asserts.isNotNull(config.getParallelism())) { + sb.append("set " + CoreOptions.DEFAULT_PARALLELISM.key() + " = " + config.getParallelism() + ";\r\n"); + } + if (Asserts.isNotNull(config.getCheckpoint())) { + sb.append("set " + + ExecutionCheckpointingOptions.CHECKPOINTING_INTERVAL.key() + + " = " + + config.getCheckpoint() + + ";\r\n"); + } + if (Asserts.isNotNullString(config.getSavePointPath())) { + sb.append("set " + SavepointConfigOptions.SAVEPOINT_PATH + " = " + config.getSavePointPath() + ";\r\n"); + } + if (Asserts.isNotNull(config.getGatewayConfig()) + && Asserts.isNotNull(config.getGatewayConfig().getFlinkConfig().getConfiguration())) { + for (Map.Entry entry : config.getGatewayConfig() + .getFlinkConfig() + .getConfiguration() + .entrySet()) { + sb.append("set " + entry.getKey() + " = " + entry.getValue() + ";\r\n"); + } + } + + switch (GatewayType.get(config.getType())) { + case YARN_PER_JOB: + case YARN_APPLICATION: + sb.append("set " + + DeploymentOptions.TARGET.key() + + " = " + + GatewayType.get(config.getType()).getLongValue() + + ";\r\n"); + if (Asserts.isNotNull(config.getGatewayConfig())) { + sb.append("set " + + YarnConfigOptions.PROVIDED_LIB_DIRS.key() + + " = " + + Collections.singletonList( + config.getGatewayConfig().getClusterConfig().getFlinkLibPath()) + + ";\r\n"); + } + if (Asserts.isNotNull(config.getGatewayConfig()) + && Asserts.isNotNullString( + config.getGatewayConfig().getFlinkConfig().getJobName())) { + sb.append("set " + + YarnConfigOptions.APPLICATION_NAME.key() + + " = " + + config.getGatewayConfig().getFlinkConfig().getJobName() + + ";\r\n"); + } + break; + default: + } + sb.append(statement); + return sb.toString(); + } + + + public Logger getLog() { + return log; + } + + public void setLog(Logger log) { + this.log = log; + } + + public JobHandler getHandler() { + return handler; + } + + public void setHandler(JobHandler handler) { + this.handler = handler; + } + + public ExecutorConfig getExecutorConfig() { + return executorConfig; + } + + public void setExecutorConfig(ExecutorConfig executorConfig) { + this.executorConfig = executorConfig; + } + + public JobConfig getConfig() { + return config; + } + + public void setConfig(JobConfig config) { + this.config = config; + } + + public Executor getExecutor() { + return executor; + } + + public void setExecutor(Executor executor) { + this.executor = executor; + } + + public boolean isUseGateway() { + return useGateway; + } + + public void setUseGateway(boolean useGateway) { + this.useGateway = useGateway; + } + + public boolean isUseStatementSet() { + return useStatementSet; + } + + public void setUseStatementSet(boolean useStatementSet) { + this.useStatementSet = useStatementSet; + } + + public boolean isUseRestAPI() { + return useRestAPI; + } + + public void setUseRestAPI(boolean useRestAPI) { + this.useRestAPI = useRestAPI; + } + + public GatewayType getRunMode() { + return runMode; + } + + public void setRunMode(GatewayType runMode) { + this.runMode = runMode; + } + + public JobParam getJobParam() { + return jobParam; + } + + public void setJobParam(JobParam jobParam) { + this.jobParam = jobParam; + } + + public Job getJob() { + return job; + } + + public void setJob(Job job) { + this.job = job; + } + +} diff --git a/dinky-core/src/main/java/org/dinky/job/builder/JobDDLBuilder.java b/dinky-core/src/main/java/org/dinky/job/builder/JobDDLBuilder.java index 6cfccfe2f2..1c36e51da5 100644 --- a/dinky-core/src/main/java/org/dinky/job/builder/JobDDLBuilder.java +++ b/dinky-core/src/main/java/org/dinky/job/builder/JobDDLBuilder.java @@ -22,7 +22,7 @@ import org.dinky.executor.Executor; import org.dinky.job.ExecuteSqlException; import org.dinky.job.JobBuilder; -import org.dinky.job.JobManager; +import org.dinky.job.JobManagerHandler; import org.dinky.job.JobParam; import org.dinky.job.StatementParam; @@ -43,7 +43,7 @@ public JobDDLBuilder(JobParam jobParam, Executor executor) { this.executor = executor; } - public static JobDDLBuilder build(JobManager jobManager) { + public static JobDDLBuilder build(JobManagerHandler jobManager) { return new JobDDLBuilder(jobManager.getJobParam(), jobManager.getExecutor()); } diff --git a/dinky-core/src/main/java/org/dinky/job/builder/JobExecuteBuilder.java b/dinky-core/src/main/java/org/dinky/job/builder/JobExecuteBuilder.java index 3000614df3..5d3aecc96f 100644 --- a/dinky-core/src/main/java/org/dinky/job/builder/JobExecuteBuilder.java +++ b/dinky-core/src/main/java/org/dinky/job/builder/JobExecuteBuilder.java @@ -24,13 +24,14 @@ import org.dinky.data.result.IResult; import org.dinky.data.result.InsertResult; import org.dinky.data.result.ResultBuilder; +import org.dinky.data.result.RunResult; import org.dinky.executor.Executor; import org.dinky.gateway.Gateway; import org.dinky.gateway.result.GatewayResult; import org.dinky.job.Job; import org.dinky.job.JobBuilder; import org.dinky.job.JobConfig; -import org.dinky.job.JobManager; +import org.dinky.job.JobManagerHandler; import org.dinky.job.JobParam; import org.dinky.job.StatementParam; import org.dinky.parser.SqlType; @@ -41,6 +42,7 @@ import org.apache.flink.runtime.jobgraph.SavepointRestoreSettings; import org.apache.flink.streaming.api.graph.StreamGraph; +import java.util.ArrayList; import java.util.Collections; /** @@ -56,14 +58,8 @@ public class JobExecuteBuilder implements JobBuilder { private final GatewayType runMode; private final Job job; - public JobExecuteBuilder( - JobParam jobParam, - boolean useGateway, - Executor executor, - boolean useStatementSet, - JobConfig config, - GatewayType runMode, - Job job) { + public JobExecuteBuilder(JobParam jobParam, boolean useGateway, Executor executor, boolean useStatementSet, + JobConfig config, GatewayType runMode, Job job) { this.jobParam = jobParam; this.useGateway = useGateway; this.executor = executor; @@ -73,15 +69,11 @@ public JobExecuteBuilder( this.job = job; } - public static JobExecuteBuilder build(JobManager jobManager) { - return new JobExecuteBuilder( - jobManager.getJobParam(), + public static JobExecuteBuilder build(JobManagerHandler jobManager) { + return new JobExecuteBuilder(jobManager.getJobParam(), jobManager.isUseGateway(), - jobManager.getExecutor(), - jobManager.isUseStatementSet(), - jobManager.getConfig(), - jobManager.getRunMode(), - jobManager.getJob()); + jobManager.getExecutor(), jobManager.isUseStatementSet(), jobManager.getConfig(), + jobManager.getRunMode(), jobManager.getJob()); } @Override diff --git a/dinky-core/src/main/java/org/dinky/job/builder/JobJarStreamGraphBuilder.java b/dinky-core/src/main/java/org/dinky/job/builder/JobJarStreamGraphBuilder.java index 92938d47c2..452bd77193 100644 --- a/dinky-core/src/main/java/org/dinky/job/builder/JobJarStreamGraphBuilder.java +++ b/dinky-core/src/main/java/org/dinky/job/builder/JobJarStreamGraphBuilder.java @@ -26,6 +26,7 @@ import org.dinky.job.JobBuilder; import org.dinky.job.JobConfig; import org.dinky.job.JobManager; +import org.dinky.job.JobManagerHandler; import org.dinky.parser.SqlType; import org.dinky.trans.Operations; import org.dinky.trans.ddl.CustomSetOperation; @@ -63,15 +64,15 @@ public JobJarStreamGraphBuilder(JobConfig config, Executor executor) { this.executor = executor; } - public static JobJarStreamGraphBuilder build(JobManager jobManager) { + public static JobJarStreamGraphBuilder build(JobManagerHandler jobManager) { return new JobJarStreamGraphBuilder(jobManager.getConfig(), jobManager.getExecutor()); } @Override public void run() throws Exception {} - public StreamGraph getJarStreamGraph(String statement, DinkyClassLoader dinkyClassLoader) { - DinkyClassLoaderUtil.initClassLoader(config, dinkyClassLoader); + public StreamGraph getJarStreamGraph(String statement) { + DinkyClassLoaderUtil.initClassLoader(config, executor.getDinkyClassLoader()); String[] statements = SqlUtil.getStatements(statement); ExecuteJarOperation executeJarOperation = null; for (String sql : statements) { diff --git a/dinky-core/src/main/java/org/dinky/job/builder/JobTransBuilder.java b/dinky-core/src/main/java/org/dinky/job/builder/JobTransBuilder.java index c25e095fae..f38b23a9f3 100644 --- a/dinky-core/src/main/java/org/dinky/job/builder/JobTransBuilder.java +++ b/dinky-core/src/main/java/org/dinky/job/builder/JobTransBuilder.java @@ -25,6 +25,7 @@ import org.dinky.data.result.IResult; import org.dinky.data.result.InsertResult; import org.dinky.data.result.ResultBuilder; +import org.dinky.data.result.RunResult; import org.dinky.executor.Executor; import org.dinky.gateway.Gateway; import org.dinky.gateway.result.GatewayResult; @@ -34,7 +35,7 @@ import org.dinky.job.Job; import org.dinky.job.JobBuilder; import org.dinky.job.JobConfig; -import org.dinky.job.JobManager; +import org.dinky.job.JobManagerHandler; import org.dinky.job.JobParam; import org.dinky.job.StatementParam; import org.dinky.parser.SqlType; @@ -62,14 +63,8 @@ public class JobTransBuilder implements JobBuilder { private final GatewayType runMode; private final Job job; - public JobTransBuilder( - JobParam jobParam, - boolean useStatementSet, - boolean useGateway, - JobConfig config, - Executor executor, - GatewayType runMode, - Job job) { + public JobTransBuilder(JobParam jobParam, boolean useStatementSet, boolean useGateway, JobConfig config, + Executor executor, GatewayType runMode, Job job) { this.jobParam = jobParam; this.useStatementSet = useStatementSet; this.useGateway = useGateway; @@ -79,7 +74,8 @@ public JobTransBuilder( this.job = job; } - public static JobTransBuilder build(JobManager jobManager) { + + public static JobTransBuilder build(JobManagerHandler jobManager) { return new JobTransBuilder( jobManager.getJobParam(), @@ -88,7 +84,8 @@ public static JobTransBuilder build(JobManager jobManager) { jobManager.getConfig(), jobManager.getExecutor(), jobManager.getRunMode(), - jobManager.getJob()); + jobManager.getJob() + ); } @Override diff --git a/dinky-core/src/main/java/org/dinky/job/builder/JobUDFBuilder.java b/dinky-core/src/main/java/org/dinky/job/builder/JobUDFBuilder.java index 8866c1e8f8..3e762d0392 100644 --- a/dinky-core/src/main/java/org/dinky/job/builder/JobUDFBuilder.java +++ b/dinky-core/src/main/java/org/dinky/job/builder/JobUDFBuilder.java @@ -23,6 +23,7 @@ import static org.dinky.function.util.UDFUtil.SESSION; import static org.dinky.function.util.UDFUtil.YARN; +import io.debezium.config.CommonConnectorConfig; import org.dinky.assertion.Asserts; import org.dinky.data.enums.GatewayType; import org.dinky.data.model.SystemConfiguration; @@ -31,7 +32,7 @@ import org.dinky.function.util.UDFUtil; import org.dinky.job.JobBuilder; import org.dinky.job.JobConfig; -import org.dinky.job.JobManager; +import org.dinky.job.JobManagerHandler; import org.dinky.job.JobParam; import org.dinky.utils.URLUtils; @@ -65,9 +66,9 @@ public JobUDFBuilder(JobParam jobParam, Executor executor, JobConfig config, Gat this.runMode = runMode; } - public static JobUDFBuilder build(JobManager jobManager) { - return new JobUDFBuilder( - jobManager.getJobParam(), jobManager.getExecutor(), jobManager.getConfig(), jobManager.getRunMode()); + public static JobUDFBuilder build(JobManagerHandler jobManager) { + return new JobUDFBuilder(jobManager.getJobParam(), jobManager.getExecutor(), jobManager.getConfig(), + jobManager.getRunMode()); } @Override @@ -116,7 +117,9 @@ public void run() throws Exception { // 3.Write the required files for UDF UDFUtil.writeManifest(taskId, jarList, executor.getUdfPathContextHolder()); UDFUtil.addConfigurationClsAndJars( - executor.getCustomTableEnvironment(), jarList, CollUtil.newArrayList(URLUtils.getURLs(jarFiles))); + executor.getCustomTableEnvironment(), + jarList, + CollUtil.newArrayList(URLUtils.getURLs(jarFiles))); } catch (Exception e) { throw new RuntimeException("add configuration failed: ", e); } diff --git a/dinky-executor-server/src/main/java/org/dinky/ServerExecutorServiceImpl.java b/dinky-executor-server/src/main/java/org/dinky/JobManagerServiceImpl.java similarity index 80% rename from dinky-executor-server/src/main/java/org/dinky/ServerExecutorServiceImpl.java rename to dinky-executor-server/src/main/java/org/dinky/JobManagerServiceImpl.java index eb992dc980..c280a2aeef 100644 --- a/dinky-executor-server/src/main/java/org/dinky/ServerExecutorServiceImpl.java +++ b/dinky-executor-server/src/main/java/org/dinky/JobManagerServiceImpl.java @@ -21,19 +21,18 @@ import org.dinky.executor.Executor; import org.dinky.executor.ExecutorConfig; -import org.dinky.executor.ExecutorFactory; import org.dinky.flink.ServerExecutorService; import java.rmi.RemoteException; import java.rmi.server.UnicastRemoteObject; -public class ServerExecutorServiceImpl extends UnicastRemoteObject implements ServerExecutorService { +public class JobManagerServiceImpl extends UnicastRemoteObject implements ServerExecutorService { private Executor executor; - public ServerExecutorServiceImpl() throws RemoteException {} + public JobManagerServiceImpl() throws RemoteException {} @Override public void init(ExecutorConfig executorConfig) { - executor = ExecutorFactory.buildExecutor(executorConfig); + } } From 48a2dd4c26a5b8aeb1b293d7f479f0a17d68fbfc Mon Sep 17 00:00:00 2001 From: leechor Date: Sun, 17 Mar 2024 15:45:38 +0000 Subject: [PATCH 21/87] Spotless Apply --- .../impl/ClusterConfigurationServiceImpl.java | 1 - .../impl/ClusterInstanceServiceImpl.java | 1 - .../main/java/org/dinky/job/JobManager.java | 14 ++-- .../java/org/dinky/job/JobManagerHandler.java | 74 +++++++++++-------- .../dinky/job/builder/JobExecuteBuilder.java | 22 ++++-- .../job/builder/JobJarStreamGraphBuilder.java | 2 - .../dinky/job/builder/JobTransBuilder.java | 15 ++-- .../org/dinky/job/builder/JobUDFBuilder.java | 9 +-- .../java/org/dinky/JobManagerServiceImpl.java | 4 +- 9 files changed, 80 insertions(+), 62 deletions(-) diff --git a/dinky-admin/src/main/java/org/dinky/service/impl/ClusterConfigurationServiceImpl.java b/dinky-admin/src/main/java/org/dinky/service/impl/ClusterConfigurationServiceImpl.java index 66dbfe7054..322a7bba16 100644 --- a/dinky-admin/src/main/java/org/dinky/service/impl/ClusterConfigurationServiceImpl.java +++ b/dinky-admin/src/main/java/org/dinky/service/impl/ClusterConfigurationServiceImpl.java @@ -30,7 +30,6 @@ import org.dinky.gateway.config.GatewayConfig; import org.dinky.gateway.model.FlinkClusterConfig; import org.dinky.gateway.result.TestResult; -import org.dinky.job.JobManager; import org.dinky.mapper.ClusterConfigurationMapper; import org.dinky.mybatis.service.impl.SuperServiceImpl; import org.dinky.service.ClusterConfigurationService; diff --git a/dinky-admin/src/main/java/org/dinky/service/impl/ClusterInstanceServiceImpl.java b/dinky-admin/src/main/java/org/dinky/service/impl/ClusterInstanceServiceImpl.java index 0c45866329..d12e9e6094 100644 --- a/dinky-admin/src/main/java/org/dinky/service/impl/ClusterInstanceServiceImpl.java +++ b/dinky-admin/src/main/java/org/dinky/service/impl/ClusterInstanceServiceImpl.java @@ -38,7 +38,6 @@ import org.dinky.gateway.model.FlinkClusterConfig; import org.dinky.gateway.result.GatewayResult; import org.dinky.job.JobConfig; -import org.dinky.job.JobManager; import org.dinky.mapper.ClusterInstanceMapper; import org.dinky.mybatis.service.impl.SuperServiceImpl; import org.dinky.service.ClusterConfigurationService; diff --git a/dinky-core/src/main/java/org/dinky/job/JobManager.java b/dinky-core/src/main/java/org/dinky/job/JobManager.java index 1b3f3af54a..3624406378 100644 --- a/dinky-core/src/main/java/org/dinky/job/JobManager.java +++ b/dinky-core/src/main/java/org/dinky/job/JobManager.java @@ -19,10 +19,6 @@ package org.dinky.job; -import com.fasterxml.jackson.databind.node.ObjectNode; -import lombok.Getter; -import lombok.Setter; -import lombok.extern.slf4j.Slf4j; import org.dinky.data.annotations.ProcessStep; import org.dinky.data.enums.ProcessStepType; import org.dinky.data.result.ExplainResult; @@ -32,14 +28,19 @@ import org.dinky.gateway.enums.SavePointType; import org.dinky.gateway.result.SavePointResult; +import com.fasterxml.jackson.databind.node.ObjectNode; + +import lombok.Getter; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; + @Slf4j @Setter @Getter public class JobManager { JobManagerHandler jobManagerHandler; - public JobManager() { - } + public JobManager() {} private JobManager(JobConfig config, boolean isPlanMode) { jobManagerHandler = JobManagerHandler.build(config, isPlanMode); @@ -102,5 +103,4 @@ public SavePointResult savepoint(String jobId, SavePointType savePointType, Stri public String exportSql(String sql) { return jobManagerHandler.exportSql(sql); } - } diff --git a/dinky-core/src/main/java/org/dinky/job/JobManagerHandler.java b/dinky-core/src/main/java/org/dinky/job/JobManagerHandler.java index aed846726f..2314f1678a 100644 --- a/dinky-core/src/main/java/org/dinky/job/JobManagerHandler.java +++ b/dinky-core/src/main/java/org/dinky/job/JobManagerHandler.java @@ -1,18 +1,24 @@ +/* + * + * 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.job; -import cn.hutool.core.text.StrFormatter; -import com.fasterxml.jackson.databind.node.ObjectNode; -import org.apache.flink.configuration.CoreOptions; -import org.apache.flink.configuration.DeploymentOptions; -import org.apache.flink.configuration.PipelineOptions; -import org.apache.flink.core.execution.JobClient; -import org.apache.flink.runtime.jobgraph.JobGraph; -import org.apache.flink.runtime.jobgraph.SavepointConfigOptions; -import org.apache.flink.runtime.jobgraph.jsonplan.JsonPlanGenerator; -import org.apache.flink.streaming.api.environment.ExecutionCheckpointingOptions; -import org.apache.flink.streaming.api.graph.StreamGraph; -import org.apache.flink.table.api.TableResult; -import org.apache.flink.yarn.configuration.YarnConfigOptions; import org.dinky.api.FlinkAPI; import org.dinky.assertion.Asserts; import org.dinky.context.CustomTableEnvironmentContext; @@ -27,13 +33,10 @@ import org.dinky.data.result.IResult; import org.dinky.data.result.InsertResult; import org.dinky.data.result.ResultBuilder; -import org.dinky.data.result.ResultPool; -import org.dinky.data.result.SelectResult; import org.dinky.executor.Executor; import org.dinky.executor.ExecutorConfig; import org.dinky.executor.ExecutorFactory; import org.dinky.explainer.Explainer; -import org.dinky.function.util.UDFUtil; import org.dinky.gateway.Gateway; import org.dinky.gateway.config.FlinkConfig; import org.dinky.gateway.config.GatewayConfig; @@ -56,8 +59,18 @@ import org.dinky.utils.LogUtil; import org.dinky.utils.SqlUtil; import org.dinky.utils.URLUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; + +import org.apache.flink.configuration.CoreOptions; +import org.apache.flink.configuration.DeploymentOptions; +import org.apache.flink.configuration.PipelineOptions; +import org.apache.flink.core.execution.JobClient; +import org.apache.flink.runtime.jobgraph.JobGraph; +import org.apache.flink.runtime.jobgraph.SavepointConfigOptions; +import org.apache.flink.runtime.jobgraph.jsonplan.JsonPlanGenerator; +import org.apache.flink.streaming.api.environment.ExecutionCheckpointingOptions; +import org.apache.flink.streaming.api.graph.StreamGraph; +import org.apache.flink.table.api.TableResult; +import org.apache.flink.yarn.configuration.YarnConfigOptions; import java.io.File; import java.io.IOException; @@ -67,6 +80,13 @@ import java.util.Map; import java.util.Set; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.databind.node.ObjectNode; + +import cn.hutool.core.text.StrFormatter; + public class JobManagerHandler { Logger log = LoggerFactory.getLogger(JobManagerHandler.class); private JobHandler handler; @@ -83,8 +103,7 @@ public class JobManagerHandler { private Job job; - public JobManagerHandler() { - } + public JobManagerHandler() {} private JobManagerHandler(JobConfig config, boolean isPlanMode) { this.config = config; @@ -135,8 +154,7 @@ public boolean close() { } public ObjectNode getJarStreamGraphJson(String statement) { - StreamGraph streamGraph = - JobJarStreamGraphBuilder.build(this).getJarStreamGraph(statement); + StreamGraph streamGraph = JobJarStreamGraphBuilder.build(this).getJarStreamGraph(statement); return JsonUtils.parseObject(JsonPlanGenerator.generatePlan(streamGraph.getJobGraph())); } @@ -160,7 +178,8 @@ public JobResult executeJarSql(String statement) throws Exception { } } else { GatewayResult gatewayResult; - config.addGatewayConfig(executor.getCustomTableEnvironment().getConfig().getConfiguration()); + config.addGatewayConfig( + executor.getCustomTableEnvironment().getConfig().getConfiguration()); if (runMode.isApplicationMode()) { gatewayResult = Gateway.build(config.getGatewayConfig()).submitJar(executor.getUdfPathContextHolder()); @@ -292,8 +311,7 @@ public ObjectNode getStreamGraph(String statement) { } public String getJobPlanJson(String statement) { - Explainer explainer = Explainer.build(executor, useStatementSet, this) - .initialize(config, statement); + Explainer explainer = Explainer.build(executor, useStatementSet, this).initialize(config, statement); JobParam jobParam = explainer.pretreatStatements(SqlUtil.getStatements(statement)); return executor.getJobPlanJson(jobParam); } @@ -364,12 +382,12 @@ public String exportSql(String sql) { + YarnConfigOptions.PROVIDED_LIB_DIRS.key() + " = " + Collections.singletonList( - config.getGatewayConfig().getClusterConfig().getFlinkLibPath()) + config.getGatewayConfig().getClusterConfig().getFlinkLibPath()) + ";\r\n"); } if (Asserts.isNotNull(config.getGatewayConfig()) && Asserts.isNotNullString( - config.getGatewayConfig().getFlinkConfig().getJobName())) { + config.getGatewayConfig().getFlinkConfig().getJobName())) { sb.append("set " + YarnConfigOptions.APPLICATION_NAME.key() + " = " @@ -383,7 +401,6 @@ public String exportSql(String sql) { return sb.toString(); } - public Logger getLog() { return log; } @@ -471,5 +488,4 @@ public Job getJob() { public void setJob(Job job) { this.job = job; } - } diff --git a/dinky-core/src/main/java/org/dinky/job/builder/JobExecuteBuilder.java b/dinky-core/src/main/java/org/dinky/job/builder/JobExecuteBuilder.java index 5d3aecc96f..3a5f35c888 100644 --- a/dinky-core/src/main/java/org/dinky/job/builder/JobExecuteBuilder.java +++ b/dinky-core/src/main/java/org/dinky/job/builder/JobExecuteBuilder.java @@ -24,7 +24,6 @@ import org.dinky.data.result.IResult; import org.dinky.data.result.InsertResult; import org.dinky.data.result.ResultBuilder; -import org.dinky.data.result.RunResult; import org.dinky.executor.Executor; import org.dinky.gateway.Gateway; import org.dinky.gateway.result.GatewayResult; @@ -42,7 +41,6 @@ import org.apache.flink.runtime.jobgraph.SavepointRestoreSettings; import org.apache.flink.streaming.api.graph.StreamGraph; -import java.util.ArrayList; import java.util.Collections; /** @@ -58,8 +56,14 @@ public class JobExecuteBuilder implements JobBuilder { private final GatewayType runMode; private final Job job; - public JobExecuteBuilder(JobParam jobParam, boolean useGateway, Executor executor, boolean useStatementSet, - JobConfig config, GatewayType runMode, Job job) { + public JobExecuteBuilder( + JobParam jobParam, + boolean useGateway, + Executor executor, + boolean useStatementSet, + JobConfig config, + GatewayType runMode, + Job job) { this.jobParam = jobParam; this.useGateway = useGateway; this.executor = executor; @@ -70,10 +74,14 @@ public JobExecuteBuilder(JobParam jobParam, boolean useGateway, Executor executo } public static JobExecuteBuilder build(JobManagerHandler jobManager) { - return new JobExecuteBuilder(jobManager.getJobParam(), + return new JobExecuteBuilder( + jobManager.getJobParam(), jobManager.isUseGateway(), - jobManager.getExecutor(), jobManager.isUseStatementSet(), jobManager.getConfig(), - jobManager.getRunMode(), jobManager.getJob()); + jobManager.getExecutor(), + jobManager.isUseStatementSet(), + jobManager.getConfig(), + jobManager.getRunMode(), + jobManager.getJob()); } @Override diff --git a/dinky-core/src/main/java/org/dinky/job/builder/JobJarStreamGraphBuilder.java b/dinky-core/src/main/java/org/dinky/job/builder/JobJarStreamGraphBuilder.java index 452bd77193..882309c4a8 100644 --- a/dinky-core/src/main/java/org/dinky/job/builder/JobJarStreamGraphBuilder.java +++ b/dinky-core/src/main/java/org/dinky/job/builder/JobJarStreamGraphBuilder.java @@ -20,12 +20,10 @@ package org.dinky.job.builder; import org.dinky.assertion.Asserts; -import org.dinky.classloader.DinkyClassLoader; import org.dinky.data.exception.DinkyException; import org.dinky.executor.Executor; import org.dinky.job.JobBuilder; import org.dinky.job.JobConfig; -import org.dinky.job.JobManager; import org.dinky.job.JobManagerHandler; import org.dinky.parser.SqlType; import org.dinky.trans.Operations; diff --git a/dinky-core/src/main/java/org/dinky/job/builder/JobTransBuilder.java b/dinky-core/src/main/java/org/dinky/job/builder/JobTransBuilder.java index f38b23a9f3..5cf73bfa1f 100644 --- a/dinky-core/src/main/java/org/dinky/job/builder/JobTransBuilder.java +++ b/dinky-core/src/main/java/org/dinky/job/builder/JobTransBuilder.java @@ -25,7 +25,6 @@ import org.dinky.data.result.IResult; import org.dinky.data.result.InsertResult; import org.dinky.data.result.ResultBuilder; -import org.dinky.data.result.RunResult; import org.dinky.executor.Executor; import org.dinky.gateway.Gateway; import org.dinky.gateway.result.GatewayResult; @@ -63,8 +62,14 @@ public class JobTransBuilder implements JobBuilder { private final GatewayType runMode; private final Job job; - public JobTransBuilder(JobParam jobParam, boolean useStatementSet, boolean useGateway, JobConfig config, - Executor executor, GatewayType runMode, Job job) { + public JobTransBuilder( + JobParam jobParam, + boolean useStatementSet, + boolean useGateway, + JobConfig config, + Executor executor, + GatewayType runMode, + Job job) { this.jobParam = jobParam; this.useStatementSet = useStatementSet; this.useGateway = useGateway; @@ -74,7 +79,6 @@ public JobTransBuilder(JobParam jobParam, boolean useStatementSet, boolean useGa this.job = job; } - public static JobTransBuilder build(JobManagerHandler jobManager) { return new JobTransBuilder( @@ -84,8 +88,7 @@ public static JobTransBuilder build(JobManagerHandler jobManager) { jobManager.getConfig(), jobManager.getExecutor(), jobManager.getRunMode(), - jobManager.getJob() - ); + jobManager.getJob()); } @Override diff --git a/dinky-core/src/main/java/org/dinky/job/builder/JobUDFBuilder.java b/dinky-core/src/main/java/org/dinky/job/builder/JobUDFBuilder.java index 3e762d0392..51cac32669 100644 --- a/dinky-core/src/main/java/org/dinky/job/builder/JobUDFBuilder.java +++ b/dinky-core/src/main/java/org/dinky/job/builder/JobUDFBuilder.java @@ -23,7 +23,6 @@ import static org.dinky.function.util.UDFUtil.SESSION; import static org.dinky.function.util.UDFUtil.YARN; -import io.debezium.config.CommonConnectorConfig; import org.dinky.assertion.Asserts; import org.dinky.data.enums.GatewayType; import org.dinky.data.model.SystemConfiguration; @@ -67,8 +66,8 @@ public JobUDFBuilder(JobParam jobParam, Executor executor, JobConfig config, Gat } public static JobUDFBuilder build(JobManagerHandler jobManager) { - return new JobUDFBuilder(jobManager.getJobParam(), jobManager.getExecutor(), jobManager.getConfig(), - jobManager.getRunMode()); + return new JobUDFBuilder( + jobManager.getJobParam(), jobManager.getExecutor(), jobManager.getConfig(), jobManager.getRunMode()); } @Override @@ -117,9 +116,7 @@ public void run() throws Exception { // 3.Write the required files for UDF UDFUtil.writeManifest(taskId, jarList, executor.getUdfPathContextHolder()); UDFUtil.addConfigurationClsAndJars( - executor.getCustomTableEnvironment(), - jarList, - CollUtil.newArrayList(URLUtils.getURLs(jarFiles))); + executor.getCustomTableEnvironment(), jarList, CollUtil.newArrayList(URLUtils.getURLs(jarFiles))); } catch (Exception e) { throw new RuntimeException("add configuration failed: ", e); } diff --git a/dinky-executor-server/src/main/java/org/dinky/JobManagerServiceImpl.java b/dinky-executor-server/src/main/java/org/dinky/JobManagerServiceImpl.java index c280a2aeef..6df7fe03a8 100644 --- a/dinky-executor-server/src/main/java/org/dinky/JobManagerServiceImpl.java +++ b/dinky-executor-server/src/main/java/org/dinky/JobManagerServiceImpl.java @@ -32,7 +32,5 @@ public class JobManagerServiceImpl extends UnicastRemoteObject implements Server public JobManagerServiceImpl() throws RemoteException {} @Override - public void init(ExecutorConfig executorConfig) { - - } + public void init(ExecutorConfig executorConfig) {} } From 40190dd0188c3c5505fa9ce4a1efb6603f3ac29d Mon Sep 17 00:00:00 2001 From: licho Date: Wed, 20 Mar 2024 21:45:03 +0800 Subject: [PATCH 22/87] refactor: --- .../java/org/dinky/job/JobManagerHandler.java | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/dinky-core/src/main/java/org/dinky/job/JobManagerHandler.java b/dinky-core/src/main/java/org/dinky/job/JobManagerHandler.java index 2314f1678a..5124ae5f58 100644 --- a/dinky-core/src/main/java/org/dinky/job/JobManagerHandler.java +++ b/dinky-core/src/main/java/org/dinky/job/JobManagerHandler.java @@ -103,8 +103,6 @@ public class JobManagerHandler { private Job job; - public JobManagerHandler() {} - private JobManagerHandler(JobConfig config, boolean isPlanMode) { this.config = config; @@ -121,10 +119,6 @@ private JobManagerHandler(JobConfig config, boolean isPlanMode) { executor = ExecutorFactory.buildExecutor(executorConfig); } - public static JobManagerHandler build(JobConfig config) { - return build(config, false); - } - public static JobManagerHandler build(JobConfig config, boolean isPlanMode) { return new JobManagerHandler(config, isPlanMode); } @@ -336,10 +330,6 @@ public SavePointResult savepoint(String jobId, SavePointType savePointType, Stri } } - public static TestResult testGateway(GatewayConfig gatewayConfig) { - return Gateway.build(gatewayConfig).test(); - } - public String exportSql(String sql) { String statement = executor.pretreatStatement(sql); StringBuilder sb = new StringBuilder(); @@ -382,12 +372,12 @@ public String exportSql(String sql) { + YarnConfigOptions.PROVIDED_LIB_DIRS.key() + " = " + Collections.singletonList( - config.getGatewayConfig().getClusterConfig().getFlinkLibPath()) + config.getGatewayConfig().getClusterConfig().getFlinkLibPath()) + ";\r\n"); } if (Asserts.isNotNull(config.getGatewayConfig()) && Asserts.isNotNullString( - config.getGatewayConfig().getFlinkConfig().getJobName())) { + config.getGatewayConfig().getFlinkConfig().getJobName())) { sb.append("set " + YarnConfigOptions.APPLICATION_NAME.key() + " = " @@ -401,6 +391,7 @@ public String exportSql(String sql) { return sb.toString(); } + public Logger getLog() { return log; } @@ -488,4 +479,5 @@ public Job getJob() { public void setJob(Job job) { this.job = job; } + } From 6f3239ce4c709c15dc6ea11a3a71cb6b8b0352a1 Mon Sep 17 00:00:00 2001 From: licho Date: Wed, 20 Mar 2024 23:10:18 +0800 Subject: [PATCH 23/87] feat: compile complete --- dinky-admin/pom.xml | 4 ++ .../dinky/service/impl/StudioServiceImpl.java | 38 ++++++++++--------- .../explainer/lineage/LineageBuilder.java | 10 ++--- dinky-executor-server/pom.xml | 6 +-- .../main/java/org/dinky/job/JobManager.java | 4 -- .../java/org/dinky/job}/JobManagerTest.java | 5 +-- pom.xml | 5 +++ 7 files changed, 35 insertions(+), 37 deletions(-) rename {dinky-core => dinky-executor-server}/src/main/java/org/dinky/job/JobManager.java (98%) rename {dinky-core/src/test/java/org/dinky/core => dinky-executor-server/src/test/java/org/dinky/job}/JobManagerTest.java (95%) diff --git a/dinky-admin/pom.xml b/dinky-admin/pom.xml index 5d64298fd4..aab8efdb1e 100644 --- a/dinky-admin/pom.xml +++ b/dinky-admin/pom.xml @@ -36,6 +36,10 @@ + + org.dinky + dinky-executor-server + org.mitre.dsmiley.httpproxy smiley-http-proxy-servlet diff --git a/dinky-admin/src/main/java/org/dinky/service/impl/StudioServiceImpl.java b/dinky-admin/src/main/java/org/dinky/service/impl/StudioServiceImpl.java index e87e03ed73..00baa22a11 100644 --- a/dinky-admin/src/main/java/org/dinky/service/impl/StudioServiceImpl.java +++ b/dinky-admin/src/main/java/org/dinky/service/impl/StudioServiceImpl.java @@ -148,11 +148,12 @@ public List getMSCatalogs(StudioMetaStoreDTO studioMetaStoreDTO) { catalogs.add(defaultCatalog); } } else { - String envSql = taskService.buildEnvSql(studioMetaStoreDTO); - JobManager jobManager = getJobManager(studioMetaStoreDTO, envSql); - CustomTableEnvironment customTableEnvironment = - jobManager.getExecutor().getCustomTableEnvironment(); - catalogs.addAll(FlinkTableMetadataUtil.getCatalog(customTableEnvironment)); + // TODO: 2024/3/20 remote +// String envSql = taskService.buildEnvSql(studioMetaStoreDTO); +// JobManager jobManager = getJobManager(studioMetaStoreDTO, envSql); +// CustomTableEnvironment customTableEnvironment = +// jobManager.getJobManagerHandler().getExecutor().getCustomTableEnvironment(); +// catalogs.addAll(FlinkTableMetadataUtil.getCatalog(customTableEnvironment)); } return catalogs; } @@ -169,12 +170,13 @@ public Schema getMSSchemaInfo(StudioMetaStoreDTO studioMetaStoreDTO) { tables.addAll(driver.listTables(database)); } } else { - String envSql = taskService.buildEnvSql(studioMetaStoreDTO); - JobManager jobManager = getJobManager(studioMetaStoreDTO, envSql); - CustomTableEnvironment customTableEnvironment = - jobManager.getExecutor().getCustomTableEnvironment(); - FlinkTableMetadataUtil.setSchemaInfo( - customTableEnvironment, studioMetaStoreDTO.getCatalog(), database, schema, tables); + // TODO: 2024/3/20 remote +// String envSql = taskService.buildEnvSql(studioMetaStoreDTO); +// JobManager jobManager = getJobManager(studioMetaStoreDTO, envSql); +// CustomTableEnvironment customTableEnvironment = +// jobManager.getJobManagerHandler().getExecutor().getCustomTableEnvironment(); +// FlinkTableMetadataUtil.setSchemaInfo( +// customTableEnvironment, studioMetaStoreDTO.getCatalog(), database, schema, tables); } schema.setTables(tables); return schema; @@ -193,13 +195,13 @@ public List getMSColumns(StudioMetaStoreDTO studioMetaStoreDTO) { columns.addAll(driver.listColumns(database, tableName)); } } else { - - String envSql = taskService.buildEnvSql(studioMetaStoreDTO); - JobManager jobManager = getJobManager(studioMetaStoreDTO, envSql); - CustomTableEnvironment customTableEnvironment = - jobManager.getExecutor().getCustomTableEnvironment(); - columns.addAll( - FlinkTableMetadataUtil.getColumnList(customTableEnvironment, catalogName, database, tableName)); + // TODO: 2024/3/20 remote +// String envSql = taskService.buildEnvSql(studioMetaStoreDTO); +// JobManager jobManager = getJobManager(studioMetaStoreDTO, envSql); +// CustomTableEnvironment customTableEnvironment = +// jobManager.getJobManagerHandler().getExecutor().getCustomTableEnvironment(); +// columns.addAll( +// FlinkTableMetadataUtil.getColumnList(customTableEnvironment, catalogName, database, tableName)); } return columns; } diff --git a/dinky-core/src/main/java/org/dinky/explainer/lineage/LineageBuilder.java b/dinky-core/src/main/java/org/dinky/explainer/lineage/LineageBuilder.java index 6a399d0d87..f724557776 100644 --- a/dinky-core/src/main/java/org/dinky/explainer/lineage/LineageBuilder.java +++ b/dinky-core/src/main/java/org/dinky/explainer/lineage/LineageBuilder.java @@ -19,13 +19,14 @@ package org.dinky.explainer.lineage; +import org.apache.phoenix.job.JobManager; import org.dinky.data.enums.GatewayType; import org.dinky.data.model.LineageRel; import org.dinky.executor.Executor; import org.dinky.executor.ExecutorFactory; import org.dinky.explainer.Explainer; import org.dinky.job.JobConfig; -import org.dinky.job.JobManager; +import org.dinky.job.JobManagerHandler; import java.util.ArrayList; import java.util.HashMap; @@ -40,18 +41,15 @@ public class LineageBuilder { public static LineageResult getColumnLineageByLogicalPlan(String statement) { - Executor executor = ExecutorFactory.getDefaultExecutor(); JobConfig jobConfig = JobConfig.builder() .type(GatewayType.LOCAL.getLongValue()) .useRemote(false) .fragment(true) .statementSet(false) .parallelism(1) - .configJson(executor.getTableConfig().getConfiguration().toMap()) .build(); - JobManager jobManager = JobManager.build(jobConfig); - jobManager.setExecutor(executor); - Explainer explainer = new Explainer(executor, false, jobManager); + JobManagerHandler jobManagerHandler = JobManagerHandler.build(jobConfig, false); + Explainer explainer = new Explainer(jobManagerHandler.getExecutor(), false, jobManagerHandler); List lineageRelList = explainer.getLineage(statement); List relations = new ArrayList<>(); Map tableMap = new HashMap<>(); diff --git a/dinky-executor-server/pom.xml b/dinky-executor-server/pom.xml index debf7d6e3c..97ce288128 100644 --- a/dinky-executor-server/pom.xml +++ b/dinky-executor-server/pom.xml @@ -6,15 +6,11 @@ org.dinky dinky ${revision} + ../pom.xml dinky-executor-server - - 8 - 8 - UTF-8 - org.dinky diff --git a/dinky-core/src/main/java/org/dinky/job/JobManager.java b/dinky-executor-server/src/main/java/org/dinky/job/JobManager.java similarity index 98% rename from dinky-core/src/main/java/org/dinky/job/JobManager.java rename to dinky-executor-server/src/main/java/org/dinky/job/JobManager.java index 3624406378..9682ef0119 100644 --- a/dinky-core/src/main/java/org/dinky/job/JobManager.java +++ b/dinky-executor-server/src/main/java/org/dinky/job/JobManager.java @@ -30,13 +30,9 @@ import com.fasterxml.jackson.databind.node.ObjectNode; -import lombok.Getter; -import lombok.Setter; import lombok.extern.slf4j.Slf4j; @Slf4j -@Setter -@Getter public class JobManager { JobManagerHandler jobManagerHandler; diff --git a/dinky-core/src/test/java/org/dinky/core/JobManagerTest.java b/dinky-executor-server/src/test/java/org/dinky/job/JobManagerTest.java similarity index 95% rename from dinky-core/src/test/java/org/dinky/core/JobManagerTest.java rename to dinky-executor-server/src/test/java/org/dinky/job/JobManagerTest.java index 978a92024b..5cc0cd2c32 100644 --- a/dinky-core/src/test/java/org/dinky/core/JobManagerTest.java +++ b/dinky-executor-server/src/test/java/org/dinky/job/JobManagerTest.java @@ -17,14 +17,11 @@ * */ -package org.dinky.core; +package org.dinky.job; import org.dinky.data.enums.GatewayType; import org.dinky.data.result.ResultPool; import org.dinky.data.result.SelectResult; -import org.dinky.job.JobConfig; -import org.dinky.job.JobManager; -import org.dinky.job.JobResult; import org.junit.Ignore; import org.junit.Test; diff --git a/pom.xml b/pom.xml index 26973d4d61..19ad9db680 100644 --- a/pom.xml +++ b/pom.xml @@ -617,6 +617,11 @@ dinky-cdc-plus ${project.version} + + org.dinky + dinky-executor-server + ${project.version} + org.apache.httpcomponents httpclient From 175a39400b35ccc23c35006761b0beb7aad16afe Mon Sep 17 00:00:00 2001 From: licho Date: Thu, 21 Mar 2024 07:19:31 +0800 Subject: [PATCH 24/87] feat: could startup --- .../main/java/org/dinky/job/JobManager.java | 22 +++++- .../dinky/service/impl/StudioServiceImpl.java | 2 - .../java/org/dinky/job/JobManagerTest.java | 0 .../java/org/dinky/flink/DinkyExecutor.java | 7 +- .../org/dinky/flink/LocalExecutorService.java | 70 ++++++++++++++++-- .../main/java/org/dinky/job/IJobManager.java | 33 +++++++++ .../java/org/dinky/job/JobManagerHandler.java | 19 ++++- .../java/org/dinky/JobManagerServiceImpl.java | 74 +++++++++++++++++-- .../src/main/java/org/dinky/Main.java | 26 ------- .../src/main/java/org/dinky/RMIServer.java | 24 ++++++ .../org/dinky}/ServerExecutorService.java | 9 ++- 11 files changed, 234 insertions(+), 52 deletions(-) rename {dinky-executor-server => dinky-admin}/src/main/java/org/dinky/job/JobManager.java (84%) rename {dinky-executor-server => dinky-admin}/src/test/java/org/dinky/job/JobManagerTest.java (100%) create mode 100644 dinky-core/src/main/java/org/dinky/job/IJobManager.java delete mode 100644 dinky-executor-server/src/main/java/org/dinky/Main.java create mode 100644 dinky-executor-server/src/main/java/org/dinky/RMIServer.java rename {dinky-core/src/main/java/org/dinky/flink => dinky-executor-server/src/main/java/org/dinky}/ServerExecutorService.java (90%) diff --git a/dinky-executor-server/src/main/java/org/dinky/job/JobManager.java b/dinky-admin/src/main/java/org/dinky/job/JobManager.java similarity index 84% rename from dinky-executor-server/src/main/java/org/dinky/job/JobManager.java rename to dinky-admin/src/main/java/org/dinky/job/JobManager.java index 9682ef0119..65416191e2 100644 --- a/dinky-executor-server/src/main/java/org/dinky/job/JobManager.java +++ b/dinky-admin/src/main/java/org/dinky/job/JobManager.java @@ -19,6 +19,7 @@ package org.dinky.job; +import org.dinky.ServerExecutorService; import org.dinky.data.annotations.ProcessStep; import org.dinky.data.enums.ProcessStepType; import org.dinky.data.result.ExplainResult; @@ -32,14 +33,27 @@ import lombok.extern.slf4j.Slf4j; +import java.rmi.registry.LocateRegistry; +import java.rmi.registry.Registry; + @Slf4j public class JobManager { - JobManagerHandler jobManagerHandler; - - public JobManager() {} + private IJobManager jobManagerHandler; private JobManager(JobConfig config, boolean isPlanMode) { - jobManagerHandler = JobManagerHandler.build(config, isPlanMode); + registerRemote(); + jobManagerHandler.init(config, isPlanMode); + } + + private void registerRemote() { + try { + Registry registry = LocateRegistry.getRegistry("localhost"); + + // 从Registry中检索远程对象的存根/代理 + jobManagerHandler = (ServerExecutorService)registry.lookup("Compute"); + }catch (Exception exception) { + System.out.println(exception); + } } public static JobManager build(JobConfig config) { diff --git a/dinky-admin/src/main/java/org/dinky/service/impl/StudioServiceImpl.java b/dinky-admin/src/main/java/org/dinky/service/impl/StudioServiceImpl.java index 00baa22a11..24799effd5 100644 --- a/dinky-admin/src/main/java/org/dinky/service/impl/StudioServiceImpl.java +++ b/dinky-admin/src/main/java/org/dinky/service/impl/StudioServiceImpl.java @@ -34,7 +34,6 @@ import org.dinky.data.result.DDLResult; import org.dinky.data.result.IResult; import org.dinky.data.result.SelectResult; -import org.dinky.executor.CustomTableEnvironment; import org.dinky.explainer.lineage.LineageBuilder; import org.dinky.explainer.lineage.LineageResult; import org.dinky.explainer.sqllineage.SQLLineageBuilder; @@ -45,7 +44,6 @@ import org.dinky.service.DataBaseService; import org.dinky.service.StudioService; import org.dinky.service.TaskService; -import org.dinky.utils.FlinkTableMetadataUtil; import org.dinky.utils.RunTimeUtil; import java.util.ArrayList; diff --git a/dinky-executor-server/src/test/java/org/dinky/job/JobManagerTest.java b/dinky-admin/src/test/java/org/dinky/job/JobManagerTest.java similarity index 100% rename from dinky-executor-server/src/test/java/org/dinky/job/JobManagerTest.java rename to dinky-admin/src/test/java/org/dinky/job/JobManagerTest.java diff --git a/dinky-core/src/main/java/org/dinky/flink/DinkyExecutor.java b/dinky-core/src/main/java/org/dinky/flink/DinkyExecutor.java index f1ce6d1bd9..1f5a43188f 100644 --- a/dinky-core/src/main/java/org/dinky/flink/DinkyExecutor.java +++ b/dinky-core/src/main/java/org/dinky/flink/DinkyExecutor.java @@ -19,8 +19,9 @@ package org.dinky.flink; -import org.dinky.executor.ExecutorConfig; +import org.dinky.job.IJobManager; -public interface DinkyExecutor { - void init(ExecutorConfig executorConfig); +import java.rmi.Remote; + +public interface DinkyExecutor extends Remote, IJobManager { } diff --git a/dinky-core/src/main/java/org/dinky/flink/LocalExecutorService.java b/dinky-core/src/main/java/org/dinky/flink/LocalExecutorService.java index 4f5a495b61..6c8011d597 100644 --- a/dinky-core/src/main/java/org/dinky/flink/LocalExecutorService.java +++ b/dinky-core/src/main/java/org/dinky/flink/LocalExecutorService.java @@ -19,18 +19,72 @@ package org.dinky.flink; -import org.dinky.classloader.DinkyClassLoader; -import org.dinky.executor.ExecutorConfig; - -import java.lang.ref.WeakReference; +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.dinky.data.result.ExplainResult; +import org.dinky.data.result.IResult; +import org.dinky.gateway.enums.SavePointType; +import org.dinky.gateway.result.SavePointResult; +import org.dinky.job.JobConfig; +import org.dinky.job.JobResult; public class LocalExecutorService implements DinkyExecutor { - private final WeakReference dinkyClassLoader; + @Override + public void init(JobConfig config, boolean isPlanMode) { - public LocalExecutorService() { - dinkyClassLoader = new WeakReference<>(DinkyClassLoader.build()); } @Override - public void init(ExecutorConfig executorConfig) {} + public boolean close() { + return false; + } + + @Override + public ObjectNode getJarStreamGraphJson(String statement) { + return null; + } + + @Override + public JobResult executeJarSql(String statement) throws Exception { + return null; + } + + @Override + public JobResult executeSql(String statement) throws Exception { + return null; + } + + @Override + public IResult executeDDL(String statement) { + return null; + } + + @Override + public ExplainResult explainSql(String statement) { + return null; + } + + @Override + public ObjectNode getStreamGraph(String statement) { + return null; + } + + @Override + public String getJobPlanJson(String statement) { + return null; + } + + @Override + public boolean cancelNormal(String jobId) { + return false; + } + + @Override + public SavePointResult savepoint(String jobId, SavePointType savePointType, String savePoint) { + return null; + } + + @Override + public String exportSql(String sql) { + return null; + } } diff --git a/dinky-core/src/main/java/org/dinky/job/IJobManager.java b/dinky-core/src/main/java/org/dinky/job/IJobManager.java new file mode 100644 index 0000000000..764a103858 --- /dev/null +++ b/dinky-core/src/main/java/org/dinky/job/IJobManager.java @@ -0,0 +1,33 @@ +package org.dinky.job; + +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.dinky.data.result.ExplainResult; +import org.dinky.data.result.IResult; +import org.dinky.gateway.enums.SavePointType; +import org.dinky.gateway.result.SavePointResult; + +public interface IJobManager { + void init(JobConfig config, boolean isPlanMode); + + boolean close(); + + ObjectNode getJarStreamGraphJson(String statement); + + JobResult executeJarSql(String statement) throws Exception; + + JobResult executeSql(String statement) throws Exception; + + IResult executeDDL(String statement); + + ExplainResult explainSql(String statement); + + ObjectNode getStreamGraph(String statement); + + String getJobPlanJson(String statement); + + boolean cancelNormal(String jobId); + + SavePointResult savepoint(String jobId, SavePointType savePointType, String savePoint); + + String exportSql(String sql); +} diff --git a/dinky-core/src/main/java/org/dinky/job/JobManagerHandler.java b/dinky-core/src/main/java/org/dinky/job/JobManagerHandler.java index 5124ae5f58..ef5eacf435 100644 --- a/dinky-core/src/main/java/org/dinky/job/JobManagerHandler.java +++ b/dinky-core/src/main/java/org/dinky/job/JobManagerHandler.java @@ -44,7 +44,6 @@ import org.dinky.gateway.enums.SavePointType; import org.dinky.gateway.result.GatewayResult; import org.dinky.gateway.result.SavePointResult; -import org.dinky.gateway.result.TestResult; import org.dinky.job.builder.JobDDLBuilder; import org.dinky.job.builder.JobExecuteBuilder; import org.dinky.job.builder.JobJarStreamGraphBuilder; @@ -87,7 +86,7 @@ import cn.hutool.core.text.StrFormatter; -public class JobManagerHandler { +public class JobManagerHandler implements IJobManager { Logger log = LoggerFactory.getLogger(JobManagerHandler.class); private JobHandler handler; private ExecutorConfig executorConfig; @@ -119,6 +118,11 @@ private JobManagerHandler(JobConfig config, boolean isPlanMode) { executor = ExecutorFactory.buildExecutor(executorConfig); } + @Override + public void init(JobConfig config, boolean isPlanMode) { + + } + public static JobManagerHandler build(JobConfig config, boolean isPlanMode) { return new JobManagerHandler(config, isPlanMode); } @@ -136,6 +140,7 @@ private boolean failed() { return handler.failed(); } + @Override public boolean close() { CustomTableEnvironmentContext.clear(); RowLevelPermissionsContext.clear(); @@ -147,11 +152,13 @@ public boolean close() { return true; } + @Override public ObjectNode getJarStreamGraphJson(String statement) { StreamGraph streamGraph = JobJarStreamGraphBuilder.build(this).getJarStreamGraph(statement); return JsonUtils.parseObject(JsonPlanGenerator.generatePlan(streamGraph.getJobGraph())); } + @Override @ProcessStep(type = ProcessStepType.SUBMIT_EXECUTE) public JobResult executeJarSql(String statement) throws Exception { prepare(statement); @@ -218,6 +225,7 @@ public JobResult executeJarSql(String statement) throws Exception { return job.getJobResult(); } + @Override @ProcessStep(type = ProcessStepType.SUBMIT_EXECUTE) public JobResult executeSql(String statement) throws Exception { prepare(statement); @@ -256,6 +264,7 @@ public JobResult executeSql(String statement) throws Exception { return job.getJobResult(); } + @Override public IResult executeDDL(String statement) { String[] statements = SqlUtil.getStatements(statement); try { @@ -292,24 +301,28 @@ public IResult executeDDL(String statement) { return new ErrorResult(); } + @Override public ExplainResult explainSql(String statement) { return Explainer.build(executor, useStatementSet, this) .initialize(config, statement) .explainSql(statement); } + @Override public ObjectNode getStreamGraph(String statement) { return Explainer.build(executor, useStatementSet, this) .initialize(config, statement) .getStreamGraph(statement); } + @Override public String getJobPlanJson(String statement) { Explainer explainer = Explainer.build(executor, useStatementSet, this).initialize(config, statement); JobParam jobParam = explainer.pretreatStatements(SqlUtil.getStatements(statement)); return executor.getJobPlanJson(jobParam); } + @Override public boolean cancelNormal(String jobId) { try { return FlinkAPI.build(config.getAddress()).stop(jobId); @@ -319,6 +332,7 @@ public boolean cancelNormal(String jobId) { } } + @Override public SavePointResult savepoint(String jobId, SavePointType savePointType, String savePoint) { if (useGateway && !useRestAPI) { config.getGatewayConfig() @@ -330,6 +344,7 @@ public SavePointResult savepoint(String jobId, SavePointType savePointType, Stri } } + @Override public String exportSql(String sql) { String statement = executor.pretreatStatement(sql); StringBuilder sb = new StringBuilder(); diff --git a/dinky-executor-server/src/main/java/org/dinky/JobManagerServiceImpl.java b/dinky-executor-server/src/main/java/org/dinky/JobManagerServiceImpl.java index 6df7fe03a8..0043d9a9d8 100644 --- a/dinky-executor-server/src/main/java/org/dinky/JobManagerServiceImpl.java +++ b/dinky-executor-server/src/main/java/org/dinky/JobManagerServiceImpl.java @@ -19,18 +19,82 @@ package org.dinky; -import org.dinky.executor.Executor; -import org.dinky.executor.ExecutorConfig; -import org.dinky.flink.ServerExecutorService; +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.dinky.data.result.ExplainResult; +import org.dinky.data.result.IResult; +import org.dinky.gateway.enums.SavePointType; +import org.dinky.gateway.result.SavePointResult; +import org.dinky.job.JobConfig; +import org.dinky.job.JobManagerHandler; +import org.dinky.job.JobResult; import java.rmi.RemoteException; import java.rmi.server.UnicastRemoteObject; public class JobManagerServiceImpl extends UnicastRemoteObject implements ServerExecutorService { - private Executor executor; + + JobManagerHandler jobManagerHandler; public JobManagerServiceImpl() throws RemoteException {} @Override - public void init(ExecutorConfig executorConfig) {} + public void init(JobConfig config, boolean isPlanMode) { + jobManagerHandler = JobManagerHandler.build(config, isPlanMode); + } + + @Override + public boolean close() { + return jobManagerHandler.close(); + } + + @Override + public ObjectNode getJarStreamGraphJson(String statement) { + return jobManagerHandler.getJarStreamGraphJson(statement); + } + + @Override + public JobResult executeJarSql(String statement) throws Exception { + return jobManagerHandler.executeJarSql(statement); + } + + @Override + public JobResult executeSql(String statement) throws Exception { + return jobManagerHandler.executeSql(statement); + } + + @Override + public IResult executeDDL(String statement) { + return jobManagerHandler.executeDDL(statement); + } + + @Override + public ExplainResult explainSql(String statement) { + return jobManagerHandler.explainSql(statement); + } + + @Override + public ObjectNode getStreamGraph(String statement) { + return jobManagerHandler.getStreamGraph(statement); + } + + @Override + public String getJobPlanJson(String statement) { + return jobManagerHandler.getJobPlanJson(statement); + } + + @Override + public boolean cancelNormal(String jobId) { + return jobManagerHandler.cancelNormal(jobId); + } + + @Override + public SavePointResult savepoint(String jobId, SavePointType savePointType, String savePoint) { + return jobManagerHandler.savepoint(jobId, savePointType, savePoint); + } + + @Override + public String exportSql(String sql) { + return jobManagerHandler.exportSql(sql); + } + } diff --git a/dinky-executor-server/src/main/java/org/dinky/Main.java b/dinky-executor-server/src/main/java/org/dinky/Main.java deleted file mode 100644 index 4b30599758..0000000000 --- a/dinky-executor-server/src/main/java/org/dinky/Main.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * - * 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; - -public class Main { - public static void main(String[] args) { - System.out.println("Hello world!"); - } -} diff --git a/dinky-executor-server/src/main/java/org/dinky/RMIServer.java b/dinky-executor-server/src/main/java/org/dinky/RMIServer.java new file mode 100644 index 0000000000..e33dc44a01 --- /dev/null +++ b/dinky-executor-server/src/main/java/org/dinky/RMIServer.java @@ -0,0 +1,24 @@ +package org.dinky; + +import java.rmi.registry.LocateRegistry; +import java.rmi.registry.Registry; + +public class RMIServer { + + public static void main(String[] args) { + + try { + ServerExecutorService remoteMath = new JobManagerServiceImpl(); + LocateRegistry.createRegistry(9099); + Registry registry = LocateRegistry.getRegistry(); + registry.bind("Compute", remoteMath); + System.out.println("Math server ready"); + // 如果不想再让该对象被继续调用,使用下面一行 + // UnicastRemoteObject.unexportObject(remoteMath, false); + } catch (Exception e) { + e.printStackTrace(); + } + + } + +} \ No newline at end of file diff --git a/dinky-core/src/main/java/org/dinky/flink/ServerExecutorService.java b/dinky-executor-server/src/main/java/org/dinky/ServerExecutorService.java similarity index 90% rename from dinky-core/src/main/java/org/dinky/flink/ServerExecutorService.java rename to dinky-executor-server/src/main/java/org/dinky/ServerExecutorService.java index e4915abcc8..f2080f19a3 100644 --- a/dinky-core/src/main/java/org/dinky/flink/ServerExecutorService.java +++ b/dinky-executor-server/src/main/java/org/dinky/ServerExecutorService.java @@ -17,8 +17,13 @@ * */ -package org.dinky.flink; +package org.dinky; + +import org.dinky.flink.DinkyExecutor; +import org.dinky.job.JobConfig; import java.rmi.Remote; -public interface ServerExecutorService extends Remote, DinkyExecutor {} +public interface ServerExecutorService extends Remote, DinkyExecutor { + +} From 9c597f0a97007822ea4312e101053ad36e7e4507 Mon Sep 17 00:00:00 2001 From: licho Date: Thu, 21 Mar 2024 23:24:13 +0800 Subject: [PATCH 25/87] feat: could startup --- .../main/java/org/dinky/job/JobManager.java | 63 ++++++++++--- .../java/org/dinky/flink/DinkyExecutor.java | 4 +- .../org/dinky/flink/LocalExecutorService.java | 90 ------------------- .../main/java/org/dinky/job/JobConfig.java | 3 +- .../java/org/dinky/job/JobManagerHandler.java | 8 -- .../java/org/dinky/JobManagerServiceImpl.java | 33 ++++--- .../src/main/java/org/dinky/RMIServer.java | 2 +- .../java/org/dinky/ServerExecutorService.java | 32 ++++++- 8 files changed, 107 insertions(+), 128 deletions(-) delete mode 100644 dinky-core/src/main/java/org/dinky/flink/LocalExecutorService.java diff --git a/dinky-admin/src/main/java/org/dinky/job/JobManager.java b/dinky-admin/src/main/java/org/dinky/job/JobManager.java index 65416191e2..8097307281 100644 --- a/dinky-admin/src/main/java/org/dinky/job/JobManager.java +++ b/dinky-admin/src/main/java/org/dinky/job/JobManager.java @@ -33,16 +33,21 @@ import lombok.extern.slf4j.Slf4j; +import java.rmi.RemoteException; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; @Slf4j public class JobManager { - private IJobManager jobManagerHandler; + private ServerExecutorService jobManagerHandler; private JobManager(JobConfig config, boolean isPlanMode) { registerRemote(); - jobManagerHandler.init(config, isPlanMode); + try { + jobManagerHandler.init(config, isPlanMode); + } catch (RemoteException e) { + throw new RuntimeException(e); + } } private void registerRemote() { @@ -65,11 +70,19 @@ public static JobManager build(JobConfig config, boolean isPlanMode) { } public boolean close() { - return jobManagerHandler.close(); + try { + return jobManagerHandler.close(); + } catch (RemoteException e) { + throw new RuntimeException(e); + } } public ObjectNode getJarStreamGraphJson(String statement) { - return jobManagerHandler.getJarStreamGraphJson(statement); + try { + return jobManagerHandler.getJarStreamGraphJson(statement); + } catch (RemoteException e) { + throw new RuntimeException(e); + } } @ProcessStep(type = ProcessStepType.SUBMIT_EXECUTE) @@ -83,7 +96,11 @@ public JobResult executeSql(String statement) throws Exception { } public IResult executeDDL(String statement) { - return jobManagerHandler.executeDDL(statement); + try { + return jobManagerHandler.executeDDL(statement); + } catch (RemoteException e) { + throw new RuntimeException(e); + } } public static SelectResult getJobData(String jobId) { @@ -91,26 +108,50 @@ public static SelectResult getJobData(String jobId) { } public ExplainResult explainSql(String statement) { - return jobManagerHandler.explainSql(statement); + try { + return jobManagerHandler.explainSql(statement); + } catch (RemoteException e) { + throw new RuntimeException(e); + } } public ObjectNode getStreamGraph(String statement) { - return jobManagerHandler.getStreamGraph(statement); + try { + return jobManagerHandler.getStreamGraph(statement); + } catch (RemoteException e) { + throw new RuntimeException(e); + } } public String getJobPlanJson(String statement) { - return jobManagerHandler.getJobPlanJson(statement); + try { + return jobManagerHandler.getJobPlanJson(statement); + } catch (RemoteException e) { + throw new RuntimeException(e); + } } public boolean cancelNormal(String jobId) { - return jobManagerHandler.cancelNormal(jobId); + try { + return jobManagerHandler.cancelNormal(jobId); + } catch (RemoteException e) { + throw new RuntimeException(e); + } } public SavePointResult savepoint(String jobId, SavePointType savePointType, String savePoint) { - return jobManagerHandler.savepoint(jobId, savePointType, savePoint); + try { + return jobManagerHandler.savepoint(jobId, savePointType, savePoint); + } catch (RemoteException e) { + throw new RuntimeException(e); + } } public String exportSql(String sql) { - return jobManagerHandler.exportSql(sql); + try { + return jobManagerHandler.exportSql(sql); + } catch (RemoteException e) { + throw new RuntimeException(e); + } } } diff --git a/dinky-core/src/main/java/org/dinky/flink/DinkyExecutor.java b/dinky-core/src/main/java/org/dinky/flink/DinkyExecutor.java index 1f5a43188f..803bd5f022 100644 --- a/dinky-core/src/main/java/org/dinky/flink/DinkyExecutor.java +++ b/dinky-core/src/main/java/org/dinky/flink/DinkyExecutor.java @@ -19,9 +19,7 @@ package org.dinky.flink; -import org.dinky.job.IJobManager; - import java.rmi.Remote; -public interface DinkyExecutor extends Remote, IJobManager { +public interface DinkyExecutor extends Remote { } diff --git a/dinky-core/src/main/java/org/dinky/flink/LocalExecutorService.java b/dinky-core/src/main/java/org/dinky/flink/LocalExecutorService.java deleted file mode 100644 index 6c8011d597..0000000000 --- a/dinky-core/src/main/java/org/dinky/flink/LocalExecutorService.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * - * 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.flink; - -import com.fasterxml.jackson.databind.node.ObjectNode; -import org.dinky.data.result.ExplainResult; -import org.dinky.data.result.IResult; -import org.dinky.gateway.enums.SavePointType; -import org.dinky.gateway.result.SavePointResult; -import org.dinky.job.JobConfig; -import org.dinky.job.JobResult; - -public class LocalExecutorService implements DinkyExecutor { - @Override - public void init(JobConfig config, boolean isPlanMode) { - - } - - @Override - public boolean close() { - return false; - } - - @Override - public ObjectNode getJarStreamGraphJson(String statement) { - return null; - } - - @Override - public JobResult executeJarSql(String statement) throws Exception { - return null; - } - - @Override - public JobResult executeSql(String statement) throws Exception { - return null; - } - - @Override - public IResult executeDDL(String statement) { - return null; - } - - @Override - public ExplainResult explainSql(String statement) { - return null; - } - - @Override - public ObjectNode getStreamGraph(String statement) { - return null; - } - - @Override - public String getJobPlanJson(String statement) { - return null; - } - - @Override - public boolean cancelNormal(String jobId) { - return false; - } - - @Override - public SavePointResult savepoint(String jobId, SavePointType savePointType, String savePoint) { - return null; - } - - @Override - public String exportSql(String sql) { - return null; - } -} diff --git a/dinky-core/src/main/java/org/dinky/job/JobConfig.java b/dinky-core/src/main/java/org/dinky/job/JobConfig.java index e01ba358c4..4bd1ce5118 100644 --- a/dinky-core/src/main/java/org/dinky/job/JobConfig.java +++ b/dinky-core/src/main/java/org/dinky/job/JobConfig.java @@ -32,6 +32,7 @@ import org.apache.flink.configuration.CoreOptions; import org.apache.flink.configuration.RestOptions; +import java.io.Serializable; import java.util.HashMap; import java.util.Map; @@ -50,7 +51,7 @@ @Builder @AllArgsConstructor @ApiModel(value = "JobConfig", description = "Configuration details of a job") -public class JobConfig { +public class JobConfig implements Serializable { @ApiModelProperty( value = "Flink run mode", diff --git a/dinky-core/src/main/java/org/dinky/job/JobManagerHandler.java b/dinky-core/src/main/java/org/dinky/job/JobManagerHandler.java index ef5eacf435..a60cde2da5 100644 --- a/dinky-core/src/main/java/org/dinky/job/JobManagerHandler.java +++ b/dinky-core/src/main/java/org/dinky/job/JobManagerHandler.java @@ -415,14 +415,6 @@ public void setLog(Logger log) { this.log = log; } - public JobHandler getHandler() { - return handler; - } - - public void setHandler(JobHandler handler) { - this.handler = handler; - } - public ExecutorConfig getExecutorConfig() { return executorConfig; } diff --git a/dinky-executor-server/src/main/java/org/dinky/JobManagerServiceImpl.java b/dinky-executor-server/src/main/java/org/dinky/JobManagerServiceImpl.java index 0043d9a9d8..62b82b44e5 100644 --- a/dinky-executor-server/src/main/java/org/dinky/JobManagerServiceImpl.java +++ b/dinky-executor-server/src/main/java/org/dinky/JobManagerServiceImpl.java @@ -35,7 +35,8 @@ public class JobManagerServiceImpl extends UnicastRemoteObject implements Server JobManagerHandler jobManagerHandler; - public JobManagerServiceImpl() throws RemoteException {} + public JobManagerServiceImpl() throws RemoteException { + } @Override public void init(JobConfig config, boolean isPlanMode) { @@ -48,47 +49,55 @@ public boolean close() { } @Override - public ObjectNode getJarStreamGraphJson(String statement) { + public ObjectNode getJarStreamGraphJson(String statement) throws RemoteException { return jobManagerHandler.getJarStreamGraphJson(statement); } @Override - public JobResult executeJarSql(String statement) throws Exception { - return jobManagerHandler.executeJarSql(statement); + public JobResult executeJarSql(String statement) throws RemoteException { + try { + return jobManagerHandler.executeJarSql(statement); + } catch (Exception ex) { + return null; + } } @Override - public JobResult executeSql(String statement) throws Exception { - return jobManagerHandler.executeSql(statement); + public JobResult executeSql(String statement) throws RemoteException { + try { + return jobManagerHandler.executeSql(statement); + } catch (Exception ex) { + return null; + } } @Override - public IResult executeDDL(String statement) { + public IResult executeDDL(String statement) throws RemoteException { return jobManagerHandler.executeDDL(statement); } @Override - public ExplainResult explainSql(String statement) { + public ExplainResult explainSql(String statement) throws RemoteException { return jobManagerHandler.explainSql(statement); } @Override - public ObjectNode getStreamGraph(String statement) { + public ObjectNode getStreamGraph(String statement) throws RemoteException { return jobManagerHandler.getStreamGraph(statement); } @Override - public String getJobPlanJson(String statement) { + public String getJobPlanJson(String statement) throws RemoteException { return jobManagerHandler.getJobPlanJson(statement); } @Override - public boolean cancelNormal(String jobId) { + public boolean cancelNormal(String jobId) throws RemoteException { return jobManagerHandler.cancelNormal(jobId); } @Override - public SavePointResult savepoint(String jobId, SavePointType savePointType, String savePoint) { + public SavePointResult savepoint(String jobId, SavePointType savePointType, String savePoint) throws RemoteException { return jobManagerHandler.savepoint(jobId, savePointType, savePoint); } diff --git a/dinky-executor-server/src/main/java/org/dinky/RMIServer.java b/dinky-executor-server/src/main/java/org/dinky/RMIServer.java index e33dc44a01..7647cad518 100644 --- a/dinky-executor-server/src/main/java/org/dinky/RMIServer.java +++ b/dinky-executor-server/src/main/java/org/dinky/RMIServer.java @@ -9,7 +9,7 @@ public static void main(String[] args) { try { ServerExecutorService remoteMath = new JobManagerServiceImpl(); - LocateRegistry.createRegistry(9099); + LocateRegistry.createRegistry(1099); Registry registry = LocateRegistry.getRegistry(); registry.bind("Compute", remoteMath); System.out.println("Math server ready"); diff --git a/dinky-executor-server/src/main/java/org/dinky/ServerExecutorService.java b/dinky-executor-server/src/main/java/org/dinky/ServerExecutorService.java index f2080f19a3..92db07dc3f 100644 --- a/dinky-executor-server/src/main/java/org/dinky/ServerExecutorService.java +++ b/dinky-executor-server/src/main/java/org/dinky/ServerExecutorService.java @@ -19,11 +19,39 @@ package org.dinky; -import org.dinky.flink.DinkyExecutor; +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.dinky.data.result.ExplainResult; +import org.dinky.data.result.IResult; +import org.dinky.gateway.enums.SavePointType; +import org.dinky.gateway.result.SavePointResult; import org.dinky.job.JobConfig; +import org.dinky.job.JobResult; import java.rmi.Remote; +import java.rmi.RemoteException; -public interface ServerExecutorService extends Remote, DinkyExecutor { +public interface ServerExecutorService extends Remote { + void init(JobConfig config, boolean isPlanMode) throws RemoteException; + boolean close() throws RemoteException; + + ObjectNode getJarStreamGraphJson(String statement) throws RemoteException; + + JobResult executeJarSql(String statement) throws RemoteException; + + JobResult executeSql(String statement) throws RemoteException; + + IResult executeDDL(String statement) throws RemoteException; + + ExplainResult explainSql(String statement) throws RemoteException; + + ObjectNode getStreamGraph(String statement) throws RemoteException; + + String getJobPlanJson(String statement) throws RemoteException; + + boolean cancelNormal(String jobId) throws RemoteException; + + SavePointResult savepoint(String jobId, SavePointType savePointType, String savePoint) throws RemoteException; + + String exportSql(String sql) throws RemoteException; } From c788625cb45fcaaddd84ed62d5048b94b9e706ae Mon Sep 17 00:00:00 2001 From: licho Date: Sat, 23 Mar 2024 20:20:20 +0800 Subject: [PATCH 26/87] storage --- .../main/java/org/dinky/job/JobManager.java | 16 ++++++ .../dinky/service/task/FlinkJarSqlTask.java | 13 ++++- .../org/dinky/service/task/FlinkSqlTask.java | 13 ++++- .../org/dinky/executor/ExecutorConfig.java | 3 +- .../main/java/org/dinky/job/IJobManager.java | 2 + .../src/main/java/org/dinky/job/Job.java | 8 ++- .../java/org/dinky/job/JobManagerHandler.java | 51 +++---------------- .../main/java/org/dinky/job/JobResult.java | 8 --- .../java/org/dinky/JobManagerServiceImpl.java | 11 ++++ .../java/org/dinky/ServerExecutorService.java | 5 ++ 10 files changed, 71 insertions(+), 59 deletions(-) diff --git a/dinky-admin/src/main/java/org/dinky/job/JobManager.java b/dinky-admin/src/main/java/org/dinky/job/JobManager.java index 8097307281..6eab85819e 100644 --- a/dinky-admin/src/main/java/org/dinky/job/JobManager.java +++ b/dinky-admin/src/main/java/org/dinky/job/JobManager.java @@ -85,6 +85,14 @@ public ObjectNode getJarStreamGraphJson(String statement) { } } + public void prepare(String statement) { + try { + jobManagerHandler.prepare(statement); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + @ProcessStep(type = ProcessStepType.SUBMIT_EXECUTE) public JobResult executeJarSql(String statement) throws Exception { return jobManagerHandler.executeJarSql(statement); @@ -154,4 +162,12 @@ public String exportSql(String sql) { throw new RuntimeException(e); } } + + public Job getJob() { + try { + return jobManagerHandler.getJob(); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } } diff --git a/dinky-admin/src/main/java/org/dinky/service/task/FlinkJarSqlTask.java b/dinky-admin/src/main/java/org/dinky/service/task/FlinkJarSqlTask.java index de2716d3fb..93a6ece6de 100644 --- a/dinky-admin/src/main/java/org/dinky/service/task/FlinkJarSqlTask.java +++ b/dinky-admin/src/main/java/org/dinky/service/task/FlinkJarSqlTask.java @@ -23,6 +23,7 @@ import org.dinky.data.annotations.SupportDialect; import org.dinky.data.dto.TaskDTO; import org.dinky.data.result.SqlExplainResult; +import org.dinky.job.JobHandler; import org.dinky.job.JobResult; import java.util.List; @@ -42,8 +43,16 @@ public List explain() { @Override public JobResult execute() throws Exception { - - return jobManager.executeJarSql(task.getStatement()); + JobHandler handler = JobHandler.build(); + jobManager.prepare(task.getStatement()); + handler.init(jobManager.getJob()); + JobResult result = jobManager.executeJarSql(task.getStatement()); + if (result.isSuccess()) { + handler.success(); + }else { + handler.failed(); + } + return result; } @Override diff --git a/dinky-admin/src/main/java/org/dinky/service/task/FlinkSqlTask.java b/dinky-admin/src/main/java/org/dinky/service/task/FlinkSqlTask.java index 481f25fe41..cc17c1a7b6 100644 --- a/dinky-admin/src/main/java/org/dinky/service/task/FlinkSqlTask.java +++ b/dinky-admin/src/main/java/org/dinky/service/task/FlinkSqlTask.java @@ -25,6 +25,7 @@ import org.dinky.data.dto.TaskDTO; import org.dinky.data.enums.GatewayType; import org.dinky.data.result.SqlExplainResult; +import org.dinky.job.JobHandler; import org.dinky.job.JobManager; import org.dinky.job.JobResult; import org.dinky.service.TaskService; @@ -64,8 +65,16 @@ public ObjectNode getJobPlan() { @Override public JobResult execute() throws Exception { - log.info("Initializing Flink job config..."); - return jobManager.executeSql(task.getStatement()); + JobHandler handler = JobHandler.build(); + jobManager.prepare(task.getStatement()); + handler.init(jobManager.getJob()); + JobResult result = jobManager.executeSql(task.getStatement()); + if (result.isSuccess()) { + handler.success(); + }else { + handler.failed(); + } + return result; } protected JobManager getJobManager() { diff --git a/dinky-core/src/main/java/org/dinky/executor/ExecutorConfig.java b/dinky-core/src/main/java/org/dinky/executor/ExecutorConfig.java index 64471d44e8..c3ae51f8bc 100644 --- a/dinky-core/src/main/java/org/dinky/executor/ExecutorConfig.java +++ b/dinky-core/src/main/java/org/dinky/executor/ExecutorConfig.java @@ -22,6 +22,7 @@ import org.dinky.assertion.Asserts; import org.dinky.data.enums.GatewayType; +import java.io.Serializable; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -50,7 +51,7 @@ @Builder @AllArgsConstructor @ApiModel(value = "ExecutorConfig", description = "Executor config for a job") -public class ExecutorConfig { +public class ExecutorConfig implements Serializable { private static final Logger log = LoggerFactory.getLogger(ExecutorConfig.class); private static final ObjectMapper mapper = new ObjectMapper(); diff --git a/dinky-core/src/main/java/org/dinky/job/IJobManager.java b/dinky-core/src/main/java/org/dinky/job/IJobManager.java index 764a103858..17022bedd9 100644 --- a/dinky-core/src/main/java/org/dinky/job/IJobManager.java +++ b/dinky-core/src/main/java/org/dinky/job/IJobManager.java @@ -9,6 +9,8 @@ public interface IJobManager { void init(JobConfig config, boolean isPlanMode); + void prepare(String statement); + boolean close(); ObjectNode getJarStreamGraphJson(String statement); diff --git a/dinky-core/src/main/java/org/dinky/job/Job.java b/dinky-core/src/main/java/org/dinky/job/Job.java index e352e6d8cc..012fcb98f3 100644 --- a/dinky-core/src/main/java/org/dinky/job/Job.java +++ b/dinky-core/src/main/java/org/dinky/job/Job.java @@ -24,6 +24,7 @@ import org.dinky.executor.Executor; import org.dinky.executor.ExecutorConfig; +import java.io.Serializable; import java.time.LocalDateTime; import java.util.List; @@ -35,9 +36,9 @@ * * @since 2021/6/26 23:39 */ -@Getter @Setter -public class Job { +@Getter +public class Job implements Serializable { private Integer id; private Integer jobInstanceId; private JobConfig jobConfig; @@ -55,6 +56,9 @@ public class Job { private boolean useGateway; private List jids; + public Job() { + } + @Getter public enum JobStatus { INITIALIZE(0), diff --git a/dinky-core/src/main/java/org/dinky/job/JobManagerHandler.java b/dinky-core/src/main/java/org/dinky/job/JobManagerHandler.java index a60cde2da5..680f904340 100644 --- a/dinky-core/src/main/java/org/dinky/job/JobManagerHandler.java +++ b/dinky-core/src/main/java/org/dinky/job/JobManagerHandler.java @@ -77,6 +77,7 @@ import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import org.slf4j.Logger; @@ -88,14 +89,12 @@ public class JobManagerHandler implements IJobManager { Logger log = LoggerFactory.getLogger(JobManagerHandler.class); - private JobHandler handler; private ExecutorConfig executorConfig; private JobConfig config; private Executor executor; private boolean useGateway = false; private boolean useStatementSet = false; - private boolean useRestAPI = false; private GatewayType runMode = GatewayType.LOCAL; private JobParam jobParam = null; @@ -108,11 +107,9 @@ private JobManagerHandler(JobConfig config, boolean isPlanMode) { if (!isPlanMode) { runMode = GatewayType.get(config.getType()); useGateway = GatewayType.isDeployCluster(config.getType()); - handler = JobHandler.build(); } useStatementSet = config.isStatementSet(); - useRestAPI = SystemConfiguration.getInstances().isUseRestAPI(); executorConfig = config.createExecutorSetting(); executorConfig.setPlan(isPlanMode); executor = ExecutorFactory.buildExecutor(executorConfig); @@ -127,17 +124,9 @@ public static JobManagerHandler build(JobConfig config, boolean isPlanMode) { return new JobManagerHandler(config, isPlanMode); } - private void prepare(String statement) { + @Override + public void prepare(String statement) { job = Job.build(runMode, config, executorConfig, executor, statement, useGateway); - handler.init(job); - } - - private boolean success() { - return handler.success(); - } - - private boolean failed() { - return handler.failed(); } @Override @@ -161,7 +150,7 @@ public ObjectNode getJarStreamGraphJson(String statement) { @Override @ProcessStep(type = ProcessStepType.SUBMIT_EXECUTE) public JobResult executeJarSql(String statement) throws Exception { - prepare(statement); + Objects.requireNonNull(job, "job is null, prepare() first"); JobJarStreamGraphBuilder jobJarStreamGraphBuilder = JobJarStreamGraphBuilder.build(this); StreamGraph streamGraph = jobJarStreamGraphBuilder.getJarStreamGraph(statement); @@ -172,10 +161,8 @@ public JobResult executeJarSql(String statement) throws Exception { job.setJobId(jobClient.getJobID().toHexString()); job.setJids(Collections.singletonList(job.getJobId())); job.setStatus(Job.JobStatus.SUCCESS); - success(); } else { job.setStatus(Job.JobStatus.FAILED); - failed(); } } else { GatewayResult gatewayResult; @@ -203,12 +190,10 @@ public JobResult executeJarSql(String statement) throws Exception { if (gatewayResult.isSuccess()) { job.setStatus(Job.JobStatus.SUCCESS); - success(); } else { job.setStatus(Job.JobStatus.FAILED); job.setError(gatewayResult.getError()); log.error(gatewayResult.getError()); - failed(); } } } catch (Exception e) { @@ -217,7 +202,6 @@ public JobResult executeJarSql(String statement) throws Exception { job.setEndTime(LocalDateTime.now()); job.setStatus(Job.JobStatus.FAILED); job.setError(error); - failed(); throw new Exception(error, e); } finally { close(); @@ -228,8 +212,7 @@ public JobResult executeJarSql(String statement) throws Exception { @Override @ProcessStep(type = ProcessStepType.SUBMIT_EXECUTE) public JobResult executeSql(String statement) throws Exception { - prepare(statement); - + Objects.requireNonNull(job, "job is null, prepare() first"); DinkyClassLoaderUtil.initClassLoader(config, executor.getDinkyClassLoader()); jobParam = Explainer.build(executor, useStatementSet, this).pretreatStatements(SqlUtil.getStatements(statement)); @@ -244,11 +227,8 @@ public JobResult executeSql(String statement) throws Exception { JobExecuteBuilder.build(this).run(); // finished job.setEndTime(LocalDateTime.now()); - if (job.isFailed()) { - failed(); - } else { + if (!job.isFailed()) { job.setStatus(Job.JobStatus.SUCCESS); - success(); } } catch (ExecuteSqlException e) { String error = StrFormatter.format( @@ -256,7 +236,6 @@ public JobResult executeSql(String statement) throws Exception { job.setEndTime(LocalDateTime.now()); job.setStatus(Job.JobStatus.FAILED); job.setError(error); - failed(); throw new Exception(error, e); } finally { close(); @@ -334,7 +313,7 @@ public boolean cancelNormal(String jobId) { @Override public SavePointResult savepoint(String jobId, SavePointType savePointType, String savePoint) { - if (useGateway && !useRestAPI) { + if (useGateway && !SystemConfiguration.getInstances().isUseRestAPI()) { config.getGatewayConfig() .setFlinkConfig( FlinkConfig.build(jobId, ActionType.SAVEPOINT.getValue(), savePointType.getValue(), null)); @@ -443,10 +422,6 @@ public boolean isUseGateway() { return useGateway; } - public void setUseGateway(boolean useGateway) { - this.useGateway = useGateway; - } - public boolean isUseStatementSet() { return useStatementSet; } @@ -455,22 +430,10 @@ public void setUseStatementSet(boolean useStatementSet) { this.useStatementSet = useStatementSet; } - public boolean isUseRestAPI() { - return useRestAPI; - } - - public void setUseRestAPI(boolean useRestAPI) { - this.useRestAPI = useRestAPI; - } - public GatewayType getRunMode() { return runMode; } - public void setRunMode(GatewayType runMode) { - this.runMode = runMode; - } - public JobParam getJobParam() { return jobParam; } diff --git a/dinky-core/src/main/java/org/dinky/job/JobResult.java b/dinky-core/src/main/java/org/dinky/job/JobResult.java index 6f127a6c5d..2039304e7f 100644 --- a/dinky-core/src/main/java/org/dinky/job/JobResult.java +++ b/dinky-core/src/main/java/org/dinky/job/JobResult.java @@ -141,12 +141,4 @@ public JobResult( this.startTime = startTime; this.endTime = endTime; } - - public void setStartTimeNow() { - this.setStartTime(LocalDateTime.now()); - } - - public void setEndTimeNow() { - this.setEndTime(LocalDateTime.now()); - } } diff --git a/dinky-executor-server/src/main/java/org/dinky/JobManagerServiceImpl.java b/dinky-executor-server/src/main/java/org/dinky/JobManagerServiceImpl.java index 62b82b44e5..40a62ca46d 100644 --- a/dinky-executor-server/src/main/java/org/dinky/JobManagerServiceImpl.java +++ b/dinky-executor-server/src/main/java/org/dinky/JobManagerServiceImpl.java @@ -24,6 +24,7 @@ import org.dinky.data.result.IResult; import org.dinky.gateway.enums.SavePointType; import org.dinky.gateway.result.SavePointResult; +import org.dinky.job.Job; import org.dinky.job.JobConfig; import org.dinky.job.JobManagerHandler; import org.dinky.job.JobResult; @@ -106,4 +107,14 @@ public String exportSql(String sql) { return jobManagerHandler.exportSql(sql); } + @Override + public Job getJob() throws RemoteException { + return jobManagerHandler.getJob(); + } + + @Override + public void prepare(String statement) throws RemoteException { + jobManagerHandler.prepare(statement); + } + } diff --git a/dinky-executor-server/src/main/java/org/dinky/ServerExecutorService.java b/dinky-executor-server/src/main/java/org/dinky/ServerExecutorService.java index 92db07dc3f..31b9d39b11 100644 --- a/dinky-executor-server/src/main/java/org/dinky/ServerExecutorService.java +++ b/dinky-executor-server/src/main/java/org/dinky/ServerExecutorService.java @@ -24,6 +24,7 @@ import org.dinky.data.result.IResult; import org.dinky.gateway.enums.SavePointType; import org.dinky.gateway.result.SavePointResult; +import org.dinky.job.Job; import org.dinky.job.JobConfig; import org.dinky.job.JobResult; @@ -54,4 +55,8 @@ public interface ServerExecutorService extends Remote { SavePointResult savepoint(String jobId, SavePointType savePointType, String savePoint) throws RemoteException; String exportSql(String sql) throws RemoteException; + + Job getJob() throws RemoteException; + + void prepare(String statement) throws RemoteException; } From 931cccd8df991e9f975c411be945a159d80ad062 Mon Sep 17 00:00:00 2001 From: leechor Date: Sat, 23 Mar 2024 12:22:59 +0000 Subject: [PATCH 27/87] Spotless Apply --- .../main/java/org/dinky/job/JobManager.java | 12 +++---- .../dinky/service/impl/StudioServiceImpl.java | 35 ++++++++++--------- .../dinky/service/task/FlinkJarSqlTask.java | 2 +- .../org/dinky/service/task/FlinkSqlTask.java | 2 +- .../explainer/lineage/LineageBuilder.java | 3 -- .../java/org/dinky/flink/DinkyExecutor.java | 3 +- .../main/java/org/dinky/job/IJobManager.java | 22 +++++++++++- .../src/main/java/org/dinky/job/Job.java | 3 +- .../java/org/dinky/job/JobManagerHandler.java | 10 ++---- .../java/org/dinky/JobManagerServiceImpl.java | 10 +++--- .../src/main/java/org/dinky/RMIServer.java | 25 ++++++++++--- .../java/org/dinky/ServerExecutorService.java | 3 +- 12 files changed, 80 insertions(+), 50 deletions(-) diff --git a/dinky-admin/src/main/java/org/dinky/job/JobManager.java b/dinky-admin/src/main/java/org/dinky/job/JobManager.java index 6eab85819e..dc6cde21c8 100644 --- a/dinky-admin/src/main/java/org/dinky/job/JobManager.java +++ b/dinky-admin/src/main/java/org/dinky/job/JobManager.java @@ -29,14 +29,14 @@ import org.dinky.gateway.enums.SavePointType; import org.dinky.gateway.result.SavePointResult; -import com.fasterxml.jackson.databind.node.ObjectNode; - -import lombok.extern.slf4j.Slf4j; - import java.rmi.RemoteException; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; +import com.fasterxml.jackson.databind.node.ObjectNode; + +import lombok.extern.slf4j.Slf4j; + @Slf4j public class JobManager { private ServerExecutorService jobManagerHandler; @@ -55,8 +55,8 @@ private void registerRemote() { Registry registry = LocateRegistry.getRegistry("localhost"); // 从Registry中检索远程对象的存根/代理 - jobManagerHandler = (ServerExecutorService)registry.lookup("Compute"); - }catch (Exception exception) { + jobManagerHandler = (ServerExecutorService) registry.lookup("Compute"); + } catch (Exception exception) { System.out.println(exception); } } diff --git a/dinky-admin/src/main/java/org/dinky/service/impl/StudioServiceImpl.java b/dinky-admin/src/main/java/org/dinky/service/impl/StudioServiceImpl.java index 24799effd5..00bfb2c127 100644 --- a/dinky-admin/src/main/java/org/dinky/service/impl/StudioServiceImpl.java +++ b/dinky-admin/src/main/java/org/dinky/service/impl/StudioServiceImpl.java @@ -147,11 +147,11 @@ public List getMSCatalogs(StudioMetaStoreDTO studioMetaStoreDTO) { } } else { // TODO: 2024/3/20 remote -// String envSql = taskService.buildEnvSql(studioMetaStoreDTO); -// JobManager jobManager = getJobManager(studioMetaStoreDTO, envSql); -// CustomTableEnvironment customTableEnvironment = -// jobManager.getJobManagerHandler().getExecutor().getCustomTableEnvironment(); -// catalogs.addAll(FlinkTableMetadataUtil.getCatalog(customTableEnvironment)); + // String envSql = taskService.buildEnvSql(studioMetaStoreDTO); + // JobManager jobManager = getJobManager(studioMetaStoreDTO, envSql); + // CustomTableEnvironment customTableEnvironment = + // jobManager.getJobManagerHandler().getExecutor().getCustomTableEnvironment(); + // catalogs.addAll(FlinkTableMetadataUtil.getCatalog(customTableEnvironment)); } return catalogs; } @@ -169,12 +169,12 @@ public Schema getMSSchemaInfo(StudioMetaStoreDTO studioMetaStoreDTO) { } } else { // TODO: 2024/3/20 remote -// String envSql = taskService.buildEnvSql(studioMetaStoreDTO); -// JobManager jobManager = getJobManager(studioMetaStoreDTO, envSql); -// CustomTableEnvironment customTableEnvironment = -// jobManager.getJobManagerHandler().getExecutor().getCustomTableEnvironment(); -// FlinkTableMetadataUtil.setSchemaInfo( -// customTableEnvironment, studioMetaStoreDTO.getCatalog(), database, schema, tables); + // String envSql = taskService.buildEnvSql(studioMetaStoreDTO); + // JobManager jobManager = getJobManager(studioMetaStoreDTO, envSql); + // CustomTableEnvironment customTableEnvironment = + // jobManager.getJobManagerHandler().getExecutor().getCustomTableEnvironment(); + // FlinkTableMetadataUtil.setSchemaInfo( + // customTableEnvironment, studioMetaStoreDTO.getCatalog(), database, schema, tables); } schema.setTables(tables); return schema; @@ -194,12 +194,13 @@ public List getMSColumns(StudioMetaStoreDTO studioMetaStoreDTO) { } } else { // TODO: 2024/3/20 remote -// String envSql = taskService.buildEnvSql(studioMetaStoreDTO); -// JobManager jobManager = getJobManager(studioMetaStoreDTO, envSql); -// CustomTableEnvironment customTableEnvironment = -// jobManager.getJobManagerHandler().getExecutor().getCustomTableEnvironment(); -// columns.addAll( -// FlinkTableMetadataUtil.getColumnList(customTableEnvironment, catalogName, database, tableName)); + // String envSql = taskService.buildEnvSql(studioMetaStoreDTO); + // JobManager jobManager = getJobManager(studioMetaStoreDTO, envSql); + // CustomTableEnvironment customTableEnvironment = + // jobManager.getJobManagerHandler().getExecutor().getCustomTableEnvironment(); + // columns.addAll( + // FlinkTableMetadataUtil.getColumnList(customTableEnvironment, catalogName, database, + // tableName)); } return columns; } diff --git a/dinky-admin/src/main/java/org/dinky/service/task/FlinkJarSqlTask.java b/dinky-admin/src/main/java/org/dinky/service/task/FlinkJarSqlTask.java index 93a6ece6de..3ab1fdafe4 100644 --- a/dinky-admin/src/main/java/org/dinky/service/task/FlinkJarSqlTask.java +++ b/dinky-admin/src/main/java/org/dinky/service/task/FlinkJarSqlTask.java @@ -49,7 +49,7 @@ public JobResult execute() throws Exception { JobResult result = jobManager.executeJarSql(task.getStatement()); if (result.isSuccess()) { handler.success(); - }else { + } else { handler.failed(); } return result; diff --git a/dinky-admin/src/main/java/org/dinky/service/task/FlinkSqlTask.java b/dinky-admin/src/main/java/org/dinky/service/task/FlinkSqlTask.java index cc17c1a7b6..abc6e2363b 100644 --- a/dinky-admin/src/main/java/org/dinky/service/task/FlinkSqlTask.java +++ b/dinky-admin/src/main/java/org/dinky/service/task/FlinkSqlTask.java @@ -71,7 +71,7 @@ public JobResult execute() throws Exception { JobResult result = jobManager.executeSql(task.getStatement()); if (result.isSuccess()) { handler.success(); - }else { + } else { handler.failed(); } return result; diff --git a/dinky-core/src/main/java/org/dinky/explainer/lineage/LineageBuilder.java b/dinky-core/src/main/java/org/dinky/explainer/lineage/LineageBuilder.java index f724557776..6d124ac11d 100644 --- a/dinky-core/src/main/java/org/dinky/explainer/lineage/LineageBuilder.java +++ b/dinky-core/src/main/java/org/dinky/explainer/lineage/LineageBuilder.java @@ -19,11 +19,8 @@ package org.dinky.explainer.lineage; -import org.apache.phoenix.job.JobManager; import org.dinky.data.enums.GatewayType; import org.dinky.data.model.LineageRel; -import org.dinky.executor.Executor; -import org.dinky.executor.ExecutorFactory; import org.dinky.explainer.Explainer; import org.dinky.job.JobConfig; import org.dinky.job.JobManagerHandler; diff --git a/dinky-core/src/main/java/org/dinky/flink/DinkyExecutor.java b/dinky-core/src/main/java/org/dinky/flink/DinkyExecutor.java index 803bd5f022..2252bda04d 100644 --- a/dinky-core/src/main/java/org/dinky/flink/DinkyExecutor.java +++ b/dinky-core/src/main/java/org/dinky/flink/DinkyExecutor.java @@ -21,5 +21,4 @@ import java.rmi.Remote; -public interface DinkyExecutor extends Remote { -} +public interface DinkyExecutor extends Remote {} diff --git a/dinky-core/src/main/java/org/dinky/job/IJobManager.java b/dinky-core/src/main/java/org/dinky/job/IJobManager.java index 17022bedd9..ebe1ad92b3 100644 --- a/dinky-core/src/main/java/org/dinky/job/IJobManager.java +++ b/dinky-core/src/main/java/org/dinky/job/IJobManager.java @@ -1,11 +1,31 @@ +/* + * + * 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.job; -import com.fasterxml.jackson.databind.node.ObjectNode; import org.dinky.data.result.ExplainResult; import org.dinky.data.result.IResult; import org.dinky.gateway.enums.SavePointType; import org.dinky.gateway.result.SavePointResult; +import com.fasterxml.jackson.databind.node.ObjectNode; + public interface IJobManager { void init(JobConfig config, boolean isPlanMode); diff --git a/dinky-core/src/main/java/org/dinky/job/Job.java b/dinky-core/src/main/java/org/dinky/job/Job.java index 012fcb98f3..772f763716 100644 --- a/dinky-core/src/main/java/org/dinky/job/Job.java +++ b/dinky-core/src/main/java/org/dinky/job/Job.java @@ -56,8 +56,7 @@ public class Job implements Serializable { private boolean useGateway; private List jids; - public Job() { - } + public Job() {} @Getter public enum JobStatus { diff --git a/dinky-core/src/main/java/org/dinky/job/JobManagerHandler.java b/dinky-core/src/main/java/org/dinky/job/JobManagerHandler.java index 680f904340..743c701bb6 100644 --- a/dinky-core/src/main/java/org/dinky/job/JobManagerHandler.java +++ b/dinky-core/src/main/java/org/dinky/job/JobManagerHandler.java @@ -116,9 +116,7 @@ private JobManagerHandler(JobConfig config, boolean isPlanMode) { } @Override - public void init(JobConfig config, boolean isPlanMode) { - - } + public void init(JobConfig config, boolean isPlanMode) {} public static JobManagerHandler build(JobConfig config, boolean isPlanMode) { return new JobManagerHandler(config, isPlanMode); @@ -366,12 +364,12 @@ public String exportSql(String sql) { + YarnConfigOptions.PROVIDED_LIB_DIRS.key() + " = " + Collections.singletonList( - config.getGatewayConfig().getClusterConfig().getFlinkLibPath()) + config.getGatewayConfig().getClusterConfig().getFlinkLibPath()) + ";\r\n"); } if (Asserts.isNotNull(config.getGatewayConfig()) && Asserts.isNotNullString( - config.getGatewayConfig().getFlinkConfig().getJobName())) { + config.getGatewayConfig().getFlinkConfig().getJobName())) { sb.append("set " + YarnConfigOptions.APPLICATION_NAME.key() + " = " @@ -385,7 +383,6 @@ public String exportSql(String sql) { return sb.toString(); } - public Logger getLog() { return log; } @@ -449,5 +446,4 @@ public Job getJob() { public void setJob(Job job) { this.job = job; } - } diff --git a/dinky-executor-server/src/main/java/org/dinky/JobManagerServiceImpl.java b/dinky-executor-server/src/main/java/org/dinky/JobManagerServiceImpl.java index 40a62ca46d..00b28632a4 100644 --- a/dinky-executor-server/src/main/java/org/dinky/JobManagerServiceImpl.java +++ b/dinky-executor-server/src/main/java/org/dinky/JobManagerServiceImpl.java @@ -19,7 +19,6 @@ package org.dinky; -import com.fasterxml.jackson.databind.node.ObjectNode; import org.dinky.data.result.ExplainResult; import org.dinky.data.result.IResult; import org.dinky.gateway.enums.SavePointType; @@ -32,12 +31,13 @@ import java.rmi.RemoteException; import java.rmi.server.UnicastRemoteObject; +import com.fasterxml.jackson.databind.node.ObjectNode; + public class JobManagerServiceImpl extends UnicastRemoteObject implements ServerExecutorService { JobManagerHandler jobManagerHandler; - public JobManagerServiceImpl() throws RemoteException { - } + public JobManagerServiceImpl() throws RemoteException {} @Override public void init(JobConfig config, boolean isPlanMode) { @@ -98,7 +98,8 @@ public boolean cancelNormal(String jobId) throws RemoteException { } @Override - public SavePointResult savepoint(String jobId, SavePointType savePointType, String savePoint) throws RemoteException { + public SavePointResult savepoint(String jobId, SavePointType savePointType, String savePoint) + throws RemoteException { return jobManagerHandler.savepoint(jobId, savePointType, savePoint); } @@ -116,5 +117,4 @@ public Job getJob() throws RemoteException { public void prepare(String statement) throws RemoteException { jobManagerHandler.prepare(statement); } - } diff --git a/dinky-executor-server/src/main/java/org/dinky/RMIServer.java b/dinky-executor-server/src/main/java/org/dinky/RMIServer.java index 7647cad518..e34a5085d1 100644 --- a/dinky-executor-server/src/main/java/org/dinky/RMIServer.java +++ b/dinky-executor-server/src/main/java/org/dinky/RMIServer.java @@ -1,3 +1,22 @@ +/* + * + * 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; import java.rmi.registry.LocateRegistry; @@ -5,7 +24,7 @@ public class RMIServer { - public static void main(String[] args) { + public static void main(String[] args) { try { ServerExecutorService remoteMath = new JobManagerServiceImpl(); @@ -18,7 +37,5 @@ public static void main(String[] args) { } catch (Exception e) { e.printStackTrace(); } - } - -} \ No newline at end of file +} diff --git a/dinky-executor-server/src/main/java/org/dinky/ServerExecutorService.java b/dinky-executor-server/src/main/java/org/dinky/ServerExecutorService.java index 31b9d39b11..d5aee3e8b0 100644 --- a/dinky-executor-server/src/main/java/org/dinky/ServerExecutorService.java +++ b/dinky-executor-server/src/main/java/org/dinky/ServerExecutorService.java @@ -19,7 +19,6 @@ package org.dinky; -import com.fasterxml.jackson.databind.node.ObjectNode; import org.dinky.data.result.ExplainResult; import org.dinky.data.result.IResult; import org.dinky.gateway.enums.SavePointType; @@ -31,6 +30,8 @@ import java.rmi.Remote; import java.rmi.RemoteException; +import com.fasterxml.jackson.databind.node.ObjectNode; + public interface ServerExecutorService extends Remote { void init(JobConfig config, boolean isPlanMode) throws RemoteException; From 9a4906c320e998dd3700113bf94c5b2728d0189f Mon Sep 17 00:00:00 2001 From: licho Date: Sun, 24 Mar 2024 15:45:14 +0800 Subject: [PATCH 28/87] feat: could execute --- dinky-core/src/main/java/org/dinky/job/Job.java | 6 +----- .../main/java/org/dinky/job/JobManagerHandler.java | 2 +- .../src/main/java/org/dinky/job/JobResult.java | 3 ++- .../main/java/org/dinky/JobManagerServiceImpl.java | 13 ++++++++----- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/dinky-core/src/main/java/org/dinky/job/Job.java b/dinky-core/src/main/java/org/dinky/job/Job.java index 772f763716..bae5787b9e 100644 --- a/dinky-core/src/main/java/org/dinky/job/Job.java +++ b/dinky-core/src/main/java/org/dinky/job/Job.java @@ -52,7 +52,6 @@ public class Job implements Serializable { private ExecutorConfig executorConfig; private LocalDateTime startTime; private LocalDateTime endTime; - private Executor executor; private boolean useGateway; private List jids; @@ -78,7 +77,6 @@ public Job( JobStatus status, String statement, ExecutorConfig executorConfig, - Executor executor, boolean useGateway) { this.jobConfig = jobConfig; this.type = type; @@ -86,7 +84,6 @@ public Job( this.statement = statement; this.executorConfig = executorConfig; this.startTime = LocalDateTime.now(); - this.executor = executor; this.useGateway = useGateway; } @@ -94,10 +91,9 @@ public static Job build( GatewayType type, JobConfig jobConfig, ExecutorConfig executorConfig, - Executor executor, String statement, boolean useGateway) { - Job job = new Job(jobConfig, type, JobStatus.INITIALIZE, statement, executorConfig, executor, useGateway); + Job job = new Job(jobConfig, type, JobStatus.INITIALIZE, statement, executorConfig, useGateway); if (!useGateway) { job.setJobManagerAddress(executorConfig.getJobManagerAddress()); } diff --git a/dinky-core/src/main/java/org/dinky/job/JobManagerHandler.java b/dinky-core/src/main/java/org/dinky/job/JobManagerHandler.java index 743c701bb6..75bf86d6c5 100644 --- a/dinky-core/src/main/java/org/dinky/job/JobManagerHandler.java +++ b/dinky-core/src/main/java/org/dinky/job/JobManagerHandler.java @@ -124,7 +124,7 @@ public static JobManagerHandler build(JobConfig config, boolean isPlanMode) { @Override public void prepare(String statement) { - job = Job.build(runMode, config, executorConfig, executor, statement, useGateway); + job = Job.build(runMode, config, executorConfig, statement, useGateway); } @Override diff --git a/dinky-core/src/main/java/org/dinky/job/JobResult.java b/dinky-core/src/main/java/org/dinky/job/JobResult.java index 2039304e7f..45f44d1875 100644 --- a/dinky-core/src/main/java/org/dinky/job/JobResult.java +++ b/dinky-core/src/main/java/org/dinky/job/JobResult.java @@ -22,6 +22,7 @@ import org.dinky.data.result.IResult; import org.dinky.metadata.result.JdbcSelectResult; +import java.io.Serializable; import java.time.LocalDateTime; import java.util.List; @@ -38,7 +39,7 @@ @Getter @Setter @ApiModel(value = "JobResult", description = "Result of a job execution") -public class JobResult { +public class JobResult implements Serializable { @ApiModelProperty(value = "Unique identifier for the job result", dataType = "Integer", example = "123") private Integer id; diff --git a/dinky-executor-server/src/main/java/org/dinky/JobManagerServiceImpl.java b/dinky-executor-server/src/main/java/org/dinky/JobManagerServiceImpl.java index 00b28632a4..e9ef25bc09 100644 --- a/dinky-executor-server/src/main/java/org/dinky/JobManagerServiceImpl.java +++ b/dinky-executor-server/src/main/java/org/dinky/JobManagerServiceImpl.java @@ -19,6 +19,8 @@ package org.dinky; +import com.fasterxml.jackson.databind.node.ObjectNode; +import lombok.extern.slf4j.Slf4j; import org.dinky.data.result.ExplainResult; import org.dinky.data.result.IResult; import org.dinky.gateway.enums.SavePointType; @@ -31,13 +33,13 @@ import java.rmi.RemoteException; import java.rmi.server.UnicastRemoteObject; -import com.fasterxml.jackson.databind.node.ObjectNode; - +@Slf4j public class JobManagerServiceImpl extends UnicastRemoteObject implements ServerExecutorService { JobManagerHandler jobManagerHandler; - public JobManagerServiceImpl() throws RemoteException {} + public JobManagerServiceImpl() throws RemoteException { + } @Override public void init(JobConfig config, boolean isPlanMode) { @@ -68,6 +70,7 @@ public JobResult executeSql(String statement) throws RemoteException { try { return jobManagerHandler.executeSql(statement); } catch (Exception ex) { + log.error("executeSql error", ex); return null; } } @@ -98,8 +101,7 @@ public boolean cancelNormal(String jobId) throws RemoteException { } @Override - public SavePointResult savepoint(String jobId, SavePointType savePointType, String savePoint) - throws RemoteException { + public SavePointResult savepoint(String jobId, SavePointType savePointType, String savePoint) throws RemoteException { return jobManagerHandler.savepoint(jobId, savePointType, savePoint); } @@ -117,4 +119,5 @@ public Job getJob() throws RemoteException { public void prepare(String statement) throws RemoteException { jobManagerHandler.prepare(statement); } + } From f080cabe5e80fb460537cebcb96fbb9166b84096 Mon Sep 17 00:00:00 2001 From: leechor Date: Sun, 24 Mar 2024 07:48:27 +0000 Subject: [PATCH 29/87] Spotless Apply --- dinky-core/src/main/java/org/dinky/job/Job.java | 1 - .../main/java/org/dinky/JobManagerServiceImpl.java | 13 +++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/dinky-core/src/main/java/org/dinky/job/Job.java b/dinky-core/src/main/java/org/dinky/job/Job.java index bae5787b9e..455dbe06a1 100644 --- a/dinky-core/src/main/java/org/dinky/job/Job.java +++ b/dinky-core/src/main/java/org/dinky/job/Job.java @@ -21,7 +21,6 @@ import org.dinky.data.enums.GatewayType; import org.dinky.data.result.IResult; -import org.dinky.executor.Executor; import org.dinky.executor.ExecutorConfig; import java.io.Serializable; diff --git a/dinky-executor-server/src/main/java/org/dinky/JobManagerServiceImpl.java b/dinky-executor-server/src/main/java/org/dinky/JobManagerServiceImpl.java index e9ef25bc09..7a79f0928b 100644 --- a/dinky-executor-server/src/main/java/org/dinky/JobManagerServiceImpl.java +++ b/dinky-executor-server/src/main/java/org/dinky/JobManagerServiceImpl.java @@ -19,8 +19,6 @@ package org.dinky; -import com.fasterxml.jackson.databind.node.ObjectNode; -import lombok.extern.slf4j.Slf4j; import org.dinky.data.result.ExplainResult; import org.dinky.data.result.IResult; import org.dinky.gateway.enums.SavePointType; @@ -33,13 +31,16 @@ import java.rmi.RemoteException; import java.rmi.server.UnicastRemoteObject; +import com.fasterxml.jackson.databind.node.ObjectNode; + +import lombok.extern.slf4j.Slf4j; + @Slf4j public class JobManagerServiceImpl extends UnicastRemoteObject implements ServerExecutorService { JobManagerHandler jobManagerHandler; - public JobManagerServiceImpl() throws RemoteException { - } + public JobManagerServiceImpl() throws RemoteException {} @Override public void init(JobConfig config, boolean isPlanMode) { @@ -101,7 +102,8 @@ public boolean cancelNormal(String jobId) throws RemoteException { } @Override - public SavePointResult savepoint(String jobId, SavePointType savePointType, String savePoint) throws RemoteException { + public SavePointResult savepoint(String jobId, SavePointType savePointType, String savePoint) + throws RemoteException { return jobManagerHandler.savepoint(jobId, savePointType, savePoint); } @@ -119,5 +121,4 @@ public Job getJob() throws RemoteException { public void prepare(String statement) throws RemoteException { jobManagerHandler.prepare(statement); } - } From d38f779f2392de14a4ad7a1a185dc26198d3c5da Mon Sep 17 00:00:00 2001 From: sunlichao11 Date: Sun, 24 Mar 2024 23:39:57 +0800 Subject: [PATCH 30/87] fix: remvoe weak dinkyClassLoader --- .../src/main/java/org/dinky/executor/AbstractExecutor.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dinky-core/src/main/java/org/dinky/executor/AbstractExecutor.java b/dinky-core/src/main/java/org/dinky/executor/AbstractExecutor.java index 89c62b3507..279bcc4f2a 100644 --- a/dinky-core/src/main/java/org/dinky/executor/AbstractExecutor.java +++ b/dinky-core/src/main/java/org/dinky/executor/AbstractExecutor.java @@ -92,7 +92,7 @@ public abstract class AbstractExecutor implements Executor { // The config of Dinky executor. protected ExecutorConfig executorConfig; - protected WeakReference dinkyClassLoader = new WeakReference<>(DinkyClassLoader.build()); + protected DinkyClassLoader dinkyClassLoader = DinkyClassLoader.build(); // Flink configuration, such as set rest.port = 8086 protected Map setConfig = new HashMap<>(); @@ -103,7 +103,7 @@ public abstract class AbstractExecutor implements Executor { // return dinkyClassLoader @Override public DinkyClassLoader getDinkyClassLoader() { - return dinkyClassLoader.get(); + return dinkyClassLoader; } @Override From 94dbab663e5a7f0064e2510bfd46fcc8c3f227fd Mon Sep 17 00:00:00 2001 From: leechor Date: Sun, 24 Mar 2024 15:43:21 +0000 Subject: [PATCH 31/87] Spotless Apply --- .../src/main/java/org/dinky/executor/AbstractExecutor.java | 1 - 1 file changed, 1 deletion(-) diff --git a/dinky-core/src/main/java/org/dinky/executor/AbstractExecutor.java b/dinky-core/src/main/java/org/dinky/executor/AbstractExecutor.java index 279bcc4f2a..cf66073c3e 100644 --- a/dinky-core/src/main/java/org/dinky/executor/AbstractExecutor.java +++ b/dinky-core/src/main/java/org/dinky/executor/AbstractExecutor.java @@ -52,7 +52,6 @@ import org.apache.flink.table.api.TableResult; import java.io.File; -import java.lang.ref.WeakReference; import java.net.URL; import java.util.Arrays; import java.util.Collections; From 69df3d218908edab3833a4e84e971f300bcd2223 Mon Sep 17 00:00:00 2001 From: sunlichao11 Date: Tue, 26 Mar 2024 08:19:22 +0800 Subject: [PATCH 32/87] fix: job change at remote server --- .../main/java/org/dinky/job/handler/AbsJobHandler.java | 4 ++++ .../java/org/dinky/job/handler/Job2MysqlHandler.java | 10 ---------- .../java/org/dinky/service/task/FlinkJarSqlTask.java | 1 + .../main/java/org/dinky/service/task/FlinkSqlTask.java | 2 ++ dinky-core/src/main/java/org/dinky/job/JobHandler.java | 4 +--- 5 files changed, 8 insertions(+), 13 deletions(-) diff --git a/dinky-admin/src/main/java/org/dinky/job/handler/AbsJobHandler.java b/dinky-admin/src/main/java/org/dinky/job/handler/AbsJobHandler.java index 55fa6ab630..7469c685a2 100644 --- a/dinky-admin/src/main/java/org/dinky/job/handler/AbsJobHandler.java +++ b/dinky-admin/src/main/java/org/dinky/job/handler/AbsJobHandler.java @@ -24,4 +24,8 @@ public abstract class AbsJobHandler implements JobHandler { protected Job job; + + public void setJob(Job job) { + this.job = job; + } } diff --git a/dinky-admin/src/main/java/org/dinky/job/handler/Job2MysqlHandler.java b/dinky-admin/src/main/java/org/dinky/job/handler/Job2MysqlHandler.java index ea474b121d..22dfb333d9 100644 --- a/dinky-admin/src/main/java/org/dinky/job/handler/Job2MysqlHandler.java +++ b/dinky-admin/src/main/java/org/dinky/job/handler/Job2MysqlHandler.java @@ -96,11 +96,6 @@ public boolean init(Job job) { return true; } - @Override - public boolean ready() { - return true; - } - @Override public boolean running() { return true; @@ -221,11 +216,6 @@ public boolean failed() { return true; } - @Override - public boolean callback() { - return true; - } - @Override public boolean close() { return true; diff --git a/dinky-admin/src/main/java/org/dinky/service/task/FlinkJarSqlTask.java b/dinky-admin/src/main/java/org/dinky/service/task/FlinkJarSqlTask.java index 3ab1fdafe4..502bbd007d 100644 --- a/dinky-admin/src/main/java/org/dinky/service/task/FlinkJarSqlTask.java +++ b/dinky-admin/src/main/java/org/dinky/service/task/FlinkJarSqlTask.java @@ -47,6 +47,7 @@ public JobResult execute() throws Exception { jobManager.prepare(task.getStatement()); handler.init(jobManager.getJob()); JobResult result = jobManager.executeJarSql(task.getStatement()); + handler.setJob(jobManager.getJob()); if (result.isSuccess()) { handler.success(); } else { diff --git a/dinky-admin/src/main/java/org/dinky/service/task/FlinkSqlTask.java b/dinky-admin/src/main/java/org/dinky/service/task/FlinkSqlTask.java index abc6e2363b..a9201ebcd2 100644 --- a/dinky-admin/src/main/java/org/dinky/service/task/FlinkSqlTask.java +++ b/dinky-admin/src/main/java/org/dinky/service/task/FlinkSqlTask.java @@ -69,6 +69,8 @@ public JobResult execute() throws Exception { jobManager.prepare(task.getStatement()); handler.init(jobManager.getJob()); JobResult result = jobManager.executeSql(task.getStatement()); + // Job class maybe change at remote server + handler.setJob(jobManager.getJob()); if (result.isSuccess()) { handler.success(); } else { diff --git a/dinky-core/src/main/java/org/dinky/job/JobHandler.java b/dinky-core/src/main/java/org/dinky/job/JobHandler.java index d7dd668274..548ba659b1 100644 --- a/dinky-core/src/main/java/org/dinky/job/JobHandler.java +++ b/dinky-core/src/main/java/org/dinky/job/JobHandler.java @@ -32,7 +32,7 @@ public interface JobHandler { boolean init(Job job); - boolean ready(); + void setJob(Job job); boolean running(); @@ -40,8 +40,6 @@ public interface JobHandler { boolean failed(); - boolean callback(); - boolean close(); static JobHandler build() { From a32643989ca7114bdc95048a2b315b95f0debdd2 Mon Sep 17 00:00:00 2001 From: sunlichao11 Date: Tue, 26 Mar 2024 08:30:23 +0800 Subject: [PATCH 33/87] refactor: remove job member --- .../src/main/java/org/dinky/job/handler/AbsJobHandler.java | 4 ---- .../main/java/org/dinky/job/handler/Job2MysqlHandler.java | 5 ++--- .../main/java/org/dinky/service/task/FlinkJarSqlTask.java | 7 ++++--- .../src/main/java/org/dinky/service/task/FlinkSqlTask.java | 7 ++++--- dinky-core/src/main/java/org/dinky/job/JobHandler.java | 6 ++---- 5 files changed, 12 insertions(+), 17 deletions(-) diff --git a/dinky-admin/src/main/java/org/dinky/job/handler/AbsJobHandler.java b/dinky-admin/src/main/java/org/dinky/job/handler/AbsJobHandler.java index 7469c685a2..4eaf9f2711 100644 --- a/dinky-admin/src/main/java/org/dinky/job/handler/AbsJobHandler.java +++ b/dinky-admin/src/main/java/org/dinky/job/handler/AbsJobHandler.java @@ -23,9 +23,5 @@ import org.dinky.job.JobHandler; public abstract class AbsJobHandler implements JobHandler { - protected Job job; - public void setJob(Job job) { - this.job = job; - } } diff --git a/dinky-admin/src/main/java/org/dinky/job/handler/Job2MysqlHandler.java b/dinky-admin/src/main/java/org/dinky/job/handler/Job2MysqlHandler.java index 22dfb333d9..74edc672ed 100644 --- a/dinky-admin/src/main/java/org/dinky/job/handler/Job2MysqlHandler.java +++ b/dinky-admin/src/main/java/org/dinky/job/handler/Job2MysqlHandler.java @@ -74,7 +74,6 @@ public class Job2MysqlHandler extends AbsJobHandler { @Override public boolean init(Job job) { - this.job = job; History history = new History(); history.setType(job.getType().getLongValue()); if (job.isUseGateway()) { @@ -102,7 +101,7 @@ public boolean running() { } @Override - public boolean success() { + public boolean success(Job job) { Integer taskId = job.getJobConfig().getTaskId(); History history = new History(); @@ -203,7 +202,7 @@ public boolean success() { } @Override - public boolean failed() { + public boolean failed(Job job) { History history = new History(); history.setBatchModel(job.getJobConfig().isBatchModel()); history.setId(job.getId()); diff --git a/dinky-admin/src/main/java/org/dinky/service/task/FlinkJarSqlTask.java b/dinky-admin/src/main/java/org/dinky/service/task/FlinkJarSqlTask.java index 502bbd007d..3f28bfea74 100644 --- a/dinky-admin/src/main/java/org/dinky/service/task/FlinkJarSqlTask.java +++ b/dinky-admin/src/main/java/org/dinky/service/task/FlinkJarSqlTask.java @@ -23,6 +23,7 @@ import org.dinky.data.annotations.SupportDialect; import org.dinky.data.dto.TaskDTO; import org.dinky.data.result.SqlExplainResult; +import org.dinky.job.Job; import org.dinky.job.JobHandler; import org.dinky.job.JobResult; @@ -47,11 +48,11 @@ public JobResult execute() throws Exception { jobManager.prepare(task.getStatement()); handler.init(jobManager.getJob()); JobResult result = jobManager.executeJarSql(task.getStatement()); - handler.setJob(jobManager.getJob()); + Job afterJob = jobManager.getJob(); if (result.isSuccess()) { - handler.success(); + handler.success(afterJob); } else { - handler.failed(); + handler.failed(afterJob); } return result; } diff --git a/dinky-admin/src/main/java/org/dinky/service/task/FlinkSqlTask.java b/dinky-admin/src/main/java/org/dinky/service/task/FlinkSqlTask.java index a9201ebcd2..b23bf7cdf0 100644 --- a/dinky-admin/src/main/java/org/dinky/service/task/FlinkSqlTask.java +++ b/dinky-admin/src/main/java/org/dinky/service/task/FlinkSqlTask.java @@ -25,6 +25,7 @@ import org.dinky.data.dto.TaskDTO; import org.dinky.data.enums.GatewayType; import org.dinky.data.result.SqlExplainResult; +import org.dinky.job.Job; import org.dinky.job.JobHandler; import org.dinky.job.JobManager; import org.dinky.job.JobResult; @@ -70,11 +71,11 @@ public JobResult execute() throws Exception { handler.init(jobManager.getJob()); JobResult result = jobManager.executeSql(task.getStatement()); // Job class maybe change at remote server - handler.setJob(jobManager.getJob()); + Job afterJob = jobManager.getJob(); if (result.isSuccess()) { - handler.success(); + handler.success(afterJob); } else { - handler.failed(); + handler.failed(afterJob); } return result; } diff --git a/dinky-core/src/main/java/org/dinky/job/JobHandler.java b/dinky-core/src/main/java/org/dinky/job/JobHandler.java index 548ba659b1..a38773e9c6 100644 --- a/dinky-core/src/main/java/org/dinky/job/JobHandler.java +++ b/dinky-core/src/main/java/org/dinky/job/JobHandler.java @@ -32,13 +32,11 @@ public interface JobHandler { boolean init(Job job); - void setJob(Job job); - boolean running(); - boolean success(); + boolean success(Job job); - boolean failed(); + boolean failed(Job job); boolean close(); From e91a68d3be656ad657fdbfe53f3de6e5cd4dbd1e Mon Sep 17 00:00:00 2001 From: sunlichao11 Date: Sun, 31 Mar 2024 20:53:08 +0800 Subject: [PATCH 34/87] feat: refactor remove dinky-core dependence and could compile --- .idea/icon.svg | 1400 ----------------- .idea/vcs.xml | 1 - dinky-admin/pom.xml | 60 +- .../dinky/controller/DownloadController.java | 7 +- .../org/dinky/controller/FlinkController.java | 6 +- .../org/dinky/controller/JarController.java | 5 +- .../main/java/org/dinky/init/SystemInit.java | 10 +- .../dinky/interceptor/TenantInterceptor.java | 2 +- .../main/java/org/dinky/job/JobManager.java | 306 +++- .../dinky/job/handler/JobRefreshHandler.java | 31 +- .../impl/ClusterConfigurationServiceImpl.java | 5 +- .../impl/ClusterInstanceServiceImpl.java | 15 +- .../service/impl/GitProjectServiceImpl.java | 5 +- .../service/impl/JobInstanceServiceImpl.java | 5 +- .../service/impl/PrintTableServiceImpl.java | 13 +- .../dinky/service/impl/StudioServiceImpl.java | 64 +- .../dinky/service/impl/TaskServiceImpl.java | 28 +- .../dinky/service/impl/UDFServiceImpl.java | 11 +- .../dinky/service/impl/UserServiceImpl.java | 6 +- .../resource/impl/ResourceServiceImpl.java | 22 +- .../java/org/dinky/service/task/UdfTask.java | 5 +- .../main/java/org/dinky/sse/DoneStepSse.java | 5 +- .../sse/git/AnalysisUdfClassStepSse.java | 9 +- .../sse/git/AnalysisUdfPythonStepSse.java | 5 +- .../java/org/dinky/url/RsURLConnection.java | 15 +- .../main/java/org/dinky/utils/UDFUtils.java | 6 +- .../src/main/resources/application-mysql.yml | 4 +- .../src/main/resources/application.yml | 2 +- .../dinky/resource/BaseResourceManager.java | 8 + .../resource/impl/HdfsResourceManager.java | 5 + .../resource/impl/OssResourceManager.java | 7 +- .../flink/table/catalog/FunctionLanguage.java | 1 - .../src/main/java/org/dinky/api/FlinkAPI.java | 0 .../org/dinky/cluster/FlinkClusterInfo.java | 0 .../org/dinky/constant/FlinkSQLConstant.java | 0 .../data/constant/FlinkHistoryConstant.java | 0 .../data/constant/FlinkRestAPIConstant.java | 0 .../constant/FlinkRestResultConstant.java | 0 .../org/dinky/data/model/FunctionResult.java | 0 .../java/org/dinky/data/model/LineageRel.java | 0 .../org/dinky/data/model/ResourcesVO.java | 3 +- .../java/org/dinky/data/result/DDLResult.java | 0 .../org/dinky/data/result/ResultPool.java | 0 .../org/dinky/data/result/SelectResult.java | 0 .../org/dinky/executor/ExecutorConfig.java | 0 .../explainer/lineage/LineageColumn.java | 0 .../explainer/lineage/LineageRelation.java | 0 .../explainer/lineage/LineageResult.java | 0 .../dinky/explainer/lineage/LineageTable.java | 0 .../compiler/CustomStringJavaCompiler.java | 0 .../dinky/function/constant/PathConstant.java | 0 .../org/dinky/function/data/model/UDF.java | 0 .../dinky/function/data/model/UDFPath.java | 0 .../org/dinky/function/util/ZipWriter.java | 0 .../org/dinky/gateway/config/AppConfig.java | 0 .../dinky/gateway/config/ClusterConfig.java | 0 .../org/dinky/gateway/config/FlinkConfig.java | 0 .../dinky/gateway/config/GatewayConfig.java | 0 .../org/dinky/gateway/config/K8sConfig.java | 0 .../org/dinky/gateway/enums/ActionType.java | 0 .../gateway/enums/SavePointStrategy.java | 0 .../dinky/gateway/enums/SavePointType.java | 0 .../org/dinky/gateway/model/CustomConfig.java | 0 .../gateway/model/FlinkClusterConfig.java | 0 .../java/org/dinky/gateway/model/JobInfo.java | 0 .../gateway/result/AbstractGatewayResult.java | 0 .../dinky/gateway/result/GatewayResult.java | 0 .../dinky/gateway/result/SavePointResult.java | 0 .../org/dinky/gateway/result/TestResult.java | 0 .../src/main/java/org/dinky/job/Job.java | 0 .../main/java/org/dinky/job/JobConfig.java | 21 +- .../main/java/org/dinky/job/JobHandler.java | 0 .../main/java/org/dinky/job/JobResult.java | 2 +- .../dinky/metadata/config/DriverConfig.java | 0 .../dinky/metadata/config/IConnectConfig.java | 0 .../metadata/result/JdbcSelectResult.java | 0 .../dinky/remote/ServerExecutorService.java | 147 ++ .../java/org/dinky/job/JobManagerHandler.java | 2 +- .../dinky/job/builder/JobTransBuilder.java | 2 +- .../org/dinky/utils/DinkyClassLoaderUtil.java | 3 +- .../java/org/dinky/JobManagerServiceImpl.java | 214 ++- .../src/main/java/org/dinky/RMIServer.java | 2 + .../java/org/dinky/ServerExecutorService.java | 63 - dinky-function/pom.xml | 4 + .../dinky/function/compiler/JavaCompiler.java | 1 - .../java/org/dinky/function/util/UDFUtil.java | 82 +- 86 files changed, 878 insertions(+), 1742 deletions(-) delete mode 100644 .idea/icon.svg rename {dinky-function => dinky-common}/src/main/java/org/apache/flink/table/catalog/FunctionLanguage.java (92%) rename {dinky-core => dinky-common}/src/main/java/org/dinky/api/FlinkAPI.java (100%) rename {dinky-core => dinky-common}/src/main/java/org/dinky/cluster/FlinkClusterInfo.java (100%) rename {dinky-core => dinky-common}/src/main/java/org/dinky/constant/FlinkSQLConstant.java (100%) rename {dinky-core => dinky-common}/src/main/java/org/dinky/data/constant/FlinkHistoryConstant.java (100%) rename {dinky-core => dinky-common}/src/main/java/org/dinky/data/constant/FlinkRestAPIConstant.java (100%) rename {dinky-core => dinky-common}/src/main/java/org/dinky/data/constant/FlinkRestResultConstant.java (100%) rename {dinky-client/dinky-client-base => dinky-common}/src/main/java/org/dinky/data/model/FunctionResult.java (100%) rename {dinky-client/dinky-client-base => dinky-common}/src/main/java/org/dinky/data/model/LineageRel.java (100%) rename {dinky-core => dinky-common}/src/main/java/org/dinky/data/result/DDLResult.java (100%) rename {dinky-core => dinky-common}/src/main/java/org/dinky/data/result/ResultPool.java (100%) rename {dinky-core => dinky-common}/src/main/java/org/dinky/data/result/SelectResult.java (100%) rename {dinky-core => dinky-common}/src/main/java/org/dinky/executor/ExecutorConfig.java (100%) rename {dinky-core => dinky-common}/src/main/java/org/dinky/explainer/lineage/LineageColumn.java (100%) rename {dinky-core => dinky-common}/src/main/java/org/dinky/explainer/lineage/LineageRelation.java (100%) rename {dinky-core => dinky-common}/src/main/java/org/dinky/explainer/lineage/LineageResult.java (100%) rename {dinky-core => dinky-common}/src/main/java/org/dinky/explainer/lineage/LineageTable.java (100%) rename {dinky-function => dinky-common}/src/main/java/org/dinky/function/compiler/CustomStringJavaCompiler.java (100%) rename {dinky-function => dinky-common}/src/main/java/org/dinky/function/constant/PathConstant.java (100%) rename {dinky-function => dinky-common}/src/main/java/org/dinky/function/data/model/UDF.java (100%) rename {dinky-function => dinky-common}/src/main/java/org/dinky/function/data/model/UDFPath.java (100%) rename {dinky-function => dinky-common}/src/main/java/org/dinky/function/util/ZipWriter.java (100%) rename {dinky-gateway => dinky-common}/src/main/java/org/dinky/gateway/config/AppConfig.java (100%) rename {dinky-gateway => dinky-common}/src/main/java/org/dinky/gateway/config/ClusterConfig.java (100%) rename {dinky-gateway => dinky-common}/src/main/java/org/dinky/gateway/config/FlinkConfig.java (100%) rename {dinky-gateway => dinky-common}/src/main/java/org/dinky/gateway/config/GatewayConfig.java (100%) rename {dinky-gateway => dinky-common}/src/main/java/org/dinky/gateway/config/K8sConfig.java (100%) rename {dinky-gateway => dinky-common}/src/main/java/org/dinky/gateway/enums/ActionType.java (100%) rename {dinky-gateway => dinky-common}/src/main/java/org/dinky/gateway/enums/SavePointStrategy.java (100%) rename {dinky-gateway => dinky-common}/src/main/java/org/dinky/gateway/enums/SavePointType.java (100%) rename {dinky-gateway => dinky-common}/src/main/java/org/dinky/gateway/model/CustomConfig.java (100%) rename {dinky-gateway => dinky-common}/src/main/java/org/dinky/gateway/model/FlinkClusterConfig.java (100%) rename {dinky-gateway => dinky-common}/src/main/java/org/dinky/gateway/model/JobInfo.java (100%) rename {dinky-gateway => dinky-common}/src/main/java/org/dinky/gateway/result/AbstractGatewayResult.java (100%) rename {dinky-gateway => dinky-common}/src/main/java/org/dinky/gateway/result/GatewayResult.java (100%) rename {dinky-gateway => dinky-common}/src/main/java/org/dinky/gateway/result/SavePointResult.java (100%) rename {dinky-gateway => dinky-common}/src/main/java/org/dinky/gateway/result/TestResult.java (100%) rename {dinky-core => dinky-common}/src/main/java/org/dinky/job/Job.java (100%) rename {dinky-core => dinky-common}/src/main/java/org/dinky/job/JobConfig.java (92%) rename {dinky-core => dinky-common}/src/main/java/org/dinky/job/JobHandler.java (100%) rename {dinky-core => dinky-common}/src/main/java/org/dinky/job/JobResult.java (100%) rename {dinky-metadata/dinky-metadata-base => dinky-common}/src/main/java/org/dinky/metadata/config/DriverConfig.java (100%) rename {dinky-metadata/dinky-metadata-base => dinky-common}/src/main/java/org/dinky/metadata/config/IConnectConfig.java (100%) rename {dinky-metadata/dinky-metadata-base => dinky-common}/src/main/java/org/dinky/metadata/result/JdbcSelectResult.java (100%) create mode 100644 dinky-common/src/main/java/org/dinky/remote/ServerExecutorService.java delete mode 100644 dinky-executor-server/src/main/java/org/dinky/ServerExecutorService.java diff --git a/.idea/icon.svg b/.idea/icon.svg deleted file mode 100644 index b4d5df8343..0000000000 --- a/.idea/icon.svg +++ /dev/null @@ -1,1400 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/.idea/vcs.xml b/.idea/vcs.xml index 830674470f..35eb1ddfbb 100644 --- a/.idea/vcs.xml +++ b/.idea/vcs.xml @@ -2,6 +2,5 @@ - \ No newline at end of file diff --git a/dinky-admin/pom.xml b/dinky-admin/pom.xml index aab8efdb1e..5620d6638f 100644 --- a/dinky-admin/pom.xml +++ b/dinky-admin/pom.xml @@ -36,10 +36,10 @@ - - org.dinky - dinky-executor-server - + + + + org.mitre.dsmiley.httpproxy smiley-http-proxy-servlet @@ -177,28 +177,28 @@ org.springframework.boot spring-boot-starter-actuator - - org.dinky - dinky-gateway - - - org.dinky - dinky-core - - - cn.hutool - hutool-crypto - - - cn.hutool - hutool-http - - - cn.hutool - hutool-json - - - + + + + + + + + + + + + + + + + + + + + + + org.dinky dinky-daemon @@ -219,10 +219,10 @@ org.dinky dinky-alert-base - - org.dinky - dinky-client-base - + + + + org.dinky dinky-client-hadoop diff --git a/dinky-admin/src/main/java/org/dinky/controller/DownloadController.java b/dinky-admin/src/main/java/org/dinky/controller/DownloadController.java index b6d0fbf6de..77d6732867 100644 --- a/dinky-admin/src/main/java/org/dinky/controller/DownloadController.java +++ b/dinky-admin/src/main/java/org/dinky/controller/DownloadController.java @@ -24,7 +24,7 @@ import org.dinky.data.model.FlinkUdfManifest; import org.dinky.function.constant.PathConstant; import org.dinky.function.util.ZipWriter; -import org.dinky.resource.BaseResourceManager; +//import org.dinky.resource.BaseResourceManager; import java.io.File; import java.io.InputStream; @@ -117,7 +117,8 @@ public void downloadAppJar(@PathVariable String version, HttpServletResponse res @GetMapping("downloadFromRs") @ApiOperation("Download From Resource") public void downloadJavaUDF(String path, HttpServletResponse resp) { - InputStream inputStream = BaseResourceManager.getInstance().readFile(path); - ServletUtil.write(resp, inputStream); + // TODO: 2024/3/31 +// InputStream inputStream = BaseResourceManager.getInstance().readFile(path); +// ServletUtil.write(resp, inputStream); } } diff --git a/dinky-admin/src/main/java/org/dinky/controller/FlinkController.java b/dinky-admin/src/main/java/org/dinky/controller/FlinkController.java index df2c545bc7..582fbeee2e 100644 --- a/dinky-admin/src/main/java/org/dinky/controller/FlinkController.java +++ b/dinky-admin/src/main/java/org/dinky/controller/FlinkController.java @@ -22,7 +22,8 @@ import org.dinky.data.model.CheckPointReadTable; import org.dinky.data.result.Result; import org.dinky.data.vo.CascaderVO; -import org.dinky.flink.checkpoint.CheckpointRead; +import org.dinky.job.JobConfig; +import org.dinky.job.JobManager; import org.dinky.service.FlinkService; import java.util.List; @@ -44,13 +45,12 @@ @RequiredArgsConstructor public class FlinkController { - protected static final CheckpointRead INSTANCE = new CheckpointRead(); private final FlinkService flinkService; @GetMapping("/readCheckPoint") @ApiOperation("Read Checkpoint") public Result>> readCheckPoint(String path, String operatorId) { - return Result.data(INSTANCE.readCheckpoint(path, operatorId)); + return Result.data(JobManager.build(new JobConfig()).readCheckpoint(path, operatorId)); } @GetMapping("/configOptions") diff --git a/dinky-admin/src/main/java/org/dinky/controller/JarController.java b/dinky-admin/src/main/java/org/dinky/controller/JarController.java index bad026c07a..cf47d75e18 100644 --- a/dinky-admin/src/main/java/org/dinky/controller/JarController.java +++ b/dinky-admin/src/main/java/org/dinky/controller/JarController.java @@ -23,7 +23,8 @@ import org.dinky.data.result.Result; import org.dinky.function.constant.PathConstant; import org.dinky.function.data.model.UDF; -import org.dinky.function.util.UDFUtil; +import org.dinky.job.JobConfig; +import org.dinky.job.JobManager; import org.dinky.service.TaskService; import org.apache.flink.table.catalog.FunctionLanguage; @@ -68,7 +69,7 @@ public Result>> generateJar() { FunctionLanguage.valueOf(task.getDialect().toUpperCase())) .build()) .collect(Collectors.toList()); - Map> resultMap = UDFUtil.buildJar(udfCodes); + Map> resultMap = JobManager.build(new JobConfig()).buildJar(udfCodes); String msg = StrUtil.format( "udf jar生成成功,jar文件在{};\n本次成功 class:{}。\n失败 class:{}", PathConstant.UDF_JAR_TMP_PATH, diff --git a/dinky-admin/src/main/java/org/dinky/init/SystemInit.java b/dinky-admin/src/main/java/org/dinky/init/SystemInit.java index a5605b1f59..cffa31512e 100644 --- a/dinky-admin/src/main/java/org/dinky/init/SystemInit.java +++ b/dinky-admin/src/main/java/org/dinky/init/SystemInit.java @@ -32,11 +32,11 @@ import org.dinky.data.model.job.JobInstance; import org.dinky.data.model.rbac.Tenant; import org.dinky.function.constant.PathConstant; -import org.dinky.function.pool.UdfCodePool; import org.dinky.job.ClearJobHistoryTask; import org.dinky.job.FlinkJobTask; +import org.dinky.job.JobConfig; +import org.dinky.job.JobManager; import org.dinky.job.SystemMetricsTask; -import org.dinky.resource.BaseResourceManager; import org.dinky.scheduler.client.ProjectClient; import org.dinky.scheduler.exception.SchedulerException; import org.dinky.scheduler.model.Project; @@ -134,7 +134,7 @@ private void initResources() { if (Boolean.TRUE.equals( systemConfiguration.getResourcesEnable().getValue())) { try { - BaseResourceManager.initResourceManager(); + JobManager.build(new JobConfig()).initResourceManager(); } catch (Exception e) { log.error("Init resource error: ", e); } @@ -223,9 +223,9 @@ public static Project getProject() { public void registerUDF() { List allUDF = taskService.getReleaseUDF(); if (CollUtil.isNotEmpty(allUDF)) { - UdfCodePool.registerPool(allUDF.stream().map(UDFUtils::taskToUDF).collect(Collectors.toList())); + JobManager.build(new JobConfig()).registerPool(allUDF.stream().map(UDFUtils::taskToUDF).collect(Collectors.toList())); } - UdfCodePool.updateGitPool(gitProjectService.getGitPool()); + JobManager.build(new JobConfig()).updateGitPool(gitProjectService.getGitPool()); } public void updateGitBuildState() { diff --git a/dinky-admin/src/main/java/org/dinky/interceptor/TenantInterceptor.java b/dinky-admin/src/main/java/org/dinky/interceptor/TenantInterceptor.java index 330509e29f..5a7ec9a8cd 100644 --- a/dinky-admin/src/main/java/org/dinky/interceptor/TenantInterceptor.java +++ b/dinky-admin/src/main/java/org/dinky/interceptor/TenantInterceptor.java @@ -34,8 +34,8 @@ import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import javax.validation.constraints.NotNull; -import org.jetbrains.annotations.NotNull; import org.springframework.web.servlet.AsyncHandlerInterceptor; import cn.dev33.satoken.SaManager; diff --git a/dinky-admin/src/main/java/org/dinky/job/JobManager.java b/dinky-admin/src/main/java/org/dinky/job/JobManager.java index dc6cde21c8..b52d8fa90f 100644 --- a/dinky-admin/src/main/java/org/dinky/job/JobManager.java +++ b/dinky-admin/src/main/java/org/dinky/job/JobManager.java @@ -19,7 +19,21 @@ package org.dinky.job; -import org.dinky.ServerExecutorService; +import org.dinky.cluster.FlinkClusterInfo; +import org.dinky.data.enums.JobStatus; +import org.dinky.data.model.Catalog; +import org.dinky.data.model.CheckPointReadTable; +import org.dinky.data.model.Column; +import org.dinky.data.model.ResourcesVO; +import org.dinky.data.model.Schema; +import org.dinky.data.model.Table; +import org.dinky.explainer.lineage.LineageResult; +import org.dinky.function.data.model.UDF; +import org.dinky.function.data.model.UDFPath; +import org.dinky.gateway.config.GatewayConfig; +import org.dinky.gateway.result.GatewayResult; +import org.dinky.metadata.config.DriverConfig; +import org.dinky.remote.ServerExecutorService; import org.dinky.data.annotations.ProcessStep; import org.dinky.data.enums.ProcessStepType; import org.dinky.data.result.ExplainResult; @@ -32,6 +46,9 @@ import java.rmi.RemoteException; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import com.fasterxml.jackson.databind.node.ObjectNode; @@ -39,23 +56,26 @@ @Slf4j public class JobManager { - private ServerExecutorService jobManagerHandler; + private static ServerExecutorService serverExecutorService; - private JobManager(JobConfig config, boolean isPlanMode) { + static { registerRemote(); + } + + private JobManager(JobConfig config, boolean isPlanMode) { try { - jobManagerHandler.init(config, isPlanMode); + serverExecutorService.init(config, isPlanMode); } catch (RemoteException e) { throw new RuntimeException(e); } } - private void registerRemote() { + private static void registerRemote() { try { Registry registry = LocateRegistry.getRegistry("localhost"); // 从Registry中检索远程对象的存根/代理 - jobManagerHandler = (ServerExecutorService) registry.lookup("Compute"); + serverExecutorService = (ServerExecutorService) registry.lookup("Compute"); } catch (Exception exception) { System.out.println(exception); } @@ -71,7 +91,7 @@ public static JobManager build(JobConfig config, boolean isPlanMode) { public boolean close() { try { - return jobManagerHandler.close(); + return serverExecutorService.close(); } catch (RemoteException e) { throw new RuntimeException(e); } @@ -79,7 +99,7 @@ public boolean close() { public ObjectNode getJarStreamGraphJson(String statement) { try { - return jobManagerHandler.getJarStreamGraphJson(statement); + return serverExecutorService.getJarStreamGraphJson(statement); } catch (RemoteException e) { throw new RuntimeException(e); } @@ -87,7 +107,7 @@ public ObjectNode getJarStreamGraphJson(String statement) { public void prepare(String statement) { try { - jobManagerHandler.prepare(statement); + serverExecutorService.prepare(statement); } catch (RemoteException e) { throw new RuntimeException(e); } @@ -95,17 +115,17 @@ public void prepare(String statement) { @ProcessStep(type = ProcessStepType.SUBMIT_EXECUTE) public JobResult executeJarSql(String statement) throws Exception { - return jobManagerHandler.executeJarSql(statement); + return serverExecutorService.executeJarSql(statement); } @ProcessStep(type = ProcessStepType.SUBMIT_EXECUTE) public JobResult executeSql(String statement) throws Exception { - return jobManagerHandler.executeSql(statement); + return serverExecutorService.executeSql(statement); } public IResult executeDDL(String statement) { try { - return jobManagerHandler.executeDDL(statement); + return serverExecutorService.executeDDL(statement); } catch (RemoteException e) { throw new RuntimeException(e); } @@ -117,7 +137,7 @@ public static SelectResult getJobData(String jobId) { public ExplainResult explainSql(String statement) { try { - return jobManagerHandler.explainSql(statement); + return serverExecutorService.explainSql(statement); } catch (RemoteException e) { throw new RuntimeException(e); } @@ -125,7 +145,7 @@ public ExplainResult explainSql(String statement) { public ObjectNode getStreamGraph(String statement) { try { - return jobManagerHandler.getStreamGraph(statement); + return serverExecutorService.getStreamGraph(statement); } catch (RemoteException e) { throw new RuntimeException(e); } @@ -133,7 +153,7 @@ public ObjectNode getStreamGraph(String statement) { public String getJobPlanJson(String statement) { try { - return jobManagerHandler.getJobPlanJson(statement); + return serverExecutorService.getJobPlanJson(statement); } catch (RemoteException e) { throw new RuntimeException(e); } @@ -141,7 +161,7 @@ public String getJobPlanJson(String statement) { public boolean cancelNormal(String jobId) { try { - return jobManagerHandler.cancelNormal(jobId); + return serverExecutorService.cancelNormal(jobId); } catch (RemoteException e) { throw new RuntimeException(e); } @@ -149,7 +169,7 @@ public boolean cancelNormal(String jobId) { public SavePointResult savepoint(String jobId, SavePointType savePointType, String savePoint) { try { - return jobManagerHandler.savepoint(jobId, savePointType, savePoint); + return serverExecutorService.savepoint(jobId, savePointType, savePoint); } catch (RemoteException e) { throw new RuntimeException(e); } @@ -157,7 +177,7 @@ public SavePointResult savepoint(String jobId, SavePointType savePointType, Stri public String exportSql(String sql) { try { - return jobManagerHandler.exportSql(sql); + return serverExecutorService.exportSql(sql); } catch (RemoteException e) { throw new RuntimeException(e); } @@ -165,7 +185,255 @@ public String exportSql(String sql) { public Job getJob() { try { - return jobManagerHandler.getJob(); + return serverExecutorService.getJob(); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + public List getPythonUdfList(String udfFile) { + try { + return serverExecutorService.getPythonUdfList(udfFile); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + public JobStatus getJobStatus(GatewayConfig gatewayConfig, String appId) { + try { + return serverExecutorService.getJobStatus(gatewayConfig, appId); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + public void onJobGatewayFinishCallback(JobConfig jobConfig, String status) { + try { + serverExecutorService.onJobGatewayFinishCallback(jobConfig, status); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + public List getUdfClassNameByJarPath(String path) { + try { + return serverExecutorService.getUdfClassNameByJarPath(path); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + public void putFile(String fullName, byte[] context) { + try { + serverExecutorService.putFile(fullName, context); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + public List getFullDirectoryStructure(int rootId) { + try { + return serverExecutorService.getFullDirectoryStructure(rootId); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + public void rename(String path, String newPath) { + try { + serverExecutorService.rename(path, newPath); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + public String getFileContent(String path) { + try { + return serverExecutorService.getFileContent(path); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + public void updateGitPool(Map newPool) { + try { + serverExecutorService.updateGitPool(newPool); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + public UDFPath initUDF(List udfClassList, Integer missionId) { + try { + return serverExecutorService.initUDF(udfClassList, missionId); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + public LineageResult getColumnLineageByLogicalPlan(String statement) { + try { + return serverExecutorService.getColumnLineageByLogicalPlan(statement); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + public LineageResult getSqlLineage(String statement, String mysql, DriverConfig> driverConfig) { + try { + return serverExecutorService.getSqlLineage(statement, mysql, driverConfig); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + public List getCatalog() { + try { + return serverExecutorService.getCatalog(); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + public void setSchemaInfo( + String catalogName, + String databaseName, + Schema schema, + List tables) { + try { + serverExecutorService.setSchemaInfo( + catalogName, + databaseName, + schema, + tables); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + public List getColumnList(String catalogName, String databaseName, String tableName) { + try { + return serverExecutorService.getColumnList(catalogName, databaseName, tableName); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + public Map> readCheckpoint(String path, String operatorId) { + try { + return serverExecutorService.readCheckpoint(path, operatorId); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + public byte[] readFIle(String path) { + try { + return serverExecutorService.readFile(path); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + public Map> buildJar(List udfCodes) { + try { + return serverExecutorService.buildJar(udfCodes); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + public void buildRowPermission(ConcurrentHashMap permission) { + try { + serverExecutorService.buildRowPermission(permission); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + public List getPrintTable(String statement) { + try { + return serverExecutorService.getPrintTables(statement); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + public FlinkClusterInfo testFlinkJobManagerIP(String hosts, String host) { + try { + return serverExecutorService.testFlinkJobManagerIP(hosts, host); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + public void killCluster(GatewayConfig gatewayConfig) { + try { + serverExecutorService.killCluster(gatewayConfig); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + public GatewayResult deployCluster(GatewayConfig gatewayConfig) { + try { + return serverExecutorService.deployCluster(gatewayConfig); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + public void addOrUpdateUdfCodePool(UDF udf) { + try { + serverExecutorService.addOrUpdate(udf); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + public void removeUdfCodePool(String className) { + try { + serverExecutorService.removeUdfCodePool(className); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + public String templateParse(String dialect, String templateCode, String className) { + try { + return serverExecutorService.templateParse(dialect, templateCode, className); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + public void registerPool(List collect) { + try { + serverExecutorService.registerPool(collect); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + public void initResourceManager() { + try { + serverExecutorService.initResourceManager(); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + public String getPyUDFAttr(String statement) { + try { + return serverExecutorService.getPyUDFAttr(statement); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + public String getScalaFullClassName(String statement) { + try { + return serverExecutorService.getScalaFullClassName(statement); } catch (RemoteException e) { throw new RuntimeException(e); } diff --git a/dinky-admin/src/main/java/org/dinky/job/handler/JobRefreshHandler.java b/dinky-admin/src/main/java/org/dinky/job/handler/JobRefreshHandler.java index 9a1057dd12..2818361e2e 100644 --- a/dinky-admin/src/main/java/org/dinky/job/handler/JobRefreshHandler.java +++ b/dinky-admin/src/main/java/org/dinky/job/handler/JobRefreshHandler.java @@ -36,11 +36,10 @@ import org.dinky.data.flink.watermark.FlinkJobNodeWaterMark; import org.dinky.data.model.ext.JobInfoDetail; import org.dinky.data.model.job.JobInstance; -import org.dinky.gateway.Gateway; import org.dinky.gateway.config.GatewayConfig; -import org.dinky.gateway.exception.NotSupportGetStatusException; import org.dinky.gateway.model.FlinkClusterConfig; import org.dinky.job.JobConfig; +import org.dinky.job.JobManager; import org.dinky.service.JobHistoryService; import org.dinky.service.JobInstanceService; import org.dinky.utils.JsonUtils; @@ -159,9 +158,9 @@ public static boolean refreshJob(JobInfoDetail jobInfoDetail, boolean needSave) boolean isDone = (JobStatus.isDone(jobInstance.getStatus())) || (TimeUtil.localDateTimeToLong(jobInstance.getFinishTime()) > 0 - && Duration.between(jobInstance.getFinishTime(), LocalDateTime.now()) - .toMinutes() - >= 1); + && Duration.between(jobInstance.getFinishTime(), LocalDateTime.now()) + .toMinutes() + >= 1); isDone = !isTransition && isDone; @@ -245,21 +244,15 @@ private static JobStatus getJobStatus(JobInfoDetail jobInfoDetail) { if (!Asserts.isNull(clusterCfg) && GatewayType.YARN_PER_JOB.getLongValue().equals(clusterCfg.getType())) { - try { - String appId = jobInfoDetail.getClusterInstance().getName(); + String appId = jobInfoDetail.getClusterInstance().getName(); - GatewayConfig gatewayConfig = GatewayConfig.build(clusterCfg.getConfig()); - gatewayConfig.getClusterConfig().setAppId(appId); - gatewayConfig - .getFlinkConfig() - .setJobName(jobInfoDetail.getInstance().getName()); + GatewayConfig gatewayConfig = GatewayConfig.build(clusterCfg.getConfig()); + gatewayConfig.getClusterConfig().setAppId(appId); + gatewayConfig + .getFlinkConfig() + .setJobName(jobInfoDetail.getInstance().getName()); - Gateway gateway = Gateway.build(gatewayConfig); - return gateway.getJobStatusById(appId); - } catch (NotSupportGetStatusException ignored) { - // if the gateway does not support get status, then use the api to get job status - // ignore to do something here - } + return JobManager.build(new JobConfig()).getJobStatus(gatewayConfig, appId); } JobDataDto jobDataDto = jobInfoDetail.getJobDataDto(); String status = jobDataDto.getJob().getState(); @@ -282,7 +275,7 @@ private static void handleJobDone(JobInfoDetail jobInfoDetail) { jobConfig.buildGatewayConfig(configJson); jobConfig.getGatewayConfig().setType(GatewayType.get(clusterType)); jobConfig.getGatewayConfig().getFlinkConfig().setJobName(jobInstance.getName()); - Gateway.build(jobConfig.getGatewayConfig()).onJobFinishCallback(jobInstance.getStatus()); + JobManager.build(new JobConfig()).onJobGatewayFinishCallback(jobConfig, jobInstance.getStatus()); } } } diff --git a/dinky-admin/src/main/java/org/dinky/service/impl/ClusterConfigurationServiceImpl.java b/dinky-admin/src/main/java/org/dinky/service/impl/ClusterConfigurationServiceImpl.java index 322a7bba16..8653cdd894 100644 --- a/dinky-admin/src/main/java/org/dinky/service/impl/ClusterConfigurationServiceImpl.java +++ b/dinky-admin/src/main/java/org/dinky/service/impl/ClusterConfigurationServiceImpl.java @@ -26,7 +26,6 @@ import org.dinky.data.exception.BusException; import org.dinky.data.model.ClusterConfiguration; import org.dinky.data.model.Task; -import org.dinky.gateway.Gateway; import org.dinky.gateway.config.GatewayConfig; import org.dinky.gateway.model.FlinkClusterConfig; import org.dinky.gateway.result.TestResult; @@ -86,7 +85,9 @@ public TestResult testGateway(ClusterConfigurationDTO config) { } public static TestResult testGateway(GatewayConfig gatewayConfig) { - return Gateway.build(gatewayConfig).test(); + // TODO: 2024/3/31 + // return Gateway.build(gatewayConfig).test(); + return null; } /** diff --git a/dinky-admin/src/main/java/org/dinky/service/impl/ClusterInstanceServiceImpl.java b/dinky-admin/src/main/java/org/dinky/service/impl/ClusterInstanceServiceImpl.java index d12e9e6094..3f8872fd9e 100644 --- a/dinky-admin/src/main/java/org/dinky/service/impl/ClusterInstanceServiceImpl.java +++ b/dinky-admin/src/main/java/org/dinky/service/impl/ClusterInstanceServiceImpl.java @@ -21,7 +21,6 @@ import org.dinky.assertion.Asserts; import org.dinky.assertion.DinkyAssert; -import org.dinky.cluster.FlinkCluster; import org.dinky.cluster.FlinkClusterInfo; import org.dinky.data.dto.ClusterInstanceDTO; import org.dinky.data.enums.GatewayType; @@ -31,13 +30,11 @@ import org.dinky.data.model.ClusterConfiguration; import org.dinky.data.model.ClusterInstance; import org.dinky.data.model.Task; -import org.dinky.function.util.UDFUtil; -import org.dinky.gateway.Gateway; import org.dinky.gateway.config.GatewayConfig; -import org.dinky.gateway.exception.GatewayException; import org.dinky.gateway.model.FlinkClusterConfig; import org.dinky.gateway.result.GatewayResult; import org.dinky.job.JobConfig; +import org.dinky.job.JobManager; import org.dinky.mapper.ClusterInstanceMapper; import org.dinky.mybatis.service.impl.SuperServiceImpl; import org.dinky.service.ClusterConfigurationService; @@ -83,7 +80,7 @@ public class ClusterInstanceServiceImpl extends SuperServiceImpl UdfCodePool.updateGitPool(getGitPool())); + ThreadUtil.execAsync(() -> JobManager.build(new JobConfig()).updateGitPool(getGitPool())); } /** @param gitProjectDTOList */ diff --git a/dinky-admin/src/main/java/org/dinky/service/impl/JobInstanceServiceImpl.java b/dinky-admin/src/main/java/org/dinky/service/impl/JobInstanceServiceImpl.java index 0de903ed38..62dbe1c8e9 100644 --- a/dinky-admin/src/main/java/org/dinky/service/impl/JobInstanceServiceImpl.java +++ b/dinky-admin/src/main/java/org/dinky/service/impl/JobInstanceServiceImpl.java @@ -41,9 +41,10 @@ import org.dinky.data.model.mapping.ClusterInstanceMapping; import org.dinky.data.result.ProTableResult; import org.dinky.data.vo.task.JobInstanceVo; -import org.dinky.explainer.lineage.LineageBuilder; import org.dinky.explainer.lineage.LineageResult; import org.dinky.job.FlinkJobTask; +import org.dinky.job.JobConfig; +import org.dinky.job.JobManager; import org.dinky.mapper.JobInstanceMapper; import org.dinky.mybatis.service.impl.SuperServiceImpl; import org.dinky.mybatis.util.ProTableUtil; @@ -254,7 +255,7 @@ public void refreshJobByTaskIds(Integer... taskIds) { @Override public LineageResult getLineage(Integer id) { History history = getJobInfoDetail(id).getHistory(); - return LineageBuilder.getColumnLineageByLogicalPlan(history.getStatement()); + return JobManager.build(new JobConfig()).getColumnLineageByLogicalPlan(history.getStatement()); } @Override diff --git a/dinky-admin/src/main/java/org/dinky/service/impl/PrintTableServiceImpl.java b/dinky-admin/src/main/java/org/dinky/service/impl/PrintTableServiceImpl.java index 1ecc09d816..b4c74776d4 100644 --- a/dinky-admin/src/main/java/org/dinky/service/impl/PrintTableServiceImpl.java +++ b/dinky-admin/src/main/java/org/dinky/service/impl/PrintTableServiceImpl.java @@ -22,10 +22,9 @@ import org.dinky.context.SseSessionContextHolder; import org.dinky.data.enums.SseTopic; import org.dinky.data.vo.PrintTableVo; -import org.dinky.explainer.print_table.PrintStatementExplainer; -import org.dinky.parser.SqlType; +import org.dinky.job.JobConfig; +import org.dinky.job.JobManager; import org.dinky.service.PrintTableService; -import org.dinky.trans.Operations; import org.dinky.utils.SqlUtil; import java.net.DatagramPacket; @@ -61,13 +60,7 @@ public PrintTableServiceImpl() { @Override public List getPrintTables(String statement) { // TODO: 2023/4/7 this function not support variable sql, because, JobManager and executor - // couple function - // and status and task execute. - final String[] statements = SqlUtil.getStatements(SqlUtil.removeNote(statement)); - return Arrays.stream(statements) - .filter(t -> SqlType.PRINT.equals(Operations.getOperationType(t))) - .flatMap(t -> Arrays.stream(PrintStatementExplainer.splitTableNames(t))) - .map(t -> new PrintTableVo(t, getFullTableName(t))) + return JobManager.build(new JobConfig()).getPrintTable(statement).stream().map(t -> new PrintTableVo(t, getFullTableName(t))) .collect(Collectors.toList()); } diff --git a/dinky-admin/src/main/java/org/dinky/service/impl/StudioServiceImpl.java b/dinky-admin/src/main/java/org/dinky/service/impl/StudioServiceImpl.java index 00bfb2c127..0633f3110f 100644 --- a/dinky-admin/src/main/java/org/dinky/service/impl/StudioServiceImpl.java +++ b/dinky-admin/src/main/java/org/dinky/service/impl/StudioServiceImpl.java @@ -34,9 +34,7 @@ import org.dinky.data.result.DDLResult; import org.dinky.data.result.IResult; import org.dinky.data.result.SelectResult; -import org.dinky.explainer.lineage.LineageBuilder; import org.dinky.explainer.lineage.LineageResult; -import org.dinky.explainer.sqllineage.SQLLineageBuilder; import org.dinky.job.JobConfig; import org.dinky.job.JobManager; import org.dinky.metadata.driver.Driver; @@ -73,16 +71,6 @@ public class StudioServiceImpl implements StudioService { private final Cache jobManagerCache = CacheUtil.newTimedCache(1000 * 60 * 2); private final String DEFAULT_CATALOG = "default_catalog"; - private IResult executeMSFlinkSql(StudioMetaStoreDTO studioMetaStoreDTO) { - String envSql = taskService.buildEnvSql(studioMetaStoreDTO); - studioMetaStoreDTO.setStatement(studioMetaStoreDTO.getStatement() + envSql); - JobConfig config = studioMetaStoreDTO.getJobConfig(); - JobManager jobManager = JobManager.build(config); - IResult jobResult = jobManager.executeDDL(studioMetaStoreDTO.getStatement()); - RunTimeUtil.recovery(jobManager); - return jobResult; - } - @Override public IResult executeDDL(StudioDDLDTO studioDDLDTO) { JobConfig config = studioDDLDTO.getJobConfig(); @@ -98,6 +86,7 @@ public SelectResult getJobData(String jobId) { @Override public LineageResult getLineage(StudioLineageDTO studioCADTO) { // TODO 添加ProcessStep + JobManager jobManager = JobManager.build(new JobConfig()); if (Asserts.isNotNullString(studioCADTO.getDialect()) && !Dialect.FLINK_SQL.isDialect(studioCADTO.getDialect())) { if (Asserts.isNull(studioCADTO.getDatabaseId())) { @@ -110,15 +99,15 @@ public LineageResult getLineage(StudioLineageDTO studioCADTO) { return null; } if (Dialect.DORIS.isDialect(studioCADTO.getDialect())) { - return SQLLineageBuilder.getSqlLineage(studioCADTO.getStatement(), "mysql", dataBase.getDriverConfig()); + return jobManager.getSqlLineage(studioCADTO.getStatement(), "mysql", dataBase.getDriverConfig()); } else { - return SQLLineageBuilder.getSqlLineage( + return jobManager.getSqlLineage( studioCADTO.getStatement(), studioCADTO.getDialect().toLowerCase(), dataBase.getDriverConfig()); } } else { String envSql = taskService.buildEnvSql(studioCADTO); studioCADTO.setStatement(studioCADTO.getStatement() + envSql); - return LineageBuilder.getColumnLineageByLogicalPlan(studioCADTO.getStatement()); + return JobManager.build(new JobConfig()).getColumnLineageByLogicalPlan(studioCADTO.getStatement()); } } @@ -146,12 +135,9 @@ public List getMSCatalogs(StudioMetaStoreDTO studioMetaStoreDTO) { catalogs.add(defaultCatalog); } } else { - // TODO: 2024/3/20 remote - // String envSql = taskService.buildEnvSql(studioMetaStoreDTO); - // JobManager jobManager = getJobManager(studioMetaStoreDTO, envSql); - // CustomTableEnvironment customTableEnvironment = - // jobManager.getJobManagerHandler().getExecutor().getCustomTableEnvironment(); - // catalogs.addAll(FlinkTableMetadataUtil.getCatalog(customTableEnvironment)); + String envSql = taskService.buildEnvSql(studioMetaStoreDTO); + JobManager jobManager = getJobManager(studioMetaStoreDTO, envSql); + catalogs.addAll(jobManager.getCatalog()); } return catalogs; } @@ -168,13 +154,9 @@ public Schema getMSSchemaInfo(StudioMetaStoreDTO studioMetaStoreDTO) { tables.addAll(driver.listTables(database)); } } else { - // TODO: 2024/3/20 remote - // String envSql = taskService.buildEnvSql(studioMetaStoreDTO); - // JobManager jobManager = getJobManager(studioMetaStoreDTO, envSql); - // CustomTableEnvironment customTableEnvironment = - // jobManager.getJobManagerHandler().getExecutor().getCustomTableEnvironment(); - // FlinkTableMetadataUtil.setSchemaInfo( - // customTableEnvironment, studioMetaStoreDTO.getCatalog(), database, schema, tables); + String envSql = taskService.buildEnvSql(studioMetaStoreDTO); + JobManager jobManager = getJobManager(studioMetaStoreDTO, envSql); + jobManager.setSchemaInfo(studioMetaStoreDTO.getCatalog(), database, schema, tables); } schema.setTables(tables); return schema; @@ -193,14 +175,9 @@ public List getMSColumns(StudioMetaStoreDTO studioMetaStoreDTO) { columns.addAll(driver.listColumns(database, tableName)); } } else { - // TODO: 2024/3/20 remote - // String envSql = taskService.buildEnvSql(studioMetaStoreDTO); - // JobManager jobManager = getJobManager(studioMetaStoreDTO, envSql); - // CustomTableEnvironment customTableEnvironment = - // jobManager.getJobManagerHandler().getExecutor().getCustomTableEnvironment(); - // columns.addAll( - // FlinkTableMetadataUtil.getColumnList(customTableEnvironment, catalogName, database, - // tableName)); + String envSql = taskService.buildEnvSql(studioMetaStoreDTO); + JobManager jobManager = getJobManager(studioMetaStoreDTO, envSql); + jobManager.getColumnList(catalogName, database, tableName); } return columns; } @@ -214,19 +191,4 @@ private JobManager getJobManager(StudioMetaStoreDTO studioMetaStoreDTO, String e }); return jobManager; } - - private List showInfo(StudioMetaStoreDTO studioMetaStoreDTO, String baseStatement, String statement) { - List infos = new ArrayList<>(); - studioMetaStoreDTO.setStatement(baseStatement + statement); - IResult result = executeMSFlinkSql(studioMetaStoreDTO); - if (result instanceof DDLResult) { - DDLResult ddlResult = (DDLResult) result; - ddlResult.getColumns().stream().findFirst().ifPresent(key -> { - for (Map item : ddlResult.getRowData()) { - infos.add(item.get(key).toString()); - } - }); - } - return infos; - } } diff --git a/dinky-admin/src/main/java/org/dinky/service/impl/TaskServiceImpl.java b/dinky-admin/src/main/java/org/dinky/service/impl/TaskServiceImpl.java index 407c25c8dc..4edc773fd9 100644 --- a/dinky-admin/src/main/java/org/dinky/service/impl/TaskServiceImpl.java +++ b/dinky-admin/src/main/java/org/dinky/service/impl/TaskServiceImpl.java @@ -56,12 +56,8 @@ import org.dinky.data.model.udf.UDFTemplate; import org.dinky.data.result.Result; import org.dinky.data.result.SqlExplainResult; -import org.dinky.explainer.lineage.LineageBuilder; import org.dinky.explainer.lineage.LineageResult; -import org.dinky.explainer.sqllineage.SQLLineageBuilder; import org.dinky.function.compiler.CustomStringJavaCompiler; -import org.dinky.function.pool.UdfCodePool; -import org.dinky.function.util.UDFUtil; import org.dinky.gateway.enums.SavePointStrategy; import org.dinky.gateway.enums.SavePointType; import org.dinky.gateway.model.FlinkClusterConfig; @@ -556,13 +552,13 @@ public boolean changeTaskLifeRecyle(Integer taskId, JobLifeCycle lifeCycle) thro Integer taskVersionId = taskVersionService.createTaskVersionSnapshot(task); task.setVersionId(taskVersionId); if (Dialect.isUDF(task.getDialect())) { - UdfCodePool.addOrUpdate(UDFUtils.taskToUDF(task.buildTask())); + JobManager.build(new JobConfig()).addOrUpdateUdfCodePool(UDFUtils.taskToUDF(task.buildTask())); } } else { if (Dialect.isUDF(task.getDialect()) && Asserts.isNotNull(task.getConfigJson()) && Asserts.isNotNull(task.getConfigJson().getUdfConfig())) { - UdfCodePool.remove(task.getConfigJson().getUdfConfig().getClassName()); + JobManager.build(new JobConfig()).removeUdfCodePool(task.getConfigJson().getUdfConfig().getClassName()); } } boolean saved = saveOrUpdate(task.buildTask()); @@ -584,6 +580,7 @@ public boolean changeTaskLifeRecyle(Integer taskId, JobLifeCycle lifeCycle) thro @Override @Transactional(rollbackFor = Exception.class) public boolean saveOrUpdateTask(Task task) { + JobManager jobManager = JobManager.build(new JobConfig()); Task byId = getById(task.getId()); if (byId != null && JobLifeCycle.PUBLISH.equalsValue(byId.getStep())) { throw new BusException(Status.TASK_IS_ONLINE.getMessage()); @@ -600,7 +597,7 @@ public boolean saveOrUpdateTask(Task task) { UDFTemplate template = udfTemplateService.getById(taskConfigJson.getUdfConfig().getTemplateId()); if (template != null) { - String code = UDFUtil.templateParse( + String code = jobManager.templateParse( task.getDialect(), template.getTemplateCode(), taskConfigJson.getUdfConfig().getClassName()); @@ -615,18 +612,18 @@ public boolean saveOrUpdateTask(Task task) { CustomStringJavaCompiler compiler = new CustomStringJavaCompiler(task.getStatement()); className = compiler.getFullClassName(); } else if (Dialect.PYTHON.isDialect(task.getDialect())) { - className = task.getName() + "." + UDFUtil.getPyUDFAttr(task.getStatement()); + className = task.getName() + "." + jobManager.getPyUDFAttr(task.getStatement()); } else if (Dialect.SCALA.isDialect(task.getDialect())) { - className = UDFUtil.getScalaFullClassName(task.getStatement()); + className = jobManager.getScalaFullClassName(task.getStatement()); } if (!task.getConfigJson().getUdfConfig().getClassName().equals(className)) { - UdfCodePool.remove(task.getConfigJson().getUdfConfig().getClassName()); + jobManager.removeUdfCodePool(task.getConfigJson().getUdfConfig().getClassName()); } task.getConfigJson().getUdfConfig().setClassName(className); if (task.getStep().equals(JobLifeCycle.PUBLISH.getValue())) { - UdfCodePool.addOrUpdate(UDFUtils.taskToUDF(task)); + jobManager.addOrUpdateUdfCodePool(UDFUtils.taskToUDF(task)); } else { - UdfCodePool.remove(task.getConfigJson().getUdfConfig().getClassName()); + jobManager.removeUdfCodePool(task.getConfigJson().getUdfConfig().getClassName()); } } @@ -948,6 +945,7 @@ public Result> queryAllCatalogue() { @Override public LineageResult getTaskLineage(Integer id) { + JobManager jobManager = JobManager.build(new JobConfig()); TaskDTO task = getTaskInfoById(id); if (!Dialect.isCommonSql(task.getDialect())) { if (Asserts.isNull(task.getDatabaseId())) { @@ -958,13 +956,13 @@ public LineageResult getTaskLineage(Integer id) { return null; } if (task.getDialect().equalsIgnoreCase("doris") || task.getDialect().equalsIgnoreCase("starrocks")) { - return SQLLineageBuilder.getSqlLineage(task.getStatement(), "mysql", dataBase.getDriverConfig()); + return jobManager.getSqlLineage(task.getStatement(), "mysql", dataBase.getDriverConfig()); } else { - return SQLLineageBuilder.getSqlLineage( + return jobManager.getSqlLineage( task.getStatement(), task.getDialect().toLowerCase(), dataBase.getDriverConfig()); } } else { - return LineageBuilder.getColumnLineageByLogicalPlan(buildEnvSql(task)); + return jobManager.getColumnLineageByLogicalPlan(buildEnvSql(task)); } } diff --git a/dinky-admin/src/main/java/org/dinky/service/impl/UDFServiceImpl.java b/dinky-admin/src/main/java/org/dinky/service/impl/UDFServiceImpl.java index 6b3d6ad7e4..44d0f36a81 100644 --- a/dinky-admin/src/main/java/org/dinky/service/impl/UDFServiceImpl.java +++ b/dinky-admin/src/main/java/org/dinky/service/impl/UDFServiceImpl.java @@ -23,6 +23,8 @@ import org.dinky.data.model.Resources; import org.dinky.data.model.udf.UDFManage; import org.dinky.data.vo.UDFManageVO; +import org.dinky.job.JobConfig; +import org.dinky.job.JobManager; import org.dinky.mapper.UDFManageMapper; import org.dinky.service.UDFService; import org.dinky.service.resource.ResourcesService; @@ -109,23 +111,24 @@ public void addOrUpdateByResourceId(List resourceIds) { resourceIds.stream().filter(x -> !udfManageIdList.contains(x)).collect(Collectors.toList()); if (CollUtil.isNotEmpty(needAddList)) { List resources = resourcesService.listByIds(needAddList); + JobManager jm = JobManager.build(new JobConfig()); List manageList = resources.stream() .flatMap(x -> { String suffix = FileUtil.getSuffix(x.getFileName()); if ("jar".equals(suffix)) { File file = resourcesService.getFile(x.getId()); - List> classes = UDFUtils.getUdfClassByJar(file); + List classes = jm.getUdfClassNameByJarPath(file.getPath()); return classes.stream().map(clazz -> { UDFManage udfManage = UDFManage.builder() - .className(clazz.getName()) + .className(clazz) .resourcesId(x.getId()) .build(); - udfManage.setName(StrUtil.toUnderlineCase(getSimpleClassName(clazz.getName()))); + udfManage.setName(StrUtil.toUnderlineCase(getSimpleClassName(clazz))); return udfManage; }); } else if ("py".equals(suffix) || "zip".equals(suffix)) { File file = resourcesService.getFile(x.getId()); - List pythonUdfList = UDFUtils.getPythonUdfList(file.getAbsolutePath()); + List pythonUdfList =jm.getPythonUdfList(file.getAbsolutePath()); return pythonUdfList.stream().map(className -> { UDFManage udfManage = UDFManage.builder() .className(className) diff --git a/dinky-admin/src/main/java/org/dinky/service/impl/UserServiceImpl.java b/dinky-admin/src/main/java/org/dinky/service/impl/UserServiceImpl.java index 11b867efad..c16ad82f84 100644 --- a/dinky-admin/src/main/java/org/dinky/service/impl/UserServiceImpl.java +++ b/dinky-admin/src/main/java/org/dinky/service/impl/UserServiceImpl.java @@ -20,7 +20,6 @@ package org.dinky.service.impl; import org.dinky.assertion.Asserts; -import org.dinky.context.RowLevelPermissionsContext; import org.dinky.context.TenantContextHolder; import org.dinky.context.UserInfoContextHolder; import org.dinky.data.dto.AssignRoleDTO; @@ -44,6 +43,8 @@ import org.dinky.data.model.rbac.UserTenant; import org.dinky.data.result.Result; import org.dinky.data.vo.UserVo; +import org.dinky.job.JobConfig; +import org.dinky.job.JobManager; import org.dinky.mapper.TokenMapper; import org.dinky.mapper.UserMapper; import org.dinky.mybatis.service.impl.SuperServiceImpl; @@ -432,7 +433,8 @@ public void buildRowPermission() { permission.put(roleSelectPermissions.getTableName(), roleSelectPermissions.getExpression()); } } - RowLevelPermissionsContext.set(permission); + + JobManager.build(new JobConfig()).buildRowPermission(permission); } } diff --git a/dinky-admin/src/main/java/org/dinky/service/resource/impl/ResourceServiceImpl.java b/dinky-admin/src/main/java/org/dinky/service/resource/impl/ResourceServiceImpl.java index 38c73da3ea..826e79c565 100644 --- a/dinky-admin/src/main/java/org/dinky/service/resource/impl/ResourceServiceImpl.java +++ b/dinky-admin/src/main/java/org/dinky/service/resource/impl/ResourceServiceImpl.java @@ -19,14 +19,16 @@ package org.dinky.service.resource.impl; +import cn.hutool.core.io.FileUtil; import org.dinky.assertion.DinkyAssert; import org.dinky.data.dto.TreeNodeDTO; import org.dinky.data.enums.Status; import org.dinky.data.exception.BusException; import org.dinky.data.model.Resources; import org.dinky.data.result.Result; +import org.dinky.job.JobConfig; +import org.dinky.job.JobManager; import org.dinky.mapper.ResourcesMapper; -import org.dinky.resource.BaseResourceManager; import org.dinky.service.resource.ResourcesService; import org.dinky.utils.URLUtils; @@ -58,6 +60,7 @@ public class ResourceServiceImpl extends ServiceImpl implements ResourcesService { private static final TimedCache RESOURCES_CACHE = new TimedCache<>(30 * 1000); private static final long ALLOW_MAX_CAT_CONTENT_SIZE = 10 * 1024 * 1024; + private static final JobManager jobManager = JobManager.build(new JobConfig()); @Override @Transactional(rollbackFor = Exception.class) @@ -71,7 +74,7 @@ public boolean syncRemoteDirectoryStructure() { local.stream().collect(Collectors.toMap(Resources::getId, Function.identity())); List resourcesList = - getBaseResourceManager().getFullDirectoryStructure(rootResource.getId()).stream() + jobManager.getFullDirectoryStructure(rootResource.getId()).stream() .filter(x -> x.getPid() != -1) .map(Resources::of) .peek(x -> { @@ -172,7 +175,7 @@ public void rename(Integer id, String fileName, String desc) { } } if (isRunStorageMove) { - getBaseResourceManager().rename(sourceFullName, fullName); + jobManager.rename(sourceFullName, fullName); } } @@ -221,7 +224,7 @@ public String getContentByResourceId(Integer id) { Resources resources = getById(id); DinkyAssert.checkNull(resources, Status.RESOURCE_DIR_OR_FILE_NOT_EXIST); Assert.isFalse(resources.getSize() > ALLOW_MAX_CAT_CONTENT_SIZE, () -> new BusException("file is too large!")); - return getBaseResourceManager().getFileContent(resources.getFullName()); + return jobManager.getFileContent(resources.getFullName()); } @Override @@ -243,7 +246,9 @@ public void uploadFile(Integer pid, String desc, File file) { } long size = file.length(); String fileName = file.getName(); - upload(pid, desc, (fullName) -> getBaseResourceManager().putFile(fullName, file), fileName, pResource, size); + // get file context, and assign to context + byte[] context = FileUtil.readBytes(file); + upload(pid, desc, (fullName) -> jobManager.putFile(fullName, context), fileName, pResource, size); } /** @@ -302,7 +307,8 @@ public void uploadFile(Integer pid, String desc, MultipartFile file) { desc, (fullName) -> { try { - getBaseResourceManager().putFile(fullName, file.getInputStream()); + byte[] context = file.getBytes(); + jobManager.putFile(fullName, context); } catch (IOException e) { throw new RuntimeException(e); } @@ -484,8 +490,4 @@ private List getChildList(List list, Resources resources) private boolean hasChild(List resourcesList, Resources resources) { return !getChildList(resourcesList, resources).isEmpty(); } - - private BaseResourceManager getBaseResourceManager() { - return BaseResourceManager.getInstance(); - } } diff --git a/dinky-admin/src/main/java/org/dinky/service/task/UdfTask.java b/dinky-admin/src/main/java/org/dinky/service/task/UdfTask.java index 094670fdc2..e3d9b8d8ad 100644 --- a/dinky-admin/src/main/java/org/dinky/service/task/UdfTask.java +++ b/dinky-admin/src/main/java/org/dinky/service/task/UdfTask.java @@ -23,9 +23,10 @@ import org.dinky.data.annotations.SupportDialect; import org.dinky.data.dto.TaskDTO; import org.dinky.data.model.Task; -import org.dinky.function.FunctionFactory; import org.dinky.function.data.model.UDF; import org.dinky.job.Job; +import org.dinky.job.JobConfig; +import org.dinky.job.JobManager; import org.dinky.job.JobResult; import org.dinky.utils.UDFUtils; @@ -47,7 +48,7 @@ public JobResult execute() throws Exception { jobResult.setStatus(Job.JobStatus.SUCCESS); try { UDF udf = UDFUtils.taskToUDF(BeanUtil.toBean(task, Task.class)); - FunctionFactory.initUDF(Collections.singletonList(udf), task.getId()); + JobManager.build(new JobConfig()).initUDF(Collections.singletonList(udf), task.getId()); } catch (Exception e) { jobResult.setSuccess(false); jobResult.setError(ExceptionUtil.getRootCauseMessage(e)); diff --git a/dinky-admin/src/main/java/org/dinky/sse/DoneStepSse.java b/dinky-admin/src/main/java/org/dinky/sse/DoneStepSse.java index 6271c89718..f1567ac270 100644 --- a/dinky-admin/src/main/java/org/dinky/sse/DoneStepSse.java +++ b/dinky-admin/src/main/java/org/dinky/sse/DoneStepSse.java @@ -19,7 +19,8 @@ package org.dinky.sse; -import org.dinky.function.pool.UdfCodePool; +import org.dinky.job.JobConfig; +import org.dinky.job.JobManager; import org.dinky.service.GitProjectService; import java.util.List; @@ -50,7 +51,7 @@ public DoneStepSse( public void exec() { addFileMsgCusLog("Updating UDF pool"); GitProjectService gitProjectService = SpringUtil.getBean(GitProjectService.class); - UdfCodePool.updateGitPool(gitProjectService.getGitPool()); + JobManager.build(new JobConfig()).updateGitPool(gitProjectService.getGitPool()); addFileMsgCusLog("The UDF pool has been updated"); } } diff --git a/dinky-admin/src/main/java/org/dinky/sse/git/AnalysisUdfClassStepSse.java b/dinky-admin/src/main/java/org/dinky/sse/git/AnalysisUdfClassStepSse.java index 83802aca04..f4a122cbfe 100644 --- a/dinky-admin/src/main/java/org/dinky/sse/git/AnalysisUdfClassStepSse.java +++ b/dinky-admin/src/main/java/org/dinky/sse/git/AnalysisUdfClassStepSse.java @@ -22,7 +22,8 @@ import org.dinky.data.dto.GitAnalysisJarDTO; import org.dinky.data.exception.DinkyException; import org.dinky.data.model.GitProject; -import org.dinky.function.util.UDFUtil; +import org.dinky.job.JobConfig; +import org.dinky.job.JobManager; import org.dinky.sse.StepSse; import org.dinky.utils.URLUtils; @@ -63,14 +64,14 @@ public void exec() { List pathList = (List) params.get("jarPath"); List dataList = new ArrayList<>(); - Map>> udfMap = new TreeMap<>(); + Map> udfMap = new TreeMap<>(); try { Thread.currentThread().getContextClassLoader().loadClass("org.apache.flink.table.api.ValidationException"); } catch (ClassNotFoundException e) { throw new DinkyException("flink dependency not found"); } pathList.parallelStream().forEach(jar -> { - List> udfClassByJar = UDFUtil.getUdfClassByJar(URLUtils.toFile(jar)); + List udfClassByJar = JobManager.build(new JobConfig()).getUdfClassNameByJarPath(URLUtils.toFile(jar).getPath()); udfMap.put(jar, udfClassByJar); sendMsg(Dict.create().set(jar, udfClassByJar)); }); @@ -79,7 +80,7 @@ public void exec() { udfMap.forEach((k, v) -> { GitAnalysisJarDTO gitAnalysisJarDTO = new GitAnalysisJarDTO(); gitAnalysisJarDTO.setJarPath(k); - gitAnalysisJarDTO.setClassList(v.stream().map(Class::getName).collect(Collectors.toList())); + gitAnalysisJarDTO.setClassList(new ArrayList<>(v)); gitAnalysisJarDTO.setOrderLine(index.get()); index.getAndIncrement(); dataList.add(gitAnalysisJarDTO); diff --git a/dinky-admin/src/main/java/org/dinky/sse/git/AnalysisUdfPythonStepSse.java b/dinky-admin/src/main/java/org/dinky/sse/git/AnalysisUdfPythonStepSse.java index 18abff2a4b..55366a8c89 100644 --- a/dinky-admin/src/main/java/org/dinky/sse/git/AnalysisUdfPythonStepSse.java +++ b/dinky-admin/src/main/java/org/dinky/sse/git/AnalysisUdfPythonStepSse.java @@ -23,7 +23,8 @@ import org.dinky.data.exception.DinkyException; import org.dinky.data.model.GitProject; import org.dinky.data.model.SystemConfiguration; -import org.dinky.function.util.UDFUtil; +import org.dinky.job.JobConfig; +import org.dinky.job.JobManager; import org.dinky.sse.StepSse; import java.io.File; @@ -60,7 +61,7 @@ public void exec() { throw new DinkyException("flink dependency not found"); } List pythonUdfList = - UDFUtil.getPythonUdfList(SystemConfiguration.getInstances().getPythonHome(), zipFile.getAbsolutePath()); + JobManager.build(new JobConfig()).getPythonUdfList(zipFile.getAbsolutePath()); GitAnalysisJarDTO gitAnalysisJarDTO = new GitAnalysisJarDTO(); gitAnalysisJarDTO.setJarPath(zipFilePath); gitAnalysisJarDTO.setClassList(pythonUdfList); diff --git a/dinky-admin/src/main/java/org/dinky/url/RsURLConnection.java b/dinky-admin/src/main/java/org/dinky/url/RsURLConnection.java index 007a5b0f73..f53ba29446 100644 --- a/dinky-admin/src/main/java/org/dinky/url/RsURLConnection.java +++ b/dinky-admin/src/main/java/org/dinky/url/RsURLConnection.java @@ -19,29 +19,26 @@ package org.dinky.url; -import org.dinky.data.exception.BusException; -import org.dinky.resource.BaseResourceManager; +import cn.hutool.core.io.IoUtil; +import org.dinky.job.JobConfig; +import org.dinky.job.JobManager; import java.io.InputStream; import java.net.URL; import java.net.URLConnection; public class RsURLConnection extends URLConnection { - private InputStream inputStream; + private byte[] context; @Override public void connect() { - BaseResourceManager instance = BaseResourceManager.getInstance(); - if (instance == null) { - throw BusException.valueOf("ResourceManager is disabled"); - } - inputStream = instance.readFile(getURL().getPath()); + context = JobManager.build(new JobConfig()).readFIle(getURL().getPath()); } @Override public InputStream getInputStream() { connect(); - return inputStream; + return IoUtil.toStream(context); } public RsURLConnection(URL url) { diff --git a/dinky-admin/src/main/java/org/dinky/utils/UDFUtils.java b/dinky-admin/src/main/java/org/dinky/utils/UDFUtils.java index 1e771df27e..0d706302e5 100644 --- a/dinky-admin/src/main/java/org/dinky/utils/UDFUtils.java +++ b/dinky-admin/src/main/java/org/dinky/utils/UDFUtils.java @@ -19,15 +19,13 @@ package org.dinky.utils; +import org.apache.flink.table.catalog.FunctionLanguage; import org.dinky.assertion.Asserts; import org.dinky.data.exception.BusException; import org.dinky.data.model.Task; import org.dinky.function.data.model.UDF; -import org.dinky.function.util.UDFUtil; - -import org.apache.flink.table.catalog.FunctionLanguage; -public class UDFUtils extends UDFUtil { +public class UDFUtils { public static UDF taskToUDF(Task task) { if (Asserts.isNotNull(task.getConfigJson()) diff --git a/dinky-admin/src/main/resources/application-mysql.yml b/dinky-admin/src/main/resources/application-mysql.yml index 6c71564216..9e59bddd99 100644 --- a/dinky-admin/src/main/resources/application-mysql.yml +++ b/dinky-admin/src/main/resources/application-mysql.yml @@ -18,6 +18,6 @@ spring: datasource: url: jdbc:mysql://${MYSQL_ADDR:127.0.0.1:3306}/${MYSQL_DATABASE:dinky}?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true - username: ${MYSQL_USERNAME:dinky} - password: ${MYSQL_PASSWORD:dinky} + username: ${MYSQL_USERNAME:root} + password: ${MYSQL_PASSWORD:123456} driver-class-name: com.mysql.cj.jdbc.Driver diff --git a/dinky-admin/src/main/resources/application.yml b/dinky-admin/src/main/resources/application.yml index 1d1488222a..3dbac670d3 100644 --- a/dinky-admin/src/main/resources/application.yml +++ b/dinky-admin/src/main/resources/application.yml @@ -16,7 +16,7 @@ spring: # If you use pgsql database, please configure pgsql database connection information in application-pgsql.yml # If you use the h2 database, please configure the h2 database connection information in application-h2.yml, # note: the h2 database is only for experience use, and the related data that has been created cannot be migrated, please use it with caution - active: ${DB_ACTIVE:h2} #[h2,mysql,pgsql] + active: ${DB_ACTIVE:mysql} #[h2,mysql,pgsql] include: jmx lifecycle: timeout-per-shutdown-phase: 30s diff --git a/dinky-client/dinky-client-base/src/main/java/org/dinky/resource/BaseResourceManager.java b/dinky-client/dinky-client-base/src/main/java/org/dinky/resource/BaseResourceManager.java index de6c733a3a..72e82ea072 100644 --- a/dinky-client/dinky-client-base/src/main/java/org/dinky/resource/BaseResourceManager.java +++ b/dinky-client/dinky-client-base/src/main/java/org/dinky/resource/BaseResourceManager.java @@ -48,6 +48,10 @@ public interface BaseResourceManager { void rename(String path, String newPath); + default void putFile(String path, byte[] fileContext){ + putFile(path, IoUtil.toStream(fileContext)); + } + void putFile(String path, InputStream fileStream); void putFile(String path, File file); @@ -58,6 +62,10 @@ public interface BaseResourceManager { InputStream readFile(String path); + default byte[] readFileContext(String path) { + return IoUtil.readBytes(readFile(path)); + } + static BaseResourceManager getInstance() { switch (SystemConfiguration.getInstances().getResourcesModel().getValue()) { case HDFS: diff --git a/dinky-client/dinky-client-base/src/main/java/org/dinky/resource/impl/HdfsResourceManager.java b/dinky-client/dinky-client-base/src/main/java/org/dinky/resource/impl/HdfsResourceManager.java index b413b55f26..6c1d1d4920 100644 --- a/dinky-client/dinky-client-base/src/main/java/org/dinky/resource/impl/HdfsResourceManager.java +++ b/dinky-client/dinky-client-base/src/main/java/org/dinky/resource/impl/HdfsResourceManager.java @@ -140,6 +140,11 @@ public InputStream readFile(String path) { } } + @Override + public byte[] readFileContext(String path) { + return new byte[0]; + } + public FileSystem getHdfs() { if (hdfs == null && instances.getResourcesEnable().getValue()) { throw new BusException(Status.RESOURCE_HDFS_CONFIGURATION_ERROR); diff --git a/dinky-client/dinky-client-base/src/main/java/org/dinky/resource/impl/OssResourceManager.java b/dinky-client/dinky-client-base/src/main/java/org/dinky/resource/impl/OssResourceManager.java index 6639b53051..5f6d0a8283 100644 --- a/dinky-client/dinky-client-base/src/main/java/org/dinky/resource/impl/OssResourceManager.java +++ b/dinky-client/dinky-client-base/src/main/java/org/dinky/resource/impl/OssResourceManager.java @@ -134,8 +134,13 @@ public InputStream readFile(String path) { .getObjectContent(); } + @Override + public byte[] readFileContext(String path) { + return new byte[0]; + } + public OssTemplate getOssTemplate() { - if (ossTemplate == null && instances.getResourcesEnable().getValue()) { + if (ossTemplate == null && BaseResourceManager.instances.getResourcesEnable().getValue()) { throw new BusException(Status.RESOURCE_OSS_CONFIGURATION_ERROR); } return ossTemplate; diff --git a/dinky-function/src/main/java/org/apache/flink/table/catalog/FunctionLanguage.java b/dinky-common/src/main/java/org/apache/flink/table/catalog/FunctionLanguage.java similarity index 92% rename from dinky-function/src/main/java/org/apache/flink/table/catalog/FunctionLanguage.java rename to dinky-common/src/main/java/org/apache/flink/table/catalog/FunctionLanguage.java index 22aadb01c1..e91bcaa0d9 100644 --- a/dinky-function/src/main/java/org/apache/flink/table/catalog/FunctionLanguage.java +++ b/dinky-common/src/main/java/org/apache/flink/table/catalog/FunctionLanguage.java @@ -19,7 +19,6 @@ package org.apache.flink.table.catalog; -/** Categorizes the language semantics of a {@link CatalogFunction}. */ public enum FunctionLanguage { JAVA, diff --git a/dinky-core/src/main/java/org/dinky/api/FlinkAPI.java b/dinky-common/src/main/java/org/dinky/api/FlinkAPI.java similarity index 100% rename from dinky-core/src/main/java/org/dinky/api/FlinkAPI.java rename to dinky-common/src/main/java/org/dinky/api/FlinkAPI.java diff --git a/dinky-core/src/main/java/org/dinky/cluster/FlinkClusterInfo.java b/dinky-common/src/main/java/org/dinky/cluster/FlinkClusterInfo.java similarity index 100% rename from dinky-core/src/main/java/org/dinky/cluster/FlinkClusterInfo.java rename to dinky-common/src/main/java/org/dinky/cluster/FlinkClusterInfo.java diff --git a/dinky-core/src/main/java/org/dinky/constant/FlinkSQLConstant.java b/dinky-common/src/main/java/org/dinky/constant/FlinkSQLConstant.java similarity index 100% rename from dinky-core/src/main/java/org/dinky/constant/FlinkSQLConstant.java rename to dinky-common/src/main/java/org/dinky/constant/FlinkSQLConstant.java diff --git a/dinky-core/src/main/java/org/dinky/data/constant/FlinkHistoryConstant.java b/dinky-common/src/main/java/org/dinky/data/constant/FlinkHistoryConstant.java similarity index 100% rename from dinky-core/src/main/java/org/dinky/data/constant/FlinkHistoryConstant.java rename to dinky-common/src/main/java/org/dinky/data/constant/FlinkHistoryConstant.java diff --git a/dinky-core/src/main/java/org/dinky/data/constant/FlinkRestAPIConstant.java b/dinky-common/src/main/java/org/dinky/data/constant/FlinkRestAPIConstant.java similarity index 100% rename from dinky-core/src/main/java/org/dinky/data/constant/FlinkRestAPIConstant.java rename to dinky-common/src/main/java/org/dinky/data/constant/FlinkRestAPIConstant.java diff --git a/dinky-core/src/main/java/org/dinky/data/constant/FlinkRestResultConstant.java b/dinky-common/src/main/java/org/dinky/data/constant/FlinkRestResultConstant.java similarity index 100% rename from dinky-core/src/main/java/org/dinky/data/constant/FlinkRestResultConstant.java rename to dinky-common/src/main/java/org/dinky/data/constant/FlinkRestResultConstant.java diff --git a/dinky-client/dinky-client-base/src/main/java/org/dinky/data/model/FunctionResult.java b/dinky-common/src/main/java/org/dinky/data/model/FunctionResult.java similarity index 100% rename from dinky-client/dinky-client-base/src/main/java/org/dinky/data/model/FunctionResult.java rename to dinky-common/src/main/java/org/dinky/data/model/FunctionResult.java diff --git a/dinky-client/dinky-client-base/src/main/java/org/dinky/data/model/LineageRel.java b/dinky-common/src/main/java/org/dinky/data/model/LineageRel.java similarity index 100% rename from dinky-client/dinky-client-base/src/main/java/org/dinky/data/model/LineageRel.java rename to dinky-common/src/main/java/org/dinky/data/model/LineageRel.java diff --git a/dinky-common/src/main/java/org/dinky/data/model/ResourcesVO.java b/dinky-common/src/main/java/org/dinky/data/model/ResourcesVO.java index e7644f600f..ed99b32e51 100644 --- a/dinky-common/src/main/java/org/dinky/data/model/ResourcesVO.java +++ b/dinky-common/src/main/java/org/dinky/data/model/ResourcesVO.java @@ -19,6 +19,7 @@ package org.dinky.data.model; +import java.io.Serializable; import java.time.LocalDateTime; import com.fasterxml.jackson.annotation.JsonFormat; @@ -35,7 +36,7 @@ @Builder @NoArgsConstructor @AllArgsConstructor -public class ResourcesVO { +public class ResourcesVO implements Serializable { @ApiModelProperty(value = "ID", dataType = "Integer", example = "1", notes = "Unique identifier for the resource") private Integer id; diff --git a/dinky-core/src/main/java/org/dinky/data/result/DDLResult.java b/dinky-common/src/main/java/org/dinky/data/result/DDLResult.java similarity index 100% rename from dinky-core/src/main/java/org/dinky/data/result/DDLResult.java rename to dinky-common/src/main/java/org/dinky/data/result/DDLResult.java diff --git a/dinky-core/src/main/java/org/dinky/data/result/ResultPool.java b/dinky-common/src/main/java/org/dinky/data/result/ResultPool.java similarity index 100% rename from dinky-core/src/main/java/org/dinky/data/result/ResultPool.java rename to dinky-common/src/main/java/org/dinky/data/result/ResultPool.java diff --git a/dinky-core/src/main/java/org/dinky/data/result/SelectResult.java b/dinky-common/src/main/java/org/dinky/data/result/SelectResult.java similarity index 100% rename from dinky-core/src/main/java/org/dinky/data/result/SelectResult.java rename to dinky-common/src/main/java/org/dinky/data/result/SelectResult.java diff --git a/dinky-core/src/main/java/org/dinky/executor/ExecutorConfig.java b/dinky-common/src/main/java/org/dinky/executor/ExecutorConfig.java similarity index 100% rename from dinky-core/src/main/java/org/dinky/executor/ExecutorConfig.java rename to dinky-common/src/main/java/org/dinky/executor/ExecutorConfig.java diff --git a/dinky-core/src/main/java/org/dinky/explainer/lineage/LineageColumn.java b/dinky-common/src/main/java/org/dinky/explainer/lineage/LineageColumn.java similarity index 100% rename from dinky-core/src/main/java/org/dinky/explainer/lineage/LineageColumn.java rename to dinky-common/src/main/java/org/dinky/explainer/lineage/LineageColumn.java diff --git a/dinky-core/src/main/java/org/dinky/explainer/lineage/LineageRelation.java b/dinky-common/src/main/java/org/dinky/explainer/lineage/LineageRelation.java similarity index 100% rename from dinky-core/src/main/java/org/dinky/explainer/lineage/LineageRelation.java rename to dinky-common/src/main/java/org/dinky/explainer/lineage/LineageRelation.java diff --git a/dinky-core/src/main/java/org/dinky/explainer/lineage/LineageResult.java b/dinky-common/src/main/java/org/dinky/explainer/lineage/LineageResult.java similarity index 100% rename from dinky-core/src/main/java/org/dinky/explainer/lineage/LineageResult.java rename to dinky-common/src/main/java/org/dinky/explainer/lineage/LineageResult.java diff --git a/dinky-core/src/main/java/org/dinky/explainer/lineage/LineageTable.java b/dinky-common/src/main/java/org/dinky/explainer/lineage/LineageTable.java similarity index 100% rename from dinky-core/src/main/java/org/dinky/explainer/lineage/LineageTable.java rename to dinky-common/src/main/java/org/dinky/explainer/lineage/LineageTable.java diff --git a/dinky-function/src/main/java/org/dinky/function/compiler/CustomStringJavaCompiler.java b/dinky-common/src/main/java/org/dinky/function/compiler/CustomStringJavaCompiler.java similarity index 100% rename from dinky-function/src/main/java/org/dinky/function/compiler/CustomStringJavaCompiler.java rename to dinky-common/src/main/java/org/dinky/function/compiler/CustomStringJavaCompiler.java diff --git a/dinky-function/src/main/java/org/dinky/function/constant/PathConstant.java b/dinky-common/src/main/java/org/dinky/function/constant/PathConstant.java similarity index 100% rename from dinky-function/src/main/java/org/dinky/function/constant/PathConstant.java rename to dinky-common/src/main/java/org/dinky/function/constant/PathConstant.java diff --git a/dinky-function/src/main/java/org/dinky/function/data/model/UDF.java b/dinky-common/src/main/java/org/dinky/function/data/model/UDF.java similarity index 100% rename from dinky-function/src/main/java/org/dinky/function/data/model/UDF.java rename to dinky-common/src/main/java/org/dinky/function/data/model/UDF.java diff --git a/dinky-function/src/main/java/org/dinky/function/data/model/UDFPath.java b/dinky-common/src/main/java/org/dinky/function/data/model/UDFPath.java similarity index 100% rename from dinky-function/src/main/java/org/dinky/function/data/model/UDFPath.java rename to dinky-common/src/main/java/org/dinky/function/data/model/UDFPath.java diff --git a/dinky-function/src/main/java/org/dinky/function/util/ZipWriter.java b/dinky-common/src/main/java/org/dinky/function/util/ZipWriter.java similarity index 100% rename from dinky-function/src/main/java/org/dinky/function/util/ZipWriter.java rename to dinky-common/src/main/java/org/dinky/function/util/ZipWriter.java diff --git a/dinky-gateway/src/main/java/org/dinky/gateway/config/AppConfig.java b/dinky-common/src/main/java/org/dinky/gateway/config/AppConfig.java similarity index 100% rename from dinky-gateway/src/main/java/org/dinky/gateway/config/AppConfig.java rename to dinky-common/src/main/java/org/dinky/gateway/config/AppConfig.java diff --git a/dinky-gateway/src/main/java/org/dinky/gateway/config/ClusterConfig.java b/dinky-common/src/main/java/org/dinky/gateway/config/ClusterConfig.java similarity index 100% rename from dinky-gateway/src/main/java/org/dinky/gateway/config/ClusterConfig.java rename to dinky-common/src/main/java/org/dinky/gateway/config/ClusterConfig.java diff --git a/dinky-gateway/src/main/java/org/dinky/gateway/config/FlinkConfig.java b/dinky-common/src/main/java/org/dinky/gateway/config/FlinkConfig.java similarity index 100% rename from dinky-gateway/src/main/java/org/dinky/gateway/config/FlinkConfig.java rename to dinky-common/src/main/java/org/dinky/gateway/config/FlinkConfig.java diff --git a/dinky-gateway/src/main/java/org/dinky/gateway/config/GatewayConfig.java b/dinky-common/src/main/java/org/dinky/gateway/config/GatewayConfig.java similarity index 100% rename from dinky-gateway/src/main/java/org/dinky/gateway/config/GatewayConfig.java rename to dinky-common/src/main/java/org/dinky/gateway/config/GatewayConfig.java diff --git a/dinky-gateway/src/main/java/org/dinky/gateway/config/K8sConfig.java b/dinky-common/src/main/java/org/dinky/gateway/config/K8sConfig.java similarity index 100% rename from dinky-gateway/src/main/java/org/dinky/gateway/config/K8sConfig.java rename to dinky-common/src/main/java/org/dinky/gateway/config/K8sConfig.java diff --git a/dinky-gateway/src/main/java/org/dinky/gateway/enums/ActionType.java b/dinky-common/src/main/java/org/dinky/gateway/enums/ActionType.java similarity index 100% rename from dinky-gateway/src/main/java/org/dinky/gateway/enums/ActionType.java rename to dinky-common/src/main/java/org/dinky/gateway/enums/ActionType.java diff --git a/dinky-gateway/src/main/java/org/dinky/gateway/enums/SavePointStrategy.java b/dinky-common/src/main/java/org/dinky/gateway/enums/SavePointStrategy.java similarity index 100% rename from dinky-gateway/src/main/java/org/dinky/gateway/enums/SavePointStrategy.java rename to dinky-common/src/main/java/org/dinky/gateway/enums/SavePointStrategy.java diff --git a/dinky-gateway/src/main/java/org/dinky/gateway/enums/SavePointType.java b/dinky-common/src/main/java/org/dinky/gateway/enums/SavePointType.java similarity index 100% rename from dinky-gateway/src/main/java/org/dinky/gateway/enums/SavePointType.java rename to dinky-common/src/main/java/org/dinky/gateway/enums/SavePointType.java diff --git a/dinky-gateway/src/main/java/org/dinky/gateway/model/CustomConfig.java b/dinky-common/src/main/java/org/dinky/gateway/model/CustomConfig.java similarity index 100% rename from dinky-gateway/src/main/java/org/dinky/gateway/model/CustomConfig.java rename to dinky-common/src/main/java/org/dinky/gateway/model/CustomConfig.java diff --git a/dinky-gateway/src/main/java/org/dinky/gateway/model/FlinkClusterConfig.java b/dinky-common/src/main/java/org/dinky/gateway/model/FlinkClusterConfig.java similarity index 100% rename from dinky-gateway/src/main/java/org/dinky/gateway/model/FlinkClusterConfig.java rename to dinky-common/src/main/java/org/dinky/gateway/model/FlinkClusterConfig.java diff --git a/dinky-gateway/src/main/java/org/dinky/gateway/model/JobInfo.java b/dinky-common/src/main/java/org/dinky/gateway/model/JobInfo.java similarity index 100% rename from dinky-gateway/src/main/java/org/dinky/gateway/model/JobInfo.java rename to dinky-common/src/main/java/org/dinky/gateway/model/JobInfo.java diff --git a/dinky-gateway/src/main/java/org/dinky/gateway/result/AbstractGatewayResult.java b/dinky-common/src/main/java/org/dinky/gateway/result/AbstractGatewayResult.java similarity index 100% rename from dinky-gateway/src/main/java/org/dinky/gateway/result/AbstractGatewayResult.java rename to dinky-common/src/main/java/org/dinky/gateway/result/AbstractGatewayResult.java diff --git a/dinky-gateway/src/main/java/org/dinky/gateway/result/GatewayResult.java b/dinky-common/src/main/java/org/dinky/gateway/result/GatewayResult.java similarity index 100% rename from dinky-gateway/src/main/java/org/dinky/gateway/result/GatewayResult.java rename to dinky-common/src/main/java/org/dinky/gateway/result/GatewayResult.java diff --git a/dinky-gateway/src/main/java/org/dinky/gateway/result/SavePointResult.java b/dinky-common/src/main/java/org/dinky/gateway/result/SavePointResult.java similarity index 100% rename from dinky-gateway/src/main/java/org/dinky/gateway/result/SavePointResult.java rename to dinky-common/src/main/java/org/dinky/gateway/result/SavePointResult.java diff --git a/dinky-gateway/src/main/java/org/dinky/gateway/result/TestResult.java b/dinky-common/src/main/java/org/dinky/gateway/result/TestResult.java similarity index 100% rename from dinky-gateway/src/main/java/org/dinky/gateway/result/TestResult.java rename to dinky-common/src/main/java/org/dinky/gateway/result/TestResult.java diff --git a/dinky-core/src/main/java/org/dinky/job/Job.java b/dinky-common/src/main/java/org/dinky/job/Job.java similarity index 100% rename from dinky-core/src/main/java/org/dinky/job/Job.java rename to dinky-common/src/main/java/org/dinky/job/Job.java diff --git a/dinky-core/src/main/java/org/dinky/job/JobConfig.java b/dinky-common/src/main/java/org/dinky/job/JobConfig.java similarity index 92% rename from dinky-core/src/main/java/org/dinky/job/JobConfig.java rename to dinky-common/src/main/java/org/dinky/job/JobConfig.java index 4bd1ce5118..92a5e659f0 100644 --- a/dinky-core/src/main/java/org/dinky/job/JobConfig.java +++ b/dinky-common/src/main/java/org/dinky/job/JobConfig.java @@ -28,10 +28,6 @@ import org.dinky.gateway.enums.SavePointStrategy; import org.dinky.gateway.model.FlinkClusterConfig; -import org.apache.flink.configuration.Configuration; -import org.apache.flink.configuration.CoreOptions; -import org.apache.flink.configuration.RestOptions; - import java.io.Serializable; import java.util.HashMap; import java.util.Map; @@ -53,6 +49,8 @@ @ApiModel(value = "JobConfig", description = "Configuration details of a job") public class JobConfig implements Serializable { + private static final String REST_PORT = "rest.port"; + private static final String DEFAULT_PARAllELISM = "parallelism.default"; @ApiModelProperty( value = "Flink run mode", dataType = "String", @@ -192,12 +190,12 @@ public JobConfig() { public void setAddress(String address) { if (GatewayType.LOCAL.equalsValue(type) && Asserts.isNotNull(configJson) - && configJson.containsKey(RestOptions.PORT.key())) { + && configJson.containsKey(REST_PORT)) { int colonIndex = address.indexOf(':'); if (colonIndex == -1) { - this.address = address + NetConstant.COLON + configJson.get(RestOptions.PORT.key()); + this.address = address + NetConstant.COLON + configJson.get(REST_PORT); } else { - this.address = address.replaceAll("(?<=:)\\d{0,6}$", configJson.get(RestOptions.PORT.key())); + this.address = address.replaceAll("(?<=:)\\d{0,6}$", configJson.get(REST_PORT)); } } else { this.address = address; @@ -222,7 +220,7 @@ public ExecutorConfig createExecutorSetting() { public void buildGatewayConfig(FlinkClusterConfig config) { FlinkConfig flinkConfig = config.getFlinkConfig(); flinkConfig.getConfiguration().putAll(getConfigJson()); - flinkConfig.getConfiguration().put(CoreOptions.DEFAULT_PARALLELISM.key(), String.valueOf(parallelism)); + flinkConfig.getConfiguration().put(DEFAULT_PARAllELISM, String.valueOf(parallelism)); flinkConfig.setJobName(getJobName()); gatewayConfig = GatewayConfig.build(config); @@ -239,13 +237,6 @@ public void addGatewayConfig(Map config) { } } - public void addGatewayConfig(Configuration config) { - if (Asserts.isNull(gatewayConfig)) { - gatewayConfig = new GatewayConfig(); - } - gatewayConfig.getFlinkConfig().getConfiguration().putAll(config.toMap()); - } - public boolean isUseRemote() { return useRemote || !GatewayType.LOCAL.equalsValue(type); } diff --git a/dinky-core/src/main/java/org/dinky/job/JobHandler.java b/dinky-common/src/main/java/org/dinky/job/JobHandler.java similarity index 100% rename from dinky-core/src/main/java/org/dinky/job/JobHandler.java rename to dinky-common/src/main/java/org/dinky/job/JobHandler.java diff --git a/dinky-core/src/main/java/org/dinky/job/JobResult.java b/dinky-common/src/main/java/org/dinky/job/JobResult.java similarity index 100% rename from dinky-core/src/main/java/org/dinky/job/JobResult.java rename to dinky-common/src/main/java/org/dinky/job/JobResult.java index 45f44d1875..b19d2df6e7 100644 --- a/dinky-core/src/main/java/org/dinky/job/JobResult.java +++ b/dinky-common/src/main/java/org/dinky/job/JobResult.java @@ -20,7 +20,6 @@ package org.dinky.job; import org.dinky.data.result.IResult; -import org.dinky.metadata.result.JdbcSelectResult; import java.io.Serializable; import java.time.LocalDateTime; @@ -30,6 +29,7 @@ import io.swagger.annotations.ApiModelProperty; import lombok.Getter; import lombok.Setter; +import org.dinky.metadata.result.JdbcSelectResult; /** * JobResult diff --git a/dinky-metadata/dinky-metadata-base/src/main/java/org/dinky/metadata/config/DriverConfig.java b/dinky-common/src/main/java/org/dinky/metadata/config/DriverConfig.java similarity index 100% rename from dinky-metadata/dinky-metadata-base/src/main/java/org/dinky/metadata/config/DriverConfig.java rename to dinky-common/src/main/java/org/dinky/metadata/config/DriverConfig.java diff --git a/dinky-metadata/dinky-metadata-base/src/main/java/org/dinky/metadata/config/IConnectConfig.java b/dinky-common/src/main/java/org/dinky/metadata/config/IConnectConfig.java similarity index 100% rename from dinky-metadata/dinky-metadata-base/src/main/java/org/dinky/metadata/config/IConnectConfig.java rename to dinky-common/src/main/java/org/dinky/metadata/config/IConnectConfig.java diff --git a/dinky-metadata/dinky-metadata-base/src/main/java/org/dinky/metadata/result/JdbcSelectResult.java b/dinky-common/src/main/java/org/dinky/metadata/result/JdbcSelectResult.java similarity index 100% rename from dinky-metadata/dinky-metadata-base/src/main/java/org/dinky/metadata/result/JdbcSelectResult.java rename to dinky-common/src/main/java/org/dinky/metadata/result/JdbcSelectResult.java diff --git a/dinky-common/src/main/java/org/dinky/remote/ServerExecutorService.java b/dinky-common/src/main/java/org/dinky/remote/ServerExecutorService.java new file mode 100644 index 0000000000..2b309430af --- /dev/null +++ b/dinky-common/src/main/java/org/dinky/remote/ServerExecutorService.java @@ -0,0 +1,147 @@ +/* + * + * 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.remote; + +import org.dinky.cluster.FlinkClusterInfo; +import org.dinky.data.enums.JobStatus; +import org.dinky.data.model.Catalog; +import org.dinky.data.model.CheckPointReadTable; +import org.dinky.data.model.Column; +import org.dinky.data.model.ResourcesVO; +import org.dinky.data.model.Schema; +import org.dinky.data.model.Table; +import org.dinky.data.result.ExplainResult; +import org.dinky.data.result.IResult; + +import java.rmi.Remote; +import java.rmi.RemoteException; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.dinky.explainer.lineage.LineageResult; +import org.dinky.function.data.model.UDF; +import org.dinky.function.data.model.UDFPath; +import org.dinky.gateway.config.GatewayConfig; +import org.dinky.gateway.result.GatewayResult; +import org.dinky.gateway.result.SavePointResult; +import org.dinky.gateway.enums.SavePointType; +import org.dinky.job.Job; +import org.dinky.job.JobConfig; +import org.dinky.job.JobResult; +import org.dinky.metadata.config.DriverConfig; + + +public interface ServerExecutorService extends Remote { + void init(JobConfig config, boolean isPlanMode) throws RemoteException; + + boolean close() throws RemoteException; + + ObjectNode getJarStreamGraphJson(String statement) throws RemoteException; + + JobResult executeJarSql(String statement) throws RemoteException; + + JobResult executeSql(String statement) throws RemoteException; + + IResult executeDDL(String statement) throws RemoteException; + + ExplainResult explainSql(String statement) throws RemoteException; + + ObjectNode getStreamGraph(String statement) throws RemoteException; + + String getJobPlanJson(String statement) throws RemoteException; + + boolean cancelNormal(String jobId) throws RemoteException; + + SavePointResult savepoint(String jobId, SavePointType savePointType, String savePoint) throws RemoteException; + + String exportSql(String sql) throws RemoteException; + + Job getJob() throws RemoteException; + + void prepare(String statement) throws RemoteException; + + List getPythonUdfList(String udfFile)throws RemoteException; + + JobStatus getJobStatus(GatewayConfig gatewayConfig, String appId)throws RemoteException; + + void onJobGatewayFinishCallback(JobConfig jobConfig, String status)throws RemoteException; + + List getUdfClassNameByJarPath(String path) throws RemoteException; + + void putFile(String fullName, byte[] context) throws RemoteException; + + List getFullDirectoryStructure(int rootId) throws RemoteException; + + void rename(String path, String newPath) throws RemoteException; + + String getFileContent(String path) throws RemoteException; + + void updateGitPool(Map newPool) throws RemoteException; + + UDFPath initUDF(List udfClassList, Integer missionId) throws RemoteException; + + LineageResult getColumnLineageByLogicalPlan(String statement) throws RemoteException; + + LineageResult getSqlLineageByOne(String statement, String type) throws RemoteException; + + LineageResult getSqlLineage(String statement, String mysql, DriverConfig> driverConfig) throws RemoteException; + + List getCatalog() throws RemoteException; + + void setSchemaInfo( + String catalogName, + String database, + Schema schema, + List
tables) throws RemoteException; + + List getColumnList(String catalogName, String database, String tableName) throws RemoteException; + + Map> readCheckpoint(String path, String operatorId) throws RemoteException; + + byte[] readFile(String path) throws RemoteException; + + Map> buildJar(List udfCodes) throws RemoteException; + + void buildRowPermission(ConcurrentHashMap permission) throws RemoteException; + + List getPrintTables(String statement) throws RemoteException; + + FlinkClusterInfo testFlinkJobManagerIP(String hosts, String host) throws RemoteException; + + void killCluster(GatewayConfig gatewayConfig)throws RemoteException; + + GatewayResult deployCluster(GatewayConfig gatewayConfig) throws RemoteException; + + void addOrUpdate(UDF udf)throws RemoteException; + + void removeUdfCodePool(String className) throws RemoteException; + + String templateParse(String dialect, String templateCode, String className) throws RemoteException; + + void registerPool(List collect) throws RemoteException; + + void initResourceManager() throws RemoteException; + + String getPyUDFAttr(String statement) throws RemoteException; + + String getScalaFullClassName(String statement) throws RemoteException; +} diff --git a/dinky-core/src/main/java/org/dinky/job/JobManagerHandler.java b/dinky-core/src/main/java/org/dinky/job/JobManagerHandler.java index 75bf86d6c5..b09d327dab 100644 --- a/dinky-core/src/main/java/org/dinky/job/JobManagerHandler.java +++ b/dinky-core/src/main/java/org/dinky/job/JobManagerHandler.java @@ -165,7 +165,7 @@ public JobResult executeJarSql(String statement) throws Exception { } else { GatewayResult gatewayResult; config.addGatewayConfig( - executor.getCustomTableEnvironment().getConfig().getConfiguration()); + executor.getCustomTableEnvironment().getConfig().getConfiguration().toMap()); if (runMode.isApplicationMode()) { gatewayResult = Gateway.build(config.getGatewayConfig()).submitJar(executor.getUdfPathContextHolder()); diff --git a/dinky-core/src/main/java/org/dinky/job/builder/JobTransBuilder.java b/dinky-core/src/main/java/org/dinky/job/builder/JobTransBuilder.java index 5cf73bfa1f..66b52131bb 100644 --- a/dinky-core/src/main/java/org/dinky/job/builder/JobTransBuilder.java +++ b/dinky-core/src/main/java/org/dinky/job/builder/JobTransBuilder.java @@ -216,7 +216,7 @@ private GatewayResult submitByGateway(List inserts) { GatewayResult gatewayResult = null; // Use gateway need to build gateway config, include flink configuration. - config.addGatewayConfig(executor.getCustomTableEnvironment().getConfig().getConfiguration()); + config.addGatewayConfig(executor.getCustomTableEnvironment().getConfig().getConfiguration().toMap()); if (runMode.isApplicationMode()) { // Application mode need to submit dinky-app.jar that in the hdfs or image. diff --git a/dinky-core/src/main/java/org/dinky/utils/DinkyClassLoaderUtil.java b/dinky-core/src/main/java/org/dinky/utils/DinkyClassLoaderUtil.java index 37be2b6f25..0ab2717a72 100644 --- a/dinky-core/src/main/java/org/dinky/utils/DinkyClassLoaderUtil.java +++ b/dinky-core/src/main/java/org/dinky/utils/DinkyClassLoaderUtil.java @@ -23,7 +23,6 @@ import org.dinky.classloader.DinkyClassLoader; import org.dinky.context.FlinkUdfPathContextHolder; import org.dinky.data.exception.DinkyException; -import org.dinky.job.JobConfig; import org.apache.flink.configuration.PipelineOptions; @@ -31,6 +30,8 @@ import cn.hutool.core.collection.CollUtil; import cn.hutool.core.io.FileUtil; +import org.dinky.flink.checkpoint.CheckpointRead; +import org.dinky.job.JobConfig; public class DinkyClassLoaderUtil { diff --git a/dinky-executor-server/src/main/java/org/dinky/JobManagerServiceImpl.java b/dinky-executor-server/src/main/java/org/dinky/JobManagerServiceImpl.java index 7a79f0928b..ab77c7adfd 100644 --- a/dinky-executor-server/src/main/java/org/dinky/JobManagerServiceImpl.java +++ b/dinky-executor-server/src/main/java/org/dinky/JobManagerServiceImpl.java @@ -19,9 +19,32 @@ package org.dinky; +import org.dinky.cluster.FlinkCluster; +import org.dinky.cluster.FlinkClusterInfo; +import org.dinky.context.RowLevelPermissionsContext; +import org.dinky.data.enums.JobStatus; +import org.dinky.data.model.Catalog; +import org.dinky.data.model.CheckPointReadTable; +import org.dinky.data.model.Column; +import org.dinky.data.model.ResourcesVO; +import org.dinky.data.model.Schema; +import org.dinky.data.model.Table; import org.dinky.data.result.ExplainResult; import org.dinky.data.result.IResult; +import org.dinky.explainer.lineage.LineageBuilder; +import org.dinky.explainer.lineage.LineageResult; +import org.dinky.explainer.print_table.PrintStatementExplainer; +import org.dinky.explainer.sqllineage.SQLLineageBuilder; +import org.dinky.flink.checkpoint.CheckpointRead; +import org.dinky.function.FunctionFactory; +import org.dinky.function.data.model.UDF; +import org.dinky.function.data.model.UDFPath; +import org.dinky.function.pool.UdfCodePool; +import org.dinky.function.util.UDFUtil; +import org.dinky.gateway.Gateway; +import org.dinky.gateway.config.GatewayConfig; import org.dinky.gateway.enums.SavePointType; +import org.dinky.gateway.result.GatewayResult; import org.dinky.gateway.result.SavePointResult; import org.dinky.job.Job; import org.dinky.job.JobConfig; @@ -30,21 +53,37 @@ import java.rmi.RemoteException; import java.rmi.server.UnicastRemoteObject; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; import com.fasterxml.jackson.databind.node.ObjectNode; import lombok.extern.slf4j.Slf4j; +import org.dinky.metadata.config.DriverConfig; +import org.dinky.parser.SqlType; +import org.dinky.remote.ServerExecutorService; +import org.dinky.resource.BaseResourceManager; +import org.dinky.trans.Operations; +import org.dinky.utils.FlinkTableMetadataUtil; +import org.dinky.utils.SqlUtil; @Slf4j public class JobManagerServiceImpl extends UnicastRemoteObject implements ServerExecutorService { JobManagerHandler jobManagerHandler; + protected static final CheckpointRead INSTANCE = new CheckpointRead(); - public JobManagerServiceImpl() throws RemoteException {} + + public JobManagerServiceImpl() throws RemoteException { + } @Override - public void init(JobConfig config, boolean isPlanMode) { + public void init(JobConfig config, boolean isPlanMode) throws RemoteException { jobManagerHandler = JobManagerHandler.build(config, isPlanMode); + } @Override @@ -121,4 +160,173 @@ public Job getJob() throws RemoteException { public void prepare(String statement) throws RemoteException { jobManagerHandler.prepare(statement); } -} + + + // TODO: 2024/3/29 utils, coud individual rmeote interface + @Override + public List getPythonUdfList(String udfFile) throws RemoteException { + return UDFUtil.getPythonUdfList(udfFile); + } + + @Override + public JobStatus getJobStatus(GatewayConfig gatewayConfig, String appId) throws RemoteException { + Gateway gateway = Gateway.build(gatewayConfig); + return gateway.getJobStatusById(appId); + } + + @Override + public void onJobGatewayFinishCallback(JobConfig jobConfig, String status) throws RemoteException { + Gateway.build(jobConfig.getGatewayConfig()).onJobFinishCallback(status); + } + + @Override + public List getUdfClassNameByJarPath(String path) throws RemoteException { + return UDFUtil.getUdfClassNameByJarPath(path); + } + + @Override + public Map> buildJar(List udfCodes) throws RemoteException { + return UDFUtil.buildJar(udfCodes); + } + + @Override + public void buildRowPermission(ConcurrentHashMap permission) throws RemoteException { + RowLevelPermissionsContext.set(permission); + } + + @Override + public void putFile(String fullName, byte[] context) throws RemoteException { + BaseResourceManager.getInstance().putFile(fullName, context); + } + + @Override + public List getFullDirectoryStructure(int rootId) throws RemoteException { + return BaseResourceManager.getInstance().getFullDirectoryStructure(rootId); + } + + @Override + public void rename(String path, String newPath) throws RemoteException { + BaseResourceManager.getInstance().rename(path, newPath); + } + + @Override + public String getFileContent(String path) throws RemoteException { + return BaseResourceManager.getInstance().getFileContent(path); + } + + @Override + public byte[] readFile(String path) throws RemoteException { + return BaseResourceManager.getInstance().readFileContext(path); + } + + @Override + public void updateGitPool(Map newPool) throws RemoteException { + UdfCodePool.updateGitPool(newPool); + } + + @Override + public UDFPath initUDF(List udfClassList, Integer missionId) throws RemoteException { + return FunctionFactory.initUDF(udfClassList, missionId); + } + + @Override + public LineageResult getColumnLineageByLogicalPlan(String statement) throws RemoteException { + return LineageBuilder.getColumnLineageByLogicalPlan(statement); + } + + @Override + public LineageResult getSqlLineageByOne(String statement, String type) throws RemoteException { + return SQLLineageBuilder.getSqlLineageByOne(statement, type); + } + + @Override + public LineageResult getSqlLineage(String statement, String mysql, DriverConfig> driverConfig) throws RemoteException { + return SQLLineageBuilder.getSqlLineage(statement, mysql, driverConfig); + } + + @Override + public List getCatalog() throws RemoteException { + return FlinkTableMetadataUtil.getCatalog(jobManagerHandler.getExecutor().getCustomTableEnvironment()); + } + + @Override + public void setSchemaInfo( + String catalogName, + String database, + Schema schema, + List
tables) throws RemoteException { + FlinkTableMetadataUtil.setSchemaInfo(jobManagerHandler.getExecutor().getCustomTableEnvironment(), catalogName, database, schema, tables); + } + + @Override + public List getColumnList(String catalogName, String database, String tableName) throws RemoteException { + return FlinkTableMetadataUtil.getColumnList(jobManagerHandler.getExecutor().getCustomTableEnvironment(), catalogName, database, tableName); + } + + @Override + public Map> readCheckpoint(String path, String operatorId) throws RemoteException { + return INSTANCE.readCheckpoint(path, operatorId); + } + + @Override + public List getPrintTables(String statement) throws RemoteException{ + // TODO: 2023/4/7 this function not support variable sql, because, JobManager and executor + // couple function + // and status and task execute. + final String[] statements = SqlUtil.getStatements(SqlUtil.removeNote(statement)); + return Arrays.stream(statements) + .filter(t -> SqlType.PRINT.equals(Operations.getOperationType(t))) + .flatMap(t -> Arrays.stream(PrintStatementExplainer.splitTableNames(t))) + .collect(Collectors.toList()); + } + + @Override + public FlinkClusterInfo testFlinkJobManagerIP(String hosts, String host) throws RemoteException { + return FlinkCluster.testFlinkJobManagerIP(hosts, host); + } + + @Override + public void killCluster(GatewayConfig gatewayConfig) throws RemoteException { + Gateway.build(gatewayConfig).killCluster(); + } + + @Override + public GatewayResult deployCluster(GatewayConfig gatewayConfig) throws RemoteException { + return Gateway.build(gatewayConfig).deployCluster(UDFUtil.createFlinkUdfPathContextHolder()); + } + + @Override + public void addOrUpdate(UDF udf) throws RemoteException { + UdfCodePool.addOrUpdate(udf); + } + + @Override + public void removeUdfCodePool(String className) throws RemoteException { + UdfCodePool.remove(className); + } + + @Override + public String templateParse(String dialect, String templateCode, String className) throws RemoteException { + return UDFUtil.templateParse(dialect, templateCode, className); + } + + @Override + public void registerPool(List collect) throws RemoteException { + UdfCodePool.registerPool(collect); + } + + @Override + public void initResourceManager() throws RemoteException { + BaseResourceManager.initResourceManager(); + } + + @Override + public String getPyUDFAttr(String statement) throws RemoteException { + return UDFUtil.getPyUDFAttr(statement); + } + + @Override + public String getScalaFullClassName(String statement) throws RemoteException { + return UDFUtil.getScalaFullClassName(statement); + } +} \ No newline at end of file diff --git a/dinky-executor-server/src/main/java/org/dinky/RMIServer.java b/dinky-executor-server/src/main/java/org/dinky/RMIServer.java index e34a5085d1..304a7a3939 100644 --- a/dinky-executor-server/src/main/java/org/dinky/RMIServer.java +++ b/dinky-executor-server/src/main/java/org/dinky/RMIServer.java @@ -19,6 +19,8 @@ package org.dinky; +import org.dinky.remote.ServerExecutorService; + import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; diff --git a/dinky-executor-server/src/main/java/org/dinky/ServerExecutorService.java b/dinky-executor-server/src/main/java/org/dinky/ServerExecutorService.java deleted file mode 100644 index d5aee3e8b0..0000000000 --- a/dinky-executor-server/src/main/java/org/dinky/ServerExecutorService.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * - * 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; - -import org.dinky.data.result.ExplainResult; -import org.dinky.data.result.IResult; -import org.dinky.gateway.enums.SavePointType; -import org.dinky.gateway.result.SavePointResult; -import org.dinky.job.Job; -import org.dinky.job.JobConfig; -import org.dinky.job.JobResult; - -import java.rmi.Remote; -import java.rmi.RemoteException; - -import com.fasterxml.jackson.databind.node.ObjectNode; - -public interface ServerExecutorService extends Remote { - void init(JobConfig config, boolean isPlanMode) throws RemoteException; - - boolean close() throws RemoteException; - - ObjectNode getJarStreamGraphJson(String statement) throws RemoteException; - - JobResult executeJarSql(String statement) throws RemoteException; - - JobResult executeSql(String statement) throws RemoteException; - - IResult executeDDL(String statement) throws RemoteException; - - ExplainResult explainSql(String statement) throws RemoteException; - - ObjectNode getStreamGraph(String statement) throws RemoteException; - - String getJobPlanJson(String statement) throws RemoteException; - - boolean cancelNormal(String jobId) throws RemoteException; - - SavePointResult savepoint(String jobId, SavePointType savePointType, String savePoint) throws RemoteException; - - String exportSql(String sql) throws RemoteException; - - Job getJob() throws RemoteException; - - void prepare(String statement) throws RemoteException; -} diff --git a/dinky-function/pom.xml b/dinky-function/pom.xml index 5d542e360f..f241131abd 100644 --- a/dinky-function/pom.xml +++ b/dinky-function/pom.xml @@ -31,6 +31,10 @@ Dinky : Function + + org.dinky + dinky-common + org.freemarker freemarker diff --git a/dinky-function/src/main/java/org/dinky/function/compiler/JavaCompiler.java b/dinky-function/src/main/java/org/dinky/function/compiler/JavaCompiler.java index 4eb92d15bf..fcead89f82 100644 --- a/dinky-function/src/main/java/org/dinky/function/compiler/JavaCompiler.java +++ b/dinky-function/src/main/java/org/dinky/function/compiler/JavaCompiler.java @@ -38,7 +38,6 @@ public class JavaCompiler implements FunctionCompiler { * 函数代码在线动态编译 * * @param udf udf - * @param conf flink-conf * @param missionId 任务id * @return 是否成功 */ diff --git a/dinky-function/src/main/java/org/dinky/function/util/UDFUtil.java b/dinky-function/src/main/java/org/dinky/function/util/UDFUtil.java index 427b1dec2f..fda473431d 100644 --- a/dinky-function/src/main/java/org/dinky/function/util/UDFUtil.java +++ b/dinky-function/src/main/java/org/dinky/function/util/UDFUtil.java @@ -19,6 +19,14 @@ package org.dinky.function.util; +import cn.hutool.core.lang.Assert; +import cn.hutool.core.lang.ClassScanner; +import cn.hutool.core.lang.JarClassLoader; +import cn.hutool.core.util.ClassUtil; +import cn.hutool.core.util.ReflectUtil; +import org.apache.flink.table.api.ValidationException; +import org.apache.flink.table.functions.UserDefinedFunction; +import org.apache.flink.table.functions.UserDefinedFunctionHelper; import org.dinky.assertion.Asserts; import org.dinky.classloader.DinkyClassLoader; import org.dinky.config.Dialect; @@ -34,18 +42,17 @@ import org.dinky.function.constant.PathConstant; import org.dinky.function.data.model.UDF; import org.dinky.function.pool.UdfCodePool; +import org.dinky.parser.SqlType; import org.dinky.pool.ClassEntity; import org.dinky.pool.ClassPool; +import org.dinky.utils.SqlUtil; import org.dinky.utils.URLUtils; import org.apache.flink.client.python.PythonFunctionFactory; import org.apache.flink.configuration.Configuration; import org.apache.flink.configuration.PipelineOptions; import org.apache.flink.python.PythonOptions; -import org.apache.flink.table.api.ValidationException; import org.apache.flink.table.catalog.FunctionLanguage; -import org.apache.flink.table.functions.UserDefinedFunction; -import org.apache.flink.table.functions.UserDefinedFunctionHelper; import java.io.File; import java.io.InputStream; @@ -69,16 +76,11 @@ import cn.hutool.core.convert.Convert; import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.resource.ResourceUtil; -import cn.hutool.core.lang.Assert; -import cn.hutool.core.lang.ClassScanner; import cn.hutool.core.lang.Dict; -import cn.hutool.core.lang.JarClassLoader; import cn.hutool.core.lang.Opt; import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.ClassLoaderUtil; -import cn.hutool.core.util.ClassUtil; import cn.hutool.core.util.ReUtil; -import cn.hutool.core.util.ReflectUtil; import cn.hutool.core.util.RuntimeUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.URLUtil; @@ -375,35 +377,6 @@ public static FlinkUdfPathContextHolder createFlinkUdfPathContextHolder() { return udfPathContextHolder; } - public static List> getUdfClassByJar(File jarPath) { - Assert.notNull(jarPath); - - List> classList = new ArrayList<>(); - try (JarClassLoader loader = new JarClassLoader()) { - loader.addJar(jarPath); - - ClassScanner classScanner = - new ClassScanner("", aClass -> ClassUtil.isAssignable(UserDefinedFunction.class, aClass)); - classScanner.setClassLoader(loader); - ReflectUtil.invoke(classScanner, "scanJar", new JarFile(jarPath)); - Set> classes = - (Set>) ReflectUtil.getFieldValue(classScanner, "classes"); - for (Class aClass : classes) { - try { - UserDefinedFunctionHelper.validateClass(aClass); - classList.add(aClass); - } catch (Exception ex) { - throw new DinkyException(); - } - } - } catch (ValidationException e) { - throw e; - } catch (Exception e) { - e.printStackTrace(); - } - return classList; - } - public static List getPythonUdfList(String udfFile) { return getPythonUdfList(SystemConfiguration.getInstances().getPythonHome(), udfFile); } @@ -473,4 +446,39 @@ public static void writeManifest( JSONUtil.toJsonStr(flinkUdfManifest), PathConstant.getUdfPackagePath(taskId) + PathConstant.DEP_MANIFEST); } + + public static List getUdfClassNameByJarPath(String path) { + List> clazz = getUdfClassByJar(new File(path)); + return clazz.stream().map(Class::getName).collect(Collectors.toList()); + } + + public static List> getUdfClassByJar(File jarPath) { + Assert.notNull(jarPath); + + List> classList = new ArrayList<>(); + try (JarClassLoader loader = new JarClassLoader()) { + loader.addJar(jarPath); + + ClassScanner classScanner = + new ClassScanner("", aClass -> ClassUtil.isAssignable(UserDefinedFunction.class, aClass)); + classScanner.setClassLoader(loader); + ReflectUtil.invoke(classScanner, "scanJar", new JarFile(jarPath)); + Set> classes = + (Set>) ReflectUtil.getFieldValue(classScanner, "classes"); + for (Class aClass : classes) { + try { + UserDefinedFunctionHelper.validateClass(aClass); + classList.add(aClass); + } catch (Exception ex) { + throw new DinkyException(); + } + } + } catch (ValidationException e) { + throw e; + } catch (Exception e) { + e.printStackTrace(); + } + return classList; + } + } From 50e1e739f20c34562d5ddc5e124ffb09f540d920 Mon Sep 17 00:00:00 2001 From: leechor Date: Sun, 31 Mar 2024 13:17:05 +0000 Subject: [PATCH 35/87] Spotless Apply --- dinky-admin/pom.xml | 60 +++++++++---------- .../dinky/controller/DownloadController.java | 7 +-- .../main/java/org/dinky/init/SystemInit.java | 3 +- .../main/java/org/dinky/job/JobManager.java | 28 ++++----- .../org/dinky/job/handler/AbsJobHandler.java | 5 +- .../dinky/job/handler/JobRefreshHandler.java | 6 +- .../impl/ClusterConfigurationServiceImpl.java | 2 +- .../impl/ClusterInstanceServiceImpl.java | 4 +- .../service/impl/PrintTableServiceImpl.java | 5 +- .../dinky/service/impl/StudioServiceImpl.java | 3 - .../dinky/service/impl/TaskServiceImpl.java | 3 +- .../dinky/service/impl/UDFServiceImpl.java | 3 +- .../resource/impl/ResourceServiceImpl.java | 31 +++++----- .../sse/git/AnalysisUdfClassStepSse.java | 4 +- .../sse/git/AnalysisUdfPythonStepSse.java | 4 +- .../java/org/dinky/url/RsURLConnection.java | 3 +- .../main/java/org/dinky/utils/UDFUtils.java | 3 +- .../dinky/resource/BaseResourceManager.java | 2 +- .../resource/impl/OssResourceManager.java | 3 +- .../main/java/org/dinky/job/JobConfig.java | 5 +- .../main/java/org/dinky/job/JobResult.java | 2 +- .../dinky/remote/ServerExecutorService.java | 36 +++++------ .../java/org/dinky/job/JobManagerHandler.java | 6 +- .../dinky/job/builder/JobTransBuilder.java | 5 +- .../org/dinky/utils/DinkyClassLoaderUtil.java | 3 +- .../java/org/dinky/JobManagerServiceImpl.java | 43 +++++++------ .../java/org/dinky/function/util/UDFUtil.java | 19 +++--- 27 files changed, 138 insertions(+), 160 deletions(-) diff --git a/dinky-admin/pom.xml b/dinky-admin/pom.xml index 5620d6638f..06d3ba4f26 100644 --- a/dinky-admin/pom.xml +++ b/dinky-admin/pom.xml @@ -36,10 +36,10 @@ - - - - + + + + org.mitre.dsmiley.httpproxy smiley-http-proxy-servlet @@ -177,28 +177,28 @@ org.springframework.boot spring-boot-starter-actuator - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + org.dinky dinky-daemon @@ -219,10 +219,10 @@ org.dinky dinky-alert-base - - - - + + + + org.dinky dinky-client-hadoop diff --git a/dinky-admin/src/main/java/org/dinky/controller/DownloadController.java b/dinky-admin/src/main/java/org/dinky/controller/DownloadController.java index 77d6732867..03d76c6d4d 100644 --- a/dinky-admin/src/main/java/org/dinky/controller/DownloadController.java +++ b/dinky-admin/src/main/java/org/dinky/controller/DownloadController.java @@ -24,7 +24,6 @@ import org.dinky.data.model.FlinkUdfManifest; import org.dinky.function.constant.PathConstant; import org.dinky.function.util.ZipWriter; -//import org.dinky.resource.BaseResourceManager; import java.io.File; import java.io.InputStream; @@ -117,8 +116,8 @@ public void downloadAppJar(@PathVariable String version, HttpServletResponse res @GetMapping("downloadFromRs") @ApiOperation("Download From Resource") public void downloadJavaUDF(String path, HttpServletResponse resp) { - // TODO: 2024/3/31 -// InputStream inputStream = BaseResourceManager.getInstance().readFile(path); -// ServletUtil.write(resp, inputStream); + // TODO: 2024/3/31 + // InputStream inputStream = BaseResourceManager.getInstance().readFile(path); + // ServletUtil.write(resp, inputStream); } } diff --git a/dinky-admin/src/main/java/org/dinky/init/SystemInit.java b/dinky-admin/src/main/java/org/dinky/init/SystemInit.java index cffa31512e..48aa51c2cd 100644 --- a/dinky-admin/src/main/java/org/dinky/init/SystemInit.java +++ b/dinky-admin/src/main/java/org/dinky/init/SystemInit.java @@ -223,7 +223,8 @@ public static Project getProject() { public void registerUDF() { List allUDF = taskService.getReleaseUDF(); if (CollUtil.isNotEmpty(allUDF)) { - JobManager.build(new JobConfig()).registerPool(allUDF.stream().map(UDFUtils::taskToUDF).collect(Collectors.toList())); + JobManager.build(new JobConfig()) + .registerPool(allUDF.stream().map(UDFUtils::taskToUDF).collect(Collectors.toList())); } JobManager.build(new JobConfig()).updateGitPool(gitProjectService.getGitPool()); } diff --git a/dinky-admin/src/main/java/org/dinky/job/JobManager.java b/dinky-admin/src/main/java/org/dinky/job/JobManager.java index b52d8fa90f..0a4c14f728 100644 --- a/dinky-admin/src/main/java/org/dinky/job/JobManager.java +++ b/dinky-admin/src/main/java/org/dinky/job/JobManager.java @@ -20,28 +20,28 @@ package org.dinky.job; import org.dinky.cluster.FlinkClusterInfo; +import org.dinky.data.annotations.ProcessStep; import org.dinky.data.enums.JobStatus; +import org.dinky.data.enums.ProcessStepType; import org.dinky.data.model.Catalog; import org.dinky.data.model.CheckPointReadTable; import org.dinky.data.model.Column; import org.dinky.data.model.ResourcesVO; import org.dinky.data.model.Schema; import org.dinky.data.model.Table; +import org.dinky.data.result.ExplainResult; +import org.dinky.data.result.IResult; +import org.dinky.data.result.ResultPool; +import org.dinky.data.result.SelectResult; import org.dinky.explainer.lineage.LineageResult; import org.dinky.function.data.model.UDF; import org.dinky.function.data.model.UDFPath; import org.dinky.gateway.config.GatewayConfig; +import org.dinky.gateway.enums.SavePointType; import org.dinky.gateway.result.GatewayResult; +import org.dinky.gateway.result.SavePointResult; import org.dinky.metadata.config.DriverConfig; import org.dinky.remote.ServerExecutorService; -import org.dinky.data.annotations.ProcessStep; -import org.dinky.data.enums.ProcessStepType; -import org.dinky.data.result.ExplainResult; -import org.dinky.data.result.IResult; -import org.dinky.data.result.ResultPool; -import org.dinky.data.result.SelectResult; -import org.dinky.gateway.enums.SavePointType; -import org.dinky.gateway.result.SavePointResult; import java.rmi.RemoteException; import java.rmi.registry.LocateRegistry; @@ -295,17 +295,9 @@ public List getCatalog() { } } - public void setSchemaInfo( - String catalogName, - String databaseName, - Schema schema, - List
tables) { + public void setSchemaInfo(String catalogName, String databaseName, Schema schema, List
tables) { try { - serverExecutorService.setSchemaInfo( - catalogName, - databaseName, - schema, - tables); + serverExecutorService.setSchemaInfo(catalogName, databaseName, schema, tables); } catch (RemoteException e) { throw new RuntimeException(e); } diff --git a/dinky-admin/src/main/java/org/dinky/job/handler/AbsJobHandler.java b/dinky-admin/src/main/java/org/dinky/job/handler/AbsJobHandler.java index 4eaf9f2711..126a7890ba 100644 --- a/dinky-admin/src/main/java/org/dinky/job/handler/AbsJobHandler.java +++ b/dinky-admin/src/main/java/org/dinky/job/handler/AbsJobHandler.java @@ -19,9 +19,6 @@ package org.dinky.job.handler; -import org.dinky.job.Job; import org.dinky.job.JobHandler; -public abstract class AbsJobHandler implements JobHandler { - -} +public abstract class AbsJobHandler implements JobHandler {} diff --git a/dinky-admin/src/main/java/org/dinky/job/handler/JobRefreshHandler.java b/dinky-admin/src/main/java/org/dinky/job/handler/JobRefreshHandler.java index 2818361e2e..d4a711beea 100644 --- a/dinky-admin/src/main/java/org/dinky/job/handler/JobRefreshHandler.java +++ b/dinky-admin/src/main/java/org/dinky/job/handler/JobRefreshHandler.java @@ -158,9 +158,9 @@ public static boolean refreshJob(JobInfoDetail jobInfoDetail, boolean needSave) boolean isDone = (JobStatus.isDone(jobInstance.getStatus())) || (TimeUtil.localDateTimeToLong(jobInstance.getFinishTime()) > 0 - && Duration.between(jobInstance.getFinishTime(), LocalDateTime.now()) - .toMinutes() - >= 1); + && Duration.between(jobInstance.getFinishTime(), LocalDateTime.now()) + .toMinutes() + >= 1); isDone = !isTransition && isDone; diff --git a/dinky-admin/src/main/java/org/dinky/service/impl/ClusterConfigurationServiceImpl.java b/dinky-admin/src/main/java/org/dinky/service/impl/ClusterConfigurationServiceImpl.java index 8653cdd894..6a7c87f0e5 100644 --- a/dinky-admin/src/main/java/org/dinky/service/impl/ClusterConfigurationServiceImpl.java +++ b/dinky-admin/src/main/java/org/dinky/service/impl/ClusterConfigurationServiceImpl.java @@ -85,7 +85,7 @@ public TestResult testGateway(ClusterConfigurationDTO config) { } public static TestResult testGateway(GatewayConfig gatewayConfig) { - // TODO: 2024/3/31 + // TODO: 2024/3/31 // return Gateway.build(gatewayConfig).test(); return null; } diff --git a/dinky-admin/src/main/java/org/dinky/service/impl/ClusterInstanceServiceImpl.java b/dinky-admin/src/main/java/org/dinky/service/impl/ClusterInstanceServiceImpl.java index 3f8872fd9e..86fb79efdf 100644 --- a/dinky-admin/src/main/java/org/dinky/service/impl/ClusterInstanceServiceImpl.java +++ b/dinky-admin/src/main/java/org/dinky/service/impl/ClusterInstanceServiceImpl.java @@ -87,8 +87,8 @@ public FlinkClusterInfo checkHeartBeat(String hosts, String host) { public String getJobManagerAddress(ClusterInstance clusterInstance) { // TODO 这里判空逻辑有问题,clusterInstance有可能为null DinkyAssert.check(clusterInstance); - FlinkClusterInfo info = - JobManager.build(new JobConfig()).testFlinkJobManagerIP(clusterInstance.getHosts(), clusterInstance.getJobManagerHost()); + FlinkClusterInfo info = JobManager.build(new JobConfig()) + .testFlinkJobManagerIP(clusterInstance.getHosts(), clusterInstance.getJobManagerHost()); String host = null; if (info.isEffective()) { host = info.getJobManagerAddress(); diff --git a/dinky-admin/src/main/java/org/dinky/service/impl/PrintTableServiceImpl.java b/dinky-admin/src/main/java/org/dinky/service/impl/PrintTableServiceImpl.java index b4c74776d4..4fd7e60a1c 100644 --- a/dinky-admin/src/main/java/org/dinky/service/impl/PrintTableServiceImpl.java +++ b/dinky-admin/src/main/java/org/dinky/service/impl/PrintTableServiceImpl.java @@ -25,14 +25,12 @@ import org.dinky.job.JobConfig; import org.dinky.job.JobManager; import org.dinky.service.PrintTableService; -import org.dinky.utils.SqlUtil; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; import java.net.SocketException; import java.net.UnknownHostException; -import java.util.Arrays; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -60,7 +58,8 @@ public PrintTableServiceImpl() { @Override public List getPrintTables(String statement) { // TODO: 2023/4/7 this function not support variable sql, because, JobManager and executor - return JobManager.build(new JobConfig()).getPrintTable(statement).stream().map(t -> new PrintTableVo(t, getFullTableName(t))) + return JobManager.build(new JobConfig()).getPrintTable(statement).stream() + .map(t -> new PrintTableVo(t, getFullTableName(t))) .collect(Collectors.toList()); } diff --git a/dinky-admin/src/main/java/org/dinky/service/impl/StudioServiceImpl.java b/dinky-admin/src/main/java/org/dinky/service/impl/StudioServiceImpl.java index 0633f3110f..a40be719ce 100644 --- a/dinky-admin/src/main/java/org/dinky/service/impl/StudioServiceImpl.java +++ b/dinky-admin/src/main/java/org/dinky/service/impl/StudioServiceImpl.java @@ -31,7 +31,6 @@ import org.dinky.data.model.DataBase; import org.dinky.data.model.Schema; import org.dinky.data.model.Table; -import org.dinky.data.result.DDLResult; import org.dinky.data.result.IResult; import org.dinky.data.result.SelectResult; import org.dinky.explainer.lineage.LineageResult; @@ -42,11 +41,9 @@ import org.dinky.service.DataBaseService; import org.dinky.service.StudioService; import org.dinky.service.TaskService; -import org.dinky.utils.RunTimeUtil; import java.util.ArrayList; import java.util.List; -import java.util.Map; import org.springframework.stereotype.Service; diff --git a/dinky-admin/src/main/java/org/dinky/service/impl/TaskServiceImpl.java b/dinky-admin/src/main/java/org/dinky/service/impl/TaskServiceImpl.java index 4edc773fd9..2ee481089a 100644 --- a/dinky-admin/src/main/java/org/dinky/service/impl/TaskServiceImpl.java +++ b/dinky-admin/src/main/java/org/dinky/service/impl/TaskServiceImpl.java @@ -558,7 +558,8 @@ public boolean changeTaskLifeRecyle(Integer taskId, JobLifeCycle lifeCycle) thro if (Dialect.isUDF(task.getDialect()) && Asserts.isNotNull(task.getConfigJson()) && Asserts.isNotNull(task.getConfigJson().getUdfConfig())) { - JobManager.build(new JobConfig()).removeUdfCodePool(task.getConfigJson().getUdfConfig().getClassName()); + JobManager.build(new JobConfig()) + .removeUdfCodePool(task.getConfigJson().getUdfConfig().getClassName()); } } boolean saved = saveOrUpdate(task.buildTask()); diff --git a/dinky-admin/src/main/java/org/dinky/service/impl/UDFServiceImpl.java b/dinky-admin/src/main/java/org/dinky/service/impl/UDFServiceImpl.java index 44d0f36a81..7a93016e23 100644 --- a/dinky-admin/src/main/java/org/dinky/service/impl/UDFServiceImpl.java +++ b/dinky-admin/src/main/java/org/dinky/service/impl/UDFServiceImpl.java @@ -28,7 +28,6 @@ import org.dinky.mapper.UDFManageMapper; import org.dinky.service.UDFService; import org.dinky.service.resource.ResourcesService; -import org.dinky.utils.UDFUtils; import java.io.File; import java.util.Collection; @@ -128,7 +127,7 @@ public void addOrUpdateByResourceId(List resourceIds) { }); } else if ("py".equals(suffix) || "zip".equals(suffix)) { File file = resourcesService.getFile(x.getId()); - List pythonUdfList =jm.getPythonUdfList(file.getAbsolutePath()); + List pythonUdfList = jm.getPythonUdfList(file.getAbsolutePath()); return pythonUdfList.stream().map(className -> { UDFManage udfManage = UDFManage.builder() .className(className) diff --git a/dinky-admin/src/main/java/org/dinky/service/resource/impl/ResourceServiceImpl.java b/dinky-admin/src/main/java/org/dinky/service/resource/impl/ResourceServiceImpl.java index 826e79c565..923c5fa016 100644 --- a/dinky-admin/src/main/java/org/dinky/service/resource/impl/ResourceServiceImpl.java +++ b/dinky-admin/src/main/java/org/dinky/service/resource/impl/ResourceServiceImpl.java @@ -19,7 +19,6 @@ package org.dinky.service.resource.impl; -import cn.hutool.core.io.FileUtil; import org.dinky.assertion.DinkyAssert; import org.dinky.data.dto.TreeNodeDTO; import org.dinky.data.enums.Status; @@ -52,6 +51,7 @@ import cn.hutool.cache.impl.TimedCache; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.io.FileUtil; import cn.hutool.core.lang.Assert; import cn.hutool.core.lang.Opt; import cn.hutool.core.util.StrUtil; @@ -73,21 +73,20 @@ public boolean syncRemoteDirectoryStructure() { Map localMap = local.stream().collect(Collectors.toMap(Resources::getId, Function.identity())); - List resourcesList = - jobManager.getFullDirectoryStructure(rootResource.getId()).stream() - .filter(x -> x.getPid() != -1) - .map(Resources::of) - .peek(x -> { - // Restore the existing information. If the remotmap is not available, - // it means that the configuration is out of sync and no processing will be done. - Resources resources = localMap.get(x.getFileName().hashCode()); - if (resources != null) { - x.setDescription(resources.getDescription()); - x.setType(resources.getType()); - x.setUserId(resources.getUserId()); - } - }) - .collect(Collectors.toList()); + List resourcesList = jobManager.getFullDirectoryStructure(rootResource.getId()).stream() + .filter(x -> x.getPid() != -1) + .map(Resources::of) + .peek(x -> { + // Restore the existing information. If the remotmap is not available, + // it means that the configuration is out of sync and no processing will be done. + Resources resources = localMap.get(x.getFileName().hashCode()); + if (resources != null) { + x.setDescription(resources.getDescription()); + x.setType(resources.getType()); + x.setUserId(resources.getUserId()); + } + }) + .collect(Collectors.toList()); // not delete root directory this.remove(new LambdaQueryWrapper().ne(Resources::getPid, -1)); this.saveBatch(resourcesList); diff --git a/dinky-admin/src/main/java/org/dinky/sse/git/AnalysisUdfClassStepSse.java b/dinky-admin/src/main/java/org/dinky/sse/git/AnalysisUdfClassStepSse.java index f4a122cbfe..8adb286015 100644 --- a/dinky-admin/src/main/java/org/dinky/sse/git/AnalysisUdfClassStepSse.java +++ b/dinky-admin/src/main/java/org/dinky/sse/git/AnalysisUdfClassStepSse.java @@ -35,7 +35,6 @@ import java.util.TreeMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.atomic.AtomicInteger; -import java.util.stream.Collectors; import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; @@ -71,7 +70,8 @@ public void exec() { throw new DinkyException("flink dependency not found"); } pathList.parallelStream().forEach(jar -> { - List udfClassByJar = JobManager.build(new JobConfig()).getUdfClassNameByJarPath(URLUtils.toFile(jar).getPath()); + List udfClassByJar = JobManager.build(new JobConfig()) + .getUdfClassNameByJarPath(URLUtils.toFile(jar).getPath()); udfMap.put(jar, udfClassByJar); sendMsg(Dict.create().set(jar, udfClassByJar)); }); diff --git a/dinky-admin/src/main/java/org/dinky/sse/git/AnalysisUdfPythonStepSse.java b/dinky-admin/src/main/java/org/dinky/sse/git/AnalysisUdfPythonStepSse.java index 55366a8c89..52804bf6be 100644 --- a/dinky-admin/src/main/java/org/dinky/sse/git/AnalysisUdfPythonStepSse.java +++ b/dinky-admin/src/main/java/org/dinky/sse/git/AnalysisUdfPythonStepSse.java @@ -22,7 +22,6 @@ import org.dinky.data.dto.GitAnalysisJarDTO; import org.dinky.data.exception.DinkyException; import org.dinky.data.model.GitProject; -import org.dinky.data.model.SystemConfiguration; import org.dinky.job.JobConfig; import org.dinky.job.JobManager; import org.dinky.sse.StepSse; @@ -60,8 +59,7 @@ public void exec() { } catch (ClassNotFoundException e) { throw new DinkyException("flink dependency not found"); } - List pythonUdfList = - JobManager.build(new JobConfig()).getPythonUdfList(zipFile.getAbsolutePath()); + List pythonUdfList = JobManager.build(new JobConfig()).getPythonUdfList(zipFile.getAbsolutePath()); GitAnalysisJarDTO gitAnalysisJarDTO = new GitAnalysisJarDTO(); gitAnalysisJarDTO.setJarPath(zipFilePath); gitAnalysisJarDTO.setClassList(pythonUdfList); diff --git a/dinky-admin/src/main/java/org/dinky/url/RsURLConnection.java b/dinky-admin/src/main/java/org/dinky/url/RsURLConnection.java index f53ba29446..a225aa07ad 100644 --- a/dinky-admin/src/main/java/org/dinky/url/RsURLConnection.java +++ b/dinky-admin/src/main/java/org/dinky/url/RsURLConnection.java @@ -19,7 +19,6 @@ package org.dinky.url; -import cn.hutool.core.io.IoUtil; import org.dinky.job.JobConfig; import org.dinky.job.JobManager; @@ -27,6 +26,8 @@ import java.net.URL; import java.net.URLConnection; +import cn.hutool.core.io.IoUtil; + public class RsURLConnection extends URLConnection { private byte[] context; diff --git a/dinky-admin/src/main/java/org/dinky/utils/UDFUtils.java b/dinky-admin/src/main/java/org/dinky/utils/UDFUtils.java index 0d706302e5..c32f5636b4 100644 --- a/dinky-admin/src/main/java/org/dinky/utils/UDFUtils.java +++ b/dinky-admin/src/main/java/org/dinky/utils/UDFUtils.java @@ -19,12 +19,13 @@ package org.dinky.utils; -import org.apache.flink.table.catalog.FunctionLanguage; import org.dinky.assertion.Asserts; import org.dinky.data.exception.BusException; import org.dinky.data.model.Task; import org.dinky.function.data.model.UDF; +import org.apache.flink.table.catalog.FunctionLanguage; + public class UDFUtils { public static UDF taskToUDF(Task task) { diff --git a/dinky-client/dinky-client-base/src/main/java/org/dinky/resource/BaseResourceManager.java b/dinky-client/dinky-client-base/src/main/java/org/dinky/resource/BaseResourceManager.java index 72e82ea072..2a80d9de59 100644 --- a/dinky-client/dinky-client-base/src/main/java/org/dinky/resource/BaseResourceManager.java +++ b/dinky-client/dinky-client-base/src/main/java/org/dinky/resource/BaseResourceManager.java @@ -48,7 +48,7 @@ public interface BaseResourceManager { void rename(String path, String newPath); - default void putFile(String path, byte[] fileContext){ + default void putFile(String path, byte[] fileContext) { putFile(path, IoUtil.toStream(fileContext)); } diff --git a/dinky-client/dinky-client-base/src/main/java/org/dinky/resource/impl/OssResourceManager.java b/dinky-client/dinky-client-base/src/main/java/org/dinky/resource/impl/OssResourceManager.java index 5f6d0a8283..268c9bdf0f 100644 --- a/dinky-client/dinky-client-base/src/main/java/org/dinky/resource/impl/OssResourceManager.java +++ b/dinky-client/dinky-client-base/src/main/java/org/dinky/resource/impl/OssResourceManager.java @@ -140,7 +140,8 @@ public byte[] readFileContext(String path) { } public OssTemplate getOssTemplate() { - if (ossTemplate == null && BaseResourceManager.instances.getResourcesEnable().getValue()) { + if (ossTemplate == null + && BaseResourceManager.instances.getResourcesEnable().getValue()) { throw new BusException(Status.RESOURCE_OSS_CONFIGURATION_ERROR); } return ossTemplate; diff --git a/dinky-common/src/main/java/org/dinky/job/JobConfig.java b/dinky-common/src/main/java/org/dinky/job/JobConfig.java index 92a5e659f0..77f56b37d7 100644 --- a/dinky-common/src/main/java/org/dinky/job/JobConfig.java +++ b/dinky-common/src/main/java/org/dinky/job/JobConfig.java @@ -51,6 +51,7 @@ public class JobConfig implements Serializable { private static final String REST_PORT = "rest.port"; private static final String DEFAULT_PARAllELISM = "parallelism.default"; + @ApiModelProperty( value = "Flink run mode", dataType = "String", @@ -188,9 +189,7 @@ public JobConfig() { } public void setAddress(String address) { - if (GatewayType.LOCAL.equalsValue(type) - && Asserts.isNotNull(configJson) - && configJson.containsKey(REST_PORT)) { + if (GatewayType.LOCAL.equalsValue(type) && Asserts.isNotNull(configJson) && configJson.containsKey(REST_PORT)) { int colonIndex = address.indexOf(':'); if (colonIndex == -1) { this.address = address + NetConstant.COLON + configJson.get(REST_PORT); diff --git a/dinky-common/src/main/java/org/dinky/job/JobResult.java b/dinky-common/src/main/java/org/dinky/job/JobResult.java index b19d2df6e7..45f44d1875 100644 --- a/dinky-common/src/main/java/org/dinky/job/JobResult.java +++ b/dinky-common/src/main/java/org/dinky/job/JobResult.java @@ -20,6 +20,7 @@ package org.dinky.job; import org.dinky.data.result.IResult; +import org.dinky.metadata.result.JdbcSelectResult; import java.io.Serializable; import java.time.LocalDateTime; @@ -29,7 +30,6 @@ import io.swagger.annotations.ApiModelProperty; import lombok.Getter; import lombok.Setter; -import org.dinky.metadata.result.JdbcSelectResult; /** * JobResult diff --git a/dinky-common/src/main/java/org/dinky/remote/ServerExecutorService.java b/dinky-common/src/main/java/org/dinky/remote/ServerExecutorService.java index 2b309430af..47049910f3 100644 --- a/dinky-common/src/main/java/org/dinky/remote/ServerExecutorService.java +++ b/dinky-common/src/main/java/org/dinky/remote/ServerExecutorService.java @@ -29,26 +29,25 @@ import org.dinky.data.model.Table; import org.dinky.data.result.ExplainResult; import org.dinky.data.result.IResult; - -import java.rmi.Remote; -import java.rmi.RemoteException; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -import com.fasterxml.jackson.databind.node.ObjectNode; import org.dinky.explainer.lineage.LineageResult; import org.dinky.function.data.model.UDF; import org.dinky.function.data.model.UDFPath; import org.dinky.gateway.config.GatewayConfig; +import org.dinky.gateway.enums.SavePointType; import org.dinky.gateway.result.GatewayResult; import org.dinky.gateway.result.SavePointResult; -import org.dinky.gateway.enums.SavePointType; import org.dinky.job.Job; import org.dinky.job.JobConfig; import org.dinky.job.JobResult; import org.dinky.metadata.config.DriverConfig; +import java.rmi.Remote; +import java.rmi.RemoteException; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import com.fasterxml.jackson.databind.node.ObjectNode; public interface ServerExecutorService extends Remote { void init(JobConfig config, boolean isPlanMode) throws RemoteException; @@ -79,11 +78,11 @@ public interface ServerExecutorService extends Remote { void prepare(String statement) throws RemoteException; - List getPythonUdfList(String udfFile)throws RemoteException; + List getPythonUdfList(String udfFile) throws RemoteException; - JobStatus getJobStatus(GatewayConfig gatewayConfig, String appId)throws RemoteException; + JobStatus getJobStatus(GatewayConfig gatewayConfig, String appId) throws RemoteException; - void onJobGatewayFinishCallback(JobConfig jobConfig, String status)throws RemoteException; + void onJobGatewayFinishCallback(JobConfig jobConfig, String status) throws RemoteException; List getUdfClassNameByJarPath(String path) throws RemoteException; @@ -103,15 +102,12 @@ public interface ServerExecutorService extends Remote { LineageResult getSqlLineageByOne(String statement, String type) throws RemoteException; - LineageResult getSqlLineage(String statement, String mysql, DriverConfig> driverConfig) throws RemoteException; + LineageResult getSqlLineage(String statement, String mysql, DriverConfig> driverConfig) + throws RemoteException; List getCatalog() throws RemoteException; - void setSchemaInfo( - String catalogName, - String database, - Schema schema, - List
tables) throws RemoteException; + void setSchemaInfo(String catalogName, String database, Schema schema, List
tables) throws RemoteException; List getColumnList(String catalogName, String database, String tableName) throws RemoteException; @@ -127,11 +123,11 @@ void setSchemaInfo( FlinkClusterInfo testFlinkJobManagerIP(String hosts, String host) throws RemoteException; - void killCluster(GatewayConfig gatewayConfig)throws RemoteException; + void killCluster(GatewayConfig gatewayConfig) throws RemoteException; GatewayResult deployCluster(GatewayConfig gatewayConfig) throws RemoteException; - void addOrUpdate(UDF udf)throws RemoteException; + void addOrUpdate(UDF udf) throws RemoteException; void removeUdfCodePool(String className) throws RemoteException; diff --git a/dinky-core/src/main/java/org/dinky/job/JobManagerHandler.java b/dinky-core/src/main/java/org/dinky/job/JobManagerHandler.java index b09d327dab..9f624cd7a8 100644 --- a/dinky-core/src/main/java/org/dinky/job/JobManagerHandler.java +++ b/dinky-core/src/main/java/org/dinky/job/JobManagerHandler.java @@ -164,8 +164,10 @@ public JobResult executeJarSql(String statement) throws Exception { } } else { GatewayResult gatewayResult; - config.addGatewayConfig( - executor.getCustomTableEnvironment().getConfig().getConfiguration().toMap()); + config.addGatewayConfig(executor.getCustomTableEnvironment() + .getConfig() + .getConfiguration() + .toMap()); if (runMode.isApplicationMode()) { gatewayResult = Gateway.build(config.getGatewayConfig()).submitJar(executor.getUdfPathContextHolder()); diff --git a/dinky-core/src/main/java/org/dinky/job/builder/JobTransBuilder.java b/dinky-core/src/main/java/org/dinky/job/builder/JobTransBuilder.java index 66b52131bb..17f2c22f47 100644 --- a/dinky-core/src/main/java/org/dinky/job/builder/JobTransBuilder.java +++ b/dinky-core/src/main/java/org/dinky/job/builder/JobTransBuilder.java @@ -216,7 +216,10 @@ private GatewayResult submitByGateway(List inserts) { GatewayResult gatewayResult = null; // Use gateway need to build gateway config, include flink configuration. - config.addGatewayConfig(executor.getCustomTableEnvironment().getConfig().getConfiguration().toMap()); + config.addGatewayConfig(executor.getCustomTableEnvironment() + .getConfig() + .getConfiguration() + .toMap()); if (runMode.isApplicationMode()) { // Application mode need to submit dinky-app.jar that in the hdfs or image. diff --git a/dinky-core/src/main/java/org/dinky/utils/DinkyClassLoaderUtil.java b/dinky-core/src/main/java/org/dinky/utils/DinkyClassLoaderUtil.java index 0ab2717a72..37be2b6f25 100644 --- a/dinky-core/src/main/java/org/dinky/utils/DinkyClassLoaderUtil.java +++ b/dinky-core/src/main/java/org/dinky/utils/DinkyClassLoaderUtil.java @@ -23,6 +23,7 @@ import org.dinky.classloader.DinkyClassLoader; import org.dinky.context.FlinkUdfPathContextHolder; import org.dinky.data.exception.DinkyException; +import org.dinky.job.JobConfig; import org.apache.flink.configuration.PipelineOptions; @@ -30,8 +31,6 @@ import cn.hutool.core.collection.CollUtil; import cn.hutool.core.io.FileUtil; -import org.dinky.flink.checkpoint.CheckpointRead; -import org.dinky.job.JobConfig; public class DinkyClassLoaderUtil { diff --git a/dinky-executor-server/src/main/java/org/dinky/JobManagerServiceImpl.java b/dinky-executor-server/src/main/java/org/dinky/JobManagerServiceImpl.java index ab77c7adfd..ec43e7bade 100644 --- a/dinky-executor-server/src/main/java/org/dinky/JobManagerServiceImpl.java +++ b/dinky-executor-server/src/main/java/org/dinky/JobManagerServiceImpl.java @@ -50,6 +50,13 @@ import org.dinky.job.JobConfig; import org.dinky.job.JobManagerHandler; import org.dinky.job.JobResult; +import org.dinky.metadata.config.DriverConfig; +import org.dinky.parser.SqlType; +import org.dinky.remote.ServerExecutorService; +import org.dinky.resource.BaseResourceManager; +import org.dinky.trans.Operations; +import org.dinky.utils.FlinkTableMetadataUtil; +import org.dinky.utils.SqlUtil; import java.rmi.RemoteException; import java.rmi.server.UnicastRemoteObject; @@ -62,13 +69,6 @@ import com.fasterxml.jackson.databind.node.ObjectNode; import lombok.extern.slf4j.Slf4j; -import org.dinky.metadata.config.DriverConfig; -import org.dinky.parser.SqlType; -import org.dinky.remote.ServerExecutorService; -import org.dinky.resource.BaseResourceManager; -import org.dinky.trans.Operations; -import org.dinky.utils.FlinkTableMetadataUtil; -import org.dinky.utils.SqlUtil; @Slf4j public class JobManagerServiceImpl extends UnicastRemoteObject implements ServerExecutorService { @@ -76,14 +76,11 @@ public class JobManagerServiceImpl extends UnicastRemoteObject implements Server JobManagerHandler jobManagerHandler; protected static final CheckpointRead INSTANCE = new CheckpointRead(); - - public JobManagerServiceImpl() throws RemoteException { - } + public JobManagerServiceImpl() throws RemoteException {} @Override public void init(JobConfig config, boolean isPlanMode) throws RemoteException { jobManagerHandler = JobManagerHandler.build(config, isPlanMode); - } @Override @@ -161,7 +158,6 @@ public void prepare(String statement) throws RemoteException { jobManagerHandler.prepare(statement); } - // TODO: 2024/3/29 utils, coud individual rmeote interface @Override public List getPythonUdfList(String udfFile) throws RemoteException { @@ -240,7 +236,8 @@ public LineageResult getSqlLineageByOne(String statement, String type) throws Re } @Override - public LineageResult getSqlLineage(String statement, String mysql, DriverConfig> driverConfig) throws RemoteException { + public LineageResult getSqlLineage(String statement, String mysql, DriverConfig> driverConfig) + throws RemoteException { return SQLLineageBuilder.getSqlLineage(statement, mysql, driverConfig); } @@ -250,26 +247,26 @@ public List getCatalog() throws RemoteException { } @Override - public void setSchemaInfo( - String catalogName, - String database, - Schema schema, - List
tables) throws RemoteException { - FlinkTableMetadataUtil.setSchemaInfo(jobManagerHandler.getExecutor().getCustomTableEnvironment(), catalogName, database, schema, tables); + public void setSchemaInfo(String catalogName, String database, Schema schema, List
tables) + throws RemoteException { + FlinkTableMetadataUtil.setSchemaInfo( + jobManagerHandler.getExecutor().getCustomTableEnvironment(), catalogName, database, schema, tables); } @Override public List getColumnList(String catalogName, String database, String tableName) throws RemoteException { - return FlinkTableMetadataUtil.getColumnList(jobManagerHandler.getExecutor().getCustomTableEnvironment(), catalogName, database, tableName); + return FlinkTableMetadataUtil.getColumnList( + jobManagerHandler.getExecutor().getCustomTableEnvironment(), catalogName, database, tableName); } @Override - public Map> readCheckpoint(String path, String operatorId) throws RemoteException { + public Map> readCheckpoint(String path, String operatorId) + throws RemoteException { return INSTANCE.readCheckpoint(path, operatorId); } @Override - public List getPrintTables(String statement) throws RemoteException{ + public List getPrintTables(String statement) throws RemoteException { // TODO: 2023/4/7 this function not support variable sql, because, JobManager and executor // couple function // and status and task execute. @@ -329,4 +326,4 @@ public String getPyUDFAttr(String statement) throws RemoteException { public String getScalaFullClassName(String statement) throws RemoteException { return UDFUtil.getScalaFullClassName(statement); } -} \ No newline at end of file +} diff --git a/dinky-function/src/main/java/org/dinky/function/util/UDFUtil.java b/dinky-function/src/main/java/org/dinky/function/util/UDFUtil.java index fda473431d..227c3a745e 100644 --- a/dinky-function/src/main/java/org/dinky/function/util/UDFUtil.java +++ b/dinky-function/src/main/java/org/dinky/function/util/UDFUtil.java @@ -19,14 +19,6 @@ package org.dinky.function.util; -import cn.hutool.core.lang.Assert; -import cn.hutool.core.lang.ClassScanner; -import cn.hutool.core.lang.JarClassLoader; -import cn.hutool.core.util.ClassUtil; -import cn.hutool.core.util.ReflectUtil; -import org.apache.flink.table.api.ValidationException; -import org.apache.flink.table.functions.UserDefinedFunction; -import org.apache.flink.table.functions.UserDefinedFunctionHelper; import org.dinky.assertion.Asserts; import org.dinky.classloader.DinkyClassLoader; import org.dinky.config.Dialect; @@ -42,17 +34,18 @@ import org.dinky.function.constant.PathConstant; import org.dinky.function.data.model.UDF; import org.dinky.function.pool.UdfCodePool; -import org.dinky.parser.SqlType; import org.dinky.pool.ClassEntity; import org.dinky.pool.ClassPool; -import org.dinky.utils.SqlUtil; import org.dinky.utils.URLUtils; import org.apache.flink.client.python.PythonFunctionFactory; import org.apache.flink.configuration.Configuration; import org.apache.flink.configuration.PipelineOptions; import org.apache.flink.python.PythonOptions; +import org.apache.flink.table.api.ValidationException; import org.apache.flink.table.catalog.FunctionLanguage; +import org.apache.flink.table.functions.UserDefinedFunction; +import org.apache.flink.table.functions.UserDefinedFunctionHelper; import java.io.File; import java.io.InputStream; @@ -76,11 +69,16 @@ import cn.hutool.core.convert.Convert; import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.resource.ResourceUtil; +import cn.hutool.core.lang.Assert; +import cn.hutool.core.lang.ClassScanner; import cn.hutool.core.lang.Dict; +import cn.hutool.core.lang.JarClassLoader; import cn.hutool.core.lang.Opt; import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.ClassLoaderUtil; +import cn.hutool.core.util.ClassUtil; import cn.hutool.core.util.ReUtil; +import cn.hutool.core.util.ReflectUtil; import cn.hutool.core.util.RuntimeUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.URLUtil; @@ -480,5 +478,4 @@ public static List> getUdfClassByJar(File jarPath) { } return classList; } - } From 5ff2dbc0d907933b27aff249746402db1123e8d1 Mon Sep 17 00:00:00 2001 From: sunlichao11 Date: Sun, 31 Mar 2024 21:15:14 +0800 Subject: [PATCH 36/87] fix --- .../main/java/org/dinky/job/JobManager.java | 2 +- .../org/dinky/job/ServerExecutorService.java | 59 +++++++++++++++++++ 2 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 dinky-admin/src/main/java/org/dinky/job/ServerExecutorService.java diff --git a/dinky-admin/src/main/java/org/dinky/job/JobManager.java b/dinky-admin/src/main/java/org/dinky/job/JobManager.java index 0a4c14f728..a3bf5ad6c9 100644 --- a/dinky-admin/src/main/java/org/dinky/job/JobManager.java +++ b/dinky-admin/src/main/java/org/dinky/job/JobManager.java @@ -77,7 +77,7 @@ private static void registerRemote() { // 从Registry中检索远程对象的存根/代理 serverExecutorService = (ServerExecutorService) registry.lookup("Compute"); } catch (Exception exception) { - System.out.println(exception); + throw new RuntimeException(exception); } } diff --git a/dinky-admin/src/main/java/org/dinky/job/ServerExecutorService.java b/dinky-admin/src/main/java/org/dinky/job/ServerExecutorService.java new file mode 100644 index 0000000000..e29715c7b3 --- /dev/null +++ b/dinky-admin/src/main/java/org/dinky/job/ServerExecutorService.java @@ -0,0 +1,59 @@ +/* + * + * 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.job; + +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.dinky.data.result.ExplainResult; +import org.dinky.data.result.IResult; +import org.dinky.gateway.enums.SavePointType; +import org.dinky.gateway.result.SavePointResult; + +import java.rmi.Remote; +import java.rmi.RemoteException; + +public interface ServerExecutorService extends Remote { + void init(JobConfig config, boolean isPlanMode) throws RemoteException; + + boolean close() throws RemoteException; + + ObjectNode getJarStreamGraphJson(String statement) throws RemoteException; + + JobResult executeJarSql(String statement) throws RemoteException; + + JobResult executeSql(String statement) throws RemoteException; + + IResult executeDDL(String statement) throws RemoteException; + + ExplainResult explainSql(String statement) throws RemoteException; + + ObjectNode getStreamGraph(String statement) throws RemoteException; + + String getJobPlanJson(String statement) throws RemoteException; + + boolean cancelNormal(String jobId) throws RemoteException; + + SavePointResult savepoint(String jobId, SavePointType savePointType, String savePoint) throws RemoteException; + + String exportSql(String sql) throws RemoteException; + + Job getJob() throws RemoteException; + + void prepare(String statement) throws RemoteException; +} From ec094a49178517f9681fa45ab2b2e1191c39186c Mon Sep 17 00:00:00 2001 From: leechor Date: Sun, 31 Mar 2024 13:58:07 +0000 Subject: [PATCH 37/87] Spotless Apply --- .../src/main/java/org/dinky/job/ServerExecutorService.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dinky-admin/src/main/java/org/dinky/job/ServerExecutorService.java b/dinky-admin/src/main/java/org/dinky/job/ServerExecutorService.java index e29715c7b3..d84f55ea1c 100644 --- a/dinky-admin/src/main/java/org/dinky/job/ServerExecutorService.java +++ b/dinky-admin/src/main/java/org/dinky/job/ServerExecutorService.java @@ -19,7 +19,6 @@ package org.dinky.job; -import com.fasterxml.jackson.databind.node.ObjectNode; import org.dinky.data.result.ExplainResult; import org.dinky.data.result.IResult; import org.dinky.gateway.enums.SavePointType; @@ -28,6 +27,8 @@ import java.rmi.Remote; import java.rmi.RemoteException; +import com.fasterxml.jackson.databind.node.ObjectNode; + public interface ServerExecutorService extends Remote { void init(JobConfig config, boolean isPlanMode) throws RemoteException; From ef6f6de97bb93feeba5d1f2bc7e927e41703abdf Mon Sep 17 00:00:00 2001 From: sunlichao11 Date: Tue, 2 Apr 2024 22:18:55 +0800 Subject: [PATCH 38/87] feat: could run --- .../src/main/java/org/dinky/Dinky.java | 4 +++ .../main/java/org/dinky/init/SystemInit.java | 2 +- .../main/java/org/dinky/job/JobManager.java | 9 +++--- .../dinky/service/impl/TaskServiceImpl.java | 2 +- .../resource/impl/LocalResourceManager.java | 3 +- .../dinky/resource/BaseResourceManager.java | 6 ++-- .../resource/impl/OssResourceManager.java | 2 +- .../org/dinky/data/model/Configuration.java | 12 ++++--- .../dinky/data/model/SystemConfiguration.java | 32 ++++++++++++++++--- .../main/java/org/dinky/job/JobConfig.java | 3 ++ .../dinky/remote/ServerExecutorService.java | 5 +-- .../main/java/org/dinky/job/IJobManager.java | 2 +- .../java/org/dinky/job/JobManagerHandler.java | 5 ++- .../org/dinky/job/builder/JobUDFBuilder.java | 3 +- .../java/org/dinky/JobManagerServiceImpl.java | 9 +++--- 15 files changed, 67 insertions(+), 32 deletions(-) diff --git a/dinky-admin/src/main/java/org/dinky/Dinky.java b/dinky-admin/src/main/java/org/dinky/Dinky.java index 643aea15f3..91bca9ae5d 100644 --- a/dinky-admin/src/main/java/org/dinky/Dinky.java +++ b/dinky-admin/src/main/java/org/dinky/Dinky.java @@ -19,10 +19,14 @@ package org.dinky; +import cn.hutool.extra.spring.SpringUtil; +import cn.hutool.system.SystemUtil; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration; import org.springframework.cache.annotation.EnableCaching; +import org.springframework.context.ApplicationContext; +import org.springframework.core.env.Environment; import org.springframework.transaction.annotation.EnableTransactionManagement; import lombok.SneakyThrows; diff --git a/dinky-admin/src/main/java/org/dinky/init/SystemInit.java b/dinky-admin/src/main/java/org/dinky/init/SystemInit.java index 48aa51c2cd..dc9221bb2c 100644 --- a/dinky-admin/src/main/java/org/dinky/init/SystemInit.java +++ b/dinky-admin/src/main/java/org/dinky/init/SystemInit.java @@ -134,7 +134,7 @@ private void initResources() { if (Boolean.TRUE.equals( systemConfiguration.getResourcesEnable().getValue())) { try { - JobManager.build(new JobConfig()).initResourceManager(); + JobManager.build(new JobConfig()).initResourceManager(systemConfiguration); } catch (Exception e) { log.error("Init resource error: ", e); } diff --git a/dinky-admin/src/main/java/org/dinky/job/JobManager.java b/dinky-admin/src/main/java/org/dinky/job/JobManager.java index a3bf5ad6c9..c795d0048e 100644 --- a/dinky-admin/src/main/java/org/dinky/job/JobManager.java +++ b/dinky-admin/src/main/java/org/dinky/job/JobManager.java @@ -28,6 +28,7 @@ import org.dinky.data.model.Column; import org.dinky.data.model.ResourcesVO; import org.dinky.data.model.Schema; +import org.dinky.data.model.SystemConfiguration; import org.dinky.data.model.Table; import org.dinky.data.result.ExplainResult; import org.dinky.data.result.IResult; @@ -167,9 +168,9 @@ public boolean cancelNormal(String jobId) { } } - public SavePointResult savepoint(String jobId, SavePointType savePointType, String savePoint) { + public SavePointResult savepoint(String jobId, SavePointType savePointType, String savePoint, boolean isUseRestAPI) { try { - return serverExecutorService.savepoint(jobId, savePointType, savePoint); + return serverExecutorService.savepoint(jobId, savePointType, savePoint, isUseRestAPI); } catch (RemoteException e) { throw new RuntimeException(e); } @@ -407,9 +408,9 @@ public void registerPool(List collect) { } } - public void initResourceManager() { + public void initResourceManager(SystemConfiguration systemConfiguration) { try { - serverExecutorService.initResourceManager(); + serverExecutorService.initResourceManager(systemConfiguration); } catch (RemoteException e) { throw new RuntimeException(e); } diff --git a/dinky-admin/src/main/java/org/dinky/service/impl/TaskServiceImpl.java b/dinky-admin/src/main/java/org/dinky/service/impl/TaskServiceImpl.java index 2ee481089a..3358679a7a 100644 --- a/dinky-admin/src/main/java/org/dinky/service/impl/TaskServiceImpl.java +++ b/dinky-admin/src/main/java/org/dinky/service/impl/TaskServiceImpl.java @@ -449,7 +449,7 @@ public SavePointResult savepointTaskJob(TaskDTO task, SavePointType savePointTyp JobManager jobManager = JobManager.build(buildJobConfig(task)); String jobId = jobInstance.getJid(); - SavePointResult savePointResult = jobManager.savepoint(jobId, savePointType, null); + SavePointResult savePointResult = jobManager.savepoint(jobId, savePointType, null, SystemConfiguration.getInstances().isUseRestAPI()); Assert.notNull(savePointResult.getJobInfos()); for (JobInfo item : savePointResult.getJobInfos()) { if (Asserts.isEqualsIgnoreCase(jobId, item.getJobId()) && Asserts.isNotNull(jobInstance.getTaskId())) { diff --git a/dinky-app/dinky-app-base/src/main/java/org/dinky/resource/impl/LocalResourceManager.java b/dinky-app/dinky-app-base/src/main/java/org/dinky/resource/impl/LocalResourceManager.java index a762dd2b7c..c67ee660be 100644 --- a/dinky-app/dinky-app-base/src/main/java/org/dinky/resource/impl/LocalResourceManager.java +++ b/dinky-app/dinky-app-base/src/main/java/org/dinky/resource/impl/LocalResourceManager.java @@ -46,7 +46,6 @@ @Slf4j public class LocalResourceManager implements BaseResourceManager { - SystemConfiguration systemConfiguration = SystemConfiguration.getInstances(); @Override public void remove(String path) { @@ -130,7 +129,7 @@ public List getFullDirectoryStructure(int rootId) { @Override public InputStream readFile(String path) { try (HttpResponse exec = HttpUtil.createGet( - systemConfiguration.getDinkyAddr().getValue() + "/download/downloadFromRs?path=" + instances.getDinkyAddr().getValue() + "/download/downloadFromRs?path=" + URLUtil.encode(path)) .execute()) { return exec.bodyStream(); diff --git a/dinky-client/dinky-client-base/src/main/java/org/dinky/resource/BaseResourceManager.java b/dinky-client/dinky-client-base/src/main/java/org/dinky/resource/BaseResourceManager.java index 2a80d9de59..026590b043 100644 --- a/dinky-client/dinky-client-base/src/main/java/org/dinky/resource/BaseResourceManager.java +++ b/dinky-client/dinky-client-base/src/main/java/org/dinky/resource/BaseResourceManager.java @@ -67,7 +67,7 @@ default byte[] readFileContext(String path) { } static BaseResourceManager getInstance() { - switch (SystemConfiguration.getInstances().getResourcesModel().getValue()) { + switch (instances.getResourcesModel().getValue()) { case HDFS: return Singleton.get(HdfsResourceManager.class); case OSS: @@ -79,7 +79,9 @@ static BaseResourceManager getInstance() { } } - static void initResourceManager() { + static void initResourceManager(SystemConfiguration other) { + // the executor not at admin server + other.copyTo(BaseResourceManager.instances); switch (instances.getResourcesModel().getValue()) { case LOCAL: Singleton.get(LocalResourceManager.class); diff --git a/dinky-client/dinky-client-base/src/main/java/org/dinky/resource/impl/OssResourceManager.java b/dinky-client/dinky-client-base/src/main/java/org/dinky/resource/impl/OssResourceManager.java index 268c9bdf0f..5496f4fc73 100644 --- a/dinky-client/dinky-client-base/src/main/java/org/dinky/resource/impl/OssResourceManager.java +++ b/dinky-client/dinky-client-base/src/main/java/org/dinky/resource/impl/OssResourceManager.java @@ -141,7 +141,7 @@ public byte[] readFileContext(String path) { public OssTemplate getOssTemplate() { if (ossTemplate == null - && BaseResourceManager.instances.getResourcesEnable().getValue()) { + && instances.getResourcesEnable().getValue()) { throw new BusException(Status.RESOURCE_OSS_CONFIGURATION_ERROR); } return ossTemplate; diff --git a/dinky-common/src/main/java/org/dinky/data/model/Configuration.java b/dinky-common/src/main/java/org/dinky/data/model/Configuration.java index fe3892fb28..04d0421307 100644 --- a/dinky-common/src/main/java/org/dinky/data/model/Configuration.java +++ b/dinky-common/src/main/java/org/dinky/data/model/Configuration.java @@ -77,11 +77,15 @@ public Configuration note(Status status) { } public void setValue(Object value) { - if (getType() == Enum.class) { - this.value = (T) EnumUtil.fromString((Class) type, (String) value); - return; + try { + if (getType() == Enum.class) { + this.value = (T) EnumUtil.fromString((Class) type, (String) value); + return; + } + this.value = type.isInstance(value) ? (T) value : Convert.convert(getType(), value); + }catch (Exception e){ + System.out.println("Configuration.setValue, this" + this + ", value: " + value); } - this.value = type.isInstance(value) ? (T) value : Convert.convert(getType(), value); } public Configuration desensitizedHandler(Function desensitizedHandler) { diff --git a/dinky-common/src/main/java/org/dinky/data/model/SystemConfiguration.java b/dinky-common/src/main/java/org/dinky/data/model/SystemConfiguration.java index 83826b7a54..f86e6738dc 100644 --- a/dinky-common/src/main/java/org/dinky/data/model/SystemConfiguration.java +++ b/dinky-common/src/main/java/org/dinky/data/model/SystemConfiguration.java @@ -24,8 +24,10 @@ import org.dinky.data.enums.Status; import org.dinky.data.properties.OssProperties; +import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.TreeMap; @@ -45,7 +47,7 @@ * @since 2021/11/18 */ @Getter -public class SystemConfiguration { +public class SystemConfiguration implements Serializable { private static final SystemConfiguration systemConfiguration = new SystemConfiguration(); @@ -59,10 +61,22 @@ public static Configuration.OptionBuilder key(Status status) { return new Configuration.OptionBuilder(status.getKey()); } - private static final List> CONFIGURATION_LIST = Arrays.stream( - ReflectUtil.getFields(SystemConfiguration.class, f -> f.getType() == Configuration.class)) - .map(f -> (Configuration) ReflectUtil.getFieldValue(systemConfiguration, f)) - .collect(Collectors.toList()); + private static final List> CONFIGURATION_LIST = getConfigurationList(); + + private static List> getConfigurationList() { + return Arrays.stream( + ReflectUtil.getFields(SystemConfiguration.class, f -> f.getType() == Configuration.class)) + .map(f -> (Configuration) ReflectUtil.getFieldValue(systemConfiguration, f)) + .collect(Collectors.toList()); + } + + private List> getThisConfigurationList() { + return Arrays.stream( + ReflectUtil.getFields(SystemConfiguration.class, f -> f.getType() == Configuration.class)) + .map(f -> (Configuration) ReflectUtil.getFieldValue(this, f)) + .collect(Collectors.toList()); + } + private final Configuration useRestAPI = key(Status.SYS_FLINK_SETTINGS_USERESTAPI) .booleanType() @@ -323,6 +337,14 @@ public void initSetConfiguration(Map configMap) { CONFIGURATION_LIST.stream().peek(Configuration::runParameterCheck).forEach(Configuration::runChangeEvent); } + public void copyTo(SystemConfiguration other) { + Map configMap = new HashMap<>(); + for (Configuration config : getThisConfigurationList()) { + configMap.put(config.getKey(), String.valueOf(config.getValue())); + } + other.initSetConfiguration(configMap); + } + public void initExpressionVariableList(Map configMap) { CONFIGURATION_LIST.forEach(item -> { if (item.getKey().equals(expressionVariable.getKey())) { diff --git a/dinky-common/src/main/java/org/dinky/job/JobConfig.java b/dinky-common/src/main/java/org/dinky/job/JobConfig.java index 77f56b37d7..7cf98c1dc8 100644 --- a/dinky-common/src/main/java/org/dinky/job/JobConfig.java +++ b/dinky-common/src/main/java/org/dinky/job/JobConfig.java @@ -22,6 +22,7 @@ import org.dinky.assertion.Asserts; import org.dinky.data.constant.NetConstant; import org.dinky.data.enums.GatewayType; +import org.dinky.data.model.SystemConfiguration; import org.dinky.executor.ExecutorConfig; import org.dinky.gateway.config.FlinkConfig; import org.dinky.gateway.config.GatewayConfig; @@ -184,6 +185,8 @@ public class JobConfig implements Serializable { notes = "Map of variables") private Map variables; + private SystemConfiguration systemConfiguration; + public JobConfig() { this.configJson = new HashMap<>(); } diff --git a/dinky-common/src/main/java/org/dinky/remote/ServerExecutorService.java b/dinky-common/src/main/java/org/dinky/remote/ServerExecutorService.java index 47049910f3..4bbc9f5709 100644 --- a/dinky-common/src/main/java/org/dinky/remote/ServerExecutorService.java +++ b/dinky-common/src/main/java/org/dinky/remote/ServerExecutorService.java @@ -26,6 +26,7 @@ import org.dinky.data.model.Column; import org.dinky.data.model.ResourcesVO; import org.dinky.data.model.Schema; +import org.dinky.data.model.SystemConfiguration; import org.dinky.data.model.Table; import org.dinky.data.result.ExplainResult; import org.dinky.data.result.IResult; @@ -70,7 +71,7 @@ public interface ServerExecutorService extends Remote { boolean cancelNormal(String jobId) throws RemoteException; - SavePointResult savepoint(String jobId, SavePointType savePointType, String savePoint) throws RemoteException; + SavePointResult savepoint(String jobId, SavePointType savePointType, String savePoint, boolean isUseRestAPI) throws RemoteException; String exportSql(String sql) throws RemoteException; @@ -135,7 +136,7 @@ LineageResult getSqlLineage(String statement, String mysql, DriverConfig collect) throws RemoteException; - void initResourceManager() throws RemoteException; + void initResourceManager(SystemConfiguration systemConfiguration) throws RemoteException; String getPyUDFAttr(String statement) throws RemoteException; diff --git a/dinky-core/src/main/java/org/dinky/job/IJobManager.java b/dinky-core/src/main/java/org/dinky/job/IJobManager.java index ebe1ad92b3..cd66cf8ee1 100644 --- a/dinky-core/src/main/java/org/dinky/job/IJobManager.java +++ b/dinky-core/src/main/java/org/dinky/job/IJobManager.java @@ -49,7 +49,7 @@ public interface IJobManager { boolean cancelNormal(String jobId); - SavePointResult savepoint(String jobId, SavePointType savePointType, String savePoint); + SavePointResult savepoint(String jobId, SavePointType savePointType, String savePoint, boolean isUseRestAPI); String exportSql(String sql); } diff --git a/dinky-core/src/main/java/org/dinky/job/JobManagerHandler.java b/dinky-core/src/main/java/org/dinky/job/JobManagerHandler.java index 9f624cd7a8..c9a2c09857 100644 --- a/dinky-core/src/main/java/org/dinky/job/JobManagerHandler.java +++ b/dinky-core/src/main/java/org/dinky/job/JobManagerHandler.java @@ -27,7 +27,6 @@ import org.dinky.data.enums.GatewayType; import org.dinky.data.enums.ProcessStepType; import org.dinky.data.exception.BusException; -import org.dinky.data.model.SystemConfiguration; import org.dinky.data.result.ErrorResult; import org.dinky.data.result.ExplainResult; import org.dinky.data.result.IResult; @@ -312,8 +311,8 @@ public boolean cancelNormal(String jobId) { } @Override - public SavePointResult savepoint(String jobId, SavePointType savePointType, String savePoint) { - if (useGateway && !SystemConfiguration.getInstances().isUseRestAPI()) { + public SavePointResult savepoint(String jobId, SavePointType savePointType, String savePoint, boolean isUseRestAPI) { + if (useGateway && !isUseRestAPI) { config.getGatewayConfig() .setFlinkConfig( FlinkConfig.build(jobId, ActionType.SAVEPOINT.getValue(), savePointType.getValue(), null)); diff --git a/dinky-core/src/main/java/org/dinky/job/builder/JobUDFBuilder.java b/dinky-core/src/main/java/org/dinky/job/builder/JobUDFBuilder.java index 51cac32669..7ea0b9a93a 100644 --- a/dinky-core/src/main/java/org/dinky/job/builder/JobUDFBuilder.java +++ b/dinky-core/src/main/java/org/dinky/job/builder/JobUDFBuilder.java @@ -25,7 +25,6 @@ import org.dinky.assertion.Asserts; import org.dinky.data.enums.GatewayType; -import org.dinky.data.model.SystemConfiguration; import org.dinky.executor.Executor; import org.dinky.function.data.model.UDF; import org.dinky.function.util.UDFUtil; @@ -105,7 +104,7 @@ public void run() throws Exception { Set pyUdfFile = executor.getUdfPathContextHolder().getPyUdfFile(); executor.initPyUDF( - SystemConfiguration.getInstances().getPythonHome(), + config.getSystemConfiguration().getPythonHome(), pyUdfFile.stream().map(File::getAbsolutePath).toArray(String[]::new)); if (GATEWAY_TYPE_MAP.get(YARN).contains(runMode)) { config.getGatewayConfig().setJarPaths(ArrayUtil.append(jarPaths, pyPaths)); diff --git a/dinky-executor-server/src/main/java/org/dinky/JobManagerServiceImpl.java b/dinky-executor-server/src/main/java/org/dinky/JobManagerServiceImpl.java index ec43e7bade..cd94b63615 100644 --- a/dinky-executor-server/src/main/java/org/dinky/JobManagerServiceImpl.java +++ b/dinky-executor-server/src/main/java/org/dinky/JobManagerServiceImpl.java @@ -28,6 +28,7 @@ import org.dinky.data.model.Column; import org.dinky.data.model.ResourcesVO; import org.dinky.data.model.Schema; +import org.dinky.data.model.SystemConfiguration; import org.dinky.data.model.Table; import org.dinky.data.result.ExplainResult; import org.dinky.data.result.IResult; @@ -138,9 +139,9 @@ public boolean cancelNormal(String jobId) throws RemoteException { } @Override - public SavePointResult savepoint(String jobId, SavePointType savePointType, String savePoint) + public SavePointResult savepoint(String jobId, SavePointType savePointType, String savePoint, boolean isUseRestAPI) throws RemoteException { - return jobManagerHandler.savepoint(jobId, savePointType, savePoint); + return jobManagerHandler.savepoint(jobId, savePointType, savePoint, isUseRestAPI ); } @Override @@ -313,8 +314,8 @@ public void registerPool(List collect) throws RemoteException { } @Override - public void initResourceManager() throws RemoteException { - BaseResourceManager.initResourceManager(); + public void initResourceManager(SystemConfiguration systemConfiguration) throws RemoteException { + BaseResourceManager.initResourceManager(systemConfiguration); } @Override From 59e08f87afb96f5d373b0fb53be7e022fba74adb Mon Sep 17 00:00:00 2001 From: sunlichao11 Date: Tue, 2 Apr 2024 23:40:35 +0800 Subject: [PATCH 39/87] feat: could run task --- dinky-admin/src/main/java/org/dinky/data/dto/TaskDTO.java | 3 ++- .../src/test/java/org/dinky/utils/MavenUtilTests.java | 5 ----- .../src/main/java/org/dinky/app/flinksql/Submitter.java | 2 +- .../src/main/java/org/dinky/cluster/FlinkClusterInfo.java | 4 +++- .../src/main/java/org/dinky/data/model/Configuration.java | 2 +- dinky-core/pom.xml | 5 +++++ 6 files changed, 12 insertions(+), 9 deletions(-) diff --git a/dinky-admin/src/main/java/org/dinky/data/dto/TaskDTO.java b/dinky-admin/src/main/java/org/dinky/data/dto/TaskDTO.java index 192a077840..338e3d9521 100644 --- a/dinky-admin/src/main/java/org/dinky/data/dto/TaskDTO.java +++ b/dinky-admin/src/main/java/org/dinky/data/dto/TaskDTO.java @@ -20,6 +20,7 @@ package org.dinky.data.dto; import org.dinky.data.annotations.ProcessId; +import org.dinky.data.model.SystemConfiguration; import org.dinky.data.model.Task; import org.dinky.data.model.alert.AlertGroup; import org.dinky.data.model.ext.TaskExtConfig; @@ -225,7 +226,7 @@ public JobConfig getJobConfig() { jobConfig.setConfigJson(parsedConfig); jobConfig.setTaskId(id); jobConfig.setJobName(name); - + jobConfig.setSystemConfiguration(SystemConfiguration.getInstances()); return jobConfig; } diff --git a/dinky-admin/src/test/java/org/dinky/utils/MavenUtilTests.java b/dinky-admin/src/test/java/org/dinky/utils/MavenUtilTests.java index 83bc31957d..51855476af 100644 --- a/dinky-admin/src/test/java/org/dinky/utils/MavenUtilTests.java +++ b/dinky-admin/src/test/java/org/dinky/utils/MavenUtilTests.java @@ -19,7 +19,6 @@ package org.dinky.utils; -import org.dinky.function.util.UDFUtil; import java.io.File; import java.util.Arrays; @@ -73,10 +72,6 @@ public void getJars() { List jars = MavenUtil.getJars(pom); System.out.println(jars); - jars.parallelStream().forEach(jar -> { - List> udfClassByJar = UDFUtil.getUdfClassByJar(jar); - System.out.println(udfClassByJar); - }); } @Test diff --git a/dinky-app/dinky-app-base/src/main/java/org/dinky/app/flinksql/Submitter.java b/dinky-app/dinky-app-base/src/main/java/org/dinky/app/flinksql/Submitter.java index 2c5373714f..3707ba0fac 100644 --- a/dinky-app/dinky-app-base/src/main/java/org/dinky/app/flinksql/Submitter.java +++ b/dinky-app/dinky-app-base/src/main/java/org/dinky/app/flinksql/Submitter.java @@ -102,7 +102,7 @@ private static void initSystemConfiguration() throws SQLException { public static void submit(AppParamConfig config) throws SQLException { initSystemConfiguration(); - BaseResourceManager.initResourceManager(); + BaseResourceManager.initResourceManager(SystemConfiguration.getInstances()); URL.setURLStreamHandlerFactory(new RsURLStreamHandlerFactory()); log.info("{} Start Submit Job:{}", LocalDateTime.now(), config.getTaskId()); diff --git a/dinky-common/src/main/java/org/dinky/cluster/FlinkClusterInfo.java b/dinky-common/src/main/java/org/dinky/cluster/FlinkClusterInfo.java index 9e24abe2bd..b82a43ff8e 100644 --- a/dinky-common/src/main/java/org/dinky/cluster/FlinkClusterInfo.java +++ b/dinky-common/src/main/java/org/dinky/cluster/FlinkClusterInfo.java @@ -22,6 +22,8 @@ import lombok.Getter; import lombok.Setter; +import java.io.Serializable; + /** * FlinkClusterInfo * @@ -29,7 +31,7 @@ */ @Getter @Setter -public class FlinkClusterInfo { +public class FlinkClusterInfo implements Serializable { private boolean isEffective; private String jobManagerAddress; diff --git a/dinky-common/src/main/java/org/dinky/data/model/Configuration.java b/dinky-common/src/main/java/org/dinky/data/model/Configuration.java index 04d0421307..e619898227 100644 --- a/dinky-common/src/main/java/org/dinky/data/model/Configuration.java +++ b/dinky-common/src/main/java/org/dinky/data/model/Configuration.java @@ -129,7 +129,7 @@ public static OptionBuilder key(String key) { return new OptionBuilder(key); } - public static class OptionBuilder { + public static class OptionBuilder implements Serializable { private final String key; diff --git a/dinky-core/pom.xml b/dinky-core/pom.xml index bba08a0054..d4dd17f2ee 100644 --- a/dinky-core/pom.xml +++ b/dinky-core/pom.xml @@ -37,6 +37,11 @@ groovy 3.0.9 --> + + org.apache.commons + commons-math3 + 3.6 + org.dinky dinky-common From 8e693d738d3565ade90108824c665b7c6e0b2565 Mon Sep 17 00:00:00 2001 From: 173lyb <95527066+173lyb@users.noreply.github.com> Date: Sat, 9 Mar 2024 20:52:24 +0800 Subject: [PATCH 40/87] [Improvement] Optimize selection prompt information when querying in yarn application mode; Added Run button to check select execute (#3260) Co-authored-by: 173lyb <1733829298@qq.com> --- .../java/org/dinky/service/impl/TaskServiceImpl.java | 5 +++++ .../src/main/java/org/dinky/data/enums/Status.java | 3 ++- .../src/main/resources/i18n/messages_en_US.properties | 2 ++ .../src/main/resources/i18n/messages_zh_CN.properties | 2 ++ .../src/main/java/org/dinky/job/JobManagerHandler.java | 10 +++++++--- 5 files changed, 18 insertions(+), 4 deletions(-) diff --git a/dinky-admin/src/main/java/org/dinky/service/impl/TaskServiceImpl.java b/dinky-admin/src/main/java/org/dinky/service/impl/TaskServiceImpl.java index 3358679a7a..a08c0d2275 100644 --- a/dinky-admin/src/main/java/org/dinky/service/impl/TaskServiceImpl.java +++ b/dinky-admin/src/main/java/org/dinky/service/impl/TaskServiceImpl.java @@ -331,6 +331,11 @@ public JobResult debugTask(TaskDTO task) throws Exception { task.setUseResult(true); // Debug mode need execute task.setStatementSet(false); + // mode check + if (GatewayType.get(task.getType()).isDeployCluster()) { + throw new BusException(Status.MODE_IS_NOT_ALLOW_SELECT.getMessage()); + } + // 注解自调用会失效,这里通过获取对象方法绕过此限制 TaskServiceImpl taskServiceBean = applicationContext.getBean(TaskServiceImpl.class); JobResult jobResult; diff --git a/dinky-common/src/main/java/org/dinky/data/enums/Status.java b/dinky-common/src/main/java/org/dinky/data/enums/Status.java index 2adc547025..4423963f2f 100644 --- a/dinky-common/src/main/java/org/dinky/data/enums/Status.java +++ b/dinky-common/src/main/java/org/dinky/data/enums/Status.java @@ -189,6 +189,8 @@ public enum Status { TASK_IS_PUBLISH_CANNOT_DELETE(12011, "task.is.publish.cannot.delete"), TASK_IS_RUNNING_CANNOT_DELETE(12012, "task.is.running.cannot.delete"), JOB_ALERT_MAX_SEND_COUNT(12013, "job.alert.max.send.count"), + MODE_IS_NOT_ALLOW_SELECT(12014, "mode.is.not.allow.select"), + OPERATE_NOT_SUPPORT_QUERY(12015, "operate.not.support.query"), /** * alert instance @@ -347,7 +349,6 @@ public enum Status { SYS_ENV_SETTINGS_DIFF_MINUTE_MAX_SEND_COUNT(120, "sys.env.settings.diffMinuteMaxSendCount"), SYS_ENV_SETTINGS_DIFF_MINUTE_MAX_SEND_COUNT_NOTE(121, "sys.env.settings.diffMinuteMaxSendCount.note"), - SYS_ENV_SETTINGS_MAX_RETAIN_DAYS(1171, "sys.env.settings.maxRetainDays"), SYS_ENV_SETTINGS_MAX_RETAIN_DAYS_NOTE(1172, "sys.env.settings.maxRetainDays.note"), SYS_ENV_SETTINGS_MAX_RETAIN_COUNT(1173, "sys.env.settings.maxRetainCount"), diff --git a/dinky-common/src/main/resources/i18n/messages_en_US.properties b/dinky-common/src/main/resources/i18n/messages_en_US.properties index ac198935fc..afe031668c 100644 --- a/dinky-common/src/main/resources/i18n/messages_en_US.properties +++ b/dinky-common/src/main/resources/i18n/messages_en_US.properties @@ -275,6 +275,8 @@ gateway.kubernetes.test.failed= failed to test the Flink configuration: task.status.is.not.done=The current job status is not stopped, please stop after operation task.sql.explain.failed=SQL parsing failed, please check the SQL statement task.update.failed=Task Update failed +mode.is.not.allow.select=Application / Pre-Job mode does not allow executing select statements. To perform this operation, please switch to Local, Standalone, or Yarn session modes. +operate.not.support.query=The [Run] button does not support select statements, please switch to the [Query] button. # process process.submit.submitTask= Submit the job diff --git a/dinky-common/src/main/resources/i18n/messages_zh_CN.properties b/dinky-common/src/main/resources/i18n/messages_zh_CN.properties index 3fe5f11cde..89da9de646 100644 --- a/dinky-common/src/main/resources/i18n/messages_zh_CN.properties +++ b/dinky-common/src/main/resources/i18n/messages_zh_CN.properties @@ -275,6 +275,8 @@ gateway.kubernetes.test.failed=测试 Flink 配置失败: task.status.is.not.done=当前作业状态未停止,请停止后操作 task.sql.explain.failed=sql解析失败,请检查 task.update.failed=Task更新失败 +mode.is.not.allow.select=Application / Pre-Job 模式不允许执行 select 语句, 如需执行此操作, 请切换至 Local、Standalone、Yarn session等模式 +operate.not.support.query=[运行] 按钮不支持 select 语句,请切换至 [查询] 按钮 # process process.submit.submitTask=提交作业 diff --git a/dinky-core/src/main/java/org/dinky/job/JobManagerHandler.java b/dinky-core/src/main/java/org/dinky/job/JobManagerHandler.java index c9a2c09857..5b1569e1f1 100644 --- a/dinky-core/src/main/java/org/dinky/job/JobManagerHandler.java +++ b/dinky-core/src/main/java/org/dinky/job/JobManagerHandler.java @@ -26,6 +26,7 @@ import org.dinky.data.annotations.ProcessStep; import org.dinky.data.enums.GatewayType; import org.dinky.data.enums.ProcessStepType; +import org.dinky.data.enums.Status; import org.dinky.data.exception.BusException; import org.dinky.data.result.ErrorResult; import org.dinky.data.result.ExplainResult; @@ -95,7 +96,6 @@ public class JobManagerHandler implements IJobManager { private boolean useGateway = false; private boolean useStatementSet = false; private GatewayType runMode = GatewayType.LOCAL; - private JobParam jobParam = null; private Job job; @@ -229,9 +229,13 @@ public JobResult executeSql(String statement) throws Exception { if (!job.isFailed()) { job.setStatus(Job.JobStatus.SUCCESS); } - } catch (ExecuteSqlException e) { + } catch (Exception e) { + String errorMessage = e.getMessage(); + if (errorMessage.contains("Only insert statement is supported now")) { + throw new BusException(Status.OPERATE_NOT_SUPPORT_QUERY.getMessage()); + } String error = StrFormatter.format( - "Exception in executing FlinkSQL:\n{}\n{}", SqlUtil.addLineNumber(e.getMessage()), e.getMessage()); + "Exception in executing FlinkSQL:\n{}\n{}", SqlUtil.addLineNumber(currentSql), errorMessage); job.setEndTime(LocalDateTime.now()); job.setStatus(Job.JobStatus.FAILED); job.setError(error); From b931caf7ae60e19244587f2c688f5fcb51c705ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E5=B7=9E?= <54611681+javaht@users.noreply.github.com> Date: Sat, 9 Mar 2024 23:47:28 +0800 Subject: [PATCH 41/87] [Fix] fix the decimal precision of postgres transform (#3263) --- .../org/dinky/cdc/AbstractSinkBuilder.java | 2 +- .../dinky/cdc/utils/FlinkStatementUtil.java | 18 +++++++++++++++--- .../convert/PostgreSqlTypeConvert.java | 19 +++++++++++++++++-- 3 files changed, 33 insertions(+), 6 deletions(-) diff --git a/dinky-cdc/dinky-cdc-core/src/main/java/org/dinky/cdc/AbstractSinkBuilder.java b/dinky-cdc/dinky-cdc-core/src/main/java/org/dinky/cdc/AbstractSinkBuilder.java index 58a1aefcfe..82109481e9 100644 --- a/dinky-cdc/dinky-cdc-core/src/main/java/org/dinky/cdc/AbstractSinkBuilder.java +++ b/dinky-cdc/dinky-cdc-core/src/main/java/org/dinky/cdc/AbstractSinkBuilder.java @@ -238,7 +238,7 @@ public void addSink( protected List createInsertOperations( CustomTableEnvironment customTableEnvironment, Table table, String viewName, String tableName) { - String cdcSqlInsert = FlinkStatementUtil.getCDCInsertSql(table, tableName, viewName); + String cdcSqlInsert = FlinkStatementUtil.getCDCInsertSql(table, tableName, viewName, config); logger.info(cdcSqlInsert); List operations = customTableEnvironment.getParser().parse(cdcSqlInsert); diff --git a/dinky-cdc/dinky-cdc-core/src/main/java/org/dinky/cdc/utils/FlinkStatementUtil.java b/dinky-cdc/dinky-cdc-core/src/main/java/org/dinky/cdc/utils/FlinkStatementUtil.java index c5b388a742..ab3956eb7c 100644 --- a/dinky-cdc/dinky-cdc-core/src/main/java/org/dinky/cdc/utils/FlinkStatementUtil.java +++ b/dinky-cdc/dinky-cdc-core/src/main/java/org/dinky/cdc/utils/FlinkStatementUtil.java @@ -19,6 +19,7 @@ package org.dinky.cdc.utils; +import org.dinky.data.model.Column; import org.dinky.data.model.FlinkCDCConfig; import org.dinky.data.model.Table; import org.dinky.utils.SqlUtil; @@ -33,7 +34,7 @@ public class FlinkStatementUtil { private FlinkStatementUtil() {} - public static String getCDCInsertSql(Table table, String targetName, String sourceName) { + public static String getCDCInsertSql(Table table, String targetName, String sourceName, FlinkCDCConfig config) { StringBuilder sb = new StringBuilder("INSERT INTO "); sb.append("`").append(targetName).append("`"); sb.append(" SELECT\n"); @@ -42,8 +43,7 @@ public static String getCDCInsertSql(Table table, String targetName, String sour if (i > 0) { sb.append(","); } - sb.append(String.format("`%s`", table.getColumns().get(i).getName())) - .append(" \n"); + sb.append(getColumnProcessing(table.getColumns().get(i), config)).append(" \n"); } sb.append(" FROM `"); sb.append(sourceName); @@ -51,6 +51,18 @@ public static String getCDCInsertSql(Table table, String targetName, String sour return sb.toString(); } + public static String getColumnProcessing(Column column, FlinkCDCConfig config) { + String configType = config.getType(); + String columnType = column.getType(); + if (configType.contains("postgres-cdc") + && (columnType.contains("numeric") || columnType.contains("decimal")) + && column.getPrecision().intValue() > 38) { + return " CAST(" + column.getName() + " AS STRING) AS `" + column.getName() + "`"; + } else { + return String.format("`%s`", column.getName()); + } + } + public static String getFlinkDDL( Table table, String tableName, diff --git a/dinky-metadata/dinky-metadata-postgresql/src/main/java/org/dinky/metadata/convert/PostgreSqlTypeConvert.java b/dinky-metadata/dinky-metadata-postgresql/src/main/java/org/dinky/metadata/convert/PostgreSqlTypeConvert.java index 64315decca..4ed4621d07 100644 --- a/dinky-metadata/dinky-metadata-postgresql/src/main/java/org/dinky/metadata/convert/PostgreSqlTypeConvert.java +++ b/dinky-metadata/dinky-metadata-postgresql/src/main/java/org/dinky/metadata/convert/PostgreSqlTypeConvert.java @@ -20,6 +20,11 @@ package org.dinky.metadata.convert; import org.dinky.data.enums.ColumnType; +import org.dinky.data.model.Column; +import org.dinky.metadata.config.AbstractJdbcConfig; +import org.dinky.metadata.config.DriverConfig; + +import java.util.Optional; /** * PostgreSqlTypeConvert @@ -44,8 +49,8 @@ public PostgreSqlTypeConvert() { register("float4", ColumnType.FLOAT, ColumnType.JAVA_LANG_FLOAT); register("float8", ColumnType.DOUBLE, ColumnType.JAVA_LANG_DOUBLE); register("double precision", ColumnType.DOUBLE, ColumnType.JAVA_LANG_DOUBLE); - register("numeric", ColumnType.DECIMAL); - register("decimal", ColumnType.DECIMAL); + register("numeric", PostgreSqlTypeConvert::convertDecimalOrNumeric); + register("decimal", PostgreSqlTypeConvert::convertDecimalOrNumeric); register("boolean", ColumnType.BOOLEAN, ColumnType.JAVA_LANG_BOOLEAN); register("bool", ColumnType.BOOLEAN, ColumnType.JAVA_LANG_BOOLEAN); register("timestamp", ColumnType.TIMESTAMP); @@ -57,4 +62,14 @@ public PostgreSqlTypeConvert() { register("jsonb", ColumnType.STRING); register("json", ColumnType.STRING); } + + private static Optional convertDecimalOrNumeric( + Column column, DriverConfig driverConfig) { + // 该字段的精度 + int intValue = column.getPrecision().intValue(); + if (intValue > 38) { + return Optional.of(ColumnType.STRING); + } + return Optional.of(ColumnType.DECIMAL); + } } From 3613ff72c5ee86656e6da5449c32d1395e204368 Mon Sep 17 00:00:00 2001 From: Zzm0809 <934230207@qq.com> Date: Sat, 9 Mar 2024 23:48:09 +0800 Subject: [PATCH 42/87] [Fix] fix modal cannot close (#3264) Co-authored-by: Zzm0809 --- .../GitProject/components/BuildSteps/index.tsx | 12 +++++++++++- .../GitProject/components/CodeTree/function.tsx | 4 ++-- .../GitProject/components/CodeTree/index.tsx | 7 +++++-- .../components/UDFTemplate/TemplateModal/index.tsx | 1 + 4 files changed, 19 insertions(+), 5 deletions(-) diff --git a/dinky-web/src/pages/RegCenter/GitProject/components/BuildSteps/index.tsx b/dinky-web/src/pages/RegCenter/GitProject/components/BuildSteps/index.tsx index 531ddb3143..ca6ed12ea9 100644 --- a/dinky-web/src/pages/RegCenter/GitProject/components/BuildSteps/index.tsx +++ b/dinky-web/src/pages/RegCenter/GitProject/components/BuildSteps/index.tsx @@ -239,7 +239,17 @@ export const BuildSteps: React.FC = (props) => { * render */ return ( - + handleCancel()} + footer={footerButtons} + > - data?.map((item: any) => { +export const buildTreeData = (data: any[] = []): any => + data.map((item: any) => { // build key let buildKey = item.path + folderSeparator() + item.name; diff --git a/dinky-web/src/pages/RegCenter/GitProject/components/CodeTree/index.tsx b/dinky-web/src/pages/RegCenter/GitProject/components/CodeTree/index.tsx index 6d09e31383..a41f73eed2 100644 --- a/dinky-web/src/pages/RegCenter/GitProject/components/CodeTree/index.tsx +++ b/dinky-web/src/pages/RegCenter/GitProject/components/CodeTree/index.tsx @@ -24,7 +24,7 @@ import { Modal } from 'antd'; import React from 'react'; type CodeTreeProps = { - onCancel: (flag?: boolean) => void; + onCancel: () => void; modalVisible: boolean; values: Partial; }; @@ -41,9 +41,12 @@ export const CodeTree: React.FC = (props) => { overflow: 'auto' } }} + centered + closable={false} open={modalVisible} - maskClosable={false} onCancel={() => onCancel()} + destroyOnClose + maskClosable={false} cancelText={l('button.close')} okButtonProps={{ style: { display: 'none' } }} > diff --git a/dinky-web/src/pages/RegCenter/UDF/components/UDFTemplate/TemplateModal/index.tsx b/dinky-web/src/pages/RegCenter/UDF/components/UDFTemplate/TemplateModal/index.tsx index 084894622f..3514139868 100644 --- a/dinky-web/src/pages/RegCenter/UDF/components/UDFTemplate/TemplateModal/index.tsx +++ b/dinky-web/src/pages/RegCenter/UDF/components/UDFTemplate/TemplateModal/index.tsx @@ -103,6 +103,7 @@ const TemplateModal: React.FC = (props) => { title={values.id ? l('rc.template.modify') : l('rc.template.create')} open={visible} form={form} + modalProps={{ onCancel: handleCancel }} submitter={{ render: () => [...renderFooter()] }} initialValues={values} > From 0b6c0599b82dfe1c84e753bfae6297b860516981 Mon Sep 17 00:00:00 2001 From: Zzm0809 <934230207@qq.com> Date: Sat, 9 Mar 2024 23:49:01 +0800 Subject: [PATCH 43/87] [Optimization]Optimize the environment check of git project build (#3265) --- .../main/java/org/dinky/sse/git/HeadStepSse.java | 3 ++- .../src/main/java/org/dinky/utils/MavenUtil.java | 15 +++++++-------- .../main/java/org/dinky/data/enums/Status.java | 1 + .../main/resources/i18n/messages_en_US.properties | 1 + .../main/resources/i18n/messages_zh_CN.properties | 1 + 5 files changed, 12 insertions(+), 9 deletions(-) diff --git a/dinky-admin/src/main/java/org/dinky/sse/git/HeadStepSse.java b/dinky-admin/src/main/java/org/dinky/sse/git/HeadStepSse.java index 3db7dae401..0151db05c3 100644 --- a/dinky-admin/src/main/java/org/dinky/sse/git/HeadStepSse.java +++ b/dinky-admin/src/main/java/org/dinky/sse/git/HeadStepSse.java @@ -19,6 +19,7 @@ package org.dinky.sse.git; +import org.dinky.data.enums.Status; import org.dinky.data.model.GitProject; import org.dinky.sse.StepSse; import org.dinky.utils.MavenUtil; @@ -67,7 +68,7 @@ private void checkJava() { String mavenHome = MavenUtil.getMavenHome(); if (StrUtil.isBlank(mavenHome)) { - addFileMsg("Please set the environment variable:MAVEN_HOME"); + addFileMsg(Status.GIT_MAVEN_HOME_NOT_SET.getMessage()); setFinish(false); } String mavenVersionMsg = MavenUtil.getMavenVersion(); diff --git a/dinky-admin/src/main/java/org/dinky/utils/MavenUtil.java b/dinky-admin/src/main/java/org/dinky/utils/MavenUtil.java index 9dd5023c6e..3302f0fbfb 100644 --- a/dinky-admin/src/main/java/org/dinky/utils/MavenUtil.java +++ b/dinky-admin/src/main/java/org/dinky/utils/MavenUtil.java @@ -19,6 +19,8 @@ package org.dinky.utils; +import org.dinky.data.enums.Status; +import org.dinky.data.exception.BusException; import org.dinky.data.exception.DinkyException; import org.dinky.data.model.SystemConfiguration; import org.dinky.function.constant.PathConstant; @@ -181,11 +183,8 @@ public static String getMavenVersion() { public static String getMavenHome() { String mavenHome = SystemUtil.get("MAVEN_HOME"); if (StrUtil.isNotBlank(mavenHome)) { - return mavenHome; - } - String searchCmd = SystemUtil.getOsInfo().isWindows() ? "where" : "which"; - mavenHome = RuntimeUtil.execForStr(searchCmd + " " + EXECTOR).trim(); - if (StrUtil.isNotBlank(mavenHome)) { + String searchCmd = SystemUtil.getOsInfo().isWindows() ? "where" : "which"; + mavenHome = RuntimeUtil.execForStr(searchCmd + " " + EXECTOR).trim(); try { return new File(mavenHome) .toPath() @@ -194,11 +193,11 @@ public static String getMavenHome() { .getParent() .toString(); } catch (IOException e) { - e.printStackTrace(); - return null; + throw new RuntimeException(e); } + } else { + throw new BusException(Status.GIT_MAVEN_HOME_NOT_SET); } - return null; } public static List getJars(File pom) { diff --git a/dinky-common/src/main/java/org/dinky/data/enums/Status.java b/dinky-common/src/main/java/org/dinky/data/enums/Status.java index 4423963f2f..26933fffb5 100644 --- a/dinky-common/src/main/java/org/dinky/data/enums/Status.java +++ b/dinky-common/src/main/java/org/dinky/data/enums/Status.java @@ -232,6 +232,7 @@ public enum Status { GIT_BRANCH_NOT_FOUND(16003, "git.branch.not.found"), GIT_BUILDING(16004, "git.building"), GIT_BUILD_SUCCESS(16005, "git.build.success"), + GIT_MAVEN_HOME_NOT_SET(16006, "git.maven.home.not.set"), /** * dolphin scheduler diff --git a/dinky-common/src/main/resources/i18n/messages_en_US.properties b/dinky-common/src/main/resources/i18n/messages_en_US.properties index afe031668c..60b21f193e 100644 --- a/dinky-common/src/main/resources/i18n/messages_en_US.properties +++ b/dinky-common/src/main/resources/i18n/messages_en_US.properties @@ -13,6 +13,7 @@ schedule.status.unknown=Unknown Status: {0} user.binding.role.delete.all=User Binding Role Delete All modify.failed=Update Failed git.build.success=Pre-update status success, start executing the build process +git.maven.home.not.set=Maven Home Not Set, Please Set MAVEN_HOME Environment Variable First menu.has.child=Menu Has Child, Can Not Delete tenant.already.exists=Tenant Already Exists save.failed=Save Failed diff --git a/dinky-common/src/main/resources/i18n/messages_zh_CN.properties b/dinky-common/src/main/resources/i18n/messages_zh_CN.properties index 89da9de646..da50d559a5 100644 --- a/dinky-common/src/main/resources/i18n/messages_zh_CN.properties +++ b/dinky-common/src/main/resources/i18n/messages_zh_CN.properties @@ -13,6 +13,7 @@ schedule.status.unknown=未知状态: {0} user.binding.role.delete.all=用户绑定角色删除所有 modify.failed=修改失败 git.build.success=预更新状态成功,开始执行构建流程 +git.maven.home.not.set=未设置 Maven Home,请先设置 MAVEN_HOME 环境变量 menu.has.child=存在子菜单 不允许删除 tenant.already.exists=租户已存在 save.failed=保存失败 From 935a53c6844043f16cf61ed9aea1a0c32f55b066 Mon Sep 17 00:00:00 2001 From: Zzm0809 <934230207@qq.com> Date: Sat, 9 Mar 2024 23:50:31 +0800 Subject: [PATCH 44/87] [Fix] Fix unrecognized global variables defined by registry in application mode (#3266) Co-authored-by: Zzm0809 --- .../main/java/org/dinky/app/db/DBUtil.java | 26 +++++++++++++- .../org/dinky/app/flinksql/Submitter.java | 5 ++- .../org/dinky/data/app/AppGlobalVariable.java | 35 +++++++++++++++++++ 3 files changed, 64 insertions(+), 2 deletions(-) create mode 100644 dinky-common/src/main/java/org/dinky/data/app/AppGlobalVariable.java diff --git a/dinky-app/dinky-app-base/src/main/java/org/dinky/app/db/DBUtil.java b/dinky-app/dinky-app-base/src/main/java/org/dinky/app/db/DBUtil.java index 1f62983d69..eaac772dba 100644 --- a/dinky-app/dinky-app-base/src/main/java/org/dinky/app/db/DBUtil.java +++ b/dinky-app/dinky-app-base/src/main/java/org/dinky/app/db/DBUtil.java @@ -21,6 +21,7 @@ import org.dinky.app.model.SysConfig; import org.dinky.data.app.AppDatabase; +import org.dinky.data.app.AppGlobalVariable; import org.dinky.data.app.AppParamConfig; import org.dinky.data.app.AppTask; @@ -28,6 +29,7 @@ import java.util.List; import cn.hutool.core.text.StrFormatter; +import cn.hutool.core.util.StrUtil; import cn.hutool.db.Db; import cn.hutool.db.Entity; import cn.hutool.db.ds.simple.SimpleDataSource; @@ -61,9 +63,31 @@ public static String getDbSourceSQLStatement() throws SQLException { Entity option = Entity.create("dinky_database").set("enabled", true); List entities = db.find(option, AppDatabase.class); for (AppDatabase entity : entities) { + // Filter out items with empty FlinkConfiguration, as this item is optional in the front-end form and does + // not need to be generated when it is empty + if (StrUtil.isNotBlank(entity.getFlinkConfig())) { + sb.append(entity.getName()) + .append(":=") + .append(entity.getFlinkConfig()) + .append("\n;\n"); + } + } + return sb.toString(); + } + + /** + * Get the global variables statement获取全局变量 + * @return the global variables statement + * @throws SQLException if a database access error occurs + */ + public static String getGlobalVariablesStatement() throws SQLException { + StringBuilder sb = new StringBuilder(); + Entity option = Entity.create("dinky_fragment").set("enabled", true); + List entities = db.find(option, AppGlobalVariable.class); + for (AppGlobalVariable entity : entities) { sb.append(entity.getName()) .append(":=") - .append(entity.getFlinkConfig()) + .append(entity.getFragmentValue()) .append("\n;\n"); } return sb.toString(); diff --git a/dinky-app/dinky-app-base/src/main/java/org/dinky/app/flinksql/Submitter.java b/dinky-app/dinky-app-base/src/main/java/org/dinky/app/flinksql/Submitter.java index 3707ba0fac..58e3d53da5 100644 --- a/dinky-app/dinky-app-base/src/main/java/org/dinky/app/flinksql/Submitter.java +++ b/dinky-app/dinky-app-base/src/main/java/org/dinky/app/flinksql/Submitter.java @@ -159,8 +159,11 @@ public static String buildSql(AppTask appTask) throws SQLException { } // build Database golbal varibals if (appTask.getFragment()) { - log.info("Global env is enable, load database flink config env."); + log.info("Global env is enable, load database flink config env and global variables."); + // append database flink config env sb.append(DBUtil.getDbSourceSQLStatement()).append("\n"); + // append global variables + sb.append(DBUtil.getGlobalVariablesStatement()).append("\n"); } sb.append(appTask.getStatement()); return sb.toString(); diff --git a/dinky-common/src/main/java/org/dinky/data/app/AppGlobalVariable.java b/dinky-common/src/main/java/org/dinky/data/app/AppGlobalVariable.java new file mode 100644 index 0000000000..d621ed755a --- /dev/null +++ b/dinky-common/src/main/java/org/dinky/data/app/AppGlobalVariable.java @@ -0,0 +1,35 @@ +/* + * + * 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.data.app; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@Data +public class AppGlobalVariable { + @ApiModelProperty(value = "ID", required = true, dataType = "Integer", example = "1", notes = "Primary Key") + private Integer id; + + @ApiModelProperty(value = "Name", required = true, dataType = "String", example = "Name") + private String name; + + @ApiModelProperty(value = "flinkConfig", dataType = "String", example = "flinkConfig") + private String fragmentValue; +} From ab3ae956e178117d08606f5a22e8cd6b859eb523 Mon Sep 17 00:00:00 2001 From: Zzm0809 <934230207@qq.com> Date: Mon, 11 Mar 2024 14:37:55 +0800 Subject: [PATCH 45/87] [Fix] fix proxy url is error (#3270) --- .../RegCenter/Resource/components/ResourceOverView/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dinky-web/src/pages/RegCenter/Resource/components/ResourceOverView/index.tsx b/dinky-web/src/pages/RegCenter/Resource/components/ResourceOverView/index.tsx index 0b65db75e7..e2e82b75ee 100644 --- a/dinky-web/src/pages/RegCenter/Resource/components/ResourceOverView/index.tsx +++ b/dinky-web/src/pages/RegCenter/Resource/components/ResourceOverView/index.tsx @@ -53,7 +53,7 @@ const ResourceOverView: React.FC = () => { const refObject = useRef(null); const [uploadValue] = useState({ - url: API_CONSTANTS.RESOURCE_UPLOAD, + url: API_CONSTANTS.BASE_URL + API_CONSTANTS.RESOURCE_UPLOAD, pid: '', description: '' }); From db300d8af285006e09f5d4a1678de2a14568cb97 Mon Sep 17 00:00:00 2001 From: Zzm0809 <934230207@qq.com> Date: Mon, 11 Mar 2024 20:44:20 +0800 Subject: [PATCH 46/87] [Optimization] [web] move url to constants (#3271) Co-authored-by: Zzm0809 --- .../pages/DataStudio/LeftContainer/DataSource/service.tsx | 7 ++++--- dinky-web/src/services/endpoints.tsx | 2 ++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/dinky-web/src/pages/DataStudio/LeftContainer/DataSource/service.tsx b/dinky-web/src/pages/DataStudio/LeftContainer/DataSource/service.tsx index e2e9869007..5a5b6245ec 100644 --- a/dinky-web/src/pages/DataStudio/LeftContainer/DataSource/service.tsx +++ b/dinky-web/src/pages/DataStudio/LeftContainer/DataSource/service.tsx @@ -18,13 +18,14 @@ */ import { handleGetOption, queryDataByParams } from '@/services/BusinessCrud'; +import { API_CONSTANTS } from '@/services/endpoints'; import { l } from '@/utils/intl'; /*--- 刷新 元数据表 ---*/ export async function showDataSourceTable(id: number) { try { const result = await handleGetOption( - 'api/database/getSchemasAndTables', + API_CONSTANTS.DATASOURCE_GET_SCHEMA_TABLES, l('pages.metadata.DataSearch'), { id: id } ); @@ -37,8 +38,8 @@ export async function showDataSourceTable(id: number) { /*--- 清理 元数据表缓存 ---*/ export function clearDataSourceTable(id: number) { - return queryDataByParams('api/database/unCacheSchemasAndTables', { id: id }); + return queryDataByParams(API_CONSTANTS.DATASOURCE_UN_CACHE_SCHEMA_TABLES, { id: id }); } export function getDataSourceList() { - return queryDataByParams('api/database/listEnabledAll'); + return queryDataByParams(API_CONSTANTS.DATASOURCE_LIST_ENABLE_ALL); } diff --git a/dinky-web/src/services/endpoints.tsx b/dinky-web/src/services/endpoints.tsx index 553c5d9c01..b597be5dfe 100644 --- a/dinky-web/src/services/endpoints.tsx +++ b/dinky-web/src/services/endpoints.tsx @@ -107,6 +107,8 @@ export enum API_CONSTANTS { DATASOURCE_CHECK_HEARTBEAT_BY_ID = '/api/database/checkHeartBeatByDataSourceId', DATASOURCE_COPY = '/api/database/copyDatabase', DATASOURCE_GET_SCHEMA_TABLES = '/api/database/getSchemasAndTables', + DATASOURCE_UN_CACHE_SCHEMA_TABLES = '/api/database/unCacheSchemasAndTables', + DATASOURCE_LIST_ENABLE_ALL = '/api/database/listEnabledAll', DATASOURCE_GET_COLUMNS_BY_TABLE = '/api/database/listColumns', DATASOURCE_GET_GEN_SQL = '/api/database/getSqlGeneration', DATASOURCE_QUERY_DATA = '/api/database/queryData', From 31915dbf5cdda038996b4da011a84f854e1a6c73 Mon Sep 17 00:00:00 2001 From: Wink <32723967+aiwenmo@users.noreply.github.com> Date: Mon, 11 Mar 2024 22:36:23 +0800 Subject: [PATCH 47/87] [Document-3268][doc] Update dinky_principle.png (#3269) Co-authored-by: wenmo <32723967+wenmo@users.noreply.github.com> --- images/main/dinky_principle.png | Bin 163075 -> 173989 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/images/main/dinky_principle.png b/images/main/dinky_principle.png index cf1ecae3b0a6f649485497764c3016983eeba052..50204ec27bf9f29ad891efb12bade9ed7e6a7ae5 100644 GIT binary patch literal 173989 zcmY(q1ymf-wk?XgySqbh3GVKY;O+zs9^8U7+CYFna1ZXmHMm{tEHiciAIJ70|SGptR$xc1B0*%0|V!V0tdY_`7E^w{RQi(qbLJY zGevO({eoaGtu74%Q=f$XY>5c{j_R&t=m`UZHSqQWJM30&0|NsoQI?bb;AeIkgls~f z2ziCvmv$;|WDb&9Tg7LQ1YC6aA|7?I#n9az?v)qDumAucyNfQ10*d?(N0dih_dg*Q zr<}u|Dco69KIcx;pHEIqO--HqcHf*%O+EMAll(u|3v87V4|f#JM%TdL;3<4C!?FkT z!!!5)eh>jA(78A|q9y>a(UOLs58-!DvgTJ+3OTqsq6Slf(cs~Wk}gGf_f0QyxLf2f z;=6xmKiD|Ol90S4VmHdU&L9H_(Dbymzns{PJP*8PW@b`r0zKn&{fn~U1lbB@i-d{NrPmo1VQ!8-|r zt9u&=^KGEPVKyCZznL+{5<=oSy5EDR^x>1pkqV1~E&N5u+9rnLj>VzC0Sv{%r_q zW$@sJk(K9o2QTzQd7cufEn?S-unAa~11(X56#=m;_BMaTcntE4FXEp1phK#b(*1s0 zP0}-WUJM~ClilX^+afIF0kOjW-frwm(Y(DIKFk1UWrc+h91HNOej!g~ChcOZbvpIs z4{535=0T>chwdH)${>IM-;)N1T}N#qtd~m_=;|HMS?g5l0iiiuSA~R-ovuK~ptE`# z9Opd_!I3XNr1{QmDcAA9popf0KQmJ{`@gS|26*KJ*#vpA+o0Q@8E)^cr5CELto&2*Fk6M&l|o&Z&Xq_3en6F z=V98{V*JlC%mH*SVf*(?Wk}bZccq8+rbF)HLrOtIN?pCcb`vcw6rIEwOs$^pDn!nG zSKJ&bJjkrkQFJ-M4+4*&v4eXtbj~T65pge7N{d08={iYK=ih zlqw7mlxz4z#a!1Q>f^Qf;cSBUyFQ zmcg(TC;*UXR!@xn^IlB<^G}6i5|aU;W|J4aF#{1QwTZ;3^B zS48(BDX=+8R68nGX8iTFobs|LU9!+eis>py#me%Xm#2BDRwsdWxeq(x^jWkp!%7dp zt4ezYwdXYH$9Cl4S!T)}bN}Za0?%ZRLw23Rf?Vv0uWP*HWJBc03A6Ukh2ta&@Bcv@ zk!kuB?&TbxEi%l#;1rIKp^wpfR6>+RVR};gD7+{}00dRH9~BrWZ~+H-M!3mCufjrB zWN7xq1+!>sV)0xr%FO?a)gcfI`*>lc_wEEokS4eh-{LxHHl0KzmMY4c;a)Bco@kOM zZQxJbf%|1GgGE2SpUYsw^v6^+voZs-gJtol`alXK`f+)=2J7Gd<1)fD_Lo%@ZloHmXEY;MOy}ez!6MesFYm^{G`>Nd@U(!D)U@X~U%-e-h$QM2F_p+ygoS0b* zvS%XujT&GWsF>3d`B9!#;3--*lm5QagWxbPdcIzQ_m{4i`XIBH)}ky>>$D&GvDl(o zl+t>HzDA7nF9yy7XS$OdK}fUu#P@HH~0<$Ps1H;kJ_Z zowQmW<^?AD2EwY8FsbwH#kJ~s)Xc(8C`+(y;^)vUn6J(W@RB)65!fOz=20IM;MsL1 zqz7I<7H>K{F!}`EjRC$6_Zt#8_CmKiI*6BcjjI{XM^4<-*)~)5Yo0kUyzxf8L_$oN z^Jn%aD!6{UNESc-ODAg(yy7uW)Cq4e(lNqw@c_()pjvjmM(^}roS1G2@|w7f1}Q78 z>K;-ASuE8gMWW`|Iq<&TWy-@h)P0(5Exlq%ho^|T$Uq9X8-Z^Q7ufV+^(c2rKopihnBg4Z8TE)s(J&dErs0khm4mg0mHx2Q3I+m zJW;MZ*>(C?v`M={+^)HIum>z=3avpiUfKDAAq#e)w?UdWPl-r^N{Qw7PkIqF0__{r zg~9ocVM20bACb>cz@9%W>vVCl4kHKCJ$e4K9m!B>j#SK}#Wpfnd`S8-!t#=z*F-9p zN0?Sd&8WEIqkZ266`hVBe&5vtYZR(qQ%pV}hs_b=^?r?DLA)1JW4B0?*s%;`ja1xQ z%76SIVka=;m;;1cB&QsvdbMoAG8`h}ja-TT_($_r5bA&pit_*KfZO@p#AMJ$1{^n) z$xDcpV$|G+y3L)y^o6zEsD(+rKjuw*@(EksJlx^Q$vZ{{m1d3ixIb261_y-?|)?z37eEDJ0{d66lW7!#>HA5oVkvw!Y6QgsG( z8xZ=Qu%qX|jJl9otp($Yv)FObP8 ze_;;96ol08-0o1}lueAKh0`HAwb%sp<-QZ!m4gkiT+Wm+BbcV!wg<{YuRn#2=Pson z2K*bPTFf}D9IA&LS8oy?8Hfl3;VO!0azaw6#7u}uIo_{BxRoDv_Iz+tIm~El)x6Ta zBc)e=}A@p%;`rS!?#31o;4g92A$S0w^GtWiAJxRd+qz_D?E(pDr zQ|+PYM{0hNtcG#-vCtcwHwIx4pN>DH>H-UsG=SAXcpjG#46#u;s>5=2{V#h|bN7(Y z&PQYd$yPdf?1<-zc$C52#j4g2_^@Ie`6;YPXPIsZ>7}u-g;Qpht3SuVn-1VJAAlwK zb=@i}nMkEYHT5o$@STzqwOGQ_nx+s^6CLGW%82R7f{Hgw9>83R-~l5_ZQUz%VcZ1& zs%l`T9|3+Xy1RD53|Y)}ck1G*a?P@*1Pq-!AyfQHS4-P_St}~|*I7^xv-zA5wV7lD zOYcRxtHM2l2KueSUHlS3&*3k{T{3xl*EP5;1Ek|)w@6__RaH8JpSx6Y(@&n=OtLWa zXUl8aJNDjDBC5JWFg_@SZh79D+M`6|Pgx_vD$9Fgg?+(4tQ|Tm!flfq&Tt<+?%s5I z%)1S_aUi(=kyA36D zxmT1}3r-E205v>t^4meo8Ow{vT|i*J++ec=yUPFEN0$MP#UN_pjSY5fGaGi&Au{vq zMvrDtt`OCni=Mg1oR-M#>sM6$`wV!IX8$Lwi2sd^mO|$rNf+*Q9k7XjSqNgoH z2|uy%ndL1WxV0}Fp=c47OOQ@=M+($Le;~Vc1zI+iF+)Q>Xpkd$SnJR_Pjd84i}Ihp zxxEhlr@ZTN%8KaHFEFc7Y37a(5#3)kZm1RdzJ7FwkDQF-po1iljtut2wshT}oRnpE zHM{e^NclJ}kP_ZD(9Z?jeWN>*ysM={w0~}ny1?d#-?`~AoyWC zeAL7lL}PjdejHBH-NPLOewkd%(j>lKlGQcA=+6&9@?#i=TmFJ!CXT)jZ#ZyMQjwjZak`6bzk?k^(dXZ>-i{EUr4gbQatSH@jWM<-KqDRP@F z%S8z;2SsXBJpx~Td=_ng-!tE6`u=Oj>|qc_=pWX*kiRodFLgDniu7pO550X3vRXio zS)Sfg%IJK$m!$TKYzltd^N3$_JV?@mkqYr{vt?X|eLDKae7ik|5>`4vSofl<^9=mHc1U0W;hQe2g>2vrwS_p{`3)R-7ey?2%_IW7dcx0L57M zetm~bm1svz>b#2HGJ8q^vAE!%&%%qZN@jLBQnOSZHATPfDf<%&o6flg#mxXTW{Zs4bzgBbglu7SY1S^)l0?p^5C@@USfy^lHZZF&0M*gr0!HN zBTAOg{l14}3`|!m3C!5%eNedAtZhnIJ|npiYx@CyDF`*Iw6@p~JE{A|OgDm4tkgU+ z(%r3F9FvB3Vj6u(Z?X&XvG`ZeVE~q}{Lih*&*YOC=m+p!i#*^k2N`-YKdlfelg;s- z3G=J=A%NeIruZ7|G^G5|NOGDkiTx~xW}C_vWSIW?0%|TRnz30!!q}E)2V!4&J+gZM z=yO7~{llQfB;PI;s1fw!oY_dqbu-~7MFKCaLH|@On&4_bS%6K*YOFV?31Uq0UW;wv z!MMmYA21r`iOa#8q;1Wej^i6OqWf7>iK}8t5Z@Ja+P$F_es{yTYI_`DM8rFw{AEJn z=ZjUPkcZ;*-K>=Zh0^f6edUj|L+s}tMR|m*SqpuetnaBmFw%FW05x8#F(NThlq*Bs z4{yOG=})C;PyL$<9v(3ljm5kms&AR35bB#hyL|M}xO(u9D>a{BfqlxK`kSvzD?1;V z&f8m@^ppGeFO!Ay;t_zC>WD{=qGh!cFE)$%SJ>ad`o%l3CFXXYds+`;wbK*{YhFcIO$TRn%L382n{dN;iyRB!e zA<0gBa;M!#B)k-#03c)+q?M57;B@(_hyI5=BJlnb`)$SmCJA%UEZL@-m<9{EJwc4Z zveN^=U#pTZTXG5p6Bq?pwQqnJvI)|0N=kCyFcg#I1Fy^P=}FE~$r$;U@~JymVGi>^dPKEArF zs{C+JSBJe&YOG1bZu#bmx4h0*9ov6ZTf&4@y3HU1HeaHF~-hnWpRC5q(R@&e|G$)hp(*r$8;bKEphH_K3+Sx5zyF$o5Eb2vzCU zJWI~Lma&6}E43v3wzHauC$v|cmNjHOj9qtq{6}|7SIW`)8)>8liu!|j$4c0rB$OQ- z;_JI63(9}=X1c=azLQ7$?|kTTq2#*8Yw||X0!w`FE|es!Xuq&QR{>7dp;4A%VqvlB zERQe{ux4CpR?-~oGx|nXjkxc(mO2mbg;Tiw=2SA%!xHjwsNmcqcl;6AG9ccrJDF#VgCl5qh3k^}48|%3*Q0kra~2QXsmP-O+k#i+BqaZlOA=Q)(Y*btlmy%{ILEcS zN;1UKmA}>QU{!n(Q=l?ZhZ@fLbs%hs(L6PUy^kU|>#=$WxfWr5^mWzjvzOmk{}yN2 z#_#6M{9JkG8w8!9rFz+=K6!7=snXwgztj0}=h8F%=hm*|L_`8;gbo%FV{Oh1fy}D~ zbD1vmuUo&MI#TjzIH{}pISY2cdF5)4hiY#AB~JMl_A_Mu^w%Bx_VX~bvkSE3 zrz0Dbbu(KpThYH*LcXH+zVx`u>4d)fLmF5)*vIZwUr&#{ScccON@jTNXp9?bT(5Ia zipkjzN2Nw4zJDAp*m(bei?m-SA;>YRH#kV~Z;jF$sH2IV00SqZ*wzXlh&;sJJvVxt z@Gah)o%w<8u;x}KdoOovR^p%FBGxyH?*7YO$>UdE2P5%)h`x|ya`qDhf8)8=)q2G> z*bW7EdGQ5O@n%Y(sc4)_KNQeNT(FLpi3pv!(bM`yqYec%68KQ$7;M;-mziWnFqLrP zy*TQLy$oqnw?rH`HNTO&f0YIf1=7inZ5Z91R=58Ok(imRCox@}#^!(-&BaXS>o_Ax zRy=JF8Tm>|=dQU`C{O)K1$as5arZXCCyHx8$oZv;lP`NUg|w^^%T2`&+hhyk(1 zxa0~q43b9bo7RvU8a!b|qP!%UHJIn*MVEQs}3na>{pL0 zqvl~zBaJOQBr-!}NTcRbJMgd>{pe5!j{7-Dm5!e+E#$TSlG;1}0=?zY5&0Z$Ho~mG z566aY!nM|exoks7Zvx|}tIeBk<)LXDrf@lDs$9Qpp0)ES`$wF}(GZ$HFHXzPoeyp~ z@Ue8>BaLOBPAQ+%fq3|5uP#@B2~Y4=GGuOM0tFh(yz0*u*r$RmvJxD$>l=_f@IJNHskwanKZEyP??V_Arm&$HhP)8W zj~%GC^7zN|fYsb$0&Nu`G2Kk`B^)3}+UQxZv}WpMPnz)jthmK%A%xWzaOBWpR+Qe)@hSX8=JI7%f4fqnquQ*OSnobxng%E2m=4^^Kr?(0h;A!T(n=D*b3b5`Q z5jlVcDI5Wd-p zMD4oUmUfuyW4kCNmM<4G`Nc8QL5)UxefT+MWcBV3&B!i+hlQMLs3t>C8z^m7fVi*8 zt42+ql88sMPYS^wcQ;wCyOzG;2t{}?Wd6rY8I@@d_Z&%)*fqjS?;DaL&Do9=_D@PW zKX_17=X{Xi3#IV;m<^lok>4-VkGZgK9%mcsah9hT54K;20Zjo?h21*EQTOw+EMi$u zFjMOA3(|9F{1g;Wg^vf>yA%ud9ZFTe#kve>8nFQQHAjw-vo?K=UVF%RM=-i%(W6fg z5*MI|^1wqbkrc-H$@uq#(A~-2V07<;>UM!JYV^NJn>Z7@SU2Ap&_o)JO9Cz2xS zkrN81D~266-A|V~mfrKqn?JrQ$`m-l5VHBQ@)V_!^>_&vy8qCo>7aAT-d5W3z$*?T zGjSz{og5T$_~%N!EE#R-=8`_}y-JA3QPtUI1MHkG^=d;KF#S!fca~S!?zf%wU~Jvq zXhrK^#10qSa=qARzffgfp(QJK4g>q0i|&okoa>`Ygt$%{%EUSOh&fv7JJo3*CDrfG zS%ML&ca3x(15ktcE9%n6+A+0A6pX$`>i4^^_inYvo1^sKDK&Ac&6tDq1*aRo^I0$x zFUGbHCkSAWRS+vpxI!KYSe3`9Tg`c0Yj{(GY<~A*^S4gz9%G$O#k@aTt^YIkLi@`F z_DxPOpmMVQ1LdTCnY}u$^0@%2(`W#oa3d!<@Az?_B-XqFdhUx<#4Bf3$6+0I)(Q#% zYrT%p4tDClnxT9DXd*2@+SxA?V23gAe}zNdf9f8iZam2F9CChh5s}@~@gD(!Ec`bZ zscMKq6to=l$B|k{dNHCbuHY)yMS13O_`skW+p~a##<0i!{MC%puh(tCJXrN+RS_f) zODuyPvLx?jGAr^tg7;(q^wEhB_phuu&%^fiJZ)tme)MvrUn8?fDpt!anQK3x_OOf+ z7oe0)kW~fZ)v_~omGcCTpVIZRh66*2j(ndP{kxyIy9t^Oxkd`Wkg5UhUr9u+ZG<0B zadost8`j2iGV#={`pxw6xip%eS0wb|MUnN@vB2WgnL=59J%9B9X^zZYw`j$fz2fU7 zn#cY99Sq-di)=ko`C0)@_Zx{~CK)yrYExxveGdUOLW#5-)oRg8(N@G|OSE|#Kf1k@ zR?E=4YB|GSDwU)~G+fC!M{tL*Hw&ld_-EfE9-=|Y5laC>G`JV4@Wg*3lgfI)ghAT) zC@SClkWLjRLSJBs?s*uhzkNadAAgxHhxn0s6c4UyG>jI~*IQKQ&XLq%(Q3PNd=*I;qjnF;hp#4#J z$cIxH`jNRCif%#SF}UMw<8QTP&zf-tcruiex-Zn2pkU&C3#Uh%bziyJ8orDM3&jafwtKG#RTWr)!D=x(uNXwKfPNf~M zvC-3=}@&m7axZWH*YC=Cp*+-luM5TI;%?ee;8-kg2@^22(B zM^yoPBasg!*9Nc3lR4*yEMh;lsiN$czV(`>xDV}FYNh-kDPhjp@@e7M4AH`bdIB0e z*kQppQ22c_U%TVX@fQ>G6Z3@~QPOy@rYB9AXy)0{#gLwGirRGFm1hPYv8fH`R2+QA zxqa%l8TOrn!j1P)P3G@gN+WHEF;#{G{R6G@F5vl#@T{Gt97EaeHYt%4n z`X6DU*DT2?8bzVdYIW`}w+W@2f5>Jkgl#kDBQ5N=bPp5(Z!?C^biw%)F zL)&u=3;~ApWZI1hP!9DeVAp&1U#5R5Djo4r$FIk*wKYiI%|5bE%$dn`?gd0M7z(UG z3%`u8-P7X57TK~3?h#TtS$IX4X6g8Rxga8U5#4vPGtBSEf~u58J1RYOSE^gL)1oRV zJK9YR&*C})MN$?OBzj>wXbc^z7%lbaD}!)PS1@p&YMdiNWmSOT7YG%Pjab%QK@2~z zck|taQQKQKKY_#lc%e+tH`>ZO=q=WQ%ah`m)|n)%SN#RSgX2FA*T_aJMXqdFlh^JY ztw>{x-=p2cq)wQpS}k5mi}+g?#pPl~5MOlI4^-(X)}<;iwpDd}F_aw%Xn$F{7}Z-* zF1_%Yxhp^S+%Jt=gXWe@Wp?EWABt86SR2VCiEoj1)3Cic2Pf8ZL|=Z8rzJjmecFzd zn*P<|nG`VdIeuxFU+1k*J!`>m*z)G&0Hr?_-6T+RNcqPBSwz3D9hrFAVZ~|9TouJt z;XW(~)r#NRrBN9zqE1kV^d%GW3UQBWmMLwi5Z1l(oAI%(GvRWr@eq9)1cXuCKu!T^ zB9zNHdqgD?A{N5KRR5baEUQdC$}P9U?M~+&@6s>l0SFA~{PobWoKjX!T(BG(=>9!h zTMlM8!bVasN+2*ETEx1gB3b0&NiDcQ88am=Zgw*ybS*m`(&w)7 zDZAdMUeh2>d^yfJUvDTJ@#`%_+|+eGNVyM$gj{EbNY#fBI?k-Be2PPS{Fne$dHhN9 zu%pQqtkq+ju60`fgU7?1L>`K^aUQ!?>-h43Kp$cMI%C7l&ixO&keOuhrVm@lkBS-! zeUC3<`Wt{Qb*7Zt{z=?0HTWY3^R?ai>I9H;XbwurcA+)QX+y|zBPre8f-r>+g$$tS z>=xZ`5Vsxn02R|vYw@9xbEhX?sTo?8=K0`S-1{ZvJ3MC;F*Mcv6c3xV6#v{n@PYAg z+Pkc11fj87W|UNqGrGtT>0BqYQr<5bn?@j?9?uGG}Hmr%;hHtFwg-T=?P8zF$ z+ks=0@jVCr@E#M<7;-i)_^iB*j1unEXN4J3f)y;Jk6*;UTduaH9?a?S*^F^&b|T;P zK&z5D&aeI%^!}pl!@{oEVDj+;it!6FV$}&8q7=jCN0S9GpIfM#NBP$*rM0AY(iiSc z&pkyOTlr-pc|IeqPrtv4)H1%H^Zn|slBlzy8Gdf#Yfao;d#Fpzo~J$EXmRsi zo+xj1Q4qlwvgF#>LbAYc4dj=fdtAXVD#R{sGJ$pF!zd>PXhrUWSErB z0nIXNDXEDXJK%o z#mf4(lNC&va!Vfm-2qCY@i+a5iDmGgkv~>{V zgMt;IS0D=ya|in3PJB)9ezM5Yr3BfPY69NErKxWeUQOo|546_RbI8+O#?uSDza>HC z?Hs7?#OOryPAX*KriGTaupBx>X{}<_GFv6=RS&alz*q`%JU^d)w4Kx4`J$c8@Y$`I$rnT!SAv}LPGb~6{s^_^S^o>xvxv%6xxtxhT2() zi92Kx?HCubV+5u~Y#fNiuz$rUsHdS)43i9wZ6C=rCsBrov*a4xcF#nwFKcIIlJVlFP|M+@d~~#(z&yOTLoD|DSI*=6vP3(1&S`4=yw;QbDQzu zBVvf_JAGHu#H6npqVZ7j#J1Vh;UZcE8>;-L7m7)&cyR072CAw{f3OhR>tgbAOcga#;qd8%^n&E6ITY7XdFZd5;oXFlbVQ7w z!)C}p?IJe5Hn$3zrEZ>qQ-{_Xi$Fl47et$4jb3OX2rY7lRG%y2+%Zac%%d-#<3NiT z6vvEhGQG=Dp}ctx5#9G)SZRKgcKk|wBW_%f(&UpN}ASb0h}ifs*pyRxV|AsZrdf`LB( zw;0%(lSC)EYxni(B#<9aFkisK6Ci#4kuRX^2Uw?E6HN5-M0v#E`DvvQ3hvG7=escc z@oK}@wF18%C{CzT*Qm9+$Xl&Z;)!OHe3_KYUxxCi>eU|{UV4Fp<>LIUaJ1^fxF;Ui zf2;#*2`@1xA>>UY@fRE8qrnw>x*X;am~cId7&NG>PzF%g6%_G`pH+PI`o)=Lo9gcs z*JUZ`$$g9clp(7QEG%m|6sUmQ4|1N!3pQQlm$T7+RptE8Ar$1Kf5!$JuA@dCMo{fxH=xz&nj+ zLt=FUaW(SEWn?Q40&qS?_3xKdLI1@oLY@bWb36g}q|J9k?G-cvJvsr(iGLyt4u``n z3$MCFSW>B1_s51Vl)yO=l_ASL6dr&yKXov*&5z(+X9esXRocq(HL^M#PyxD8E`hiR zT)&_JM+$Hd-#i6JMlC6FV~a*xMXZ9%IyBcpT8K>88Z%@cz_oKET?C4d>JCO8($w%t zN`S=CKLUI6AKOcz_3tb$5*mL+l`3Ae8sd0#TtU#R)7)|(f6bPyhp;5i5pIuXRj5-{ zzSECnryrr{M$C5EvLFTiBkcukD-v3ZgmrR{Psk(D6hxneN_U+w`DHTC4l^^mXn5)w z#xvwEbY~(den=L9VW9cMONTm8Ly`h4UJqiq8eEc$&?;7zZ|10sFD$`wF|*MW48hFB z4P^H-s8Wi4FL?!(&4)Hqcv=XZlzTua*!GXfcypQY*@$ z#-erRB*&2u?f|LHab2B$A0`kLFOANzezzP+M-5K_>nY_XJ8XfXCXUMRr3xMsAtlG+ zhA$~H#mx@ORFkxt_`OLqYyx_gRrYsm<1bJ`D;_kRe9Z%^_ILM+&8D>t%tUO3T8cRA z*i-VSZ=@L9B?myuyYT`jWKJcel^;C_-%W3tAr^iwxUfW-~wJ5H@ zd7(Z4uJ}2X3M2i!40cea3g-er)1G{Yv3WSd{XXqM1g<;?gReV=Qf!Ugs8OLj4gLaLfo#%XqOZa%%hLb?C2
|QqdcbC-~}rd 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%0A?xIrK|*@H4#5j;o6on8kd^~ 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 literal 163075 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 From 44175993f7eb260a8f1653ff8d2ee0ec00be3f63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=AC=91=E4=B8=80=E7=AC=91?= <56523920+czshh0628@users.noreply.github.com> Date: Mon, 11 Mar 2024 22:41:12 +0800 Subject: [PATCH 48/87] [Optimization][admin] NULL may be used when optimizing heartbeat detection of cluster instances (#3275) Co-authored-by: czs --- dinky-admin/src/main/java/org/dinky/assertion/DinkyAssert.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/dinky-admin/src/main/java/org/dinky/assertion/DinkyAssert.java b/dinky-admin/src/main/java/org/dinky/assertion/DinkyAssert.java index b0a99e3bbf..e6c106c775 100644 --- a/dinky-admin/src/main/java/org/dinky/assertion/DinkyAssert.java +++ b/dinky-admin/src/main/java/org/dinky/assertion/DinkyAssert.java @@ -33,6 +33,9 @@ public interface DinkyAssert { static void check(ClusterInstance clusterInstance) { + if (clusterInstance == null) { + throw new BusException(Status.CLUSTER_NOT_EXIST); + } if (clusterInstance.getId() == null) { throw new BusException("Flink 集群【" + clusterInstance.getId() + "】不存在"); } From a26d92ab082860ebd454ee52b4ea76488a747153 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=AC=91=E4=B8=80=E7=AC=91?= <56523920+czshh0628@users.noreply.github.com> Date: Mon, 11 Mar 2024 22:45:50 +0800 Subject: [PATCH 49/87] [Document]Fix cdcsource practice documents for Mysql and PostgreSQL (#3276) Co-authored-by: czs --- .../cdcsource_practice/cdcsource_mysqlcdc2mysql.md | 4 ++-- .../cdcsource_practice/cdcsource_mysqlcdc2pg.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/docs/practical_guide/cdcsource_practice/cdcsource_mysqlcdc2mysql.md b/docs/docs/practical_guide/cdcsource_practice/cdcsource_mysqlcdc2mysql.md index bbbeeb5f94..5c46de3a78 100644 --- a/docs/docs/practical_guide/cdcsource_practice/cdcsource_mysqlcdc2mysql.md +++ b/docs/docs/practical_guide/cdcsource_practice/cdcsource_mysqlcdc2mysql.md @@ -15,9 +15,9 @@ title: MySQLCDC 整库到 MySQL ## 示例 注意事项: -- 该示例是将 mysql 整库同步到 Doris 表,且写入名为 ods 的库,目标表名前缀取 `test__` 并转小写。 +- 该示例是将 mysql 整库同步到 mysql 表,且写入名为 ods 的库,目标表名前缀取 `test__` 并转小写。 - 该示例参数中的 `#{tableName}` 为占位符,实际执行时会替换为实际表名,如 `ods_products`、`ods_orders` 等。 -- 该示例 sink 中的各个参数均可根据实际情况进行调整,请按照 Doris 连接器官方文档进行配置。并请遵守整库同步的规范. +- 该示例 sink 中的各个参数均可根据实际情况进行调整,请按照 mysql 连接器官方文档进行配置。并请遵守整库同步的规范. > 该示例为将 mysql 整库同步到另一个 mysql 数据库,写入 test 库,表名前缀 `test_`,表名全小写,开启自动建表。 diff --git a/docs/docs/practical_guide/cdcsource_practice/cdcsource_mysqlcdc2pg.md b/docs/docs/practical_guide/cdcsource_practice/cdcsource_mysqlcdc2pg.md index edd99d87d7..0a3513040f 100644 --- a/docs/docs/practical_guide/cdcsource_practice/cdcsource_mysqlcdc2pg.md +++ b/docs/docs/practical_guide/cdcsource_practice/cdcsource_mysqlcdc2pg.md @@ -16,9 +16,9 @@ title: MySQLCDC 整库到 PostgreSQL ## 示例 注意事项: -- 该示例是将 mysql 整库同步到 Doris 表,且写入名为 ods 的库,目标表名前缀取 `test__` 并转小写。 +- 该示例是将 mysql 整库同步到 PostgreSQL 表,且写入名为 ods 的库,目标表名前缀取 `test__` 并转小写。 - 该示例参数中的 `#{tableName}` 为占位符,实际执行时会替换为实际表名,如 `ods_products`、`ods_orders` 等。 -- 该示例 sink 中的各个参数均可根据实际情况进行调整,请按照 Doris 连接器官方文档进行配置。并请遵守整库同步的规范. +- 该示例 sink 中的各个参数均可根据实际情况进行调整,请按照 PostgreSQL 连接器官方文档进行配置。并请遵守整库同步的规范. ```sql showLineNumbers EXECUTE CDCSOURCE cdc_postgresql WITH ( From 0a12f5a3dbc9776a48a23af169289700ccb49b47 Mon Sep 17 00:00:00 2001 From: Zzm0809 <934230207@qq.com> Date: Tue, 12 Mar 2024 16:41:27 +0800 Subject: [PATCH 50/87] [Fix] fix addfile cannot parse (#3278) Co-authored-by: Zzm0809 --- .../main/java/org/dinky/app/flinksql/Submitter.java | 8 ++++---- .../main/java/org/dinky/executor/ParserWrapper.java | 10 ++++++++-- .../{AddFilerOperation.java => AddFileOperation.java} | 6 +++--- .../org/dinky/trans/parse/AddFileSqlParseStrategy.java | 4 ++-- ...che.flink.table.planner.parse.ExtendedParseStrategy | 1 + 5 files changed, 18 insertions(+), 11 deletions(-) rename dinky-client/dinky-client-base/src/main/java/org/dinky/trans/ddl/{AddFilerOperation.java => AddFileOperation.java} (88%) diff --git a/dinky-app/dinky-app-base/src/main/java/org/dinky/app/flinksql/Submitter.java b/dinky-app/dinky-app-base/src/main/java/org/dinky/app/flinksql/Submitter.java index 58e3d53da5..c9a053becb 100644 --- a/dinky-app/dinky-app-base/src/main/java/org/dinky/app/flinksql/Submitter.java +++ b/dinky-app/dinky-app-base/src/main/java/org/dinky/app/flinksql/Submitter.java @@ -250,8 +250,8 @@ public static boolean downloadFile(String url, String path) throws IOException { public static Optional executeJarJob(String type, Executor executor, String[] statements) { Optional jobClient = Optional.empty(); - for (int i = 0; i < statements.length; i++) { - String sqlStatement = executor.pretreatStatement(statements[i]); + for (String statement : statements) { + String sqlStatement = executor.pretreatStatement(statement); if (ExecuteJarParseStrategy.INSTANCE.match(sqlStatement)) { ExecuteJarOperation executeJarOperation = new ExecuteJarOperation(sqlStatement); StreamGraph streamGraph = executeJarOperation.getStreamGraph(executor.getCustomTableEnvironment()); @@ -274,13 +274,13 @@ public static Optional executeJarJob(String type, Executor executor, if (Operations.getOperationType(sqlStatement) == SqlType.ADD) { File[] info = AddJarSqlParseStrategy.getInfo(sqlStatement); Arrays.stream(info).forEach(executor.getDinkyClassLoader().getUdfPathContextHolder()::addOtherPlugins); - if ("kubernetes-application".equals(type)) { + if (GatewayType.get(type).isKubernetesApplicationMode()) { executor.addJar(info); } } else if (Operations.getOperationType(sqlStatement) == SqlType.ADD_FILE) { File[] info = AddFileSqlParseStrategy.getInfo(sqlStatement); Arrays.stream(info).forEach(executor.getDinkyClassLoader().getUdfPathContextHolder()::addFile); - if ("kubernetes-application".equals(type)) { + if (GatewayType.get(type).isKubernetesApplicationMode()) { executor.addJar(info); } } diff --git a/dinky-client/dinky-client-1.17/src/main/java/org/dinky/executor/ParserWrapper.java b/dinky-client/dinky-client-1.17/src/main/java/org/dinky/executor/ParserWrapper.java index 0f76daee30..cf1afc1afc 100644 --- a/dinky-client/dinky-client-1.17/src/main/java/org/dinky/executor/ParserWrapper.java +++ b/dinky-client/dinky-client-1.17/src/main/java/org/dinky/executor/ParserWrapper.java @@ -19,6 +19,8 @@ package org.dinky.executor; +import org.dinky.utils.LogUtil; + import org.apache.calcite.sql.SqlNode; import org.apache.flink.table.catalog.UnresolvedIdentifier; import org.apache.flink.table.expressions.ResolvedExpression; @@ -44,8 +46,12 @@ public List parse(String statement) { if (result != null) { return result; } - - return customParser.getParser().parse(statement); + try { + return customParser.getParser().parse(statement); + } catch (Exception e) { + throw new RuntimeException( + String.format("Failed to parse statement: %s , reason: %s", statement, LogUtil.getError(e)), e); + } } @Override diff --git a/dinky-client/dinky-client-base/src/main/java/org/dinky/trans/ddl/AddFilerOperation.java b/dinky-client/dinky-client-base/src/main/java/org/dinky/trans/ddl/AddFileOperation.java similarity index 88% rename from dinky-client/dinky-client-base/src/main/java/org/dinky/trans/ddl/AddFilerOperation.java rename to dinky-client/dinky-client-base/src/main/java/org/dinky/trans/ddl/AddFileOperation.java index 8dfa31ff55..a4db43558c 100644 --- a/dinky-client/dinky-client-base/src/main/java/org/dinky/trans/ddl/AddFilerOperation.java +++ b/dinky-client/dinky-client-base/src/main/java/org/dinky/trans/ddl/AddFileOperation.java @@ -30,13 +30,13 @@ /** * @since 0.7.0 */ -public class AddFilerOperation extends AbstractOperation implements ExtendOperation { +public class AddFileOperation extends AbstractOperation implements ExtendOperation { - public AddFilerOperation(String statement) { + public AddFileOperation(String statement) { super(statement); } - public AddFilerOperation() {} + public AddFileOperation() {} @Override public Optional execute(CustomTableEnvironment tEnv) { diff --git a/dinky-client/dinky-client-base/src/main/java/org/dinky/trans/parse/AddFileSqlParseStrategy.java b/dinky-client/dinky-client-base/src/main/java/org/dinky/trans/parse/AddFileSqlParseStrategy.java index f291b6b6c6..9a56382288 100644 --- a/dinky-client/dinky-client-base/src/main/java/org/dinky/trans/parse/AddFileSqlParseStrategy.java +++ b/dinky-client/dinky-client-base/src/main/java/org/dinky/trans/parse/AddFileSqlParseStrategy.java @@ -20,7 +20,7 @@ package org.dinky.trans.parse; import org.dinky.data.exception.DinkyException; -import org.dinky.trans.ddl.AddFilerOperation; +import org.dinky.trans.ddl.AddFileOperation; import org.dinky.utils.URLUtils; import org.apache.flink.table.operations.Operation; @@ -86,7 +86,7 @@ public static Set getAllFilePath(String statements) { @Override public Operation convert(String statement) { - return new AddFilerOperation(statement); + return new AddFileOperation(statement); } @Override diff --git a/dinky-client/dinky-client-base/src/main/resources/META-INF/services/org.apache.flink.table.planner.parse.ExtendedParseStrategy b/dinky-client/dinky-client-base/src/main/resources/META-INF/services/org.apache.flink.table.planner.parse.ExtendedParseStrategy index 6b1f21c164..d100529b0b 100644 --- a/dinky-client/dinky-client-base/src/main/resources/META-INF/services/org.apache.flink.table.planner.parse.ExtendedParseStrategy +++ b/dinky-client/dinky-client-base/src/main/resources/META-INF/services/org.apache.flink.table.planner.parse.ExtendedParseStrategy @@ -1,4 +1,5 @@ org.dinky.trans.parse.AddJarSqlParseStrategy +org.dinky.trans.parse.AddFileSqlParseStrategy org.dinky.trans.parse.CreateAggTableSelectSqlParseStrategy org.dinky.trans.parse.CreateTemporalTableFunctionParseStrategy org.dinky.trans.parse.ExecuteJarParseStrategy From 06f448759551189bb1c7400eed3fc1e006f6f3c9 Mon Sep 17 00:00:00 2001 From: gaoyan Date: Tue, 12 Mar 2024 16:41:48 +0800 Subject: [PATCH 51/87] [Bug] fix rs filesystem some problem (#3234) Co-authored-by: gaoyan1998 --- .../src/main/java/org/dinky/url/ResourceFileSystem.java | 6 +++--- dinky-core/src/main/java/org/dinky/explainer/Explainer.java | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/dinky-client/dinky-client-base/src/main/java/org/dinky/url/ResourceFileSystem.java b/dinky-client/dinky-client-base/src/main/java/org/dinky/url/ResourceFileSystem.java index 2b66b6c48f..009ee2d50c 100644 --- a/dinky-client/dinky-client-base/src/main/java/org/dinky/url/ResourceFileSystem.java +++ b/dinky-client/dinky-client-base/src/main/java/org/dinky/url/ResourceFileSystem.java @@ -21,6 +21,7 @@ import org.dinky.resource.BaseResourceManager; +import org.apache.flink.api.common.io.InputStreamFSInputWrapper; import org.apache.flink.core.fs.BlockLocation; import org.apache.flink.core.fs.FSDataInputStream; import org.apache.flink.core.fs.FSDataOutputStream; @@ -28,7 +29,6 @@ import org.apache.flink.core.fs.FileSystem; import org.apache.flink.core.fs.FileSystemKind; import org.apache.flink.core.fs.Path; -import org.apache.flink.core.fs.local.LocalDataInputStream; import org.apache.flink.core.fs.local.LocalFileStatus; import java.io.File; @@ -91,7 +91,7 @@ public FSDataInputStream open(Path f, int bufferSize) throws IOException { @Override public FSDataInputStream open(Path f) throws IOException { - return new LocalDataInputStream(getFile(f)); + return new InputStreamFSInputWrapper(BASE_RESOURCE_MANAGER.readFile(f.getPath())); } @Override @@ -142,6 +142,6 @@ public FileSystemKind getKind() { } public static ResourceFileSystem getSharedInstance() { - return INSTANCE; + return getInstance(); } } diff --git a/dinky-core/src/main/java/org/dinky/explainer/Explainer.java b/dinky-core/src/main/java/org/dinky/explainer/Explainer.java index d5e194178b..2f5bd20aa0 100644 --- a/dinky-core/src/main/java/org/dinky/explainer/Explainer.java +++ b/dinky-core/src/main/java/org/dinky/explainer/Explainer.java @@ -181,7 +181,7 @@ public ExplainResult explainSql(String statement) { String error = StrFormatter.format( "Exception in executing FlinkSQL:\n{}\n{}", SqlUtil.addLineNumber(item.getValue()), - e.getMessage()); + LogUtil.getError(e)); resultBuilder .error(error) .explainTrue(false) From 8c5b295dedb8a5182e37df6c76252392fcca3882 Mon Sep 17 00:00:00 2001 From: Zzm0809 <934230207@qq.com> Date: Wed, 13 Mar 2024 10:42:08 +0800 Subject: [PATCH 52/87] [Fix] fix array out of bounds (#3279) Co-authored-by: Zzm0809 --- .../org/dinky/gateway/yarn/YarnGateway.java | 25 +++++++++++++------ 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/dinky-gateway/src/main/java/org/dinky/gateway/yarn/YarnGateway.java b/dinky-gateway/src/main/java/org/dinky/gateway/yarn/YarnGateway.java index b7aff28105..7f7e5a1ed8 100644 --- a/dinky-gateway/src/main/java/org/dinky/gateway/yarn/YarnGateway.java +++ b/dinky-gateway/src/main/java/org/dinky/gateway/yarn/YarnGateway.java @@ -35,6 +35,7 @@ import org.dinky.gateway.result.TestResult; import org.dinky.gateway.result.YarnResult; import org.dinky.utils.FlinkJsonUtil; +import org.dinky.utils.ThreadUtil; import org.apache.flink.client.deployment.ClusterRetrieveException; import org.apache.flink.client.program.ClusterClient; @@ -58,6 +59,7 @@ import org.apache.hadoop.service.Service; import org.apache.hadoop.yarn.api.records.ApplicationId; import org.apache.hadoop.yarn.api.records.ApplicationReport; +import org.apache.hadoop.yarn.api.records.ContainerReport; import org.apache.hadoop.yarn.api.records.FinalApplicationStatus; import org.apache.hadoop.yarn.api.records.YarnApplicationState; import org.apache.hadoop.yarn.client.api.YarnClient; @@ -384,13 +386,20 @@ protected String getWebUrl(ClusterClient clusterClient, YarnResul } protected String getYarnContainerLog(ApplicationReport applicationReport) throws YarnException, IOException { - String logUrl = yarnClient - .getContainers(applicationReport.getCurrentApplicationAttemptId()) - .get(0) - .getLogUrl(); - String content = HttpUtil.get(logUrl + "/jobmanager.log?start=-10000"); - String log = ReUtil.getGroup1(HTML_TAG_REGEX, content); - logger.info("\n\nHistory log url is: {}\n\n ", logUrl); - return log; + // Wait for up to 2.5 s. If the history log is not found yet, a prompt message will be returned. + int counts = 5; + while (yarnClient + .getContainers(applicationReport.getCurrentApplicationAttemptId()) + .isEmpty() + && counts-- > 0) { + ThreadUtil.sleep(500); + } + List containers = yarnClient.getContainers(applicationReport.getCurrentApplicationAttemptId()); + if (CollUtil.isNotEmpty(containers)) { + String logUrl = containers.get(0).getLogUrl(); + String content = HttpUtil.get(logUrl + "/jobmanager.log?start=-10000"); + return ReUtil.getGroup1(HTML_TAG_REGEX, content); + } + return "No history log found yet. so can't get log url, please check yarn cluster status or check if the flink job is running in yarn cluster or please go to yarn interface to view the log."; } } From aca823b30baca6a24ed77f133cede00a7283b2cc Mon Sep 17 00:00:00 2001 From: Gianzie <63688853+Gianzie@users.noreply.github.com> Date: Wed, 13 Mar 2024 13:23:12 +0800 Subject: [PATCH 53/87] [Doc]Overview of entire library synchronization: update dinky-client-${version}.jar path (#3283) --- .../practical_guide/cdcsource_practice/cdcsource_overview.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/practical_guide/cdcsource_practice/cdcsource_overview.md b/docs/docs/practical_guide/cdcsource_practice/cdcsource_overview.md index ad3dd6c5d6..7713ff06b0 100644 --- a/docs/docs/practical_guide/cdcsource_practice/cdcsource_overview.md +++ b/docs/docs/practical_guide/cdcsource_practice/cdcsource_overview.md @@ -85,7 +85,7 @@ sink,也可以使用 FlinkSQL 无需修改代码直接扩展新的 sink。 # 将下面 Dinky根目录下 整库同步依赖包放置 $FLINK_HOME/lib下 lib/dinky-client-base-${version}.jar lib/dinky-common-${version}.jar -extends/flink${flink-version}/dinky-client-${version}.jar +extends/flink${flink-version}/dinky/dinky-client-${version}.jar ``` ### Application 模式提交 From 291a4a5ac3e01a39e9222ab30f513d74486a98fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=83=96=E6=A2=81?= <418094911@qq.com> Date: Wed, 13 Mar 2024 13:24:33 +0800 Subject: [PATCH 54/87] [Document-3281][web]JobManager HA address example misses port number (#3282) --- dinky-web/src/locales/en-US/pages.ts | 2 +- dinky-web/src/locales/zh-CN/pages.ts | 2 +- .../user_guide/register_center/cluster_manage.md | 14 +++++++------- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/dinky-web/src/locales/en-US/pages.ts b/dinky-web/src/locales/en-US/pages.ts index 516562edbe..34cc0375b1 100644 --- a/dinky-web/src/locales/en-US/pages.ts +++ b/dinky-web/src/locales/en-US/pages.ts @@ -809,7 +809,7 @@ export default { 'rc.ci.jma': 'JobManager Address', 'rc.ci.jmha': 'JobManager HA Address', 'rc.ci.jmha.tips': - 'Add the RestApi address of the JobManager of the Flink cluster. In HA mode, the addresses are separated by commas, for example', + 'Add the RestApi address of the JobManager of the Flink cluster. In HA mode, the addresses are separated by commas, for example: 192.168.123.101:8081', 'rc.ci.jmha.validate.port': 'Does not meet the rules! Port number range [0-65535]', 'rc.ci.jmha.validate.slash': 'Does not comply with the rules! Cannot contain /', 'rc.ci.jmhaPlaceholder': 'Please enter the JobManager HA address!', diff --git a/dinky-web/src/locales/zh-CN/pages.ts b/dinky-web/src/locales/zh-CN/pages.ts index 4321b13e1b..07769d9f6f 100644 --- a/dinky-web/src/locales/zh-CN/pages.ts +++ b/dinky-web/src/locales/zh-CN/pages.ts @@ -776,7 +776,7 @@ export default { 'rc.ci.jma': 'JobManager 地址', 'rc.ci.jmha': 'JobManager 高可用地址', 'rc.ci.jmha.tips': - '添加 Flink 集群的 JobManager 的 RestApi 地址。当 HA 模式时,地址间用英文逗号分隔,例如:192.168.123.101', + '添加 Flink 集群的 JobManager 的 RestApi 地址。当 HA 模式时,地址间用英文逗号分隔,例如:192.168.123.101:8081', 'rc.ci.jmha.validate.port': '不符合规则! 端口号区间[0-65535]', 'rc.ci.jmha.validate.slash': '不符合规则! 不能包含/', 'rc.ci.jmhaPlaceholder': '请输入 JobManager HA 地址!', diff --git a/docs/docs/user_guide/register_center/cluster_manage.md b/docs/docs/user_guide/register_center/cluster_manage.md index 6742c0da8a..815b7e804b 100644 --- a/docs/docs/user_guide/register_center/cluster_manage.md +++ b/docs/docs/user_guide/register_center/cluster_manage.md @@ -47,13 +47,13 @@ title: 集群 ### 参数解读 -| 参数 | 说明 | 是否必填 | 默认值 | 示例值 | -|-----------------|-----------------------------------------------------------------------------|:----:|:-----:|:-------------:| -| 集群名称 | 集群名称, 用于区分不同集群 | 是 | 无 | flink-session | -| 集群别名 | 集群别名, 用于区分不同集群, 如不填默认同集群名称 | 否 | 同集群名称 | flink-session | -| 集群类型 | 集群类型, 目前支持 Local, Standalone, Yarn Session, Kubernetes Session | 是 | 无 | Standalone | -| JobManager 高可用地址 | 添加 Flink 集群的 JobManager 的 RestApi 地址。当 HA 模式时,地址间用英文逗号分隔,例如:192.168.123.101 | 是 | 无 | -| 备注 | 备注, 用于备注集群信息 | 否 | 无 | flink-session | +| 参数 | 说明 | 是否必填 | 默认值 | 示例值 | +|-----------------|------------------------------------------------------------------------------------|:----:|:-----:|:-------------:| +| 集群名称 | 集群名称, 用于区分不同集群 | 是 | 无 | flink-session | +| 集群别名 | 集群别名, 用于区分不同集群, 如不填默认同集群名称 | 否 | 同集群名称 | flink-session | +| 集群类型 | 集群类型, 目前支持 Local, Standalone, Yarn Session, Kubernetes Session | 是 | 无 | Standalone | +| JobManager 高可用地址 | 添加 Flink 集群的 JobManager 的 RestApi 地址。当 HA 模式时,地址间用英文逗号分隔,例如:192.168.123.101:8081 | 是 | 无 | +| 备注 | 备注, 用于备注集群信息 | 否 | 无 | flink-session | ## 集群配置 From 6735db4408b54e6295ed1af39d2b37ff64325394 Mon Sep 17 00:00:00 2001 From: Zzm0809 <934230207@qq.com> Date: Wed, 13 Mar 2024 23:01:44 +0800 Subject: [PATCH 55/87] [Feature] Release-1.0.1 (#3287) --- docs/download/dinky-1.0.1.md | 64 ++++++++++++++++++++++++++++++++++++ docs/download/download.md | 8 +++++ pom.xml | 2 +- 3 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 docs/download/dinky-1.0.1.md diff --git a/docs/download/dinky-1.0.1.md b/docs/download/dinky-1.0.1.md new file mode 100644 index 0000000000..0b3117a5fc --- /dev/null +++ b/docs/download/dinky-1.0.1.md @@ -0,0 +1,64 @@ +--- +sidebar_position: 82 +title: 1.0.1 release +--- + +| Dinky 版本 | Flink 版本 | 二进制程序 | Source | +|----------|----------|---------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------| +| 1.0.1 | 1.14 | [dinky-release-1.14-1.0.1.tar.gz](https://github.com/DataLinkDC/dinky/releases/download/v1.0.1/dinky-release-1.14-1.0.1.tar.gz) | [Source code (zip)](https://github.com/DataLinkDC/dinky/archive/refs/tags/v1.0.1.zip) | +| 1.0.1 | 1.15 | [dinky-release-1.15-1.0.1.tar.gz](https://github.com/DataLinkDC/dinky/releases/download/v1.0.1/dinky-release-1.15-1.0.1.tar.gz) | [Source code (zip)](https://github.com/DataLinkDC/dinky/archive/refs/tags/v1.0.1.zip) | +| 1.0.1 | 1.16 | [dinky-release-1.16-1.0.1.tar.gz](https://github.com/DataLinkDC/dinky/releases/download/v1.0.1/dinky-release-1.16-1.0.1.tar.gz) | [Source code (zip)](https://github.com/DataLinkDC/dinky/archive/refs/tags/v1.0.1.zip) | +| 1.0.1 | 1.17 | [dinky-release-1.17-1.0.1.tar.gz](https://github.com/DataLinkDC/dinky/releases/download/v1.0.1/dinky-release-1.17-1.0.1.tar.gz) | [Source code (zip)](https://github.com/DataLinkDC/dinky/archive/refs/tags/v1.0.1.zip) | +| 1.0.1 | 1.18 | [dinky-release-1.18-1.0.1.tar.gz](https://github.com/DataLinkDC/dinky/releases/download/v1.0.1/dinky-release-1.18-1.0.1.tar.gz) | [Source code (zip)](https://github.com/DataLinkDC/dinky/archive/refs/tags/v1.0.1.zip) | + +## Dinky-1.0.1 发行说明 + +### 升级说明 + +:::warning 提示 +- 1.0.1 是一个 BUG 修复版本,无数据库升级变更,可以直接升级 +- 关于 SCALA 版本: 发版使用 Scala-2.12 , 如你的环境必须使用 Scala-2.11, 请自行编译,请参考 [编译部署](../docs/deploy_guide/compile_deploy) , 将 profile 的 scala-2.12 改为 scala-2.11 +::: + + +### 新功能 +- 添加一些 Flink Options 类,用于触发快捷提示 +- 实现数据开发中控制台日志自动滚动 + + +### 修复 +- 修复短信告警插件未打包的问题 +- 修复创建 UDF 时 NPE 的异常和一些其他问题 +- 修复创建任务时作业类型渲染异常 +- 修复数据开发中查看 Catalog 时页面崩溃的问题 +- 修复 `add jar` 和 s3 一起使用时的参数配置问题 +- 修复一些 ``rs`` 自愿协议的问题 +- 修复数据开发中快捷导航中的路由错误跳转问题 +- 修复当选择 UDF 任务类型时, 控制台没有关闭的问题 +- 修复 `decimal` 数据类型超过 38 时位数的问题(超过 38 位将转为 string) +- 修复一些弹框无法关闭的问题 +- 修复 application 模式下全局变量无法识别的问题 +- 修复 application 模式下获取 container 时会存在数组越界的问题 +- 修复 `add file` 无法解析的问题 + + +### 优化 +- 优化一些前端请求 URL 到同意常量中 +- 优化启动脚本,移除 FLINK_HOME 环境变量加载 +- 优化密码错误时的提示信息 +- 优化数据开发任务的 tag 展示 +- 关闭数据开发编辑器内的自动预览 +- 优化表达式变量定义方式,由文件定义改为系统配置中定义 +- 优化 application 模式下不支持查询语句的提示信息 +- 优化`FlinkSQL 环境`列表的渲染效果 +- 优化 GIT 项目构建时的环境检查异常提示 +- 优化集群在心跳检测的时候可能出现 NPE 的问题 + +### 文档 +- 添加整库同步的内置变量文档 +- 优化文档版本 +- 添加 `EXECUTE JAR` 任务 DEMO +- 优化创建集群配置时的一些文案提示 +- 优化整库同步文档中的部分路径 + + diff --git a/docs/download/download.md b/docs/download/download.md index 3ee390bbd8..5ea57de2fc 100644 --- a/docs/download/download.md +++ b/docs/download/download.md @@ -5,6 +5,14 @@ title: 下载 Dinky --------------- 使用以下链接,下载并查看发行说明 +## 1.0.1 release + +- **发行时间:** 2024-03-13 + +- **[下载及发行说明](./dinky-1.0.1)** + + + ## 1.0.0 release - **发行时间:** 2024-03-02 diff --git a/pom.xml b/pom.xml index 19ad9db680..6861941c99 100644 --- a/pom.xml +++ b/pom.xml @@ -109,7 +109,7 @@ UTF-8 2.5.0 0.10.2 - 1.0.0 + 1.0.1 1.37.0 2.12 2.12.10 From 0b00565b8ca2e6c4023ffab1ae0dd1a454242104 Mon Sep 17 00:00:00 2001 From: Zzm0809 <934230207@qq.com> Date: Thu, 14 Mar 2024 09:16:08 +0800 Subject: [PATCH 56/87] [Doc] fix jump url error (#3288) --- docs/download/dinky-1.0.1.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/download/dinky-1.0.1.md b/docs/download/dinky-1.0.1.md index 0b3117a5fc..99a2b10dc4 100644 --- a/docs/download/dinky-1.0.1.md +++ b/docs/download/dinky-1.0.1.md @@ -17,7 +17,7 @@ title: 1.0.1 release :::warning 提示 - 1.0.1 是一个 BUG 修复版本,无数据库升级变更,可以直接升级 -- 关于 SCALA 版本: 发版使用 Scala-2.12 , 如你的环境必须使用 Scala-2.11, 请自行编译,请参考 [编译部署](../docs/deploy_guide/compile_deploy) , 将 profile 的 scala-2.12 改为 scala-2.11 +- 关于 SCALA 版本: 发版使用 Scala-2.12 , 如你的环境必须使用 Scala-2.11, 请自行编译,请参考 [编译部署](../docs/next/deploy_guide/compile_deploy) , 将 profile 的 scala-2.12 改为 scala-2.11 ::: @@ -32,7 +32,7 @@ title: 1.0.1 release - 修复创建任务时作业类型渲染异常 - 修复数据开发中查看 Catalog 时页面崩溃的问题 - 修复 `add jar` 和 s3 一起使用时的参数配置问题 -- 修复一些 ``rs`` 自愿协议的问题 +- 修复一些 ``rs`` 资源协议的问题 - 修复数据开发中快捷导航中的路由错误跳转问题 - 修复当选择 UDF 任务类型时, 控制台没有关闭的问题 - 修复 `decimal` 数据类型超过 38 时位数的问题(超过 38 位将转为 string) From 15f95fc7bc35a27be2ad374be0c73a04aa34b453 Mon Sep 17 00:00:00 2001 From: Zzm0809 <934230207@qq.com> Date: Thu, 14 Mar 2024 13:41:47 +0800 Subject: [PATCH 57/87] [HotFix] hotfix auto.sh (#3289) --- script/bin/auto.sh | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/script/bin/auto.sh b/script/bin/auto.sh index 84c706e9cb..cc46516748 100644 --- a/script/bin/auto.sh +++ b/script/bin/auto.sh @@ -2,10 +2,6 @@ FLINK_VERSION=${2} -if [ -z "${FLINK_VERSION}" ]; then - echo "please specify the flink version, for example: sh auto.sh start 1.16" - exit 1 -fi APP_HOME="$(cd `dirname $0`; pwd)" @@ -14,6 +10,23 @@ EXTENDS_HOME="${APP_HOME}/extends" JAR_NAME="dinky-admin" +if [ -z "${FLINK_VERSION}" ]; then + # Obtain the Flink version under EXTENDSHOME, only perform recognition and do not perform any other operations, for prompt purposes + FLINK_VERSION_SCAN=$(ls ${EXTENDS_HOME} | grep flink | awk -F 'flink' '{print $2}') + # If FLINK_VERSION-SCAN is not empty, assign FLINK_VERSION-SCAN to FLINK_VERSION + if [ -n "${FLINK_VERSION_SCAN}" ]; then + FLINK_VERSION=${FLINK_VERSION_SCAN} + fi +fi + +# Check whether the flink version is specified +assertIsInputVersion() { + # If FLINK_VERSION is still empty, prompt the user to enter the Flink version + if [ -z "${FLINK_VERSION}" ]; then + echo "The flink version is not specified and the flink version cannot be found under the extends directory. Please specify the flink version." + exit 1 + fi +} # Use FLINK_HOME: CLASS_PATH="${APP_HOME}:${APP_HOME}/lib/*:${APP_HOME}/config:${EXTENDS_HOME}/*:${EXTENDS_HOME}/flink${FLINK_VERSION}/dinky/*:${EXTENDS_HOME}/flink${FLINK_VERSION}/*" @@ -51,6 +64,7 @@ updatePid() { } start() { + assertIsInputVersion updatePid if [ -z "$pid" ]; then nohup java -Ddruid.mysql.usePingMethod=false -Dlog4j2.isThreadContextMapInheritable=true -Xms512M -Xmx2048M -XX:PermSize=512M -XX:MaxPermSize=1024M -XX:+HeapDumpOnOutOfMemoryError -Xverify:none -cp "${CLASS_PATH}" org.dinky.Dinky > /dev/null 2>&1 & @@ -63,6 +77,7 @@ start() { } startOnPending() { + assertIsInputVersion updatePid if [ -z "$pid" ]; then java -Ddruid.mysql.usePingMethod=false -Xms512M -Xmx2048M -XX:PermSize=512M -XX:MaxPermSize=1024M -XX:+HeapDumpOnOutOfMemoryError -Xverify:none -cp "${CLASS_PATH}" org.dinky.Dinky @@ -73,6 +88,7 @@ startOnPending() { } startWithJmx() { + assertIsInputVersion updatePid if [ -z "$pid" ]; then nohup java -Ddruid.mysql.usePingMethod=false -Xms512M -Xmx2048M -XX:PermSize=512M -XX:MaxPermSize=1024M -XX:+HeapDumpOnOutOfMemoryError -Xverify:none "${JMX}" -cp "${CLASS_PATH}" org.dinky.Dinky > /dev/null 2>&1 & @@ -114,6 +130,7 @@ status() { restart() { echo "" + assertIsInputVersion stop start echo "........................................Restart Successfully........................................" @@ -121,6 +138,7 @@ restart() { restartWithJmx() { echo "" + assertIsInputVersion stop startWithJmx echo "........................................Restart with Jmx Successfully........................................" From 77d8c527e678c026be9a6130db9d2ceaf61218ae Mon Sep 17 00:00:00 2001 From: Zzm0809 <934230207@qq.com> Date: Fri, 15 Mar 2024 14:33:55 +0800 Subject: [PATCH 58/87] [Fix] Fix the global variable recognition order issue in the application mode (#3291) Co-authored-by: Zzm0809 --- .../org/dinky/app/flinksql/Submitter.java | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/dinky-app/dinky-app-base/src/main/java/org/dinky/app/flinksql/Submitter.java b/dinky-app/dinky-app-base/src/main/java/org/dinky/app/flinksql/Submitter.java index c9a053becb..1a9ac95ef3 100644 --- a/dinky-app/dinky-app-base/src/main/java/org/dinky/app/flinksql/Submitter.java +++ b/dinky-app/dinky-app-base/src/main/java/org/dinky/app/flinksql/Submitter.java @@ -149,15 +149,10 @@ public static void submit(AppParamConfig config) throws SQLException { public static String buildSql(AppTask appTask) throws SQLException { StringBuilder sb = new StringBuilder(); - // build env task - if (Asserts.isNotNull(appTask.getEnvId()) && appTask.getEnvId() > 0) { - AppTask envTask = DBUtil.getTask(appTask.getEnvId()); - if (Asserts.isNotNullString(envTask.getStatement())) { - log.info("use statement is enable, load env:{}", envTask.getName()); - sb.append(envTask.getStatement()).append("\n"); - } - } - // build Database golbal varibals + + // 1. build Database golbal varibals + // Note: It is necessary to first build the global variables defined in the database and registry, otherwise it + // may cause some variables to be unable to be resolved and referenced properly due to order issues if (appTask.getFragment()) { log.info("Global env is enable, load database flink config env and global variables."); // append database flink config env @@ -165,6 +160,16 @@ public static String buildSql(AppTask appTask) throws SQLException { // append global variables sb.append(DBUtil.getGlobalVariablesStatement()).append("\n"); } + + // 2. build env task + if (Asserts.isNotNull(appTask.getEnvId()) && appTask.getEnvId() > 0) { + AppTask envTask = DBUtil.getTask(appTask.getEnvId()); + if (Asserts.isNotNullString(envTask.getStatement())) { + log.info("use statement is enable, load env:{}", envTask.getName()); + sb.append(envTask.getStatement()).append("\n"); + } + } + sb.append(appTask.getStatement()); return sb.toString(); } From 5ef12b9659667c3c9bb4db2153d7e369d5c11ad8 Mon Sep 17 00:00:00 2001 From: Zzm0809 <934230207@qq.com> Date: Fri, 15 Mar 2024 23:56:15 +0800 Subject: [PATCH 59/87] [Optimization] Optimization start scripts (#3296) --- script/bin/auto.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/script/bin/auto.sh b/script/bin/auto.sh index cc46516748..1d29f2e877 100644 --- a/script/bin/auto.sh +++ b/script/bin/auto.sh @@ -12,7 +12,7 @@ JAR_NAME="dinky-admin" if [ -z "${FLINK_VERSION}" ]; then # Obtain the Flink version under EXTENDSHOME, only perform recognition and do not perform any other operations, for prompt purposes - FLINK_VERSION_SCAN=$(ls ${EXTENDS_HOME} | grep flink | awk -F 'flink' '{print $2}') + FLINK_VERSION_SCAN=$(ll ${EXTENDS_HOME} | grep '^d' | grep flink | awk -F 'flink' '{print $2}') # If FLINK_VERSION-SCAN is not empty, assign FLINK_VERSION-SCAN to FLINK_VERSION if [ -n "${FLINK_VERSION_SCAN}" ]; then FLINK_VERSION=${FLINK_VERSION_SCAN} @@ -29,7 +29,7 @@ assertIsInputVersion() { } # Use FLINK_HOME: -CLASS_PATH="${APP_HOME}:${APP_HOME}/lib/*:${APP_HOME}/config:${EXTENDS_HOME}/*:${EXTENDS_HOME}/flink${FLINK_VERSION}/dinky/*:${EXTENDS_HOME}/flink${FLINK_VERSION}/*" +CLASS_PATH="${APP_HOME}:${APP_HOME}/lib/*:${APP_HOME}/config:${EXTENDS_HOME}/*:${APP_HOME}/customJar/*:${EXTENDS_HOME}/flink${FLINK_VERSION}/dinky/*:${EXTENDS_HOME}/flink${FLINK_VERSION}/*" PID_FILE="dinky.pid" # JMX path From 29a54eb5024086e7cce2d48c4b109c2059541782 Mon Sep 17 00:00:00 2001 From: Zzm0809 <934230207@qq.com> Date: Mon, 18 Mar 2024 11:26:33 +0800 Subject: [PATCH 60/87] [Optimization] Optimization setting page overflow (#3297) Co-authored-by: Zzm0809 --- .../GlobalSetting/SettingOverView/index.tsx | 49 +++++++++---------- 1 file changed, 24 insertions(+), 25 deletions(-) diff --git a/dinky-web/src/pages/SettingCenter/GlobalSetting/SettingOverView/index.tsx b/dinky-web/src/pages/SettingCenter/GlobalSetting/SettingOverView/index.tsx index 4f1d48d62d..fa08e35ebf 100644 --- a/dinky-web/src/pages/SettingCenter/GlobalSetting/SettingOverView/index.tsx +++ b/dinky-web/src/pages/SettingCenter/GlobalSetting/SettingOverView/index.tsx @@ -232,32 +232,31 @@ const SettingOverView = () => { return ( -

- setActiveKey(key), - items: renderDataTag().filter( - (menu) => - !!!menu.path || !!AuthorizedObject({ path: menu.path, children: menu, access }) - ) - }} - /> -
+ boxShadow: true + }, + animated: true, + onChange: (key: any) => setActiveKey(key), + items: renderDataTag().filter( + (menu) => + !!!menu.path || !!AuthorizedObject({ path: menu.path, children: menu, access }) + ) + }} + /> ); }; From be79c90fe74166e31fcf8ad03bbf41d72993de6a Mon Sep 17 00:00:00 2001 From: Zzm0809 <934230207@qq.com> Date: Mon, 18 Mar 2024 11:26:59 +0800 Subject: [PATCH 61/87] [Optimization] Optimization udf manage tips (#3295) Co-authored-by: Zzm0809 --- .../UDF/components/UDFRegister/index.tsx | 1 - dinky-web/src/pages/RegCenter/UDF/index.tsx | 15 ++++++++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/dinky-web/src/pages/RegCenter/UDF/components/UDFRegister/index.tsx b/dinky-web/src/pages/RegCenter/UDF/components/UDFRegister/index.tsx index f7ee8d736c..9c5f5ca2d6 100644 --- a/dinky-web/src/pages/RegCenter/UDF/components/UDFRegister/index.tsx +++ b/dinky-web/src/pages/RegCenter/UDF/components/UDFRegister/index.tsx @@ -175,7 +175,6 @@ const UDFRegister: React.FC = (props) => { { @@ -52,7 +53,19 @@ export default () => { { tab: l('rc.udf.register.management'), key: 'udf-register', - children: + children: ( + <> + {/* TODO: 等待UDF和数据开发联动功能开发完成之后,删除该提示语*/} + + + + ) }, { tab: l('rc.udf.template.management'), From 2d40f50846f2ab1567d980f8873fc6a85a271b77 Mon Sep 17 00:00:00 2001 From: luoshangjie <75019445+18216499322@users.noreply.github.com> Date: Mon, 18 Mar 2024 21:39:55 +0800 Subject: [PATCH 62/87] [Fix][Admin] Fix the SQL error for querying user roles (#3305) Co-authored-by: 00225658 --- dinky-admin/src/main/resources/mapper/RoleMapper.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dinky-admin/src/main/resources/mapper/RoleMapper.xml b/dinky-admin/src/main/resources/mapper/RoleMapper.xml index e82344054a..604c187a18 100644 --- a/dinky-admin/src/main/resources/mapper/RoleMapper.xml +++ b/dinky-admin/src/main/resources/mapper/RoleMapper.xml @@ -97,7 +97,7 @@ from dinky_role r left join dinky_user_role urole on urole.role_id = r.id left join dinky_user u on u.id = urole.user_id - WHERE r.is_delete = 0 and ur.user_id = #{userId} + WHERE r.is_delete = 0 and urole.user_id = #{userId}