blob: 4f5d8d04c437288f009206b3d1d9ea2478f7b62f [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.structure;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import org.apache.hugegraph.HugeException;
import org.apache.hugegraph.HugeGraph;
import org.apache.hugegraph.backend.id.Id;
import org.apache.hugegraph.backend.id.Id.IdType;
import org.apache.hugegraph.backend.id.IdGenerator;
import org.apache.hugegraph.backend.id.SplicingIdGenerator;
import org.apache.hugegraph.schema.IndexLabel;
import org.apache.hugegraph.schema.SchemaElement;
import org.apache.hugegraph.type.HugeType;
import org.apache.hugegraph.type.define.DataType;
import org.apache.hugegraph.backend.serializer.BytesBuffer;
import org.apache.hugegraph.util.E;
import org.apache.hugegraph.util.HashUtil;
import org.apache.hugegraph.util.InsertionOrderUtil;
import org.apache.hugegraph.util.NumericUtil;
public class HugeIndex implements GraphType, Cloneable {
private final HugeGraph graph;
private Object fieldValues;
private IndexLabel indexLabel;
private Set<IdWithExpiredTime> elementIds;
public HugeIndex(HugeGraph graph, IndexLabel indexLabel) {
E.checkNotNull(graph, "graph");
E.checkNotNull(indexLabel, "label");
E.checkNotNull(indexLabel.id(), "label id");
this.graph = graph;
this.indexLabel = indexLabel;
this.elementIds = new LinkedHashSet<>();
this.fieldValues = null;
}
@Override
public String name() {
return this.indexLabel.name();
}
@Override
public HugeType type() {
if (this.indexLabel == IndexLabel.label(HugeType.VERTEX)) {
return HugeType.VERTEX_LABEL_INDEX;
} else if (this.indexLabel == IndexLabel.label(HugeType.EDGE)) {
return HugeType.EDGE_LABEL_INDEX;
}
return this.indexLabel.indexType().type();
}
public HugeGraph graph() {
return this.graph;
}
public Id id() {
return formatIndexId(type(), this.indexLabelId(), this.fieldValues());
}
public Id hashId() {
return formatIndexHashId(type(), this.indexLabelId(), this.fieldValues());
}
public Object fieldValues() {
return this.fieldValues;
}
public void fieldValues(Object fieldValues) {
this.fieldValues = fieldValues;
}
public Id indexLabelId() {
return this.indexLabel.id();
}
public IndexLabel indexLabel() {
return this.indexLabel;
}
public IdWithExpiredTime elementIdWithExpiredTime() {
E.checkState(this.elementIds.size() == 1,
"Expect one element id, actual %s",
this.elementIds.size());
return this.elementIds.iterator().next();
}
public Id elementId() {
return this.elementIdWithExpiredTime().id();
}
public Set<Id> elementIds() {
Set<Id> ids = InsertionOrderUtil.newSet(this.elementIds.size());
for (IdWithExpiredTime idWithExpiredTime : this.elementIds) {
ids.add(idWithExpiredTime.id());
}
return Collections.unmodifiableSet(ids);
}
public Set<IdWithExpiredTime> expiredElementIds() {
long now = this.graph.now();
Set<IdWithExpiredTime> expired = InsertionOrderUtil.newSet();
for (IdWithExpiredTime id : this.elementIds) {
if (0L < id.expiredTime && id.expiredTime < now) {
expired.add(id);
}
}
this.elementIds.removeAll(expired);
return expired;
}
public void elementIds(Id elementId) {
this.elementIds(elementId, 0L);
}
public void elementIds(Id elementId, long expiredTime) {
this.elementIds.add(new IdWithExpiredTime(elementId, expiredTime));
}
public void resetElementIds() {
this.elementIds = new LinkedHashSet<>();
}
public long expiredTime() {
return this.elementIdWithExpiredTime().expiredTime();
}
public boolean hasTtl() {
if (this.indexLabel.system()) {
return false;
}
return this.indexLabel.baseLabel().ttl() > 0L;
}
public long ttl() {
return this.expiredTime() - this.graph.now();
}
@Override
public HugeIndex clone() {
try {
return (HugeIndex) super.clone();
} catch (CloneNotSupportedException e) {
throw new HugeException("Failed to clone HugeIndex", e);
}
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof HugeIndex)) {
return false;
}
HugeIndex other = (HugeIndex) obj;
return this.id().equals(other.id());
}
@Override
public int hashCode() {
return this.id().hashCode();
}
@Override
public String toString() {
return String.format("{label=%s<%s>, fieldValues=%s, elementIds=%s}",
this.indexLabel.name(),
this.indexLabel.indexType().string(),
this.fieldValues, this.elementIds);
}
public static Id formatIndexHashId(HugeType type, Id indexLabel,
Object fieldValues) {
E.checkState(!type.isRangeIndex(),
"RangeIndex can't return a hash id");
String value = fieldValues == null ? "" : fieldValues.toString();
return formatIndexId(type, indexLabel, HashUtil.hash(value));
}
public static Id formatIndexId(HugeType type, Id indexLabelId,
Object fieldValues) {
if (type.isStringIndex()) {
String value = "";
if (fieldValues instanceof Id) {
value = IdGenerator.asStoredString((Id) fieldValues);
} else if (fieldValues != null) {
value = fieldValues.toString();
}
/*
* Modify order between index label and field-values to put the
* index label in front(hugegraph-1317)
*/
String strIndexLabelId = IdGenerator.asStoredString(indexLabelId);
return SplicingIdGenerator.splicing(strIndexLabelId, value);
} else {
assert type.isRangeIndex();
int length = type.isRange4Index() ? 4 : 8;
BytesBuffer buffer = BytesBuffer.allocate(4 + length);
buffer.writeInt(SchemaElement.schemaId(indexLabelId));
if (fieldValues != null) {
E.checkState(fieldValues instanceof Number,
"Field value of range index must be number:" +
" %s", fieldValues.getClass().getSimpleName());
byte[] bytes = number2bytes((Number) fieldValues);
buffer.write(bytes);
}
return buffer.asId();
}
}
public static HugeIndex parseIndexId(HugeGraph graph, HugeType type,
byte[] id) {
Object values;
IndexLabel indexLabel;
if (type.isStringIndex()) {
Id idObject = IdGenerator.of(id, IdType.STRING);
String[] parts = SplicingIdGenerator.parse(idObject);
E.checkState(parts.length == 2, "Invalid secondary index id");
Id label = IdGenerator.ofStoredString(parts[0], IdType.LONG);
indexLabel = IndexLabel.label(graph, label);
values = parts[1];
} else {
assert type.isRange4Index() || type.isRange8Index();
final int labelLength = 4;
E.checkState(id.length > labelLength, "Invalid range index id");
BytesBuffer buffer = BytesBuffer.wrap(id);
Id label = IdGenerator.of(buffer.readInt());
indexLabel = IndexLabel.label(graph, label);
List<Id> fields = indexLabel.indexFields();
E.checkState(fields.size() == 1, "Invalid range index fields");
DataType dataType = graph.propertyKey(fields.get(0)).dataType();
E.checkState(dataType.isNumber() || dataType.isDate(),
"Invalid range index field type");
Class<?> clazz = dataType.isNumber() ?
dataType.clazz() : DataType.LONG.clazz();
values = bytes2number(buffer.read(id.length - labelLength), clazz);
}
HugeIndex index = new HugeIndex(graph, indexLabel);
index.fieldValues(values);
return index;
}
public static byte[] number2bytes(Number number) {
if (number instanceof Byte) {
// Handle byte as integer to store as 4 bytes in RANGE4_INDEX
number = number.intValue();
}
return NumericUtil.numberToSortableBytes(number);
}
public static Number bytes2number(byte[] bytes, Class<?> clazz) {
return NumericUtil.sortableBytesToNumber(bytes, clazz);
}
public static class IdWithExpiredTime {
private Id id;
private long expiredTime;
public IdWithExpiredTime(Id id, long expiredTime) {
this.id = id;
this.expiredTime = expiredTime;
}
public Id id() {
return this.id;
}
public long expiredTime() {
return this.expiredTime;
}
@Override
public String toString() {
return String.format("%s(%s)", this.id, this.expiredTime);
}
}
}