blob: c59933f365a543c5596ac4e269d8855fe4d443a4 [file] [log] [blame]
/*
* 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.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 org.apache.hugegraph.backend.id.Id;
import org.apache.hugegraph.backend.id.IdGenerator;
import org.apache.hugegraph.backend.tx.SchemaTransaction;
import org.apache.hugegraph.schema.PropertyKey;
import org.apache.hugegraph.schema.Userdata;
import org.apache.hugegraph.schema.VertexLabel;
import org.apache.hugegraph.HugeGraph;
import org.apache.hugegraph.exception.ExistedException;
import org.apache.hugegraph.exception.NotAllowException;
import org.apache.hugegraph.exception.NotFoundException;
import org.apache.hugegraph.type.HugeType;
import org.apache.hugegraph.type.define.Action;
import org.apache.hugegraph.type.define.IdStrategy;
import org.apache.hugegraph.util.CollectionUtil;
import org.apache.hugegraph.util.E;
import com.google.common.collect.ImmutableList;
public class VertexLabelBuilder extends AbstractBuilder
implements VertexLabel.Builder {
private Id id;
private String name;
private IdStrategy idStrategy;
private Set<String> properties;
private List<String> primaryKeys;
private Set<String> nullableKeys;
private long ttl;
private String ttlStartTime;
private Boolean enableLabelIndex;
private Userdata userdata;
private boolean checkExist;
public VertexLabelBuilder(SchemaTransaction transaction,
HugeGraph graph, String name) {
super(transaction, graph);
E.checkNotNull(name, "name");
this.id = null;
this.name = name;
this.idStrategy = IdStrategy.DEFAULT;
this.properties = new HashSet<>();
this.primaryKeys = new ArrayList<>();
this.nullableKeys = new HashSet<>();
this.ttl = 0L;
this.ttlStartTime = null;
this.enableLabelIndex = null;
this.userdata = new Userdata();
this.checkExist = true;
}
public VertexLabelBuilder(SchemaTransaction transaction,
HugeGraph graph, VertexLabel copy) {
super(transaction, graph);
E.checkNotNull(copy, "copy");
HugeGraph origin = copy.graph();
this.id = null;
this.name = copy.name();
this.idStrategy = copy.idStrategy();
this.properties = mapPkId2Name(origin, copy.properties());
this.primaryKeys = mapPkId2Name(origin, copy.primaryKeys());
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 VertexLabel build() {
Id id = this.validOrGenerateId(HugeType.VERTEX_LABEL,
this.id, this.name);
VertexLabel vertexLabel = new VertexLabel(this.graph(), id, this.name);
vertexLabel.idStrategy(this.idStrategy);
vertexLabel.enableLabelIndex(this.enableLabelIndex == null ||
this.enableLabelIndex);
vertexLabel.ttl(this.ttl);
if (this.ttlStartTime != null) {
vertexLabel.ttlStartTime(this.graph().propertyKey(
this.ttlStartTime).id());
}
// Assign properties
for (String key : this.properties) {
PropertyKey propertyKey = this.graph().propertyKey(key);
vertexLabel.property(propertyKey.id());
}
for (String key : this.primaryKeys) {
PropertyKey propertyKey = this.graph().propertyKey(key);
vertexLabel.primaryKey(propertyKey.id());
}
for (String key : this.nullableKeys) {
PropertyKey propertyKey = this.graph().propertyKey(key);
vertexLabel.nullableKey(propertyKey.id());
}
vertexLabel.userdata(this.userdata);
return vertexLabel;
}
@Override
public VertexLabel create() {
HugeType type = HugeType.VERTEX_LABEL;
this.checkSchemaName(this.name);
return this.lockCheckAndCreateSchema(type, this.name, name -> {
VertexLabel vertexLabel = this.vertexLabelOrNull(name);
if (vertexLabel != null) {
if (this.checkExist || !hasSameProperties(vertexLabel)) {
throw new ExistedException(type, name);
}
return vertexLabel;
}
this.checkSchemaIdIfRestoringMode(type, this.id);
this.checkProperties(Action.INSERT);
this.checkIdStrategy();
this.checkNullableKeys(Action.INSERT);
Userdata.check(this.userdata, Action.INSERT);
this.checkTtl();
this.checkUserdata(Action.INSERT);
vertexLabel = this.build();
assert vertexLabel.name().equals(name);
this.graph().addVertexLabel(vertexLabel);
return vertexLabel;
});
}
/**
* Check whether this has same properties with existedVertexLabel.
* Only properties, primaryKeys, nullableKeys, enableLabelIndex are checked.
* The id, idStrategy, checkExist, userdata are not checked.
* @param existedVertexLabel to be compared with
* @return true if this has same properties with existedVertexLabel
*/
private boolean hasSameProperties(VertexLabel existedVertexLabel) {
HugeGraph graph = this.graph();
Set<Id> existedProperties = existedVertexLabel.properties();
if (this.properties.size() != existedProperties.size()) {
return false;
}
for (String propertyName : this.properties) {
PropertyKey propertyKey = graph.propertyKey(propertyName);
if (!existedProperties.contains(propertyKey.id())) {
return false;
}
}
List<Id> existedPrimaryKeys = existedVertexLabel.primaryKeys();
if (this.primaryKeys.size() != existedPrimaryKeys.size()) {
return false;
}
for (String primaryKeyName : this.primaryKeys) {
PropertyKey primaryKey = graph.propertyKey(primaryKeyName);
if (!existedPrimaryKeys.contains(primaryKey.id())) {
return false;
}
}
Set<Id> existedNullableKeys = existedVertexLabel.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;
}
}
// this.enableLabelIndex == null, it means true.
if (this.enableLabelIndex == null || this.enableLabelIndex) {
return existedVertexLabel.enableLabelIndex();
} else { // this.enableLabelIndex is false
return !existedVertexLabel.enableLabelIndex();
}
}
@Override
public VertexLabel append() {
VertexLabel vertexLabel = this.vertexLabelOrNull(this.name);
if (vertexLabel == null) {
throw new NotFoundException("Can't update vertex label '%s' " +
"since it doesn't exist", this.name);
}
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);
vertexLabel.property(propertyKey.id());
}
for (String key : this.nullableKeys) {
PropertyKey propertyKey = this.graph().propertyKey(key);
vertexLabel.nullableKey(propertyKey.id());
}
vertexLabel.userdata(this.userdata);
this.graph().updateVertexLabel(vertexLabel);
return vertexLabel;
}
@Override
public VertexLabel eliminate() {
VertexLabel vertexLabel = this.vertexLabelOrNull(this.name);
if (vertexLabel == null) {
throw new NotFoundException("Can't update vertex 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);
vertexLabel.removeUserdata(this.userdata);
this.graph().updateVertexLabel(vertexLabel);
return vertexLabel;
}
@Override
public Id remove() {
VertexLabel vertexLabel = this.vertexLabelOrNull(this.name);
if (vertexLabel == null) {
return null;
}
return this.graph().removeVertexLabel(vertexLabel.id());
}
@Override
public Id rebuildIndex() {
VertexLabel vertexLabel = this.vertexLabelOrNull(this.name);
if (vertexLabel == null) {
return null;
}
return this.graph().rebuildIndex(vertexLabel);
}
@Override
public VertexLabelBuilder id(long id) {
E.checkArgument(id != 0L,
"Not allowed to assign 0 as vertex label id");
this.id = IdGenerator.of(id);
return this;
}
@Override
public VertexLabelBuilder idStrategy(IdStrategy idStrategy) {
E.checkArgument(this.idStrategy == IdStrategy.DEFAULT ||
this.idStrategy == idStrategy,
"Not allowed to change id strategy for " +
"vertex label '%s'", this.name);
this.idStrategy = idStrategy;
return this;
}
@Override
public VertexLabelBuilder useAutomaticId() {
E.checkArgument(this.idStrategy == IdStrategy.DEFAULT ||
this.idStrategy == IdStrategy.AUTOMATIC,
"Not allowed to change id strategy for " +
"vertex label '%s'", this.name);
this.idStrategy = IdStrategy.AUTOMATIC;
return this;
}
@Override
public VertexLabelBuilder usePrimaryKeyId() {
E.checkArgument(this.idStrategy == IdStrategy.DEFAULT ||
this.idStrategy == IdStrategy.PRIMARY_KEY,
"Not allowed to change id strategy for " +
"vertex label '%s'", this.name);
this.idStrategy = IdStrategy.PRIMARY_KEY;
return this;
}
@Override
public VertexLabelBuilder useCustomizeStringId() {
E.checkArgument(this.idStrategy == IdStrategy.DEFAULT ||
this.idStrategy == IdStrategy.CUSTOMIZE_STRING,
"Not allowed to change id strategy for " +
"vertex label '%s'", this.name);
this.idStrategy = IdStrategy.CUSTOMIZE_STRING;
return this;
}
@Override
public VertexLabelBuilder useCustomizeNumberId() {
E.checkArgument(this.idStrategy == IdStrategy.DEFAULT ||
this.idStrategy == IdStrategy.CUSTOMIZE_NUMBER,
"Not allowed to change id strategy for " +
"vertex label '%s'", this.name);
this.idStrategy = IdStrategy.CUSTOMIZE_NUMBER;
return this;
}
@Override
public VertexLabelBuilder useCustomizeUuidId() {
E.checkArgument(this.idStrategy == IdStrategy.DEFAULT ||
this.idStrategy == IdStrategy.CUSTOMIZE_UUID,
"Not allowed to change id strategy for " +
"vertex label '%s'", this.name);
this.idStrategy = IdStrategy.CUSTOMIZE_UUID;
return this;
}
@Override
public VertexLabelBuilder properties(String... properties) {
this.properties.addAll(Arrays.asList(properties));
return this;
}
@Override
public VertexLabelBuilder primaryKeys(String... keys) {
if (keys.length == 0) {
return this;
}
E.checkArgument(this.primaryKeys.isEmpty(),
"Not allowed to assign primary keys multitimes");
List<String> primaryKeys = Arrays.asList(keys);
E.checkArgument(CollectionUtil.allUnique(primaryKeys),
"Invalid primary keys %s, which contains some " +
"duplicate properties", primaryKeys);
this.primaryKeys.addAll(primaryKeys);
return this;
}
@Override
public VertexLabelBuilder nullableKeys(String... keys) {
this.nullableKeys.addAll(Arrays.asList(keys));
return this;
}
@Override
public VertexLabel.Builder ttl(long ttl) {
this.ttl = ttl;
return this;
}
@Override
public VertexLabel.Builder ttlStartTime(String ttlStartTime) {
this.ttlStartTime = ttlStartTime;
return this;
}
@Override
public VertexLabelBuilder enableLabelIndex(boolean enable) {
this.enableLabelIndex = enable;
return this;
}
@Override
public VertexLabelBuilder userdata(String key, Object value) {
this.userdata.put(key, value);
return this;
}
@Override
public VertexLabelBuilder userdata(Map<String, Object> userdata) {
this.userdata.putAll(userdata);
return this;
}
@Override
public VertexLabelBuilder ifNotExist() {
this.checkExist = false;
return this;
}
@Override
public VertexLabelBuilder 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 vertex 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 vertex label currently");
}
return;
}
VertexLabel vertexLabel = this.vertexLabelOrNull(this.name);
// The originProps is empty when firstly create vertex label
List<String> originProps = vertexLabel == null ?
ImmutableList.of() :
this.graph()
.mapPkId2Name(vertexLabel.properties());
Set<String> 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);
List<String> primaryKeys = vertexLabel == null ?
this.primaryKeys :
this.graph()
.mapPkId2Name(vertexLabel.primaryKeys());
E.checkArgument(!CollectionUtil.hasIntersection(primaryKeys,
this.nullableKeys),
"The nullableKeys: %s are not allowed to " +
"belong to primaryKeys: %s of vertex label '%s'",
this.nullableKeys, primaryKeys, this.name);
if (action == Action.APPEND) {
Collection<String> newAddedProps = CollectionUtils.subtract(
appendProps, originProps);
E.checkArgument(this.nullableKeys.containsAll(newAddedProps),
"The new added properties: %s must be nullable",
newAddedProps);
}
}
private void checkIdStrategy() {
IdStrategy strategy = this.idStrategy;
boolean hasPrimaryKey = this.primaryKeys.size() > 0;
switch (strategy) {
case DEFAULT:
if (hasPrimaryKey) {
this.idStrategy = IdStrategy.PRIMARY_KEY;
} else {
this.idStrategy = IdStrategy.AUTOMATIC;
}
break;
case AUTOMATIC:
case CUSTOMIZE_STRING:
case CUSTOMIZE_NUMBER:
case CUSTOMIZE_UUID:
E.checkArgument(!hasPrimaryKey,
"Not allowed to assign primary keys " +
"when using '%s' id strategy", strategy);
break;
case PRIMARY_KEY:
E.checkArgument(hasPrimaryKey,
"Must assign some primary keys " +
"when using '%s' id strategy", strategy);
break;
default:
throw new AssertionError(String.format(
"Unknown id strategy '%s'", strategy));
}
if (this.idStrategy == IdStrategy.PRIMARY_KEY) {
this.checkPrimaryKeys();
}
}
private void checkPrimaryKeys() {
E.checkArgument(this.idStrategy == IdStrategy.DEFAULT ||
this.idStrategy == IdStrategy.PRIMARY_KEY,
"Not allowed to use id strategy '%s' and assign " +
"primary keys at the same time for vertex label '%s'",
this.idStrategy, this.name);
E.checkArgument(!this.properties.isEmpty(),
"The properties of vertex label '%s' " +
"can't be empty when id strategy is '%s'",
this.name, IdStrategy.PRIMARY_KEY);
E.checkNotEmpty(this.primaryKeys, "primary keys", this.name);
// Use loop instead containAll for more detailed exception info.
for (String key : this.primaryKeys) {
E.checkArgument(this.properties.contains(key),
"The primary key '%s' of vertex label '%s' " +
"must be contained in properties: %s",
key, this.name, this.properties);
}
}
private void checkStableVars() {
if (!this.primaryKeys.isEmpty()) {
throw new NotAllowException(
"Not allowed to update primary keys " +
"for vertex label '%s'", this.name);
}
if (this.idStrategy != IdStrategy.DEFAULT) {
throw new NotAllowException(
"Not allowed to update id strategy " +
"for vertex label '%s'", this.name);
}
if (this.enableLabelIndex != null) {
throw new NotAllowException(
"Not allowed to update enable_label_index " +
"for vertex 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;
}
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 vertex 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<String, Object> 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<String> mapPkId2Name(HugeGraph graph, Set<Id> ids) {
return new HashSet<>(graph.mapPkId2Name(ids));
}
private static List<String> mapPkId2Name(HugeGraph graph, List<Id> ids) {
return graph.mapPkId2Name(ids);
}
}