All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.baidu.hugegraph.schema.builder.EdgeLabelBuilder Maven / Gradle / Ivy

/*
 * 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.schema.builder;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.collections.CollectionUtils;

import com.baidu.hugegraph.HugeGraph;
import com.baidu.hugegraph.backend.id.Id;
import com.baidu.hugegraph.backend.id.IdGenerator;
import com.baidu.hugegraph.backend.tx.SchemaTransaction;
import com.baidu.hugegraph.exception.ExistedException;
import com.baidu.hugegraph.exception.NotAllowException;
import com.baidu.hugegraph.exception.NotFoundException;
import com.baidu.hugegraph.schema.EdgeLabel;
import com.baidu.hugegraph.schema.PropertyKey;
import com.baidu.hugegraph.schema.Userdata;
import com.baidu.hugegraph.schema.VertexLabel;
import com.baidu.hugegraph.type.HugeType;
import com.baidu.hugegraph.type.define.Action;
import com.baidu.hugegraph.type.define.Frequency;
import com.baidu.hugegraph.util.CollectionUtil;
import com.baidu.hugegraph.util.E;
import com.google.common.collect.ImmutableList;

public class EdgeLabelBuilder extends AbstractBuilder
                              implements EdgeLabel.Builder {

    private Id id;
    private String name;
    private String sourceLabel;
    private String targetLabel;
    private Frequency frequency;
    private Set properties;
    private List sortKeys;
    private Set nullableKeys;
    private long ttl;
    private String ttlStartTime;
    private Boolean enableLabelIndex;
    private Userdata userdata;
    private boolean checkExist;

    public EdgeLabelBuilder(SchemaTransaction transaction,
                            HugeGraph graph, String name) {
        super(transaction, graph);
        E.checkNotNull(name, "name");
        this.id = null;
        this.name = name;
        this.sourceLabel = null;
        this.targetLabel = null;
        this.frequency = Frequency.DEFAULT;
        this.properties = new HashSet<>();
        this.sortKeys = new ArrayList<>();
        this.nullableKeys = new HashSet<>();
        this.ttl = 0L;
        this.ttlStartTime = null;
        this.enableLabelIndex = null;
        this.userdata = new Userdata();
        this.checkExist = true;
    }

    public EdgeLabelBuilder(SchemaTransaction transaction,
                            HugeGraph graph, EdgeLabel copy) {
        super(transaction, graph);
        E.checkNotNull(copy, "copy");
        HugeGraph origin = copy.graph();
        this.id = null;
        this.name = copy.name();
        this.sourceLabel = copy.sourceLabelName();
        this.targetLabel = copy.targetLabelName();
        this.frequency = copy.frequency();
        this.properties = mapPkId2Name(origin, copy.properties());
        this.sortKeys = mapPkId2Name(origin, copy.sortKeys());
        this.nullableKeys = mapPkId2Name(origin, copy.nullableKeys());
        this.ttl = copy.ttl();
        this.ttlStartTime = copy.ttlStartTimeName();
        this.enableLabelIndex = copy.enableLabelIndex();
        this.userdata = new Userdata(copy.userdata());
        this.checkExist = false;
    }

    @Override
    public EdgeLabel build() {
        Id id = this.validOrGenerateId(HugeType.EDGE_LABEL,
                                       this.id, this.name);
        HugeGraph graph = this.graph();
        EdgeLabel edgeLabel = new EdgeLabel(graph, id, this.name);
        edgeLabel.sourceLabel(graph.vertexLabel(this.sourceLabel).id());
        edgeLabel.targetLabel(graph.vertexLabel(this.targetLabel).id());
        edgeLabel.frequency(this.frequency == Frequency.DEFAULT ?
                            Frequency.SINGLE : this.frequency);
        edgeLabel.ttl(this.ttl);
        if (this.ttlStartTime != null) {
            edgeLabel.ttlStartTime(this.graph().propertyKey(
                                   this.ttlStartTime).id());
        }
        edgeLabel.enableLabelIndex(this.enableLabelIndex == null ||
                                   this.enableLabelIndex);
        for (String key : this.properties) {
            PropertyKey propertyKey = graph.propertyKey(key);
            edgeLabel.property(propertyKey.id());
        }
        for (String key : this.sortKeys) {
            PropertyKey propertyKey = graph.propertyKey(key);
            edgeLabel.sortKey(propertyKey.id());
        }
        for (String key : this.nullableKeys) {
            PropertyKey propertyKey = graph.propertyKey(key);
            edgeLabel.nullableKey(propertyKey.id());
        }
        edgeLabel.userdata(this.userdata);
        return edgeLabel;
    }

    @Override
    public EdgeLabel create() {
        HugeType type = HugeType.EDGE_LABEL;
        this.checkSchemaName(this.name);

        return this.lockCheckAndCreateSchema(type, this.name, name -> {
            EdgeLabel edgeLabel = this.edgeLabelOrNull(this.name);
            if (edgeLabel != null) {
                if (this.checkExist || !hasSameProperties(edgeLabel)) {
                    throw new ExistedException(type, this.name);
                }
                return edgeLabel;
            }
            this.checkSchemaIdIfRestoringMode(type, this.id);

            // These methods will check params and fill to member variables
            this.checkRelation();
            this.checkProperties(Action.INSERT);
            this.checkSortKeys();
            this.checkNullableKeys(Action.INSERT);
            Userdata.check(this.userdata, Action.INSERT);
            this.checkTtl();
            this.checkUserdata(Action.INSERT);

            edgeLabel = this.build();
            assert edgeLabel.name().equals(name);
            this.graph().addEdgeLabel(edgeLabel);
            return edgeLabel;
        });
    }

    /**
     * Check whether this has same properties with existedEdgeLabel.
     * Only sourceId, targetId, frequency, enableLabelIndex, properties, sortKeys,
     * nullableKeys are checked.
     * The id, ttl, ttlStartTime, userdata are not checked.
     * @param existedEdgeLabel to be compared with
     * @return true if this has same properties with existedVertexLabel
     */
    private boolean hasSameProperties(EdgeLabel existedEdgeLabel) {
        HugeGraph graph = this.graph();
        Id sourceId = graph.vertexLabel(this.sourceLabel).id();
        if (!existedEdgeLabel.sourceLabel().equals(sourceId)) {
            return false;
        }

        Id targetId = graph.vertexLabel(this.targetLabel).id();
        if (!existedEdgeLabel.targetLabel().equals(targetId)) {
            return false;
        }

        if ((this.frequency == Frequency.DEFAULT &&
             existedEdgeLabel.frequency() != Frequency.SINGLE) ||
            (this.frequency != Frequency.DEFAULT &&
             this.frequency != existedEdgeLabel.frequency())) {
            return false;
        }

        // this.enableLabelIndex == null, it means true.
        if (this.enableLabelIndex == null || this.enableLabelIndex) {
            if (!existedEdgeLabel.enableLabelIndex()) {
                return false;
            }
        } else { // this false
            if (existedEdgeLabel.enableLabelIndex() == true) {
                return false;
            }
        }

        Set existedProperties = existedEdgeLabel.properties();
        if (this.properties.size() != existedProperties.size()) {
            return false;
        }
        for (String key : this.properties) {
            PropertyKey propertyKey = graph.propertyKey(key);
            if (!existedProperties.contains(propertyKey.id())) {
                return false;
            }
        }

        List existedSortKeys = existedEdgeLabel.sortKeys();
        if (this.sortKeys.size() != existedSortKeys.size()) {
            return false;
        }
        for (String key : this.sortKeys) {
            PropertyKey propertyKey = graph.propertyKey(key);
            if (!existedSortKeys.contains(propertyKey.id())) {
                return false;
            }
        }

        Set existedNullableKeys = existedEdgeLabel.nullableKeys();
        if (this.nullableKeys.size() != existedNullableKeys.size()) {
            return false;
        }
        for (String nullableKeyName : this.nullableKeys) {
            PropertyKey nullableKey = graph.propertyKey(nullableKeyName);
            if (!existedNullableKeys.contains(nullableKey.id())) {
                return false;
            }
        }
        return true;
    }

    @Override
    public EdgeLabel append() {
        EdgeLabel edgeLabel = this.edgeLabelOrNull(this.name);
        if (edgeLabel == null) {
            throw new NotFoundException("Can't update edge label '%s' " +
                                        "since it doesn't exist", this.name);
        }
        // These methods will check params and fill to member variables
        this.checkStableVars();
        this.checkProperties(Action.APPEND);
        this.checkNullableKeys(Action.APPEND);
        Userdata.check(this.userdata, Action.APPEND);

        for (String key : this.properties) {
            PropertyKey propertyKey = this.graph().propertyKey(key);
            edgeLabel.property(propertyKey.id());
        }
        for (String key : this.nullableKeys) {
            PropertyKey propertyKey = this.graph().propertyKey(key);
            edgeLabel.nullableKey(propertyKey.id());
        }
        edgeLabel.userdata(this.userdata);
        this.graph().addEdgeLabel(edgeLabel);
        return edgeLabel;
    }

    @Override
    public EdgeLabel eliminate() {
        EdgeLabel edgeLabel = this.edgeLabelOrNull(this.name);
        if (edgeLabel == null) {
            throw new NotFoundException("Can't update edge label '%s' " +
                                        "since it doesn't exist", this.name);
        }
        // Only allowed to eliminate user data
        this.checkStableVars();
        this.checkProperties(Action.ELIMINATE);
        this.checkNullableKeys(Action.ELIMINATE);
        Userdata.check(this.userdata, Action.ELIMINATE);

        edgeLabel.removeUserdata(this.userdata);
        this.graph().addEdgeLabel(edgeLabel);
        return edgeLabel;
    }

    @Override
    public Id remove() {
        EdgeLabel edgeLabel = this.edgeLabelOrNull(this.name);
        if (edgeLabel == null) {
            return null;
        }
        return this.graph().removeEdgeLabel(edgeLabel.id());
    }

    @Override
    public Id rebuildIndex() {
        EdgeLabel edgeLabel = this.edgeLabelOrNull(this.name);
        if (edgeLabel == null) {
            return null;
        }
        return this.graph().rebuildIndex(edgeLabel);
    }

    @Override
    public EdgeLabelBuilder id(long id) {
        E.checkArgument(id != 0L, "Not allowed to assign 0 as edge label id");
        this.id = IdGenerator.of(id);
        return this;
    }

    @Override
    public EdgeLabelBuilder properties(String... properties) {
        this.properties.addAll(Arrays.asList(properties));
        return this;
    }

    @Override
    public EdgeLabelBuilder nullableKeys(String... keys) {
        this.nullableKeys.addAll(Arrays.asList(keys));
        return this;
    }

    @Override
    public EdgeLabelBuilder sortKeys(String... keys) {
        if (keys.length == 0) {
            return this;
        }

        E.checkArgument(this.sortKeys.isEmpty(),
                        "Not allowed to assign sort keys multitimes");

        List sortKeys = Arrays.asList(keys);
        E.checkArgument(CollectionUtil.allUnique(sortKeys),
                        "Invalid sort keys %s, which contains some " +
                        "duplicate properties", sortKeys);
        this.sortKeys.addAll(sortKeys);
        return this;
    }

    @Override
    public EdgeLabelBuilder link(String sourceLabel, String targetLabel) {
        this.sourceLabel(sourceLabel);
        this.targetLabel(targetLabel);
        return this;
    }

    @Override
    public EdgeLabelBuilder sourceLabel(String label) {
        this.sourceLabel = label;
        return this;
    }

    @Override
    public EdgeLabelBuilder targetLabel(String label) {
        this.targetLabel = label;
        return this;
    }

    @Override
    public EdgeLabelBuilder singleTime() {
        this.frequency = Frequency.SINGLE;
        return this;
    }

    @Override
    public EdgeLabelBuilder multiTimes() {
        this.frequency = Frequency.MULTIPLE;
        return this;
    }

    @Override
    public EdgeLabelBuilder ifNotExist() {
        this.checkExist = false;
        return this;
    }

    @Override
    public EdgeLabelBuilder frequency(Frequency frequency) {
        this.frequency = frequency;
        return this;
    }

    @Override
    public EdgeLabel.Builder ttl(long ttl) {
        this.ttl = ttl;
        return this;
    }

    @Override
    public EdgeLabel.Builder ttlStartTime(String ttlStartTime) {
        this.ttlStartTime = ttlStartTime;
        return this;
    }

    @Override
    public EdgeLabelBuilder enableLabelIndex(boolean enable) {
        this.enableLabelIndex = enable;
        return this;
    }

    @Override
    public EdgeLabelBuilder userdata(String key, Object value) {
        this.userdata.put(key, value);
        return this;
    }

    @Override
    public EdgeLabelBuilder userdata(Map userdata) {
        this.userdata.putAll(userdata);
        return this;
    }

    @Override
    public EdgeLabelBuilder checkExist(boolean checkExist) {
        this.checkExist = checkExist;
        return this;
    }

    private void checkProperties(Action action) {
        switch (action) {
            case INSERT:
            case APPEND:
                for (String key : this.properties) {
                    this.graph().propertyKey(key);
                }
                break;
            case ELIMINATE:
                if (!this.properties.isEmpty()) {
                    throw new NotAllowException(
                              "Not support to eliminate properties " +
                              "for edge label currently");
                }
                break;
            case DELETE:
                break;
            default:
                throw new AssertionError(String.format(
                          "Unknown schema action '%s'", action));
        }
    }

    @SuppressWarnings("unchecked")
    private void checkNullableKeys(Action action) {
        // Not using switch-case to avoid indent too much
        if (action == Action.ELIMINATE) {
            if (!this.nullableKeys.isEmpty()) {
                throw new NotAllowException(
                          "Not support to eliminate nullableKeys " +
                          "for edge label currently");
            }
            return;
        }

        EdgeLabel edgeLabel = this.edgeLabelOrNull(this.name);
        // The originProps is empty when firstly create edge label
        List originProps = edgeLabel == null ?
                                   ImmutableList.of() :
                                   this.graph()
                                       .mapPkId2Name(edgeLabel.properties());
        Set appendProps = this.properties;

        E.checkArgument(CollectionUtil.union(originProps, appendProps)
                        .containsAll(this.nullableKeys),
                        "The nullableKeys: %s to be created or appended " +
                        "must belong to the origin/new properties: %s/%s ",
                        this.nullableKeys, originProps, appendProps);

        E.checkArgument(!CollectionUtil.hasIntersection(this.sortKeys,
                                                        this.nullableKeys),
                        "The nullableKeys: %s are not allowed to " +
                        "belong to sortKeys: %s of edge label '%s'",
                        this.nullableKeys, this.sortKeys, this.name);

        if (action == Action.APPEND) {
            Collection newAddedProps = CollectionUtils.subtract(
                                               appendProps, originProps);
            E.checkArgument(this.nullableKeys.containsAll(newAddedProps),
                            "The new added properties: %s must be nullable",
                            newAddedProps);
        }
    }

    private void checkSortKeys() {
        if (this.frequency == Frequency.SINGLE ||
            this.frequency == Frequency.DEFAULT) {
            E.checkArgument(this.sortKeys.isEmpty(),
                            "EdgeLabel can't contain sortKeys " +
                            "when the cardinality property is single");
        } else {
            E.checkState(this.sortKeys != null,
                         "The sortKeys can't be null when the " +
                         "cardinality property is multiple");
            E.checkArgument(!this.sortKeys.isEmpty(),
                            "EdgeLabel must contain sortKeys " +
                            "when the cardinality property is multiple");
        }

        if (this.sortKeys.isEmpty()) {
            return;
        }

        // Check whether the properties contains the specified keys
        E.checkArgument(!this.properties.isEmpty(),
                        "The properties can't be empty when exist " +
                        "sort keys for edge label '%s'", this.name);

        for (String key : this.sortKeys) {
            E.checkArgument(this.properties.contains(key),
                            "The sort key '%s' must be contained in " +
                            "properties '%s' for edge label '%s'",
                            key, this.name, this.properties);
        }
    }

    private void checkRelation() {
        String srcLabel = this.sourceLabel;
        String tgtLabel = this.targetLabel;

        E.checkArgument(srcLabel != null && tgtLabel != null,
                        "Must set source and target label " +
                        "for edge label '%s'", this.name);

        E.checkArgumentNotNull(this.vertexLabelOrNull(srcLabel),
                               "Undefined source vertex label '%s' " +
                               "in edge label '%s'", srcLabel, this.name);
        E.checkArgumentNotNull(this.vertexLabelOrNull(tgtLabel),
                               "Undefined target vertex label '%s' " +
                               "in edge label '%s'", tgtLabel, this.name);
    }

    private void checkStableVars() {
        if (this.sourceLabel != null) {
            throw new NotAllowException(
                      "Not allowed to update source label " +
                      "for edge label '%s', it must be null", this.name);
        }
        if (this.targetLabel != null) {
            throw new NotAllowException(
                      "Not allowed to update target label " +
                      "for edge label '%s', it must be null", this.name);
        }
        if (this.frequency != Frequency.DEFAULT) {
            throw new NotAllowException(
                      "Not allowed to update frequency " +
                      "for edge label '%s'", this.name);
        }
        if (!this.sortKeys.isEmpty()) {
            throw new NotAllowException(
                      "Not allowed to update sort keys " +
                      "for edge label '%s'", this.name);
        }
        if (this.enableLabelIndex != null) {
            throw new NotAllowException(
                      "Not allowed to update enable_label_index " +
                      "for edge label '%s'", this.name);
        }
    }

    private void checkTtl() {
        E.checkArgument(this.ttl >= 0,
                        "The ttl must be >= 0, but got: %s", this.ttl);
        if (this.ttl == 0L) {
            E.checkArgument(this.ttlStartTime == null,
                            "Can't set ttl start time if ttl is not set");
            return;
        }
        VertexLabel source = this.graph().vertexLabel(this.sourceLabel);
        VertexLabel target = this.graph().vertexLabel(this.targetLabel);
        E.checkArgument((source.ttl() == 0L || this.ttl <= source.ttl()) &&
                        (target.ttl() == 0L || this.ttl <= target.ttl()),
                        "The ttl(%s) of edge label '%s' should less than " +
                        "ttl(%s) of source label '%s' and ttl(%s) of target " +
                        "label '%s'", this.ttl, this.name,
                        source.ttl(), this.sourceLabel,
                        target.ttl(), this.targetLabel);
        if (this.ttlStartTime == null) {
            return;
        }
        // Check whether the properties contains the specified keys
        E.checkArgument(!this.properties.isEmpty(),
                        "The properties can't be empty when exist " +
                        "ttl start time for edge label '%s'", this.name);
        E.checkArgument(this.properties.contains(this.ttlStartTime),
                        "The ttl start time '%s' must be contained in " +
                        "properties '%s' for edge label '%s'",
                        this.ttlStartTime, this.name, this.properties);
        PropertyKey pkey = this.graph().propertyKey(this.ttlStartTime);
        E.checkArgument(pkey.dataType().isDate(),
                        "The ttl start time property must be date type," +
                        "but got '%s(%s)'", this.ttlStartTime, pkey.dataType());
    }

    private void checkUserdata(Action action) {
        switch (action) {
            case INSERT:
            case APPEND:
                for (Map.Entry e : this.userdata.entrySet()) {
                    if (e.getValue() == null) {
                        throw new NotAllowException(
                                  "Not allowed pass null userdata value when " +
                                  "create or append edge label");
                    }
                }
                break;
            case ELIMINATE:
            case DELETE:
                // pass
                break;
            default:
                throw new AssertionError(String.format(
                          "Unknown schema action '%s'", action));
        }
    }

    private static Set mapPkId2Name(HugeGraph graph, Set ids) {
        return new HashSet<>(graph.mapPkId2Name(ids));
    }

    private static List mapPkId2Name(HugeGraph graph, List ids) {
        return graph.mapPkId2Name(ids);
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy