This is an automated email from the ASF dual-hosted git repository.
hansva pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/hop.git
The following commit(s) were added to refs/heads/main by this push:
new 4ca5cf42bb [Feature Request]: add Snowflake ID Generator Transform
(#5853)
4ca5cf42bb is described below
commit 4ca5cf42bb670183db81bd3fd904a6264343010d
Author: lance <[email protected]>
AuthorDate: Tue Oct 21 19:52:11 2025 +0800
[Feature Request]: add Snowflake ID Generator Transform (#5853)
* add Snowflake ID Generator Transform
Signed-off-by: lance <[email protected]>
* Add license header to 0078-add-snowflake-id.hpl
Signed-off-by: lance <[email protected]>
* fix checkstyle AvoidStarImport
Signed-off-by: lance <[email protected]>
* add to client, remove author, add documentation page
---------
Signed-off-by: lance <[email protected]>
Co-authored-by: Hans Van Akelyen <[email protected]>
---
assemblies/plugins/pom.xml | 6 +
.../images/transforms/icons/addsnowflakeid.svg | 41 ++++
.../pages/pipeline/transforms/snowflakeid.adoc | 42 ++++
.../transforms/0078-add-snowflake-id.hpl | 132 ++++++++++++
plugins/transforms/addsnowflakeid/pom.xml | 32 +++
.../addsnowflakeid/src/assembly/assembly.xml | 51 +++++
.../transforms/addsnowflakeid/AddSnowflakeId.java | 123 +++++++++++
.../addsnowflakeid/AddSnowflakeIdData.java | 27 +++
.../addsnowflakeid/AddSnowflakeIdDialog.java | 228 +++++++++++++++++++++
.../addsnowflakeid/AddSnowflakeIdMeta.java | 82 ++++++++
.../addsnowflakeid/SnowflakeSafeIdGenerator.java | 123 +++++++++++
.../src/main/resources/addsnowflakeid.svg | 41 ++++
.../messages/messages_en_US.properties | 33 +++
.../messages/messages_zh_CN.properties | 33 +++
.../addsnowflakeid/src/main/resources/version.xml | 20 ++
.../addsnowflakeid/AddSnowflakeIdMetaTests.java | 50 +++++
.../addsnowflakeid/AddSnowflakeIdTests.java | 141 +++++++++++++
.../SnowflakeSafeIdGeneratorTests.java | 61 ++++++
plugins/transforms/pom.xml | 1 +
19 files changed, 1267 insertions(+)
diff --git a/assemblies/plugins/pom.xml b/assemblies/plugins/pom.xml
index 28793b9e3b..f1580bc4b2 100644
--- a/assemblies/plugins/pom.xml
+++ b/assemblies/plugins/pom.xml
@@ -805,6 +805,12 @@
<version>${project.version}</version>
<type>zip</type>
</dependency>
+ <dependency>
+ <groupId>org.apache.hop</groupId>
+ <artifactId>hop-transform-addsnowflakeid</artifactId>
+ <version>${project.version}</version>
+ <type>zip</type>
+ </dependency>
<dependency>
<groupId>org.apache.hop</groupId>
<artifactId>hop-transform-analyticquery</artifactId>
diff --git
a/docs/hop-user-manual/modules/ROOT/assets/images/transforms/icons/addsnowflakeid.svg
b/docs/hop-user-manual/modules/ROOT/assets/images/transforms/icons/addsnowflakeid.svg
new file mode 100644
index 0000000000..8b3c0798ec
--- /dev/null
+++
b/docs/hop-user-manual/modules/ROOT/assets/images/transforms/icons/addsnowflakeid.svg
@@ -0,0 +1,41 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<svg xmlns='http://www.w3.org/2000/svg' width='42' height='42' viewBox='0 0 42
42'>
+ <defs>
+ <linearGradient id='g' x1='0' x2='1' y1='0' y2='1'>
+ <stop offset='0' stop-color='#6fc1ff'/>
+ <stop offset='1' stop-color='#2b9cff'/>
+ </linearGradient>
+ <filter id='f' x='-50%' y='-50%' width='200%' height='200%'>
+ <feGaussianBlur in='SourceAlpha' stdDeviation='1' result='blur'/>
+ <feOffset in='blur' dx='0' dy='1' result='off'/>
+ <feMerge>
+ <feMergeNode in='off'/>
+ <feMergeNode in='SourceGraphic'/>
+ </feMerge>
+ </filter>
+ </defs>
+ <circle cx='21' cy='21' r='20' fill='white' stroke='rgba(43,156,255,0.12)'
stroke-width='0.8'/>
+ <circle cx='21' cy='21' r='19' fill='url(#g)' opacity='0.06'/>
+ <g transform='translate(21,21)' stroke='#2b9cff' stroke-width='1.6'
stroke-linecap='round' stroke-linejoin='round' filter='url(#f)' fill='none'>
+ <g>
+ <line x1='0' y1='-11' x2='0' y2='11'/>
+ <line x1='-9.5' y1='-5.5' x2='9.5' y2='5.5'/>
+ <line x1='-9.5' y1='5.5' x2='9.5' y2='-5.5'/>
+ </g>
+ <g stroke-width='1.2'>
+ <path d='M0 -7 L-3 -9 M0 -7 L3 -9'/>
+ <path d='M0 7 L-3 9 M0 7 L3 9'/>
+ <path d='M6 3 L8 0 M6 3 L8 6'/>
+ <path d='M-6 -3 L-8 -6 M-6 -3 L-8 0'/>
+ <path d='M6 -3 L8 -6 M6 -3 L8 0'/>
+ <path d='M-6 3 L-8 6 M-6 3 L-8 0'/>
+ </g>
+ <polygon points='0,-4 3,-2 3,2 0,4 -3,2 -3,-2' fill='#eaffff'
stroke='#2b9cff' stroke-width='1'/>
+ </g>
+ <g>
+ <rect x='7' y='6' width='3.6' height='3.6' rx='0.6' fill='#2b9cff'
opacity='0.95'/>
+ <rect x='29' y='10' width='3' height='3' rx='0.5' fill='#6fc1ff'
opacity='0.95'/>
+ <rect x='9' y='28' width='2.6' height='2.6' rx='0.4' fill='#2b9cff'
opacity='0.95'/>
+ </g>
+ <text x='27' y='33.2' font-family='Inter, Arial, Helvetica, sans-serif'
font-size='7.5' font-weight='600' fill='#0b6fb6'>ID</text>
+</svg>
diff --git
a/docs/hop-user-manual/modules/ROOT/pages/pipeline/transforms/snowflakeid.adoc
b/docs/hop-user-manual/modules/ROOT/pages/pipeline/transforms/snowflakeid.adoc
new file mode 100644
index 0000000000..e09fa7496b
--- /dev/null
+++
b/docs/hop-user-manual/modules/ROOT/pages/pipeline/transforms/snowflakeid.adoc
@@ -0,0 +1,42 @@
+////
+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.
+////
+:documentationPath: /pipeline/transforms/
+:language: en_US
+:description: The Snowflake id transform generates a unique id for snowflake
rows
+
+= image:transforms/icons/addsnowflakeid.svg[Snowflake Bulk Loader transform
Icon, role="image-doc-icon"] Snowflake Bulk Loader
+
+[%noheader,cols="3a,1a", role="table-no-borders" ]
+|===
+|
+== Description
+
+Generate a unique identifier for a row to insert into snowflake
+
+|
+== Supported Engines
+[%noheader,cols="2,1a",frame=none, role="table-supported-engines"]
+!===
+!Hop Engine! image:check_mark.svg[Supported, 24]
+!Spark! image:question_mark.svg[Maybe Supported, 24]
+!Flink! image:question_mark.svg[Maybe Supported, 24]
+!Dataflow! image:question_mark.svg[Maybe Supported, 24]
+!===
+|===
+
+== Options
+
diff --git a/integration-tests/transforms/0078-add-snowflake-id.hpl
b/integration-tests/transforms/0078-add-snowflake-id.hpl
new file mode 100644
index 0000000000..7a64b17ab3
--- /dev/null
+++ b/integration-tests/transforms/0078-add-snowflake-id.hpl
@@ -0,0 +1,132 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+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.
+
+-->
+<pipeline>
+ <info>
+ <name>0078-add-snowflake-id</name>
+ <name_sync_with_filename>Y</name_sync_with_filename>
+ <description/>
+ <extended_description/>
+ <pipeline_version/>
+ <pipeline_type>Normal</pipeline_type>
+ <parameters>
+ </parameters>
+ <capture_transform_performance>N</capture_transform_performance>
+
<transform_performance_capturing_delay>1000</transform_performance_capturing_delay>
+
<transform_performance_capturing_size_limit>100</transform_performance_capturing_size_limit>
+ <created_user>-</created_user>
+ <created_date>2025/10/17 14:52:28.135</created_date>
+ <modified_user>-</modified_user>
+ <modified_date>2025/10/17 14:52:28.135</modified_date>
+ </info>
+ <notepads>
+ </notepads>
+ <order>
+ <hop>
+ <from>Data grid</from>
+ <to>Generate snowflake id</to>
+ <enabled>Y</enabled>
+ </hop>
+ <hop>
+ <from>Generate snowflake id</from>
+ <to>Write to log</to>
+ <enabled>Y</enabled>
+ </hop>
+ </order>
+ <transform>
+ <name>Data grid</name>
+ <type>DataGrid</type>
+ <description/>
+ <distribute>Y</distribute>
+ <custom_distribution/>
+ <copies>1</copies>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <data>
+ <line>
+ <item>Tom</item>
+ </line>
+ <line>
+ <item>Tim</item>
+ </line>
+ </data>
+ <fields>
+ <field>
+ <length>-1</length>
+ <precision>-1</precision>
+ <set_empty_string>N</set_empty_string>
+ <name>name</name>
+ <type>String</type>
+ </field>
+ </fields>
+ <attributes/>
+ <GUI>
+ <xloc>112</xloc>
+ <yloc>112</yloc>
+ </GUI>
+ </transform>
+ <transform>
+ <name>Generate snowflake id</name>
+ <type>SnowflakeId</type>
+ <description/>
+ <distribute>Y</distribute>
+ <custom_distribution/>
+ <copies>1</copies>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <data_center_id>2</data_center_id>
+ <machine_id>31</machine_id>
+ <val_name>snowflakeId</val_name>
+ <attributes/>
+ <GUI>
+ <xloc>112</xloc>
+ <yloc>256</yloc>
+ </GUI>
+ </transform>
+ <transform>
+ <name>Write to log</name>
+ <type>WriteToLog</type>
+ <description/>
+ <distribute>Y</distribute>
+ <custom_distribution/>
+ <copies>1</copies>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <displayHeader>Y</displayHeader>
+ <fields>
+</fields>
+ <limitRows>N</limitRows>
+ <limitRowsNumber>0</limitRowsNumber>
+ <loglevel>Basic</loglevel>
+ <attributes/>
+ <GUI>
+ <xloc>320</xloc>
+ <yloc>256</yloc>
+ </GUI>
+ </transform>
+ <transform_error_handling>
+ </transform_error_handling>
+ <attributes/>
+</pipeline>
diff --git a/plugins/transforms/addsnowflakeid/pom.xml
b/plugins/transforms/addsnowflakeid/pom.xml
new file mode 100644
index 0000000000..9a2cc5cf25
--- /dev/null
+++ b/plugins/transforms/addsnowflakeid/pom.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ 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.
+ ~
+ -->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.apache.hop</groupId>
+ <artifactId>hop-plugins-transforms</artifactId>
+ <version>2.16.0-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>hop-transform-addsnowflakeid</artifactId>
+ <packaging>jar</packaging>
+ <name>Hop Plugins Transforms Add Snowflake Id</name>
+
+</project>
diff --git a/plugins/transforms/addsnowflakeid/src/assembly/assembly.xml
b/plugins/transforms/addsnowflakeid/src/assembly/assembly.xml
new file mode 100644
index 0000000000..b4aaa18c87
--- /dev/null
+++ b/plugins/transforms/addsnowflakeid/src/assembly/assembly.xml
@@ -0,0 +1,51 @@
+<!--
+ ~ 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.
+ ~
+ -->
+
+<assembly xmlns="http://maven.apache.org/ASSEMBLY/2.2.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.2.0
http://maven.apache.org/xsd/assembly-2.2.0.xsd">
+ <id>hop-transform-addsnowflakeid</id>
+ <formats>
+ <format>zip</format>
+ </formats>
+ <baseDirectory>.</baseDirectory>
+ <files>
+ <file>
+ <source>${project.basedir}/src/main/resources/version.xml</source>
+
<outputDirectory>plugins/transforms/addsnowflakeid</outputDirectory>
+ <filtered>true</filtered>
+ </file>
+ </files>
+
+ <fileSets>
+ <fileSet>
+ <directory>${project.basedir}/src/main/samples</directory>
+ <outputDirectory>config/projects/samples/</outputDirectory>
+ </fileSet>
+ </fileSets>
+
+ <dependencySets>
+ <dependencySet>
+
+ <includes>
+
<include>org.apache.hop:hop-transform-addsnowflakeid:jar</include>
+ </includes>
+
<outputDirectory>plugins/transforms/addsnowflakeid</outputDirectory>
+ </dependencySet>
+ </dependencySets>
+</assembly>
\ No newline at end of file
diff --git
a/plugins/transforms/addsnowflakeid/src/main/java/org/apache/hop/pipeline/transforms/addsnowflakeid/AddSnowflakeId.java
b/plugins/transforms/addsnowflakeid/src/main/java/org/apache/hop/pipeline/transforms/addsnowflakeid/AddSnowflakeId.java
new file mode 100644
index 0000000000..cc3c87642b
--- /dev/null
+++
b/plugins/transforms/addsnowflakeid/src/main/java/org/apache/hop/pipeline/transforms/addsnowflakeid/AddSnowflakeId.java
@@ -0,0 +1,123 @@
+/*
+ * 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.apache.hop.pipeline.transforms.addsnowflakeid;
+
+import org.apache.hop.core.exception.HopException;
+import org.apache.hop.core.row.IRowMeta;
+import org.apache.hop.core.row.RowDataUtil;
+import org.apache.hop.i18n.BaseMessages;
+import org.apache.hop.pipeline.Pipeline;
+import org.apache.hop.pipeline.PipelineMeta;
+import org.apache.hop.pipeline.transform.BaseTransform;
+import org.apache.hop.pipeline.transform.TransformMeta;
+
+/** Adds snowflake ID is a 64-bit globally unique identifier to a stream of
rows */
+public class AddSnowflakeId extends BaseTransform<AddSnowflakeIdMeta,
AddSnowflakeIdData> {
+ private static final Class<?> PKG = AddSnowflakeId.class;
+ private SnowflakeSafeIdGenerator generator;
+
+ /**
+ * This is the base transform that forms that basis for all transforms. You
can derive from this
+ * class to implement your own transforms.
+ *
+ * @param transformMeta The TransformMeta object to run.
+ * @param meta AddSnowflakeIdMeta
+ * @param data the data object to store temporary data, database
connections, caches, result sets,
+ * hashtables etc.
+ * @param copyNr The copynumber for this transform.
+ * @param pipelineMeta The PipelineMeta of which the transform transformMeta
is part of.
+ * @param pipeline The (running) pipeline to obtain information shared among
the transforms.
+ */
+ public AddSnowflakeId(
+ TransformMeta transformMeta,
+ AddSnowflakeIdMeta meta,
+ AddSnowflakeIdData data,
+ int copyNr,
+ PipelineMeta pipelineMeta,
+ Pipeline pipeline) {
+ super(transformMeta, meta, data, copyNr, pipelineMeta, pipeline);
+ }
+
+ @Override
+ public boolean init() {
+ if (super.init()) {
+ if (isBasic()) {
+ logBasic(
+ "snowflake dataCenter: {0},machine: {1}", meta.getDataCenterId(),
meta.getMachineId());
+ }
+
+ generator = new SnowflakeSafeIdGenerator(meta.getDataCenterId(),
meta.getMachineId());
+ return true;
+ }
+
+ return false;
+ }
+
+ @Override
+ public boolean processRow() throws HopException {
+ // Get row from input rowSet & set row busy!
+ Object[] r = getRow();
+ if (r == null) {
+ setOutputDone();
+ return false;
+ }
+
+ if (first) {
+ first = false;
+ data.outputRowMeta = getInputRowMeta().clone();
+ meta.getFields(data.outputRowMeta, getTransformName(), null, null, this,
metadataProvider);
+ }
+
+ if (isRowLevel()) {
+ logRowlevel(
+ BaseMessages.getString(PKG, "AddSnowflakeId.Log.ReadRow")
+ + getLinesRead()
+ + " : "
+ + getInputRowMeta().getString(r));
+ }
+
+ try {
+ IRowMeta outputRowMeta = data.outputRowMeta;
+ // resize array
+ Object[] outputRowData = RowDataUtil.resizeArray(r,
outputRowMeta.size());
+ outputRowData[outputRowMeta.size() - 1] = generator.nextId();
+
+ putRow(data.outputRowMeta, outputRowData);
+
+ if (isRowLevel()) {
+ logRowlevel(
+ BaseMessages.getString(PKG, "AddSnowflakeId.Log.WriteRow")
+ + getLinesWritten()
+ + " : "
+ + getInputRowMeta().getString(r));
+ }
+
+ if (checkFeedback(getLinesRead()) && isBasic()) {
+ logBasic(BaseMessages.getString(PKG, "AddSnowflakeId.Log.LineNumber")
+ getLinesRead());
+ }
+ } catch (HopException e) {
+ logError(BaseMessages.getString(PKG,
"AddSnowflakeId.Log.ErrorInTransform") + e.getMessage());
+ setErrors(1);
+ stopAll();
+ setOutputDone();
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git
a/plugins/transforms/addsnowflakeid/src/main/java/org/apache/hop/pipeline/transforms/addsnowflakeid/AddSnowflakeIdData.java
b/plugins/transforms/addsnowflakeid/src/main/java/org/apache/hop/pipeline/transforms/addsnowflakeid/AddSnowflakeIdData.java
new file mode 100644
index 0000000000..2b41b19bd9
--- /dev/null
+++
b/plugins/transforms/addsnowflakeid/src/main/java/org/apache/hop/pipeline/transforms/addsnowflakeid/AddSnowflakeIdData.java
@@ -0,0 +1,27 @@
+/*
+ * 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.apache.hop.pipeline.transforms.addsnowflakeid;
+
+import org.apache.hop.core.row.IRowMeta;
+import org.apache.hop.pipeline.transform.BaseTransformData;
+import org.apache.hop.pipeline.transform.ITransformData;
+
+/** AddSnowflakeId data */
+public class AddSnowflakeIdData extends BaseTransformData implements
ITransformData {
+ public IRowMeta outputRowMeta;
+}
diff --git
a/plugins/transforms/addsnowflakeid/src/main/java/org/apache/hop/pipeline/transforms/addsnowflakeid/AddSnowflakeIdDialog.java
b/plugins/transforms/addsnowflakeid/src/main/java/org/apache/hop/pipeline/transforms/addsnowflakeid/AddSnowflakeIdDialog.java
new file mode 100644
index 0000000000..c7f1e451ce
--- /dev/null
+++
b/plugins/transforms/addsnowflakeid/src/main/java/org/apache/hop/pipeline/transforms/addsnowflakeid/AddSnowflakeIdDialog.java
@@ -0,0 +1,228 @@
+/*
+ * 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.apache.hop.pipeline.transforms.addsnowflakeid;
+
+import org.apache.hop.core.util.Utils;
+import org.apache.hop.core.variables.IVariables;
+import org.apache.hop.i18n.BaseMessages;
+import org.apache.hop.pipeline.PipelineMeta;
+import org.apache.hop.ui.core.FormDataBuilder;
+import org.apache.hop.ui.core.PropsUi;
+import org.apache.hop.ui.core.dialog.BaseDialog;
+import org.apache.hop.ui.pipeline.transform.BaseTransformDialog;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.ModifyListener;
+import org.eclipse.swt.layout.FormAttachment;
+import org.eclipse.swt.layout.FormData;
+import org.eclipse.swt.layout.FormLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.Spinner;
+import org.eclipse.swt.widgets.Text;
+
+/** add snowflakeId dialog */
+public class AddSnowflakeIdDialog extends BaseTransformDialog {
+ private static final Class<?> PKG = AddSnowflakeIdDialog.class;
+
+ private Text wValueName;
+ private Spinner wDataCenterId;
+ private Spinner wMachineId;
+
+ private final AddSnowflakeIdMeta input;
+
+ public AddSnowflakeIdDialog(
+ Shell parent,
+ IVariables variables,
+ AddSnowflakeIdMeta transformMeta,
+ PipelineMeta pipelineMeta) {
+ super(parent, variables, transformMeta, pipelineMeta);
+ input = transformMeta;
+ }
+
+ @Override
+ public String open() {
+ Shell parent = getParent();
+
+ shell = new Shell(parent, SWT.DIALOG_TRIM | SWT.RESIZE | SWT.MAX |
SWT.MIN);
+ PropsUi.setLook(shell);
+ setShellImage(shell, input);
+
+ ModifyListener lsMod = e -> input.setChanged();
+ changed = input.hasChanged();
+
+ FormLayout formLayout = new FormLayout();
+ formLayout.marginWidth = PropsUi.getFormMargin();
+ formLayout.marginHeight = PropsUi.getFormMargin();
+
+ shell.setLayout(formLayout);
+ shell.setText(BaseMessages.getString(PKG,
"AddSnowflakeIdDialog.Shell.Title"));
+
+ int middle = props.getMiddlePct();
+ int margin = PropsUi.getMargin();
+
+ // TransformName line
+ wlTransformName = new Label(shell, SWT.RIGHT);
+ wlTransformName.setText(
+ BaseMessages.getString(PKG,
"AddSnowflakeIdDialog.TransformName.Label"));
+ PropsUi.setLook(wlTransformName);
+ fdlTransformName = new FormData();
+ fdlTransformName.left = new FormAttachment(0, 0);
+ fdlTransformName.right = new FormAttachment(middle, -margin);
+ fdlTransformName.top = new FormAttachment(0, margin);
+ wlTransformName.setLayoutData(fdlTransformName);
+ wTransformName = new Text(shell, SWT.SINGLE | SWT.LEFT | SWT.BORDER);
+ wTransformName.setText(transformName);
+ wTransformName.addModifyListener(lsMod);
+ fdTransformName = new FormData();
+ fdTransformName.left = new FormAttachment(middle, 0);
+ fdTransformName.top = new FormAttachment(0, margin);
+ fdTransformName.right = new FormAttachment(100, 0);
+ wTransformName.setLayoutData(fdTransformName);
+ PropsUi.setLook(wTransformName);
+
+ // Value name line
+ Label wlValueName = new Label(shell, SWT.RIGHT);
+ wlValueName.setText(BaseMessages.getString(PKG,
"AddSnowflakeIdDialog.ValueName.Label"));
+ PropsUi.setLook(wlValueName);
+ wlValueName.setLayoutData(
+ new FormDataBuilder()
+ .left(0, 0)
+ .right(middle, -margin)
+ .top(wTransformName, margin)
+ .result());
+ wValueName = new Text(shell, SWT.SINGLE | SWT.LEFT | SWT.BORDER);
+ wValueName.setText("");
+ PropsUi.setLook(wValueName);
+ wValueName.addModifyListener(lsMod);
+ FormData fdValueName = new FormData();
+ fdValueName.left = new FormAttachment(middle, 5);
+ fdValueName.top = new FormAttachment(wTransformName, margin);
+ fdValueName.right = new FormAttachment(100, 0);
+ wValueName.setLayoutData(fdValueName);
+
+ // data center no
+ Label lDataCenterId = new Label(shell, SWT.RIGHT);
+ lDataCenterId.setText(BaseMessages.getString(PKG,
"AddSnowflakeIdDialog.DataCenterNo.Label"));
+ lDataCenterId.setLayoutData(
+ new FormDataBuilder().left(0, 0).right(middle,
-margin).top(wlValueName, margin).result());
+ PropsUi.setLook(lDataCenterId);
+ wDataCenterId = createSpinner();
+ PropsUi.setLook(wDataCenterId);
+ wDataCenterId.addModifyListener(lsMod);
+ wDataCenterId.setLayoutData(
+ new FormDataBuilder().left(middle, 5).right(100, 0).top(wValueName,
margin).result());
+
+ // machine no
+ Label lMachine = new Label(shell, SWT.RIGHT);
+ lMachine.setText(BaseMessages.getString(PKG,
"AddSnowflakeIdDialog.MachineNo.Label"));
+ PropsUi.setLook(lMachine);
+ lMachine.setLayoutData(
+ new FormDataBuilder()
+ .left(0, 0)
+ .right(middle, -margin)
+ .top(wDataCenterId, margin)
+ .result());
+
+ wMachineId = createSpinner();
+ PropsUi.setLook(wMachineId);
+ wMachineId.addModifyListener(lsMod);
+ wMachineId.setLayoutData(
+ new FormDataBuilder().left(middle, 5).right(100, 0).top(wDataCenterId,
margin).result());
+
+ // THE BUTTONS
+ wOk = new Button(shell, SWT.PUSH);
+ wOk.setText(BaseMessages.getString(PKG, "System.Button.OK"));
+ wCancel = new Button(shell, SWT.PUSH);
+ wCancel.setText(BaseMessages.getString(PKG, "System.Button.Cancel"));
+
+ setButtonPositions(new Button[] {wOk, wCancel}, margin, wMachineId);
+
+ // Add listeners
+ wOk.addListener(SWT.Selection, e -> ok());
+ wCancel.addListener(SWT.Selection, e -> cancel());
+
+ getData();
+
+ BaseDialog.defaultShellHandling(shell, c -> ok(), c -> cancel());
+ return transformName;
+ }
+
+ /** Copy information from the meta-data input to the dialog fields. */
+ public void getData() {
+ logDebug(BaseMessages.getString(PKG,
"AddSnowflakeIdDialog.Log.GettingKeyInfo"));
+
+ if (input.getValueName() != null) {
+ wValueName.setText(input.getValueName());
+ }
+
+ wDataCenterId.setSelection(ensureRange0To32(input.getDataCenterId()));
+ wMachineId.setSelection(ensureRange0To32(input.getMachineId()));
+
+ wTransformName.selectAll();
+ wTransformName.setFocus();
+ }
+
+ private void cancel() {
+ transformName = null;
+ input.setChanged(changed);
+ dispose();
+ }
+
+ private void ok() {
+ if (Utils.isEmpty(wTransformName.getText())) {
+ return;
+ }
+
+ transformName = wTransformName.getText();
+ input.setDataCenterId(wDataCenterId.getSelection());
+ input.setMachineId(wMachineId.getSelection());
+ dispose();
+ }
+
+ /**
+ * handler value [0,32)
+ *
+ * @param value init value
+ * @return real value
+ */
+ private Integer ensureRange0To32(Integer value) {
+ if (value == null || value < 0) {
+ return 0;
+ }
+
+ if (value > 31) {
+ return 31;
+ }
+
+ return value;
+ }
+
+ /**
+ * create spinner control
+ *
+ * @return Spinner
+ */
+ private Spinner createSpinner() {
+ Spinner spinner = new Spinner(shell, SWT.SINGLE | SWT.LEFT | SWT.BORDER);
+ spinner.setMinimum(0);
+ spinner.setMaximum(31);
+ spinner.setIncrement(1);
+ return spinner;
+ }
+}
diff --git
a/plugins/transforms/addsnowflakeid/src/main/java/org/apache/hop/pipeline/transforms/addsnowflakeid/AddSnowflakeIdMeta.java
b/plugins/transforms/addsnowflakeid/src/main/java/org/apache/hop/pipeline/transforms/addsnowflakeid/AddSnowflakeIdMeta.java
new file mode 100644
index 0000000000..b09790d6b3
--- /dev/null
+++
b/plugins/transforms/addsnowflakeid/src/main/java/org/apache/hop/pipeline/transforms/addsnowflakeid/AddSnowflakeIdMeta.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.apache.hop.pipeline.transforms.addsnowflakeid;
+
+import lombok.Getter;
+import lombok.Setter;
+import org.apache.hop.core.annotations.Transform;
+import org.apache.hop.core.exception.HopTransformException;
+import org.apache.hop.core.row.IRowMeta;
+import org.apache.hop.core.row.IValueMeta;
+import org.apache.hop.core.row.value.ValueMetaInteger;
+import org.apache.hop.core.variables.IVariables;
+import org.apache.hop.metadata.api.HopMetadataProperty;
+import org.apache.hop.metadata.api.IHopMetadataProvider;
+import org.apache.hop.pipeline.transform.BaseTransformMeta;
+import org.apache.hop.pipeline.transform.TransformMeta;
+
+/** Meta data for the Add Snowflake Id transform */
+@Transform(
+ id = "SnowflakeId",
+ image = "addsnowflakeid.svg",
+ name = "i18n::BaseTransform.TypeLongDesc.AddSnowflakeId",
+ description = "i18n::BaseTransform.TypeTooltipDesc.AddSnowflakeId",
+ categoryDescription =
"i18n:org.apache.hop.pipeline.transform:BaseTransform.Category.Transform",
+ documentationUrl = "/pipeline/transforms/snowflakeid.html",
+ keywords = "i18n::AddSnowflakeIdMeta.keyword")
+@Getter
+@Setter
+public class AddSnowflakeIdMeta extends BaseTransformMeta<AddSnowflakeId,
AddSnowflakeIdData> {
+ private static final Class<?> PKG = AddSnowflakeIdMeta.class;
+
+ @HopMetadataProperty(
+ key = "val_name",
+ injectionKeyDescription = "AddSnowflakeIdMeta.Injection.valueName")
+ private String valueName;
+
+ @HopMetadataProperty(
+ key = "data_center_id",
+ injectionKeyDescription = "AddSnowflakeIdMeta.Injection.DataCenterId")
+ private Integer dataCenterId;
+
+ @HopMetadataProperty(
+ key = "machine_id",
+ injectionKeyDescription = "AddSnowflakeIdMeta.Injection.MachineId")
+ private Integer machineId;
+
+ @Override
+ public void setDefault() {
+ valueName = "snowflakeId";
+ dataCenterId = 1;
+ machineId = 1;
+ }
+
+ @Override
+ public void getFields(
+ IRowMeta row,
+ String name,
+ IRowMeta[] info,
+ TransformMeta nextTransform,
+ IVariables variables,
+ IHopMetadataProvider provider)
+ throws HopTransformException {
+ IValueMeta v = new ValueMetaInteger(valueName);
+ v.setOrigin(name);
+ row.addValueMeta(v);
+ }
+}
diff --git
a/plugins/transforms/addsnowflakeid/src/main/java/org/apache/hop/pipeline/transforms/addsnowflakeid/SnowflakeSafeIdGenerator.java
b/plugins/transforms/addsnowflakeid/src/main/java/org/apache/hop/pipeline/transforms/addsnowflakeid/SnowflakeSafeIdGenerator.java
new file mode 100644
index 0000000000..41634e22de
--- /dev/null
+++
b/plugins/transforms/addsnowflakeid/src/main/java/org/apache/hop/pipeline/transforms/addsnowflakeid/SnowflakeSafeIdGenerator.java
@@ -0,0 +1,123 @@
+/*
+ * 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.apache.hop.pipeline.transforms.addsnowflakeid;
+
+import java.util.concurrent.ThreadLocalRandom;
+
+/** snowflake id utils */
+public class SnowflakeSafeIdGenerator {
+ /** 2020-01-01 */
+ private static final long START_EPOCH = 1577836800000L;
+
+ private static final long DATA_CENTER_ID_BITS = 5L;
+ private static final long MACHINE_ID_BITS = 5L;
+ private static final long SEQUENCE_BITS = 12L;
+
+ private static final long MAX_DATA_CENTER_ID = ~(-1L << DATA_CENTER_ID_BITS);
+ private static final long MAX_MACHINE_ID = ~(-1L << MACHINE_ID_BITS);
+ private static final long MAX_SEQUENCE = ~(-1L << SEQUENCE_BITS);
+
+ private static final long MACHINE_ID_SHIFT = SEQUENCE_BITS;
+ private static final long DATA_CENTER_ID_SHIFT = SEQUENCE_BITS +
MACHINE_ID_BITS;
+ private static final long TIMESTAMP_SHIFT = SEQUENCE_BITS + MACHINE_ID_BITS
+ DATA_CENTER_ID_BITS;
+
+ private final long dataCenterId;
+ private final long machineId;
+
+ /** Maximum tolerable rollback time in milliseconds */
+ private final long maxBackwardsMs;
+
+ private long sequence = 0L;
+ private long lastTimestamp = -1L;
+
+ /** logical timestamp */
+ private long logicalTimestamp = -1L;
+
+ public SnowflakeSafeIdGenerator(long dataCenterId, long machineId) {
+ this(dataCenterId, machineId, 10);
+ }
+
+ public SnowflakeSafeIdGenerator(long dataCenterId, long machineId, long
maxBackwardsMs) {
+ if (dataCenterId > MAX_DATA_CENTER_ID || dataCenterId < 0) {
+ throw new IllegalArgumentException("dataCenterId out of range");
+ }
+ if (machineId > MAX_MACHINE_ID || machineId < 0) {
+ throw new IllegalArgumentException("machineId out of range");
+ }
+
+ this.dataCenterId = dataCenterId;
+ this.machineId = machineId;
+ this.maxBackwardsMs = maxBackwardsMs;
+ }
+
+ /**
+ * default SnowflakeSafeIdGenerator
+ *
+ * @return SnowflakeSafeIdGenerator
+ */
+ public static SnowflakeSafeIdGenerator createDefault() {
+ long dataCenterId = ThreadLocalRandom.current().nextInt(0, 32);
+ long machineId = ThreadLocalRandom.current().nextInt(0, 32);
+ return new SnowflakeSafeIdGenerator(dataCenterId, machineId, 10);
+ }
+
+ public synchronized long nextId() {
+ long now = currentTimeMillis();
+
+ // Timestamp rollback detection
+ if (now < lastTimestamp) {
+ long offset = lastTimestamp - now;
+ if (offset <= maxBackwardsMs) {
+ // For minor clock rollbacks, continue running using logical time.
+ now = lastTimestamp;
+ } else {
+ throw new IllegalArgumentException(
+ "Clock moved backwards beyond tolerance. Refusing for " + offset +
"ms");
+ }
+ }
+
+ if (now == lastTimestamp) {
+ sequence = (sequence + 1) & MAX_SEQUENCE;
+ if (sequence == 0) {
+ now = waitUntilNextMillis(lastTimestamp);
+ }
+ } else {
+ sequence = 0;
+ }
+
+ lastTimestamp = now;
+ logicalTimestamp = Math.max(now, logicalTimestamp + 1);
+
+ return ((logicalTimestamp - START_EPOCH) << TIMESTAMP_SHIFT)
+ | (dataCenterId << DATA_CENTER_ID_SHIFT)
+ | (machineId << MACHINE_ID_SHIFT)
+ | sequence;
+ }
+
+ private long waitUntilNextMillis(long lastTime) {
+ long ts = currentTimeMillis();
+ while (ts <= lastTime) {
+ ts = currentTimeMillis();
+ }
+ return ts;
+ }
+
+ private long currentTimeMillis() {
+ return System.currentTimeMillis();
+ }
+}
diff --git
a/plugins/transforms/addsnowflakeid/src/main/resources/addsnowflakeid.svg
b/plugins/transforms/addsnowflakeid/src/main/resources/addsnowflakeid.svg
new file mode 100644
index 0000000000..8b3c0798ec
--- /dev/null
+++ b/plugins/transforms/addsnowflakeid/src/main/resources/addsnowflakeid.svg
@@ -0,0 +1,41 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<svg xmlns='http://www.w3.org/2000/svg' width='42' height='42' viewBox='0 0 42
42'>
+ <defs>
+ <linearGradient id='g' x1='0' x2='1' y1='0' y2='1'>
+ <stop offset='0' stop-color='#6fc1ff'/>
+ <stop offset='1' stop-color='#2b9cff'/>
+ </linearGradient>
+ <filter id='f' x='-50%' y='-50%' width='200%' height='200%'>
+ <feGaussianBlur in='SourceAlpha' stdDeviation='1' result='blur'/>
+ <feOffset in='blur' dx='0' dy='1' result='off'/>
+ <feMerge>
+ <feMergeNode in='off'/>
+ <feMergeNode in='SourceGraphic'/>
+ </feMerge>
+ </filter>
+ </defs>
+ <circle cx='21' cy='21' r='20' fill='white' stroke='rgba(43,156,255,0.12)'
stroke-width='0.8'/>
+ <circle cx='21' cy='21' r='19' fill='url(#g)' opacity='0.06'/>
+ <g transform='translate(21,21)' stroke='#2b9cff' stroke-width='1.6'
stroke-linecap='round' stroke-linejoin='round' filter='url(#f)' fill='none'>
+ <g>
+ <line x1='0' y1='-11' x2='0' y2='11'/>
+ <line x1='-9.5' y1='-5.5' x2='9.5' y2='5.5'/>
+ <line x1='-9.5' y1='5.5' x2='9.5' y2='-5.5'/>
+ </g>
+ <g stroke-width='1.2'>
+ <path d='M0 -7 L-3 -9 M0 -7 L3 -9'/>
+ <path d='M0 7 L-3 9 M0 7 L3 9'/>
+ <path d='M6 3 L8 0 M6 3 L8 6'/>
+ <path d='M-6 -3 L-8 -6 M-6 -3 L-8 0'/>
+ <path d='M6 -3 L8 -6 M6 -3 L8 0'/>
+ <path d='M-6 3 L-8 6 M-6 3 L-8 0'/>
+ </g>
+ <polygon points='0,-4 3,-2 3,2 0,4 -3,2 -3,-2' fill='#eaffff'
stroke='#2b9cff' stroke-width='1'/>
+ </g>
+ <g>
+ <rect x='7' y='6' width='3.6' height='3.6' rx='0.6' fill='#2b9cff'
opacity='0.95'/>
+ <rect x='29' y='10' width='3' height='3' rx='0.5' fill='#6fc1ff'
opacity='0.95'/>
+ <rect x='9' y='28' width='2.6' height='2.6' rx='0.4' fill='#2b9cff'
opacity='0.95'/>
+ </g>
+ <text x='27' y='33.2' font-family='Inter, Arial, Helvetica, sans-serif'
font-size='7.5' font-weight='600' fill='#0b6fb6'>ID</text>
+</svg>
diff --git
a/plugins/transforms/addsnowflakeid/src/main/resources/org/apache/hop/pipeline/transforms/addsnowflakeid/messages/messages_en_US.properties
b/plugins/transforms/addsnowflakeid/src/main/resources/org/apache/hop/pipeline/transforms/addsnowflakeid/messages/messages_en_US.properties
new file mode 100644
index 0000000000..b37bc3e505
--- /dev/null
+++
b/plugins/transforms/addsnowflakeid/src/main/resources/org/apache/hop/pipeline/transforms/addsnowflakeid/messages/messages_en_US.properties
@@ -0,0 +1,33 @@
+#
+# 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.
+#
+
+AddSnowflakeId.Log.ErrorInTransform=Because of an error, this transform can''t
continue\:
+AddSnowflakeId.Log.LineNumber=linenr
+AddSnowflakeId.Log.ReadRow=Read row \#
+AddSnowflakeId.Log.WriteRow=Wrote row \#
+AddSnowflakeIdDialog.MachineNo.Label=Machine No
+AddSnowflakeIdDialog.Log.GettingKeyInfo=getting key info...
+AddSnowflakeIdDialog.Shell.Title=Generate snowflake id
+AddSnowflakeIdDialog.DataCenterNo.Label=Data Center No
+AddSnowflakeIdDialog.TransformName.Label=Transform name
+AddSnowflakeIdDialog.ValueName.Label=Name of value
+AddSnowflakeIdMeta.Injection.valueName=Value name
+AddSnowflakeIdMeta.Injection.DataCenterId=Data Center Id
+AddSnowflakeIdMeta.Injection.MachineId=Machine id
+AddSnowflakeIdMeta.keyword=add,generate,snowflake,id
+BaseTransform.TypeLongDesc.AddSnowflakeId=Generate snowflake id
+BaseTransform.TypeTooltipDesc.AddSnowflakeId=Get the next value from an
snowflakeId
diff --git
a/plugins/transforms/addsnowflakeid/src/main/resources/org/apache/hop/pipeline/transforms/addsnowflakeid/messages/messages_zh_CN.properties
b/plugins/transforms/addsnowflakeid/src/main/resources/org/apache/hop/pipeline/transforms/addsnowflakeid/messages/messages_zh_CN.properties
new file mode 100644
index 0000000000..52631a19ff
--- /dev/null
+++
b/plugins/transforms/addsnowflakeid/src/main/resources/org/apache/hop/pipeline/transforms/addsnowflakeid/messages/messages_zh_CN.properties
@@ -0,0 +1,33 @@
+#
+# 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.
+#
+
+AddSnowflakeId.Log.ErrorInTransform=Because of an error, this transform can''t
continue\:
+AddSnowflakeId.Log.LineNumber=\u884C\u53F7
+AddSnowflakeId.Log.ReadRow=\u8BFB\u4E00\u884C \#
+AddSnowflakeId.Log.WriteRow=\u5199\u4E00\u884C \#
+AddSnowflakeIdDialog.MachineNo.Label=\u5E94\u7528\u7F16\u53F7
+AddSnowflakeIdDialog.Log.GettingKeyInfo=Getting key info...
+AddSnowflakeIdDialog.Shell.Title=\u4EA7\u751F\u96EA\u82B1\u7B97\u6CD5ID
+AddSnowflakeIdDialog.DataCenterNo.Label=\u5E94\u7528\u7F16\u53F7
+AddSnowflakeIdDialog.TransformName.Label=\u8F6C\u6362\u540D\u79F0
+AddSnowflakeIdDialog.ValueName.Label=\u751F\u6210id\u5B57\u6BB5
+AddSnowflakeIdMeta.Injection.valueName=Value name
+AddSnowflakeIdMeta.Injection.DataCenterId=Data Center Id
+AddSnowflakeIdMeta.Injection.MachineId=Machine id
+AddSnowflakeIdMeta.keyword=\u6DFB\u52A0,\u96EA\u82B1ID,ID,\u5168\u5C40\u552F\u4E00ID
+BaseTransform.TypeLongDesc.AddSnowflakeId=\u4EA7\u751F\u96EA\u82B1\u7B97\u6CD5ID
+BaseTransform.TypeTooltipDesc.AddSnowflakeId=\u83B7\u53D6\u4E0B\u4E00\u4E2A\u96EA\u82B1ID
diff --git a/plugins/transforms/addsnowflakeid/src/main/resources/version.xml
b/plugins/transforms/addsnowflakeid/src/main/resources/version.xml
new file mode 100644
index 0000000000..6be576acae
--- /dev/null
+++ b/plugins/transforms/addsnowflakeid/src/main/resources/version.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ 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.
+ ~
+ -->
+
+<version>${project.version}</version>
\ No newline at end of file
diff --git
a/plugins/transforms/addsnowflakeid/src/test/java/org/apache/hop/pipeline/transforms/addsnowflakeid/AddSnowflakeIdMetaTests.java
b/plugins/transforms/addsnowflakeid/src/test/java/org/apache/hop/pipeline/transforms/addsnowflakeid/AddSnowflakeIdMetaTests.java
new file mode 100644
index 0000000000..bd7aa15247
--- /dev/null
+++
b/plugins/transforms/addsnowflakeid/src/test/java/org/apache/hop/pipeline/transforms/addsnowflakeid/AddSnowflakeIdMetaTests.java
@@ -0,0 +1,50 @@
+/*
+ * 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.apache.hop.pipeline.transforms.addsnowflakeid;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+/** AddSnowflakeIdMeta test */
+class AddSnowflakeIdMetaTests {
+
+ @Test
+ void setDefault() {
+ AddSnowflakeIdMeta meta = new AddSnowflakeIdMeta();
+ meta.setDefault();
+
+ Assertions.assertEquals(1, meta.getMachineId());
+ Assertions.assertEquals(1, meta.getDataCenterId());
+ Assertions.assertEquals("snowflakeId", meta.getValueName());
+ }
+
+ @Test
+ void testClone() {
+ AddSnowflakeIdMeta meta = new AddSnowflakeIdMeta();
+ meta.setValueName("snowflake_id");
+ meta.setDataCenterId(10);
+ meta.setMachineId(20);
+
+ AddSnowflakeIdMeta cloned = (AddSnowflakeIdMeta) meta.clone();
+
+ Assertions.assertNotNull(cloned);
+ Assertions.assertNotSame(meta, cloned);
+ Assertions.assertEquals(meta.getValueName(), cloned.getValueName());
+ Assertions.assertEquals(meta.getDataCenterId(), cloned.getDataCenterId());
+ Assertions.assertEquals(meta.getMachineId(), cloned.getMachineId());
+ }
+}
diff --git
a/plugins/transforms/addsnowflakeid/src/test/java/org/apache/hop/pipeline/transforms/addsnowflakeid/AddSnowflakeIdTests.java
b/plugins/transforms/addsnowflakeid/src/test/java/org/apache/hop/pipeline/transforms/addsnowflakeid/AddSnowflakeIdTests.java
new file mode 100644
index 0000000000..cd48ef5362
--- /dev/null
+++
b/plugins/transforms/addsnowflakeid/src/test/java/org/apache/hop/pipeline/transforms/addsnowflakeid/AddSnowflakeIdTests.java
@@ -0,0 +1,141 @@
+/*
+ * 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.apache.hop.pipeline.transforms.addsnowflakeid;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.apache.hop.core.HopEnvironment;
+import org.apache.hop.core.encryption.HopTwoWayPasswordEncoder;
+import org.apache.hop.core.encryption.TwoWayPasswordEncoderPluginType;
+import org.apache.hop.core.exception.HopException;
+import org.apache.hop.core.logging.ILoggingObject;
+import org.apache.hop.core.plugins.Plugin;
+import org.apache.hop.core.plugins.PluginRegistry;
+import org.apache.hop.core.row.IRowMeta;
+import org.apache.hop.core.row.RowMeta;
+import org.apache.hop.core.row.value.ValueMetaInteger;
+import org.apache.hop.pipeline.PipelineTestingUtil;
+import org.apache.hop.pipeline.transforms.mock.TransformMockHelper;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+/** AddSnowflakeId test */
+class AddSnowflakeIdTests {
+ private TransformMockHelper<AddSnowflakeIdMeta, AddSnowflakeIdData>
transformMockHelper;
+
+ @BeforeAll
+ static void setUpBeforeClass() throws HopException {
+ Map<Class<?>, String> map = new HashMap<>();
+ map.put(HopTwoWayPasswordEncoder.class,
HopTwoWayPasswordEncoder.class.getName());
+ List<String> libraries = new ArrayList<>();
+
+ Plugin plugin =
+ new Plugin(
+ new String[] {"Hop"},
+ TwoWayPasswordEncoderPluginType.class,
+ HopTwoWayPasswordEncoder.class,
+ "Encryption",
+ "Hop",
+ null,
+ null,
+ false,
+ false,
+ map,
+ libraries,
+ null,
+ null,
+ null,
+ false);
+
+
PluginRegistry.getInstance().registerPlugin(TwoWayPasswordEncoderPluginType.class,
plugin);
+
+ HopEnvironment.init();
+ }
+
+ @BeforeEach
+ void setUp() {
+ transformMockHelper =
+ new TransformMockHelper<>(
+ "AddSnowflakeId", AddSnowflakeIdMeta.class,
AddSnowflakeIdData.class);
+ when(transformMockHelper.logChannelFactory.create(any(),
any(ILoggingObject.class)))
+ .thenReturn(transformMockHelper.iLogChannel);
+ when(transformMockHelper.pipeline.isRunning()).thenReturn(true);
+
when(transformMockHelper.transformMeta.getName()).thenReturn("AddSnowflakeId");
+ }
+
+ @Test
+ void processRow() throws Exception {
+ when(transformMockHelper.iTransformMeta.getValueName()).thenReturn("id");
+ when(transformMockHelper.iTransformMeta.getDataCenterId()).thenReturn(10);
+ when(transformMockHelper.iTransformMeta.getMachineId()).thenReturn(20);
+ when(transformMockHelper.pipeline.getContainerId())
+ .thenReturn("cId-" + System.currentTimeMillis());
+
+ AddSnowflakeId addSnowflakeId =
+ new AddSnowflakeId(
+ transformMockHelper.transformMeta,
+ transformMockHelper.iTransformMeta,
+ transformMockHelper.iTransformData,
+ 0,
+ transformMockHelper.pipelineMeta,
+ transformMockHelper.pipeline);
+
+ // init success.
+ boolean result = addSnowflakeId.init();
+ assertTrue(result);
+
+ // Set up input row meta
+ RowMeta inputRowMeta = new RowMeta();
+ inputRowMeta.addValueMeta(new ValueMetaInteger("No"));
+ inputRowMeta.addValueMeta(new ValueMetaInteger("Id"));
+ addSnowflakeId.setInputRowMeta(inputRowMeta);
+
+ // spy
+ addSnowflakeId = spy(addSnowflakeId);
+ doReturn(new Object[] {1000L})
+ .doReturn(new Object[] {2000L})
+ .doReturn(new Object[] {3000L})
+ .doReturn(null)
+ .when(addSnowflakeId)
+ .getRow();
+
+ List<Object[]> execCount = PipelineTestingUtil.execute(addSnowflakeId, 3,
false);
+ assertEquals(3, execCount.size());
+ assertTrue((long) execCount.get(1)[1] > (long) execCount.get(0)[1]);
+
+ IRowMeta outputRowMeta = transformMockHelper.iTransformData.outputRowMeta;
+ assertEquals(2, outputRowMeta.size());
+
+ addSnowflakeId.dispose();
+ }
+
+ @AfterEach
+ void tearDown() {
+ transformMockHelper.cleanUp();
+ }
+}
diff --git
a/plugins/transforms/addsnowflakeid/src/test/java/org/apache/hop/pipeline/transforms/addsnowflakeid/SnowflakeSafeIdGeneratorTests.java
b/plugins/transforms/addsnowflakeid/src/test/java/org/apache/hop/pipeline/transforms/addsnowflakeid/SnowflakeSafeIdGeneratorTests.java
new file mode 100644
index 0000000000..d58f521836
--- /dev/null
+++
b/plugins/transforms/addsnowflakeid/src/test/java/org/apache/hop/pipeline/transforms/addsnowflakeid/SnowflakeSafeIdGeneratorTests.java
@@ -0,0 +1,61 @@
+/*
+ * 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.apache.hop.pipeline.transforms.addsnowflakeid;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+/** SnowflakeIdUtils test */
+class SnowflakeSafeIdGeneratorTests {
+
+ @Test
+ void testDefaultNextId() {
+ SnowflakeSafeIdGenerator generator =
SnowflakeSafeIdGenerator.createDefault();
+ long preId = 766649250213851136L;
+ long id = generator.nextId();
+
+ Assertions.assertTrue(id > preId);
+ }
+
+ @Test
+ void testNextId() {
+ long dataCenterId = 1;
+ long machineId = 10;
+ SnowflakeSafeIdGenerator generator = new
SnowflakeSafeIdGenerator(dataCenterId, machineId);
+
+ long preId = 766649250213851136L;
+ long id = generator.nextId();
+
+ Assertions.assertTrue(id > preId);
+ }
+
+ @Test
+ void testNextIdWithMaxBackwardsMs() {
+ long dataCenterId = 1;
+ long machineId = 10;
+ long maxBackwardsMs = 30;
+
+ SnowflakeSafeIdGenerator generator =
+ new SnowflakeSafeIdGenerator(dataCenterId, machineId, maxBackwardsMs);
+
+ long preId = 766649250213851136L;
+ long id = generator.nextId();
+
+ Assertions.assertTrue(id > preId);
+ }
+}
diff --git a/plugins/transforms/pom.xml b/plugins/transforms/pom.xml
index 29ee10336a..82d1f1c2fd 100644
--- a/plugins/transforms/pom.xml
+++ b/plugins/transforms/pom.xml
@@ -31,6 +31,7 @@
<modules>
<module>abort</module>
<module>addsequence</module>
+ <module>addsnowflakeid</module>
<module>analyticquery</module>
<module>append</module>
<module>blockingtransform</module>