This is an automated email from the ASF dual-hosted git repository.

jin pushed a commit to branch master
in repository 
https://gitbox.apache.org/repos/asf/incubator-hugegraph-toolchain.git


The following commit(s) were added to refs/heads/master by this push:
     new 9ee7abb  feat:  introduce HugeGraphSparkLoader (#282)
9ee7abb is described below

commit 9ee7abb2ee397db636e4c1a1f258f0c7bcd1b5c1
Author: Simon Cheung <[email protected]>
AuthorDate: Fri May 27 11:27:08 2022 +0800

    feat:  introduce HugeGraphSparkLoader (#282)
---
 .../assembly/static/bin/hugegraph-spark-loader.sh  |  64 ++++++
 hugegraph-loader/pom.xml                           |  11 +
 .../hugegraph/loader/executor/LoadContext.java     |   3 +-
 .../hugegraph/loader/executor/LoadOptions.java     |   3 +-
 .../hugegraph/loader/mapping/ElementMapping.java   |  10 +-
 .../hugegraph/loader/mapping/InputStruct.java      |   3 +-
 .../hugegraph/loader/source/AbstractSource.java    |   3 +-
 .../hugegraph/loader/source/file/FileFilter.java   |   3 +-
 .../hugegraph/loader/source/file/SkippedLine.java  |   3 +-
 .../loader/spark/HugeGraphSparkLoader.java         | 245 +++++++++++++++++++++
 .../loader/test/unit/MappingConverterTest.java     |   4 +-
 11 files changed, 343 insertions(+), 9 deletions(-)

diff --git a/hugegraph-loader/assembly/static/bin/hugegraph-spark-loader.sh 
b/hugegraph-loader/assembly/static/bin/hugegraph-spark-loader.sh
new file mode 100755
index 0000000..a2e6a06
--- /dev/null
+++ b/hugegraph-loader/assembly/static/bin/hugegraph-spark-loader.sh
@@ -0,0 +1,64 @@
+#!/bin/bash
+
+PARAMS=""
+while (( "$#" )); do
+  case "$1" in
+    -m|--master)
+      MASTER=$2
+      shift 2
+      ;;
+
+    -n|--name)
+      APP_NAME=$2
+      shift 2
+      ;;
+
+    -e|--deploy-mode)
+      DEPLOY_MODE=$2
+      shift 2
+      ;;
+
+    -c|--conf)
+      SPARK_CONFIG=${SPARK_CONFIG}" --conf "$2
+      shift 2
+      ;;
+
+    --) # end argument parsing
+      shift
+      break
+      ;;
+
+    *) # preserve positional arguments
+      PARAMS="$PARAMS $1"
+      shift
+      ;;
+
+  esac
+done
+
+if [ -z ${MASTER} ] || [ -z ${DEPLOY_MODE} ]; then
+  echo "Error: The following options are required:
+  [-e | --deploy-mode], [-m | --master]"
+  usage
+  exit 0
+fi
+
+BIN_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
+APP_DIR=$(dirname ${BIN_DIR})
+LIB_DIR=${APP_DIR}/lib
+
+assemblyJarName=$(find ${LIB_DIR} -name hugegraph-loader*.jar)
+
+DEFAULT_APP_NAME="hugegraph-spark-loader"
+APP_NAME=${APP_NAME:-$DEFAULT_APP_NAME}
+
+CMD="${SPARK_HOME}/bin/spark-submit
+    --name ${APP_NAME}  \
+    --master ${MASTER}  \
+    --deploy-mode ${DEPLOY_MODE} \
+    --class com.baidu.hugegraph.loader.spark.HugeGraphSparkLoader \
+    ${SPARK_CONFIG}
+    --jars $(echo ${LIB_DIR}/*.jar | tr ' ' ',') ${assemblyJarName} ${PARAMS}"
+
+echo ${CMD}
+exec ${CMD}
diff --git a/hugegraph-loader/pom.xml b/hugegraph-loader/pom.xml
index 4ffdd52..afcb9d6 100644
--- a/hugegraph-loader/pom.xml
+++ b/hugegraph-loader/pom.xml
@@ -36,6 +36,17 @@
     </properties>
 
     <dependencies>
+        <dependency>
+            <groupId>org.apache.spark</groupId>
+            <artifactId>spark-core_2.12</artifactId>
+            <version>3.1.2</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.spark</groupId>
+            <artifactId>spark-sql_2.12</artifactId>
+            <version>3.1.2</version>
+        </dependency>
+
         <dependency>
             <groupId>com.baidu.hugegraph</groupId>
             <artifactId>hugegraph-client</artifactId>
diff --git 
a/hugegraph-loader/src/main/java/com/baidu/hugegraph/loader/executor/LoadContext.java
 
b/hugegraph-loader/src/main/java/com/baidu/hugegraph/loader/executor/LoadContext.java
index 24a8df6..117124d 100644
--- 
a/hugegraph-loader/src/main/java/com/baidu/hugegraph/loader/executor/LoadContext.java
+++ 
b/hugegraph-loader/src/main/java/com/baidu/hugegraph/loader/executor/LoadContext.java
@@ -20,6 +20,7 @@
 package com.baidu.hugegraph.loader.executor;
 
 import java.io.IOException;
+import java.io.Serializable;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 
@@ -38,7 +39,7 @@ import com.baidu.hugegraph.loader.util.HugeClientHolder;
 import com.baidu.hugegraph.structure.constant.GraphMode;
 import com.baidu.hugegraph.util.Log;
 
-public final class LoadContext {
+public final class LoadContext implements Serializable {
 
     private static final Logger LOG = Log.logger(LoadContext.class);
 
diff --git 
a/hugegraph-loader/src/main/java/com/baidu/hugegraph/loader/executor/LoadOptions.java
 
b/hugegraph-loader/src/main/java/com/baidu/hugegraph/loader/executor/LoadOptions.java
index c35b5a6..c5f1bfd 100644
--- 
a/hugegraph-loader/src/main/java/com/baidu/hugegraph/loader/executor/LoadOptions.java
+++ 
b/hugegraph-loader/src/main/java/com/baidu/hugegraph/loader/executor/LoadOptions.java
@@ -20,6 +20,7 @@
 package com.baidu.hugegraph.loader.executor;
 
 import java.io.File;
+import java.io.Serializable;
 import java.util.Set;
 
 import org.apache.commons.lang3.StringUtils;
@@ -35,7 +36,7 @@ import com.beust.jcommander.Parameter;
 import com.beust.jcommander.ParameterException;
 import com.google.common.collect.ImmutableSet;
 
-public class LoadOptions {
+public class LoadOptions implements Serializable {
 
     private static final Logger LOG = Log.logger(LoadOptions.class);
 
diff --git 
a/hugegraph-loader/src/main/java/com/baidu/hugegraph/loader/mapping/ElementMapping.java
 
b/hugegraph-loader/src/main/java/com/baidu/hugegraph/loader/mapping/ElementMapping.java
index fbdeac2..48aaec6 100644
--- 
a/hugegraph-loader/src/main/java/com/baidu/hugegraph/loader/mapping/ElementMapping.java
+++ 
b/hugegraph-loader/src/main/java/com/baidu/hugegraph/loader/mapping/ElementMapping.java
@@ -19,6 +19,7 @@
 
 package com.baidu.hugegraph.loader.mapping;
 
+import java.io.Serializable;
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -37,7 +38,7 @@ import com.fasterxml.jackson.annotation.JsonPropertyOrder;
 import com.google.common.collect.ImmutableSet;
 
 @JsonPropertyOrder({"label", "skip"})
-public abstract class ElementMapping implements Checkable {
+public abstract class ElementMapping implements Checkable, Serializable {
 
     @JsonProperty("label")
     private String label;
@@ -55,6 +56,8 @@ public abstract class ElementMapping implements Checkable {
     private Set<Object> nullValues;
     @JsonProperty("update_strategies")
     private Map<String, UpdateStrategy> updateStrategies;
+    @JsonProperty("batch_size")
+    private long batchSize;
 
     public ElementMapping() {
         this.skip = false;
@@ -64,6 +67,7 @@ public abstract class ElementMapping implements Checkable {
         this.ignoredFields = new HashSet<>();
         this.nullValues = ImmutableSet.of(Constants.EMPTY_STR);
         this.updateStrategies = new HashMap<>();
+        this.batchSize = 1000;
     }
 
     public abstract ElemType type();
@@ -173,6 +177,10 @@ public abstract class ElementMapping implements Checkable {
         return mappingValue;
     }
 
+    public long batchSize() {
+       return this.batchSize;
+    }
+
     public Set<String> selectedFields() {
         return this.selectedFields;
     }
diff --git 
a/hugegraph-loader/src/main/java/com/baidu/hugegraph/loader/mapping/InputStruct.java
 
b/hugegraph-loader/src/main/java/com/baidu/hugegraph/loader/mapping/InputStruct.java
index 0b97974..850015c 100644
--- 
a/hugegraph-loader/src/main/java/com/baidu/hugegraph/loader/mapping/InputStruct.java
+++ 
b/hugegraph-loader/src/main/java/com/baidu/hugegraph/loader/mapping/InputStruct.java
@@ -19,6 +19,7 @@
 
 package com.baidu.hugegraph.loader.mapping;
 
+import java.io.Serializable;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -33,7 +34,7 @@ import com.fasterxml.jackson.annotation.JsonPropertyOrder;
 import com.google.common.collect.ImmutableList;
 
 @JsonPropertyOrder({"id", "skip", "input", "vertices", "edges"})
-public class InputStruct implements Checkable {
+public class InputStruct implements Checkable, Serializable {
 
     public static final InputStruct EMPTY = new InputStruct(ImmutableList.of(),
                                                             
ImmutableList.of());
diff --git 
a/hugegraph-loader/src/main/java/com/baidu/hugegraph/loader/source/AbstractSource.java
 
b/hugegraph-loader/src/main/java/com/baidu/hugegraph/loader/source/AbstractSource.java
index c264f83..518831a 100644
--- 
a/hugegraph-loader/src/main/java/com/baidu/hugegraph/loader/source/AbstractSource.java
+++ 
b/hugegraph-loader/src/main/java/com/baidu/hugegraph/loader/source/AbstractSource.java
@@ -19,6 +19,7 @@
 
 package com.baidu.hugegraph.loader.source;
 
+import java.io.Serializable;
 import java.nio.charset.Charset;
 import java.util.Arrays;
 import java.util.List;
@@ -29,7 +30,7 @@ import com.baidu.hugegraph.util.CollectionUtil;
 import com.baidu.hugegraph.util.E;
 import com.fasterxml.jackson.annotation.JsonProperty;
 
-public abstract class AbstractSource implements InputSource {
+public abstract class AbstractSource implements InputSource, Serializable {
 
     @JsonProperty("header")
     private String[] header;
diff --git 
a/hugegraph-loader/src/main/java/com/baidu/hugegraph/loader/source/file/FileFilter.java
 
b/hugegraph-loader/src/main/java/com/baidu/hugegraph/loader/source/file/FileFilter.java
index a190f7c..5e9acc2 100644
--- 
a/hugegraph-loader/src/main/java/com/baidu/hugegraph/loader/source/file/FileFilter.java
+++ 
b/hugegraph-loader/src/main/java/com/baidu/hugegraph/loader/source/file/FileFilter.java
@@ -19,6 +19,7 @@
 
 package com.baidu.hugegraph.loader.source.file;
 
+import java.io.Serializable;
 import java.util.Set;
 
 import org.apache.commons.io.FilenameUtils;
@@ -26,7 +27,7 @@ import org.apache.commons.io.FilenameUtils;
 import com.fasterxml.jackson.annotation.JsonProperty;
 import com.google.common.collect.ImmutableSet;
 
-public class FileFilter {
+public class FileFilter implements Serializable {
 
     private static final String ALL_EXTENSION = "*";
 
diff --git 
a/hugegraph-loader/src/main/java/com/baidu/hugegraph/loader/source/file/SkippedLine.java
 
b/hugegraph-loader/src/main/java/com/baidu/hugegraph/loader/source/file/SkippedLine.java
index 24a62da..ad16ab8 100644
--- 
a/hugegraph-loader/src/main/java/com/baidu/hugegraph/loader/source/file/SkippedLine.java
+++ 
b/hugegraph-loader/src/main/java/com/baidu/hugegraph/loader/source/file/SkippedLine.java
@@ -19,13 +19,14 @@
 
 package com.baidu.hugegraph.loader.source.file;
 
+import java.io.Serializable;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
 import com.baidu.hugegraph.loader.constant.Constants;
 import com.fasterxml.jackson.annotation.JsonProperty;
 
-public class SkippedLine {
+public class SkippedLine implements Serializable {
 
     @JsonProperty("regex")
     private String regex;
diff --git 
a/hugegraph-loader/src/main/java/com/baidu/hugegraph/loader/spark/HugeGraphSparkLoader.java
 
b/hugegraph-loader/src/main/java/com/baidu/hugegraph/loader/spark/HugeGraphSparkLoader.java
new file mode 100644
index 0000000..5d552e2
--- /dev/null
+++ 
b/hugegraph-loader/src/main/java/com/baidu/hugegraph/loader/spark/HugeGraphSparkLoader.java
@@ -0,0 +1,245 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * 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 com.baidu.hugegraph.loader.spark;
+
+import com.baidu.hugegraph.driver.GraphManager;
+import com.baidu.hugegraph.loader.builder.EdgeBuilder;
+import com.baidu.hugegraph.loader.builder.ElementBuilder;
+import com.baidu.hugegraph.loader.builder.VertexBuilder;
+import com.baidu.hugegraph.loader.executor.LoadContext;
+import com.baidu.hugegraph.loader.executor.LoadOptions;
+import com.baidu.hugegraph.loader.mapping.EdgeMapping;
+import com.baidu.hugegraph.loader.mapping.VertexMapping;
+import com.baidu.hugegraph.loader.mapping.ElementMapping;
+import com.baidu.hugegraph.loader.mapping.InputStruct;
+import com.baidu.hugegraph.loader.mapping.LoadMapping;
+import com.baidu.hugegraph.loader.source.InputSource;
+import com.baidu.hugegraph.loader.source.file.FileFilter;
+import com.baidu.hugegraph.loader.source.file.FileFormat;
+import com.baidu.hugegraph.loader.source.file.FileSource;
+import com.baidu.hugegraph.loader.source.file.SkippedLine;
+import com.baidu.hugegraph.loader.source.file.Compression;
+import com.baidu.hugegraph.loader.util.Printer;
+import com.baidu.hugegraph.structure.GraphElement;
+import com.baidu.hugegraph.structure.graph.UpdateStrategy;
+import com.baidu.hugegraph.structure.graph.Vertex;
+import com.baidu.hugegraph.structure.graph.Edge;
+import com.baidu.hugegraph.structure.graph.BatchEdgeRequest;
+import com.baidu.hugegraph.structure.graph.BatchVertexRequest;
+import com.baidu.hugegraph.util.Log;
+
+import org.apache.spark.sql.DataFrameReader;
+import org.apache.spark.sql.Dataset;
+import org.apache.spark.sql.Row;
+import org.apache.spark.sql.SparkSession;
+import org.slf4j.Logger;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+public class HugeGraphSparkLoader implements Serializable {
+
+    public static final Logger LOG = Log.logger(HugeGraphSparkLoader.class);
+
+    private final LoadOptions loadOptions;
+    private final Map<ElementBuilder, List<GraphElement>> builders;
+
+    public static void main(String[] args) {
+        HugeGraphSparkLoader loader;
+        try {
+            loader = new HugeGraphSparkLoader(args);
+        } catch (Throwable e) {
+            Printer.printError("Failed to start loading", e);
+            return;
+        }
+        loader.load();
+    }
+
+    public HugeGraphSparkLoader(String[] args) {
+        this.loadOptions = LoadOptions.parseOptions(args);
+        this.builders = new HashMap<>();
+    }
+
+    public void load() {
+        LoadMapping mapping = LoadMapping.of(this.loadOptions.file);
+        List<InputStruct> structs = mapping.structs();
+
+        SparkSession session = SparkSession.builder().getOrCreate();
+        for (InputStruct struct : structs) {
+            Dataset<Row> ds = read(session, struct);
+            ds.foreachPartition((Iterator<Row> p) -> {
+                LoadContext context = initPartition(this.loadOptions, struct);
+                p.forEachRemaining((Row row) -> {
+                    loadRow(struct, row, p, context);
+                });
+                context.close();
+            });
+        }
+        session.close();
+        session.stop();
+    }
+
+    private LoadContext initPartition(
+            LoadOptions loadOptions, InputStruct struct) {
+        LoadContext context = new LoadContext(loadOptions);
+        for (VertexMapping vertexMapping : struct.vertices()) {
+            this.builders.put(
+                    new VertexBuilder(context, struct, vertexMapping),
+                    new ArrayList<>());
+        }
+        for (EdgeMapping edgeMapping : struct.edges()) {
+            this.builders.put(new EdgeBuilder(context, struct, edgeMapping),
+                              new ArrayList<>());
+        }
+        return context;
+    }
+
+    private void loadRow(InputStruct struct, Row row, Iterator<Row> p,
+                         LoadContext context) {
+        for (Map.Entry<ElementBuilder, List<GraphElement>> builderMap :
+                this.builders.entrySet()) {
+            ElementMapping elementMapping = builderMap.getKey().mapping();
+            // Parse
+            if (elementMapping.skip()) {
+                continue;
+            }
+            parse(row, builderMap, struct);
+
+            // Insert
+            List<GraphElement> graphElements =
+                    builderMap.getValue();
+            if (graphElements.size() > elementMapping.batchSize() ||
+                (!p.hasNext() && graphElements.size() > 0)) {
+                sink(builderMap, context.client().graph(),
+                     this.loadOptions.checkVertex);
+            }
+        }
+    }
+
+    private Dataset<Row> read(SparkSession ss, InputStruct struct) {
+        InputSource input = struct.input();
+        String charset = input.charset();
+        FileSource fileSource = input.asFileSource();
+
+        String[] header = fileSource.header();
+        String delimiter = fileSource.delimiter();
+        String path = fileSource.path();
+        FileFilter filter = fileSource.filter();
+        FileFormat format = fileSource.format();
+        String dateFormat = fileSource.dateFormat();
+        String timeZone = fileSource.timeZone();
+        SkippedLine skippedLine = fileSource.skippedLine();
+        Compression compression = fileSource.compression();
+        int batchSize = fileSource.batchSize();
+
+        DataFrameReader reader = ss.read();
+        Dataset<Row> ds;
+        switch (input.type()) {
+            case FILE:
+            case HDFS:
+                switch (format) {
+                    case TEXT:
+                        ds = reader.text(path);
+                        break;
+                    case JSON:
+                        ds = reader.json(path);
+                        break;
+                    case CSV:
+                        ds = reader.csv(path);
+                        break;
+                    default:
+                        throw new IllegalStateException(
+                                  "Unexpected format value: " + format);
+                }
+                break;
+            case JDBC:
+                // TODO: implement jdbc
+            default:
+                throw new AssertionError(String.format(
+                          "Unsupported input source '%s'", input.type()));
+        }
+        return ds;
+    }
+
+    private void parse(Row row,
+                       Map.Entry<ElementBuilder, List<GraphElement>> 
builderMap,
+                       InputStruct struct) {
+        ElementBuilder builder = builderMap.getKey();
+        List<GraphElement> graphElements = builderMap.getValue();
+        if ("".equals(row.mkString())) {
+            return;
+        }
+        List<GraphElement> elements;
+        switch (struct.input().type()) {
+            case FILE:
+            case HDFS:
+                FileSource fileSource = struct.input().asFileSource();
+                elements = builder.build(fileSource.header(),
+                                         row.mkString()
+                                            .split(fileSource.delimiter()));
+                break;
+            case JDBC:
+                //TODO: implement jdbc
+            default:
+                throw new AssertionError(String.format(
+                          "Unsupported input source '%s'",
+                          struct.input().type()));
+        }
+        graphElements.addAll(elements);
+    }
+
+    private void sink(Map.Entry<ElementBuilder, List<GraphElement>> builderMap,
+                      GraphManager g, boolean isCheckVertex) {
+        ElementBuilder builder = builderMap.getKey();
+        ElementMapping elementMapping = builder.mapping();
+        List<GraphElement> graphElements = builderMap.getValue();
+        boolean isVertex = builder.mapping().type().isVertex();
+        Map<String, UpdateStrategy> updateStrategyMap = 
elementMapping.updateStrategies();
+        if (updateStrategyMap.isEmpty()) {
+            if (isVertex) {
+                g.addVertices((List<Vertex>) (Object) graphElements);
+            } else {
+                g.addEdges((List<Edge>) (Object) graphElements);
+            }
+        } else {
+            // CreateIfNotExist dose not support false now
+            if (isVertex) {
+                BatchVertexRequest.Builder req =
+                        new BatchVertexRequest.Builder();
+                req.vertices((List<Vertex>) (Object) graphElements)
+                   .updatingStrategies(updateStrategyMap)
+                   .createIfNotExist(true);
+                g.updateVertices(req.build());
+            } else {
+                BatchEdgeRequest.Builder req = new BatchEdgeRequest.Builder();
+                req.edges((List<Edge>) (Object) graphElements)
+                   .updatingStrategies(updateStrategyMap)
+                   .checkVertex(isCheckVertex)
+                   .createIfNotExist(true);
+                g.updateEdges(req.build());
+            }
+        }
+        graphElements.clear();
+    }
+}
diff --git 
a/hugegraph-loader/src/test/java/com/baidu/hugegraph/loader/test/unit/MappingConverterTest.java
 
b/hugegraph-loader/src/test/java/com/baidu/hugegraph/loader/test/unit/MappingConverterTest.java
index e09dfaf..459b3b5 100644
--- 
a/hugegraph-loader/src/test/java/com/baidu/hugegraph/loader/test/unit/MappingConverterTest.java
+++ 
b/hugegraph-loader/src/test/java/com/baidu/hugegraph/loader/test/unit/MappingConverterTest.java
@@ -99,7 +99,7 @@ public class MappingConverterTest {
                 "\"value_mapping\":{},\"selected\":[]," +
                 "\"ignored\":[\"Occupation\",\"Zip-code\",\"Gender\"," +
                 "\"Age\"],\"null_values\":[\"\"]," +
-                "\"update_strategies\":{}}],\"edges\":[]},{\"id\":\"2\"," +
+                
"\"update_strategies\":{},\"batch_size\":1000}],\"edges\":[]},{\"id\":\"2\"," +
                 "\"skip\":false,\"input\":{\"type\":\"FILE\"," +
                 "\"path\":\"ratings.dat\"," +
                 "\"file_filter\":{\"extensions\":[\"*\"]}," +
@@ -116,7 +116,7 @@ public class MappingConverterTest {
                 "\"field_mapping\":{\"UserID\":\"id\",\"MovieID\":\"id\"," +
                 "\"Rating\":\"rate\"},\"value_mapping\":{},\"selected\":[]," +
                 "\"ignored\":[\"Timestamp\"],\"null_values\":[\"\"]," +
-                "\"update_strategies\":{}}]}]}";
+                "\"update_strategies\":{},\"batch_size\":1000}]}]}";
         Assert.assertEquals(expectV2Json, actualV2Json);
 
         FileUtils.forceDelete(inputFile);

Reply via email to