This is an automated email from the ASF dual-hosted git repository.
diwu pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris.git
The following commit(s) were added to refs/heads/master by this push:
new 61a49ad9386 [improve](extension) improve kettle plugin and add some
testcase (#44324)
61a49ad9386 is described below
commit 61a49ad9386b2f3d6dd89363657d6dc06d768a13
Author: wudi <[email protected]>
AuthorDate: Wed Nov 20 15:13:30 2024 +0800
[improve](extension) improve kettle plugin and add some testcase (#44324)
### What problem does this PR solve?
improve kettle plugin and add some testcase
---
extension/kettle/impl/pom.xml | 6 +
.../steps/dorisstreamloader/DorisStreamLoader.java | 6 +
.../dorisstreamloader/DorisStreamLoaderMeta.java | 18 +-
.../load/DorisBatchStreamLoad.java | 10 +-
.../DorisBatchStreamLoadTest.java | 2 +-
.../DorisRecordSerializerTest.java | 82 +++++
.../DorisStreamLoaderMetaTest.java | 114 ++++++
.../dorisstreamloader/DorisStreamLoaderTest.java | 389 +++++++++++++++++++++
8 files changed, 623 insertions(+), 4 deletions(-)
diff --git a/extension/kettle/impl/pom.xml b/extension/kettle/impl/pom.xml
index 59b3f42934b..64eb91076dd 100644
--- a/extension/kettle/impl/pom.xml
+++ b/extension/kettle/impl/pom.xml
@@ -66,6 +66,12 @@ under the License.
<classifier>tests</classifier>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>mysql</groupId>
+ <artifactId>mysql-connector-java</artifactId>
+ <version>8.0.26</version>
+ <scope>test</scope>
+ </dependency>
<!--log-->
<dependency>
diff --git
a/extension/kettle/impl/src/main/java/org/pentaho/di/trans/steps/dorisstreamloader/DorisStreamLoader.java
b/extension/kettle/impl/src/main/java/org/pentaho/di/trans/steps/dorisstreamloader/DorisStreamLoader.java
index 85dc097be4e..91b546baf21 100644
---
a/extension/kettle/impl/src/main/java/org/pentaho/di/trans/steps/dorisstreamloader/DorisStreamLoader.java
+++
b/extension/kettle/impl/src/main/java/org/pentaho/di/trans/steps/dorisstreamloader/DorisStreamLoader.java
@@ -17,6 +17,7 @@
package org.pentaho.di.trans.steps.dorisstreamloader;
+import com.google.common.annotations.VisibleForTesting;
import org.apache.commons.lang.StringUtils;
import org.pentaho.di.core.exception.KettleException;
import org.pentaho.di.core.row.ValueMetaInterface;
@@ -172,4 +173,9 @@ public class DorisStreamLoader extends BaseStep implements
StepInterface {
super.dispose( smi, sdi );
}
+
+ @VisibleForTesting
+ public DorisBatchStreamLoad getStreamLoad(){
+ return streamLoad;
+ }
}
diff --git
a/extension/kettle/impl/src/main/java/org/pentaho/di/trans/steps/dorisstreamloader/DorisStreamLoaderMeta.java
b/extension/kettle/impl/src/main/java/org/pentaho/di/trans/steps/dorisstreamloader/DorisStreamLoaderMeta.java
index 12d72d838af..5e111b1bf38 100644
---
a/extension/kettle/impl/src/main/java/org/pentaho/di/trans/steps/dorisstreamloader/DorisStreamLoaderMeta.java
+++
b/extension/kettle/impl/src/main/java/org/pentaho/di/trans/steps/dorisstreamloader/DorisStreamLoaderMeta.java
@@ -24,9 +24,11 @@ import org.pentaho.di.core.database.DatabaseMeta;
import org.pentaho.di.core.exception.KettleException;
import org.pentaho.di.core.exception.KettleStepException;
import org.pentaho.di.core.exception.KettleXMLException;
+import org.pentaho.di.core.injection.AfterInjection;
import org.pentaho.di.core.injection.Injection;
import org.pentaho.di.core.injection.InjectionSupported;
import org.pentaho.di.core.row.RowMetaInterface;
+import org.pentaho.di.core.util.Utils;
import org.pentaho.di.core.variables.VariableSpace;
import org.pentaho.di.core.xml.XMLHandler;
import org.pentaho.di.i18n.BaseMessages;
@@ -341,7 +343,7 @@ public class DorisStreamLoaderMeta extends BaseStepMeta
implements StepMetaInter
this.deletable = deletable;
}
- public String[] getFieldTable() {
+ public String[] getFieldTable() {
return fieldTable;
}
@@ -379,4 +381,18 @@ public class DorisStreamLoaderMeta extends BaseStepMeta
implements StepMetaInter
", fieldStream=" + Arrays.toString(fieldStream) +
'}';
}
+
+ /**
+ * If we use injection we can have different arrays lengths.
+ * We need synchronize them for consistency behavior with UI
+ */
+ @AfterInjection
+ public void afterInjectionSynchronization() {
+ int nrFields = (fieldTable == null) ? -1 : fieldTable.length;
+ if (nrFields <= 0) {
+ return;
+ }
+ String[][] rtnStrings = Utils.normalizeArrays(nrFields, fieldStream);
+ fieldStream = rtnStrings[0];
+ }
}
diff --git
a/extension/kettle/impl/src/main/java/org/pentaho/di/trans/steps/dorisstreamloader/load/DorisBatchStreamLoad.java
b/extension/kettle/impl/src/main/java/org/pentaho/di/trans/steps/dorisstreamloader/load/DorisBatchStreamLoad.java
index a73725add00..226f5f61e73 100644
---
a/extension/kettle/impl/src/main/java/org/pentaho/di/trans/steps/dorisstreamloader/load/DorisBatchStreamLoad.java
+++
b/extension/kettle/impl/src/main/java/org/pentaho/di/trans/steps/dorisstreamloader/load/DorisBatchStreamLoad.java
@@ -259,6 +259,10 @@ public class DorisBatchStreamLoad implements Serializable {
}
private synchronized boolean flush(String bufferKey, boolean waitUtilDone)
{
+ if (bufferMap.isEmpty()) {
+ // bufferMap may have been flushed by other threads
+ return false;
+ }
if (null == bufferKey) {
boolean flush = false;
for (String key : bufferMap.keySet()) {
@@ -275,7 +279,7 @@ public class DorisBatchStreamLoad implements Serializable {
} else if (bufferMap.containsKey(bufferKey)) {
flushBuffer(bufferKey);
} else {
- throw new DorisRuntimeException("buffer not found for key: " +
bufferKey);
+ log.logDetailed("buffer not found for key: {}, may be already
flushed.", bufferKey);
}
if (waitUtilDone) {
waitAsyncLoadFinish();
@@ -311,7 +315,9 @@ public class DorisBatchStreamLoad implements Serializable {
}
private void waitAsyncLoadFinish() {
- for (int i = 0; i < FLUSH_QUEUE_SIZE + 1; i++) {
+ // Because the flush thread will drainTo once after polling is
completed
+ // if queue_size is 2, at least 4 empty queues must be consumed to
ensure that flush has been completed
+ for (int i = 0; i < FLUSH_QUEUE_SIZE * 2; i++) {
BatchRecordBuffer empty = new BatchRecordBuffer();
putRecordToFlushQueue(empty);
}
diff --git
a/extension/kettle/impl/src/test/java/org/pentaho/di/trans/steps/dorisstreamloader/DorisBatchStreamLoadTest.java
b/extension/kettle/impl/src/test/java/org/pentaho/di/trans/steps/dorisstreamloader/DorisBatchStreamLoadTest.java
index c8d79156c18..556d3737bf4 100644
---
a/extension/kettle/impl/src/test/java/org/pentaho/di/trans/steps/dorisstreamloader/DorisBatchStreamLoadTest.java
+++
b/extension/kettle/impl/src/test/java/org/pentaho/di/trans/steps/dorisstreamloader/DorisBatchStreamLoadTest.java
@@ -32,7 +32,7 @@ public class DorisBatchStreamLoadTest {
@Ignore
public void testStreamLoad() throws Exception {
DorisOptions options = DorisOptions.builder()
- .withFenodes("10.16.10.6:28737")
+ .withFenodes("127.0.0.1:8030")
.withDatabase("test")
.withTable("test_flink_c")
.withUsername("root")
diff --git
a/extension/kettle/impl/src/test/java/org/pentaho/di/trans/steps/dorisstreamloader/DorisRecordSerializerTest.java
b/extension/kettle/impl/src/test/java/org/pentaho/di/trans/steps/dorisstreamloader/DorisRecordSerializerTest.java
new file mode 100644
index 00000000000..19b3ba6463c
--- /dev/null
+++
b/extension/kettle/impl/src/test/java/org/pentaho/di/trans/steps/dorisstreamloader/DorisRecordSerializerTest.java
@@ -0,0 +1,82 @@
+// 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.pentaho.di.trans.steps.dorisstreamloader;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.pentaho.di.core.logging.LogChannel;
+import org.pentaho.di.core.row.ValueMetaInterface;
+import org.pentaho.di.core.row.value.ValueMetaBigNumber;
+import org.pentaho.di.core.row.value.ValueMetaBinary;
+import org.pentaho.di.core.row.value.ValueMetaBoolean;
+import org.pentaho.di.core.row.value.ValueMetaDate;
+import org.pentaho.di.core.row.value.ValueMetaInteger;
+import org.pentaho.di.core.row.value.ValueMetaInternetAddress;
+import org.pentaho.di.core.row.value.ValueMetaNumber;
+import org.pentaho.di.core.row.value.ValueMetaString;
+import org.pentaho.di.core.row.value.ValueMetaTimestamp;
+import
org.pentaho.di.trans.steps.dorisstreamloader.serializer.DorisRecordSerializer;
+
+import java.math.BigDecimal;
+import java.sql.Timestamp;
+import java.text.SimpleDateFormat;
+import javax.mail.internet.InternetAddress;
+
+import static
org.pentaho.di.trans.steps.dorisstreamloader.load.LoadConstants.CSV;
+
+public class DorisRecordSerializerTest {
+
+ @Test
+ public void testSerialize() throws Exception {
+ ValueMetaInterface[] formatMeta = new ValueMetaInterface[]{
+ new ValueMetaBoolean("boolean"),
+ new ValueMetaInteger("integer"),
+ new ValueMetaNumber("number"),
+ new ValueMetaBigNumber("bignumber"),
+ new ValueMetaDate("date"),
+ new ValueMetaTimestamp("timestamp"),
+ new ValueMetaBinary("binary"),
+ new ValueMetaString("string"),
+ new ValueMetaInternetAddress("address"),
+ };
+
+ DorisRecordSerializer serializer = DorisRecordSerializer.builder()
+ .setType(CSV)
+ .setFieldNames(new String[]{"c_boolean", "c_integer", "c_number",
"c_bignumber", "c_date", "c_timestamp", "c_binary", "c_string",
"c_internetAddress"})
+ .setFormatMeta(formatMeta)
+ .setFieldDelimiter(",")
+ .setLogChannelInterface(new LogChannel())
+ .setDeletable(false)
+ .build();
+
+ SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
+ Object[] data = new Object[]{
+ true,
+ 10L,
+ 123.23,
+ new BigDecimal("123456789.1234"),
+ dateFormat.parse("2024-01-01"),
+ Timestamp.valueOf("2024-01-01 10:11:22.123"),
+ "binary",
+ "string",
+ new InternetAddress("127.0.0.1")};
+ String actual = serializer.buildCSVString(data, formatMeta.length);
+ String except = "true,10,123.23,123456789.1234,2024-01-01,2024-01-01
10:11:22.123,binary,string,127.0.0.1";
+ Assert.assertEquals(except, actual);
+ }
+}
diff --git
a/extension/kettle/impl/src/test/java/org/pentaho/di/trans/steps/dorisstreamloader/DorisStreamLoaderMetaTest.java
b/extension/kettle/impl/src/test/java/org/pentaho/di/trans/steps/dorisstreamloader/DorisStreamLoaderMetaTest.java
new file mode 100644
index 00000000000..4a9f0f36c30
--- /dev/null
+++
b/extension/kettle/impl/src/test/java/org/pentaho/di/trans/steps/dorisstreamloader/DorisStreamLoaderMetaTest.java
@@ -0,0 +1,114 @@
+// 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.pentaho.di.trans.steps.dorisstreamloader;
+
+import org.junit.Assert;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.pentaho.di.core.exception.KettleException;
+import org.pentaho.di.junit.rules.RestorePDIEngineEnvironment;
+import org.pentaho.di.trans.steps.loadsave.LoadSaveTester;
+import org.pentaho.di.trans.steps.loadsave.validator.ArrayLoadSaveValidator;
+import org.pentaho.di.trans.steps.loadsave.validator.FieldLoadSaveValidator;
+import org.pentaho.di.trans.steps.loadsave.validator.IntLoadSaveValidator;
+import org.pentaho.di.trans.steps.loadsave.validator.LongLoadSaveValidator;
+import org.pentaho.di.trans.steps.loadsave.validator.StringLoadSaveValidator;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class DorisStreamLoaderMetaTest {
+
+ @ClassRule
+ public static RestorePDIEngineEnvironment env = new
RestorePDIEngineEnvironment();
+
+ @Test
+ public void testRoundTrip() throws KettleException {
+ List<String> attributes =
+ Arrays.asList( "fenodes", "database", "table", "username",
"password",
+ "streamLoadProp", "bufferFlushMaxRows", "bufferFlushMaxBytes",
"maxRetries",
+ "deletable", "stream_name", "field_name");
+
+ Map<String, String> getterMap = new HashMap<>();
+ getterMap.put( "fenodes", "getFenodes" );
+ getterMap.put( "database", "getDatabase" );
+ getterMap.put( "table", "getTable" );
+ getterMap.put( "username", "getUsername" );
+ getterMap.put( "password", "getPassword" );
+ getterMap.put( "streamLoadProp", "getStreamLoadProp" );
+ getterMap.put( "bufferFlushMaxRows", "getBufferFlushMaxRows" );
+ getterMap.put( "bufferFlushMaxBytes", "getBufferFlushMaxBytes" );
+ getterMap.put( "maxRetries", "getMaxRetries" );
+ getterMap.put( "deletable", "isDeletable" );
+ getterMap.put( "stream_name", "getFieldTable" );
+ getterMap.put( "field_name", "getFieldStream" );
+
+ Map<String, String> setterMap = new HashMap<>();
+ setterMap.put( "fenodes", "setFenodes" );
+ setterMap.put( "database", "setDatabase" );
+ setterMap.put( "table", "setTable" );
+ setterMap.put( "username", "setUsername" );
+ setterMap.put( "password", "setPassword" );
+ setterMap.put( "streamLoadProp", "setStreamLoadProp" );
+ setterMap.put( "bufferFlushMaxRows", "setBufferFlushMaxRows" );
+ setterMap.put( "bufferFlushMaxBytes", "setBufferFlushMaxBytes" );
+ setterMap.put( "maxRetries", "setMaxRetries" );
+ setterMap.put( "deletable", "setDeletable" );
+ setterMap.put( "stream_name", "setFieldTable" );
+ setterMap.put( "field_name", "setFieldStream" );
+
+ Map<String, FieldLoadSaveValidator<?>>
fieldLoadSaveValidatorAttributeMap = new HashMap<>();
+ FieldLoadSaveValidator<String[]> stringArrayLoadSaveValidator = new
ArrayLoadSaveValidator<>( new StringLoadSaveValidator(), 25 );
+
+ fieldLoadSaveValidatorAttributeMap.put("maxRetries", new
IntLoadSaveValidator( Integer.MAX_VALUE ));
+ fieldLoadSaveValidatorAttributeMap.put("bufferFlushMaxRows", new
LongLoadSaveValidator());
+ fieldLoadSaveValidatorAttributeMap.put("bufferFlushMaxBytes", new
LongLoadSaveValidator());
+ fieldLoadSaveValidatorAttributeMap.put("streamLoadProp", new
StringLoadSaveValidator());
+ fieldLoadSaveValidatorAttributeMap.put("stream_name",
stringArrayLoadSaveValidator );
+ fieldLoadSaveValidatorAttributeMap.put("field_name",
stringArrayLoadSaveValidator );
+
+ LoadSaveTester loadSaveTester =
+ new LoadSaveTester( DorisStreamLoaderMeta.class, attributes,
getterMap, setterMap,
+ fieldLoadSaveValidatorAttributeMap, new HashMap<String,
FieldLoadSaveValidator<?>>() );
+
+ loadSaveTester.testSerialization();
+ }
+
+ @Test
+ public void testPDI16559() throws Exception {
+ DorisStreamLoaderMeta streamLoader = new DorisStreamLoaderMeta();
+ streamLoader.setFieldTable( new String[] { "table1", "table2",
"table3" } );
+ streamLoader.setFieldStream( new String[] { "stream1" } );
+ streamLoader.setTable( "test_table" );
+
+ try {
+ String badXml = streamLoader.getXML();
+ Assert.fail( "Before calling afterInjectionSynchronization, should
have thrown an ArrayIndexOOB" );
+ } catch ( Exception expected ) {
+ // Do Nothing
+ }
+ streamLoader.afterInjectionSynchronization();
+ //run without a exception
+ String ktrXml = streamLoader.getXML();
+
+ int targetSz = streamLoader.getFieldTable().length;
+ Assert.assertEquals( targetSz, streamLoader.getFieldStream().length );
+ }
+}
diff --git
a/extension/kettle/impl/src/test/java/org/pentaho/di/trans/steps/dorisstreamloader/DorisStreamLoaderTest.java
b/extension/kettle/impl/src/test/java/org/pentaho/di/trans/steps/dorisstreamloader/DorisStreamLoaderTest.java
new file mode 100644
index 00000000000..45cadf62f78
--- /dev/null
+++
b/extension/kettle/impl/src/test/java/org/pentaho/di/trans/steps/dorisstreamloader/DorisStreamLoaderTest.java
@@ -0,0 +1,389 @@
+// 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.pentaho.di.trans.steps.dorisstreamloader;
+
+import com.google.common.base.Preconditions;
+import org.apache.commons.lang.StringUtils;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.mockito.Mockito;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+import org.pentaho.di.core.KettleEnvironment;
+import org.pentaho.di.core.exception.KettleException;
+import org.pentaho.di.core.plugins.PluginRegistry;
+import org.pentaho.di.core.plugins.StepPluginType;
+import org.pentaho.di.core.row.RowMeta;
+import org.pentaho.di.core.row.value.ValueMetaBigNumber;
+import org.pentaho.di.core.row.value.ValueMetaBoolean;
+import org.pentaho.di.core.row.value.ValueMetaDate;
+import org.pentaho.di.core.row.value.ValueMetaInteger;
+import org.pentaho.di.core.row.value.ValueMetaNumber;
+import org.pentaho.di.core.row.value.ValueMetaString;
+import org.pentaho.di.core.row.value.ValueMetaTimestamp;
+import org.pentaho.di.junit.rules.RestorePDIEngineEnvironment;
+import org.pentaho.di.trans.Trans;
+import org.pentaho.di.trans.TransMeta;
+import org.pentaho.di.trans.step.StepMeta;
+import org.pentaho.di.trans.steps.dorisstreamloader.load.DorisRuntimeException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.math.BigDecimal;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.sql.Timestamp;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+
+/**
+ * Test the deletion and writing process of kettle,
+ * temporarily using the doris environment passed in by the user.
+ * mvn clean package -Ddoris_host=127.0.0.1 -Ddoris_http_port=8030
-Ddoris_passwd= -Ddoris_query_port=9030 -Ddoris_user=root
+ */
+public class DorisStreamLoaderTest {
+
+ @ClassRule
+ public static RestorePDIEngineEnvironment env = new
RestorePDIEngineEnvironment();
+ private static final Logger LOG =
LoggerFactory.getLogger(DorisStreamLoaderTest.class);
+ DorisStreamLoaderMeta lmeta;
+ DorisStreamLoaderData ldata;
+ DorisStreamLoader lder;
+ StepMeta smeta;
+
+ private static final String JDBC_URL = "jdbc:mysql://%s:%s";
+ private static final String DATABASE = "test_kettle";
+ private static final String TABLE_INSERT = "tbl_insert";
+ private static final String TABLE_DELETE = "tbl_delete";
+ private static String HOST;
+ private static String QUERY_PORT;
+ private static String HTTP_PORT;
+ private static String USER;
+ private static String PASSWD;
+
+ @BeforeClass
+ public static void initEnvironment() throws Exception {
+ checkParams();
+ KettleEnvironment.init();
+ }
+
+ private static void checkParams() {
+ Preconditions.checkArgument(
+ System.getProperty("doris_host") != null, "doris_host is
required.");
+ Preconditions.checkArgument(
+ System.getProperty("doris_query_port") != null, "doris_query_port
is required.");
+ Preconditions.checkArgument(
+ System.getProperty("doris_http_port") != null, "doris_http_port is
required.");
+ Preconditions.checkArgument(
+ System.getProperty("doris_user") != null, "doris_user is
required.");
+ Preconditions.checkArgument(
+ System.getProperty("doris_passwd") != null, "doris_passwd is
required.");
+ HOST = System.getProperty("doris_host");
+ QUERY_PORT = System.getProperty("doris_query_port");
+ HTTP_PORT = System.getProperty("doris_http_port");
+ USER = System.getProperty("doris_user");
+ PASSWD = System.getProperty("doris_passwd");
+ }
+
+ @Test
+ public void testWrite() throws KettleException, ParseException,
InterruptedException {
+ TransMeta transMeta = new TransMeta();
+ transMeta.setName( "DorisStreamLoader" );
+
+ Map<String, String> vars = new HashMap<String, String>();
+ vars.put( "fenodes", HOST + ":" + HTTP_PORT);
+ vars.put( "database", DATABASE );
+ vars.put( "table", TABLE_INSERT );
+ vars.put( "username", USER );
+ vars.put( "password", PASSWD );
+ vars.put( "bufferFlushMaxRows", "10000");
+ vars.put( "bufferFlushMaxBytes", 10 * 1024 * 1024 + "");
+ vars.put( "streamLoadProp", "format:json;read_json_by_line:true" );
+ vars.put( "maxRetries", "3" );
+ vars.put( "deletable", "false" );
+ transMeta.injectVariables(vars);
+
+ lmeta = new DorisStreamLoaderMeta();
+ lmeta.setFenodes(transMeta.environmentSubstitute("${fenodes}"));
+ lmeta.setDatabase(transMeta.environmentSubstitute("${database}"));
+ lmeta.setTable(transMeta.environmentSubstitute("${table}"));
+ lmeta.setUsername(transMeta.environmentSubstitute("${username}"));
+ lmeta.setPassword(transMeta.environmentSubstitute("${password}"));
+
lmeta.setBufferFlushMaxRows(Long.parseLong(transMeta.environmentSubstitute("${bufferFlushMaxRows}")));
+
lmeta.setBufferFlushMaxBytes(Long.parseLong(transMeta.environmentSubstitute("${bufferFlushMaxBytes}")));
+
lmeta.setMaxRetries(Integer.parseInt(transMeta.environmentSubstitute("${maxRetries}")));
+
lmeta.setStreamLoadProp(transMeta.environmentSubstitute("${streamLoadProp}"));
+
lmeta.setDeletable(Boolean.parseBoolean(transMeta.environmentSubstitute("${deletable}")));
+ lmeta.setFieldStream(new String[]{"id", "c1", "c2",
"c3","c4","c5","c6"});
+ lmeta.setFieldTable(new String[]{"id", "c1", "c2",
"c3","c4","c5","c6"});
+
+ ldata = new DorisStreamLoaderData();
+ PluginRegistry plugReg = PluginRegistry.getInstance();
+ String mblPid = plugReg.getPluginId( StepPluginType.class, lmeta );
+ smeta = new StepMeta( mblPid, "DorisStreamLoader", lmeta );
+ Trans trans = new Trans( transMeta );
+ transMeta.addStep( smeta );
+ lder = Mockito.spy(new DorisStreamLoader( smeta, ldata, 1, transMeta,
trans ));
+
+ RowMeta rm = new RowMeta();
+ rm.addValueMeta(new ValueMetaInteger("id"));
+ rm.addValueMeta(new ValueMetaBoolean("c1"));
+ rm.addValueMeta(new ValueMetaBigNumber("c2"));
+ rm.addValueMeta(new ValueMetaNumber("c3"));
+ rm.addValueMeta(new ValueMetaDate("c4"));
+ rm.addValueMeta(new ValueMetaTimestamp("c5"));
+ rm.addValueMeta(new ValueMetaString("c6"));
+ lder.setInputRowMeta(rm);
+
+ lder.copyVariablesFrom( transMeta );
+ initializeTable(TABLE_INSERT);
+ lder.init(lmeta, ldata);
+
+ Object[] data = new Object[]{
+ 1L,
+ true,
+ BigDecimal.valueOf(123456789L),
+ 12345.6789,
+ new SimpleDateFormat("yyyy-MM-dd").parse("2024-11-18"),
+ Timestamp.valueOf("2024-11-18 15:30:45"),
+ "First Row"};
+ doReturn(data).when(lder).getRow();
+ doAnswer(new Answer() {
+ @Override
+ public Object answer(InvocationOnMock invocationOnMock) throws
Throwable {
+ lder.dispose(lmeta, ldata);
+ return null;
+ }
+ }).when(lder).putRow(any(), any());
+ lder.processRow(lmeta, ldata);
+ lder.dispose(lmeta, ldata);
+
+ while (lder.getStreamLoad() != null &&
lder.getStreamLoad().isLoadThreadAlive()){
+ Thread.sleep(1000);
+ LOG.info("Waiting for load thread to finish.");
+ }
+ List<String> expected =
Arrays.asList("1,true,123456789,12345.6789,2024-11-18,2024-11-18T15:30:45,First
Row");
+ String query =
+ String.format("select * from %s.%s order by 1", DATABASE,
TABLE_INSERT);
+ checkResult(getQueryConnection(), LOG, expected, query, 7);
+ }
+
+
+ @Test
+ public void testDelete() throws KettleException, ParseException,
InterruptedException {
+ TransMeta transMeta = new TransMeta();
+ transMeta.setName( "DorisStreamLoaderDelete" );
+
+ Map<String, String> vars = new HashMap<String, String>();
+ vars.put( "fenodes", HOST + ":" + HTTP_PORT);
+ vars.put( "database", DATABASE );
+ vars.put( "table", TABLE_DELETE );
+ vars.put( "username", USER );
+ vars.put( "password", PASSWD );
+ vars.put( "bufferFlushMaxRows", "10000");
+ vars.put( "bufferFlushMaxBytes", 10 * 1024 * 1024 + "");
+ vars.put( "streamLoadProp", "format:json;read_json_by_line:true" );
+ vars.put( "maxRetries", "3" );
+ vars.put( "deletable", "true" );
+ transMeta.injectVariables(vars);
+
+ lmeta = new DorisStreamLoaderMeta();
+ lmeta.setFenodes(transMeta.environmentSubstitute("${fenodes}"));
+ lmeta.setDatabase(transMeta.environmentSubstitute("${database}"));
+ lmeta.setTable(transMeta.environmentSubstitute("${table}"));
+ lmeta.setUsername(transMeta.environmentSubstitute("${username}"));
+ lmeta.setPassword(transMeta.environmentSubstitute("${password}"));
+
lmeta.setBufferFlushMaxRows(Long.parseLong(transMeta.environmentSubstitute("${bufferFlushMaxRows}")));
+
lmeta.setBufferFlushMaxBytes(Long.parseLong(transMeta.environmentSubstitute("${bufferFlushMaxBytes}")));
+
lmeta.setMaxRetries(Integer.parseInt(transMeta.environmentSubstitute("${maxRetries}")));
+
lmeta.setStreamLoadProp(transMeta.environmentSubstitute("${streamLoadProp}"));
+
lmeta.setDeletable(Boolean.parseBoolean(transMeta.environmentSubstitute("${deletable}")));
+ lmeta.setFieldStream(new String[]{"id", "c1", "c2",
"c3","c4","c5","c6"});
+ lmeta.setFieldTable(new String[]{"id", "c1", "c2",
"c3","c4","c5","c6"});
+
+ ldata = new DorisStreamLoaderData();
+ PluginRegistry plugReg = PluginRegistry.getInstance();
+ String mblPid = plugReg.getPluginId( StepPluginType.class, lmeta );
+ smeta = new StepMeta( mblPid, "DorisStreamLoaderDelete", lmeta );
+ Trans trans = new Trans( transMeta );
+ transMeta.addStep( smeta );
+ lder = Mockito.spy(new DorisStreamLoader( smeta, ldata, 1, transMeta,
trans ));
+
+ RowMeta rm = new RowMeta();
+ rm.addValueMeta(new ValueMetaInteger("id"));
+ rm.addValueMeta(new ValueMetaBoolean("c1"));
+ rm.addValueMeta(new ValueMetaBigNumber("c2"));
+ rm.addValueMeta(new ValueMetaNumber("c3"));
+ rm.addValueMeta(new ValueMetaDate("c4"));
+ rm.addValueMeta(new ValueMetaTimestamp("c5"));
+ rm.addValueMeta(new ValueMetaString("c6"));
+ lder.setInputRowMeta(rm);
+
+ lder.copyVariablesFrom( transMeta );
+ initializeTable(TABLE_DELETE);
+ mockDorisData(TABLE_DELETE);
+ lder.init(lmeta, ldata);
+
+ Object[] data = new Object[]{
+ 1L,
+ true,
+ BigDecimal.valueOf(123456789L),
+ 12345.6789,
+ new SimpleDateFormat("yyyy-MM-dd").parse("2024-11-18"),
+ Timestamp.valueOf("2024-11-18 15:30:45"),
+ "First Row"};
+ doReturn(data).when(lder).getRow();
+ doAnswer(new Answer() {
+ @Override
+ public Object answer(InvocationOnMock invocationOnMock) throws
Throwable {
+ lder.dispose(lmeta, ldata);
+ return null;
+ }
+ }).when(lder).putRow(any(), any());
+ lder.processRow(lmeta, ldata);
+ lder.dispose(lmeta, ldata);
+
+ while (lder.getStreamLoad() != null &&
lder.getStreamLoad().isLoadThreadAlive()){
+ Thread.sleep(1000);
+ LOG.info("Waiting for load thread to finish.");
+ }
+ List<String> expected =
Arrays.asList("2,false,987654321,98765.4321,2024-11-17,2024-11-17T08:15:30,Second
Row");
+ String query =
+ String.format("select * from %s.%s order by 1", DATABASE,
TABLE_DELETE);
+ checkResult(getQueryConnection(), LOG, expected, query, 7);
+ }
+
+
+ private void initializeTable(String table) {
+ executeSQLStatement(
+ getQueryConnection(),
+ LOG,
+ String.format("CREATE DATABASE IF NOT EXISTS %s", DATABASE),
+ String.format("DROP TABLE IF EXISTS %s.%s", DATABASE, table),
+ String.format(
+ "CREATE TABLE %s.%s (\n" +
+ " `id` int, \n" +
+ " `c1` boolean, \n" +
+ " `c2` bigint,\n" +
+ " `c3` decimal(12,4),\n" +
+ " `c4` date,\n" +
+ " `c5` datetime,\n" +
+ " `c6` string\n" +
+ ") ENGINE=OLAP\n" +
+ "UNIQUE KEY(`id`)\n" +
+ "COMMENT 'OLAP'\n" +
+ "DISTRIBUTED BY HASH(`id`) BUCKETS 1\n" +
+ "PROPERTIES (\n" +
+ "\"replication_allocation\" = \"tag.location.default:
1\"\n" +
+ "); \n",
+ DATABASE, table));
+ }
+
+ public void mockDorisData(String table){
+ executeSQLStatement(
+ getQueryConnection(),
+ LOG,
+ String.format("INSERT INTO %s.%s VALUES \n" +
+ "(1, TRUE, 123456789, 12345.6789, '2024-11-18', '2024-11-18
15:30:45', 'First Row')," +
+ "(2, FALSE, 987654321, 98765.4321, '2024-11-17', '2024-11-17
08:15:30', 'Second Row')",
+ DATABASE, table));
+ }
+
+
+ public Connection getQueryConnection() {
+ LOG.info("Try to get query connection from doris.");
+ String jdbcUrl =
+ String.format(
+ JDBC_URL,
+ System.getProperty("doris_host"),
+ System.getProperty("doris_query_port"));
+ try {
+ return DriverManager.getConnection(jdbcUrl, USER, PASSWD);
+ } catch (SQLException e) {
+ LOG.info("Failed to get doris query connection. jdbcUrl={}",
jdbcUrl, e);
+ throw new DorisRuntimeException(e);
+ }
+ }
+
+ public static void executeSQLStatement(Connection connection, Logger
logger, String... sql) {
+ if (Objects.isNull(sql) || sql.length == 0) {
+ return;
+ }
+ try (Statement statement = connection.createStatement()) {
+ for (String s : sql) {
+ if (StringUtils.isNotEmpty(s)) {
+ logger.info("start to execute sql={}", s);
+ statement.execute(s);
+ }
+ }
+ } catch (SQLException e) {
+ throw new DorisRuntimeException(e);
+ }
+ }
+
+ public static void checkResult(
+ Connection connection,
+ Logger logger,
+ List<String> expected,
+ String query,
+ int columnSize) {
+ List<String> actual = new ArrayList<>();
+ try (Statement statement = connection.createStatement()) {
+ ResultSet sinkResultSet = statement.executeQuery(query);
+ while (sinkResultSet.next()) {
+ List<String> row = new ArrayList<>();
+ for (int i = 1; i <= columnSize; i++) {
+ Object value = sinkResultSet.getObject(i);
+ if (value == null) {
+ row.add("null");
+ } else {
+ row.add(value.toString());
+ }
+ }
+ actual.add(StringUtils.join(row, ","));
+ }
+ } catch (SQLException e) {
+ logger.info(
+ "Failed to check query result. expected={}, actual={}",
+ String.join(",", expected),
+ String.join(",", actual),
+ e);
+ throw new DorisRuntimeException(e);
+ }
+ logger.info(
+ "checking test result. expected={}, actual={}",
+ String.join(",", expected),
+ String.join(",", actual));
+ Assert.assertArrayEquals(expected.toArray(), actual.toArray());
+ }
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]