sort results of search index by matched count (#653)

implement: #630

Change-Id: I6cab6741f2e0dfcdb731ffc099334508e33ca2e1
This commit is contained in:
Jermy Li 2019-09-10 14:40:35 +08:00 committed by zhoney
parent 94c2d34ce8
commit d47f309da1
33 changed files with 649 additions and 186 deletions

View File

@ -75,6 +75,11 @@ public class CassandraFeatures implements BackendFeatures {
return true;
}
@Override
public boolean supportsQuerySortByInputIds() {
return false;
}
@Override
public boolean supportsDeleteEdgeByLabel() {
return true;

View File

@ -40,6 +40,7 @@ import com.baidu.hugegraph.backend.store.BackendEntry;
import com.baidu.hugegraph.backend.store.BackendTable;
import com.baidu.hugegraph.backend.store.Shard;
import com.baidu.hugegraph.exception.NotFoundException;
import com.baidu.hugegraph.exception.NotSupportException;
import com.baidu.hugegraph.iterator.ExtendableIterator;
import com.baidu.hugegraph.type.HugeType;
import com.baidu.hugegraph.type.define.HugeKeys;
@ -303,7 +304,7 @@ public abstract class CassandraTable
// return QueryBuilder.like(key, value);
case NEQ:
default:
throw new AssertionError("Unsupported relation: " + relation);
throw new NotSupportException("relation '%s'", relation);
}
}

View File

@ -28,7 +28,7 @@ import com.baidu.hugegraph.util.E;
import com.baidu.hugegraph.util.InsertionOrderUtil;
import com.google.common.collect.ImmutableSet;
public final class IdHolder {
public class IdHolder {
private final ConditionQuery query;
private final Function<ConditionQuery, PageIds> idsFetcher;
@ -67,6 +67,7 @@ public final class IdHolder {
}
public void merge(Set<Id> ids) {
E.checkNotNull(this.ids, "ids");
this.ids.addAll(ids);
}

View File

@ -24,7 +24,7 @@ import java.util.Collection;
import com.baidu.hugegraph.util.E;
public final class IdHolderList extends ArrayList<IdHolder> {
public class IdHolderList extends ArrayList<IdHolder> {
private static final long serialVersionUID = -738694176552424990L;

View File

@ -24,6 +24,7 @@ import java.util.NoSuchElementException;
import com.baidu.hugegraph.backend.query.Query;
import com.baidu.hugegraph.backend.store.BackendEntry;
import com.baidu.hugegraph.backend.tx.AbstractTransaction.QueryResults;
import com.baidu.hugegraph.exception.NotSupportException;
import com.baidu.hugegraph.iterator.Metadatable;
import com.baidu.hugegraph.util.E;
@ -32,15 +33,19 @@ public class PageEntryIterator implements Iterator<BackendEntry>, Metadatable {
private final QueryList queries;
private final long pageSize;
private QueryList.PageIterator results;
private PageInfo pageInfo;
private final PageInfo pageInfo;
private final QueryResults queryResults; // for upper layer
private QueryList.PageIterator pageResults;
private long remaining;
public PageEntryIterator(QueryList queries, long pageSize) {
this.queries = queries;
this.pageSize = pageSize;
this.results = QueryList.PageIterator.EMPTY;
this.pageInfo = this.parsePageState();
this.queryResults = new QueryResults(this);
this.pageResults = QueryList.PageIterator.EMPTY;
this.remaining = queries.parent().limit();
}
@ -55,7 +60,7 @@ public class PageEntryIterator implements Iterator<BackendEntry>, Metadatable {
@Override
public boolean hasNext() {
if (this.results.iterator().hasNext()) {
if (this.pageResults.get().hasNext()) {
return true;
}
return this.fetch();
@ -71,16 +76,17 @@ public class PageEntryIterator implements Iterator<BackendEntry>, Metadatable {
if (this.remaining != Query.NO_LIMIT && this.remaining < pageSize) {
pageSize = this.remaining;
}
this.results = this.queries.fetchNext(this.pageInfo, pageSize);
assert this.results != null;
this.pageResults = this.queries.fetchNext(this.pageInfo, pageSize);
assert this.pageResults != null;
this.queryResults.setQuery(this.pageResults.query());
if (this.results.iterator().hasNext()) {
if (!this.results.hasNextPage()) {
if (this.pageResults.get().hasNext()) {
if (!this.pageResults.hasNextPage()) {
this.pageInfo.increase();
} else {
this.pageInfo.page(this.results.page());
this.pageInfo.page(this.pageResults.page());
}
this.remaining -= this.results.total();
this.remaining -= this.pageResults.total();
return true;
} else {
this.pageInfo.increase();
@ -93,7 +99,7 @@ public class PageEntryIterator implements Iterator<BackendEntry>, Metadatable {
if (!this.hasNext()) {
throw new NoSuchElementException();
}
return this.results.iterator().next();
return this.pageResults.get().next();
}
@Override
@ -106,4 +112,8 @@ public class PageEntryIterator implements Iterator<BackendEntry>, Metadatable {
}
throw new NotSupportException("Invalid meta '%s'", meta);
}
public QueryResults results() {
return this.queryResults;
}
}

View File

@ -26,18 +26,17 @@ import java.util.List;
import java.util.Set;
import java.util.function.Function;
import org.apache.tinkerpop.gremlin.util.iterator.IteratorUtils;
import com.baidu.hugegraph.HugeGraph;
import com.baidu.hugegraph.backend.id.Id;
import com.baidu.hugegraph.backend.query.IdQuery;
import com.baidu.hugegraph.backend.query.Query;
import com.baidu.hugegraph.backend.store.BackendEntry;
import com.baidu.hugegraph.backend.tx.AbstractTransaction.QueryResults;
import com.baidu.hugegraph.config.CoreOptions;
import com.baidu.hugegraph.iterator.FlatMapperIterator;
import com.baidu.hugegraph.util.Bytes;
import com.baidu.hugegraph.util.CollectionUtil;
import com.baidu.hugegraph.util.E;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
public final class QueryList {
@ -45,11 +44,11 @@ public final class QueryList {
private final HugeGraph graph;
private final Query parent;
// The size of each page fetched by the inner page
private final Function<Query, Iterator<BackendEntry>> fetcher;
private final Function<Query, QueryResults> fetcher;
private final List<QueryHolder> queries;
public QueryList(HugeGraph graph, Query parent,
Function<Query, Iterator<BackendEntry>> fetcher) {
Function<Query, QueryResults> fetcher) {
this.graph = graph;
this.parent = parent;
this.fetcher = fetcher;
@ -60,7 +59,7 @@ public final class QueryList {
return this.parent;
}
protected Function<Query, Iterator<BackendEntry>> fetcher() {
protected Function<Query, QueryResults> fetcher() {
return this.fetcher;
}
@ -93,21 +92,21 @@ public final class QueryList {
return this.queries.isEmpty();
}
public Iterator<BackendEntry> fetch() {
public QueryResults fetch() {
assert !this.queries.isEmpty();
if (this.parent.paging()) {
int pageSize = this.graph.configuration()
.get(CoreOptions.QUERY_PAGE_SIZE);
return new PageEntryIterator(this, pageSize);
} else {
return this.fetchAll();
PageEntryIterator iterator = new PageEntryIterator(this, pageSize);
/*
* NOTE: PageEntryIterator query will change every fetch time.
* TODO: sort results by input ids in each page.
*/
return iterator.results();
}
}
protected Iterator<BackendEntry> fetchAll() {
return new FlatMapperIterator<>(this.queries.iterator(), q -> {
return q.iterator();
});
// Fetch all results once
return QueryResults.flatMap(this.queries.iterator(), q -> q.iterator());
}
protected PageIterator fetchNext(PageInfo pageInfo, long pageSize) {
@ -157,7 +156,7 @@ public final class QueryList {
* For non-paging situation
* @return BackendEntry iterator
*/
public Iterator<BackendEntry> iterator();
public QueryResults iterator();
/**
* For paging situation
@ -183,7 +182,7 @@ public final class QueryList {
}
@Override
public Iterator<BackendEntry> iterator() {
public QueryResults iterator() {
return fetcher().apply(this.query);
}
@ -196,11 +195,11 @@ public final class QueryList {
if (this.query.limit() == Query.NO_LIMIT) {
query.limit(pageSize);
}
Iterator<BackendEntry> iterator = fetcher().apply(query);
QueryResults results = fetcher().apply(query);
// Must iterate all entries before get the next page
List<BackendEntry> results = IteratorUtils.list(iterator);
return new PageIterator(results.iterator(),
PageInfo.pageState(iterator));
return new PageIterator(results.list().iterator(),
results.queries(),
PageInfo.pageState(results.iterator()));
}
@Override
@ -222,23 +221,28 @@ public final class QueryList {
}
@Override
public Iterator<BackendEntry> iterator() {
return new FlatMapperIterator<>(this.holders.iterator(), holder -> {
if (holder.ids().isEmpty()) {
return null;
}
Set<Id> ids = holder.ids();
if (parent().limit() != Query.NO_LIMIT &&
ids.size() > parent().limit()) {
/*
* Avoid too many ids in one time query,
* Assume it will get one result by each id
*/
ids = CollectionUtil.subSet(ids, 0, (int) parent().limit());
}
IdQuery query = new IdQuery(parent(), ids);
return fetcher().apply(query);
});
public QueryResults iterator() {
if (this.holders.size() == 1) {
return this.each(this.holders.get(0));
}
return QueryResults.flatMap(this.holders.iterator(), this::each);
}
private QueryResults each(IdHolder holder) {
Set<Id> ids = holder.ids();
if (ids.isEmpty()) {
return null;
}
if (parent().limit() != Query.NO_LIMIT &&
ids.size() > parent().limit()) {
/*
* Avoid too many ids in one time query,
* Assume it will get one result by each id
*/
ids = CollectionUtil.subSet(ids, 0, (int) parent().limit());
}
IdQuery query = new IdQuery(parent(), ids);
return fetcher().apply(query);
}
@Override
@ -249,7 +253,8 @@ public final class QueryList {
return PageIterator.EMPTY;
}
IdQuery query = new IdQuery(parent(), pageIds.ids());
return new PageIterator(fetcher().apply(query),
QueryResults results = fetcher().apply(query);
return new PageIterator(results.iterator(), results.queries(),
pageIds.pageState());
}
@ -263,18 +268,21 @@ public final class QueryList {
public static final PageIterator EMPTY = new PageIterator(
Collections.emptyIterator(),
ImmutableList.of(Query.NONE),
PageState.EMPTY);
private final Iterator<BackendEntry> iterator;
private final List<Query> queries;
private final PageState pageState;
public PageIterator(Iterator<BackendEntry> iterator,
PageState pageState) {
List<Query> queries, PageState pageState) {
this.iterator = iterator;
this.queries = queries;
this.pageState = pageState;
}
public Iterator<BackendEntry> iterator() {
public Iterator<BackendEntry> get() {
return this.iterator;
}
@ -283,6 +291,12 @@ public final class QueryList {
PageState.EMPTY_BYTES);
}
public Query query() {
E.checkState(this.queries.size() == 1,
"Expect queries size 1, but got: %s", this.queries);
return this.queries.get(0);
}
public String page() {
return this.pageState.toString();
}

View File

@ -0,0 +1,85 @@
/*
* 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.backend.page;
import java.util.Map;
import java.util.Set;
import org.apache.commons.lang.NotImplementedException;
import com.baidu.hugegraph.backend.id.Id;
import com.baidu.hugegraph.util.CollectionUtil;
import com.baidu.hugegraph.util.InsertionOrderUtil;
import com.google.common.collect.ImmutableSet;
public class SortByCountIdHolderList extends IdHolderList {
private static final long serialVersionUID = -3702668078311531645L;
public SortByCountIdHolderList(boolean paging) {
super(paging);
}
@Override
public boolean add(IdHolder holder) {
if (!this.paging()) {
holder = new SortByCountIdHolder(holder);
}
return super.add(holder);
}
private static class SortByCountIdHolder extends IdHolder {
private final Map<Id, Integer> ids;
public SortByCountIdHolder(IdHolder holder) {
super(ImmutableSet.of());
this.ids = InsertionOrderUtil.newMap();
this.merge(holder.ids());
}
@Override
public void merge(Set<Id> ids) {
for (Id id : ids) {
this.ids.compute(id, (k, v) -> v == null ? 1 : v + 1);
}
}
@Override
public Set<Id> ids() {
return CollectionUtil.sortByValue(this.ids, false).keySet();
}
@Override
public int size() {
return this.ids.size();
}
@Override
public boolean paging() {
return false;
}
@Override
public PageIds fetchNext(String page, long pageSize) {
throw new NotImplementedException("SortByCountIdHolder.fetchNext");
}
}
}

View File

@ -32,6 +32,7 @@ public class IdQuery extends Query {
// The id(s) will be concated with `or`
private Set<Id> ids = InsertionOrderUtil.newSet();
private boolean mustSortByInput = true;
public IdQuery(HugeType resultType) {
super(resultType);
@ -61,6 +62,14 @@ public class IdQuery extends Query {
this.query(ids);
}
public boolean mustSortByInput() {
return this.mustSortByInput;
}
public void mustSortByInput(boolean mustSortedByInput) {
this.mustSortByInput = mustSortedByInput;
}
@Override
public Set<Id> ids() {
return Collections.unmodifiableSet(this.ids);

View File

@ -41,6 +41,10 @@ public class Query implements Cloneable {
public static final long NO_CAPACITY = -1L;
public static final long DEFAULT_CAPACITY = 800000L; // HugeGraph-777
private static final ThreadLocal<Long> capacityContex = new ThreadLocal<>();
public static final Query NONE = new Query(HugeType.UNKNOWN);
private HugeType resultType;
private Map<HugeKeys, Order> orders;
private long offset;
@ -52,8 +56,6 @@ public class Query implements Cloneable {
private Query originQuery;
private static final ThreadLocal<Long> capacityContex = new ThreadLocal<>();
public Query(HugeType resultType) {
this(resultType, null);
}
@ -305,7 +307,7 @@ public class Query implements Cloneable {
public String toString() {
Map<String, Object> pairs = InsertionOrderUtil.newMap();
if (this.page != null) {
pairs.put("page", this.page);
pairs.put("page", String.format("'%s'", this.page));
}
if (this.offset != 0) {
pairs.put("offset", this.offset);

View File

@ -364,7 +364,11 @@ public class BinarySerializer extends AbstractSerializer {
if (this.indexWithIdPrefix) {
buffer.readIndexId(index.type());
}
index.elementIds(buffer.readId(true));
Id elemId = buffer.readId(true);
if (index.indexLabel().queryType().isEdge()) {
elemId = EdgeId.parse(elemId.asString());
}
index.elementIds(elemId);
}
}
@ -713,7 +717,7 @@ public class BinarySerializer extends AbstractSerializer {
}
private BinaryBackendEntry formatILDeletion(HugeIndex index) {
Id id = index.indexLabel();
Id id = index.indexLabelId();
BinaryId bid = new BinaryId(id.asBytes(), id);
BinaryBackendEntry entry = new BinaryBackendEntry(index.type(), bid);
if (index.type().isStringIndex()) {

View File

@ -281,10 +281,10 @@ public abstract class TableSerializer extends AbstractSerializer {
* meaningful for deletion of index data in secondary/range index.
*/
if (index.fieldValues() == null && index.elementIds().size() == 0) {
entry.column(HugeKeys.INDEX_LABEL_ID, index.indexLabel().asLong());
entry.column(HugeKeys.INDEX_LABEL_ID, index.indexLabel().longId());
} else {
entry.column(HugeKeys.FIELD_VALUES, index.fieldValues());
entry.column(HugeKeys.INDEX_LABEL_ID, index.indexLabel().asLong());
entry.column(HugeKeys.INDEX_LABEL_ID, index.indexLabel().longId());
entry.column(HugeKeys.ELEMENT_IDS,
IdUtil.writeString(index.elementId()));
entry.subId(index.elementId());
@ -309,8 +309,12 @@ public abstract class TableSerializer extends AbstractSerializer {
IndexLabel indexLabel = graph.indexLabel(this.toId(indexLabelId));
HugeIndex index = new HugeIndex(indexLabel);
index.fieldValues(indexValues);
for (String id : elemIds) {
index.elementIds(IdUtil.readString(id));
for (String elemId : elemIds) {
Id id = IdUtil.readString(elemId);
if (indexLabel.queryType().isEdge()) {
id = EdgeId.parse(id.asString());
}
index.elementIds(id);
}
return index;
}

View File

@ -330,13 +330,13 @@ public class TextSerializer extends AbstractSerializer {
*/
if (index.fieldValues() == null && index.elementIds().size() == 0) {
entry.column(HugeKeys.INDEX_LABEL_ID,
writeId(index.indexLabel()));
writeId(index.indexLabelId()));
} else {
// TODO: field-values may be a number (range index)
entry.column(formatSyspropName(HugeKeys.FIELD_VALUES),
JsonUtil.toJson(index.fieldValues()));
entry.column(formatSyspropName(HugeKeys.INDEX_LABEL_ID),
writeId(index.indexLabel()));
writeId(index.indexLabelId()));
entry.column(formatSyspropName(HugeKeys.ELEMENT_IDS),
writeIds(index.elementIds()));
entry.subId(index.elementId());
@ -357,13 +357,18 @@ public class TextSerializer extends AbstractSerializer {
formatSyspropName(HugeKeys.FIELD_VALUES));
String indexLabelId = entry.column(
formatSyspropName(HugeKeys.INDEX_LABEL_ID));
String elementIds = entry.column(
String elemIds = entry.column(
formatSyspropName(HugeKeys.ELEMENT_IDS));
IndexLabel indexLabel = IndexLabel.label(graph, readId(indexLabelId));
HugeIndex index = new HugeIndex(indexLabel);
index.fieldValues(JsonUtil.fromJson(indexValues, Object.class));
index.elementIds(readIds(elementIds));
for (Id elemId : readIds(elemIds)) {
if (indexLabel.queryType().isEdge()) {
elemId = EdgeId.parse(elemId.asString());
}
index.elementIds(elemId);
}
return index;
}

View File

@ -26,10 +26,11 @@ import java.util.NoSuchElementException;
import com.baidu.hugegraph.backend.id.Id;
import com.baidu.hugegraph.type.HugeType;
import com.baidu.hugegraph.type.Idfiable;
import com.baidu.hugegraph.util.Bytes;
import com.baidu.hugegraph.util.StringEncoding;
public interface BackendEntry {
public interface BackendEntry extends Idfiable {
public static class BackendColumn implements Comparable<BackendColumn> {

View File

@ -45,6 +45,8 @@ public interface BackendFeatures {
public boolean supportsQueryByPage();
public boolean supportsQuerySortByInputIds();
public boolean supportsDeleteEdgeByLabel();
public boolean supportsUpdateVertexProperty();

View File

@ -381,6 +381,11 @@ public abstract class InMemoryDBStore
return false;
}
@Override
public boolean supportsQuerySortByInputIds() {
return true;
}
@Override
public boolean supportsDeleteEdgeByLabel() {
return false;

View File

@ -19,8 +19,15 @@
package com.baidu.hugegraph.backend.tx;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import org.apache.tinkerpop.gremlin.util.iterator.IteratorUtils;
import org.slf4j.Logger;
import com.baidu.hugegraph.HugeGraph;
@ -34,10 +41,14 @@ import com.baidu.hugegraph.backend.store.BackendEntry;
import com.baidu.hugegraph.backend.store.BackendMutation;
import com.baidu.hugegraph.backend.store.BackendStore;
import com.baidu.hugegraph.exception.NotFoundException;
import com.baidu.hugegraph.iterator.FlatMapperIterator;
import com.baidu.hugegraph.iterator.MapperIterator;
import com.baidu.hugegraph.perf.PerfUtil.Watched;
import com.baidu.hugegraph.type.HugeType;
import com.baidu.hugegraph.type.Idfiable;
import com.baidu.hugegraph.type.define.Action;
import com.baidu.hugegraph.util.E;
import com.baidu.hugegraph.util.InsertionOrderUtil;
import com.baidu.hugegraph.util.Log;
import com.google.common.util.concurrent.RateLimiter;
@ -87,7 +98,7 @@ public abstract class AbstractTransaction implements Transaction {
}
@Watched(prefix = "tx")
public Iterator<BackendEntry> query(Query query) {
public QueryResults query(Query query) {
LOG.debug("Transaction query: {}", query);
/*
* NOTE: it's dangerous if an IdQuery/ConditionQuery is empty
@ -97,11 +108,11 @@ public abstract class AbstractTransaction implements Transaction {
throw new BackendException("Query without any id or condition");
}
query = this.serializer.writeQuery(query);
Query squery = this.serializer.writeQuery(query);
this.beforeRead();
try {
return this.store.query(query);
return new QueryResults(this.store.query(squery), query);
} finally {
this.afterRead(); // TODO: not complete the iteration currently
}
@ -110,7 +121,7 @@ public abstract class AbstractTransaction implements Transaction {
@Watched(prefix = "tx")
public BackendEntry query(HugeType type, Id id) {
IdQuery q = new IdQuery(type, id);
Iterator<BackendEntry> results = this.query(q);
Iterator<BackendEntry> results = this.query(q).iterator();
if (results.hasNext()) {
BackendEntry entry = results.next();
assert !results.hasNext();
@ -308,12 +319,6 @@ public abstract class AbstractTransaction implements Transaction {
}
}
protected void doAction(Action action, BackendEntry entry) {
LOG.debug("Transaction {} entry {}", action, entry);
E.checkNotNull(entry, "entry");
this.mutation.add(entry, action);
}
@Watched(prefix = "tx")
public void doInsert(BackendEntry entry) {
this.doAction(Action.INSERT, entry);
@ -333,4 +338,144 @@ public abstract class AbstractTransaction implements Transaction {
public void doRemove(BackendEntry entry) {
this.doAction(Action.DELETE, entry);
}
protected void doAction(Action action, BackendEntry entry) {
LOG.debug("Transaction {} entry {}", action, entry);
E.checkNotNull(entry, "entry");
this.mutation.add(entry, action);
}
public static class QueryResults {
private static final QueryResults EMPTY = new QueryResults(
Collections.emptyIterator(),
Query.NONE);
private final Iterator<BackendEntry> results;
private final List<Query> queries;
public QueryResults(Iterator<BackendEntry> results, Query query) {
this(results);
this.addQuery(query);
}
public QueryResults(Iterator<BackendEntry> results) {
this.results = results;
this.queries = InsertionOrderUtil.newList();
}
public void setQuery(Query query) {
if (this.queries.size() > 0) {
this.queries.clear();
}
this.addQuery(query);
}
private void addQuery(Query query) {
E.checkNotNull(query, "query");
this.queries.add(query);
}
private void addQueries(List<Query> queries) {
for (Query query : queries) {
this.addQuery(query);
}
}
public Iterator<BackendEntry> iterator() {
return this.results;
}
public List<BackendEntry> list() {
return IteratorUtils.list(this.results);
}
public List<Query> queries() {
return Collections.unmodifiableList(this.queries);
}
protected <T extends Idfiable> Iterator<T> keepInputOrderIfNeeded(
Iterator<T> origin) {
if (!origin.hasNext()) {
// None result found
return origin;
}
Set<Id> ids;
if (this.paging() || !this.mustSortByInputIds() ||
(ids = this.queryIds()).size() <= 1) {
/*
* Return the original iterator if it's paging query or if the
* query input is less than one id, or don't have to do sort.
*/
return origin;
}
// Fill map with all elements
Map<Id, T> results = new HashMap<>();
fillMap(origin, results);
return new MapperIterator<>(ids.iterator(), id -> {
return results.get(id);
});
}
private boolean mustSortByInputIds() {
if (this.queries.size() == 1) {
Query query = this.queries.get(0);
if (query instanceof IdQuery) {
return ((IdQuery) query).mustSortByInput();
}
}
return true;
}
private boolean paging() {
for (Query query : this.queries) {
Query origin = query.originQuery();
if (query.paging() || origin != null && origin.paging()) {
return true;
}
}
return false;
}
private Set<Id> queryIds() {
if (this.queries.size() == 1) {
return this.queries.get(0).ids();
}
Set<Id> ids = InsertionOrderUtil.newSet();
for (Query query : this.queries) {
ids.addAll(query.ids());
}
return ids;
}
public static <T extends Idfiable> void fillMap(Iterator<T> iterator,
Map<Id, T> map) {
while (iterator.hasNext()) {
T result = iterator.next();
assert result.id() != null;
map.put(result.id(), result);
}
}
public static QueryResults empty() {
return EMPTY;
}
public static <T> QueryResults flatMap(Iterator<T> iterator,
Function<T, QueryResults> func) {
QueryResults[] qr = new QueryResults[1];
qr[0] = new QueryResults(new FlatMapperIterator<>(iterator, i -> {
QueryResults results = func.apply(i);
if (results == null) {
return null;
}
qr[0].addQueries(results.queries());
return results.iterator();
}));
return qr[0];
}
}
}

View File

@ -47,6 +47,7 @@ import com.baidu.hugegraph.backend.page.IdHolderList;
import com.baidu.hugegraph.backend.page.PageIds;
import com.baidu.hugegraph.backend.page.PageInfo;
import com.baidu.hugegraph.backend.page.PageState;
import com.baidu.hugegraph.backend.page.SortByCountIdHolderList;
import com.baidu.hugegraph.backend.query.Condition;
import com.baidu.hugegraph.backend.query.Condition.RangeConditions;
import com.baidu.hugegraph.backend.query.Condition.Relation;
@ -296,7 +297,7 @@ public class GraphIndexTransaction extends AbstractTransaction {
ConditionQuery query = new ConditionQuery(HugeType.UNIQUE_INDEX);
query.eq(HugeKeys.INDEX_LABEL_ID, indexLabel.id());
query.eq(HugeKeys.FIELD_VALUES, value);
Iterator<BackendEntry> iterator = this.query(query);
Iterator<BackendEntry> iterator = this.query(query).iterator();
boolean exist = iterator.hasNext();
if (exist) {
HugeIndex index = this.serializer.readIndex(graph(), query,
@ -409,8 +410,8 @@ public class GraphIndexTransaction extends AbstractTransaction {
return ImmutableList.of();
}
boolean paging = query.paging();
// Do index query
boolean paging = query.paging();
IdHolderList holders = new IdHolderList(paging);
long idsSize = 0;
for (MatchedIndex index : indexes) {
@ -425,7 +426,7 @@ public class GraphIndexTransaction extends AbstractTransaction {
// Do secondary-index, range-index or shard-index query
IndexQueries queries = index.constructIndexQueries(query);
assert !paging || queries.size() <= 1;
IdHolder holder = this.doIndexQueries(queries);
IdHolder holder = this.doSingleOrJointIndex(queries);
holders.add(holder);
}
@ -437,14 +438,16 @@ public class GraphIndexTransaction extends AbstractTransaction {
return holders;
}
@Watched(prefix = "index")
private List<IdHolder> doSearchIndex(ConditionQuery query,
MatchedIndex index) {
query = this.constructSearchQuery(query, index);
List<IdHolder> holders = new IdHolderList(query.paging());
List<IdHolder> holders = new SortByCountIdHolderList(query.paging());
// sorted by matched count
for (ConditionQuery q : ConditionQueryFlatten.flatten(query)) {
IndexQueries queries = index.constructIndexQueries(q);
assert !query.paging() || queries.size() <= 1;
IdHolder holder = this.doIndexQueries(queries);
IdHolder holder = this.doSingleOrJointIndex(queries);
// NOTE: ids will be merged into one IdHolder if not in paging
holders.add(holder);
}
@ -452,7 +455,7 @@ public class GraphIndexTransaction extends AbstractTransaction {
}
@Watched(prefix = "index")
private IdHolder doIndexQueries(IndexQueries queries) {
private IdHolder doSingleOrJointIndex(IndexQueries queries) {
if (queries.size() == 1) {
return this.doSingleOrCompositeIndex(queries);
} else {
@ -486,63 +489,6 @@ public class GraphIndexTransaction extends AbstractTransaction {
return new IdHolder(intersectIds);
}
private ConditionQuery constructSearchQuery(ConditionQuery query,
MatchedIndex index) {
ConditionQuery originQuery = query;
Set<Id> indexFields = new HashSet<>();
// Convert has(key, text) to has(key, textContainsAny(word1, word2))
for (IndexLabel il : index.indexLabels()) {
if (il.indexType() != IndexType.SEARCH) {
continue;
}
Id indexField = il.indexField();
String fieldValue = (String) query.userpropValue(indexField);
Set<String> words = this.segmentWords(fieldValue);
indexFields.add(indexField);
query = query.copy();
query.unsetCondition(indexField);
query.query(Condition.textContainsAny(indexField, words));
}
// Register results filter
query.registerResultsFilter(elem -> {
for (Condition cond : originQuery.conditions()) {
Object key = cond.isRelation() ? ((Relation) cond).key() : null;
if (key instanceof Id && indexFields.contains(key)) {
// This is an index field of search index
Id field = (Id) key;
String propValue = elem.<String>getPropertyValue(field);
String fvalue = (String) originQuery.userpropValue(field);
if (this.matchSearchIndexWords(propValue, fvalue)) {
continue;
}
return false;
}
if (!cond.test(elem)) {
return false;
}
}
return true;
});
return query;
}
private boolean matchSearchIndexWords(String propValue, String fieldValue) {
Set<String> propValues = this.segmentWords(propValue);
Set<String> words = this.segmentWords(fieldValue);
return CollectionUtil.hasIntersection(propValues, words);
}
private Set<String> segmentWords(String text) {
return this.textAnalyzer.segment(text);
}
private boolean needIndexForLabel() {
return !this.store().features().supportsQueryByLabel();
}
@Watched(prefix = "index")
private IdHolder doIndexQuery(IndexLabel indexLabel, ConditionQuery query) {
if (!query.paging()) {
@ -564,7 +510,7 @@ public class GraphIndexTransaction extends AbstractTransaction {
locks.lockReads(LockUtil.INDEX_LABEL_REBUILD, indexLabel.id());
Set<Id> ids = InsertionOrderUtil.newSet();
Iterator<BackendEntry> entries = super.query(query);
Iterator<BackendEntry> entries = super.query(query).iterator();
while(entries.hasNext()) {
HugeIndex index = this.serializer.readIndex(graph(), query,
entries.next());
@ -664,6 +610,64 @@ public class GraphIndexTransaction extends AbstractTransaction {
return null;
}
private ConditionQuery constructSearchQuery(ConditionQuery query,
MatchedIndex index) {
ConditionQuery originQuery = query;
Set<Id> indexFields = new HashSet<>();
// Convert has(key, text) to has(key, textContainsAny(word1, word2))
for (IndexLabel il : index.indexLabels()) {
if (il.indexType() != IndexType.SEARCH) {
continue;
}
Id indexField = il.indexField();
String fieldValue = (String) query.userpropValue(indexField);
Set<String> words = this.segmentWords(fieldValue);
indexFields.add(indexField);
query = query.copy();
query.unsetCondition(indexField);
query.query(Condition.textContainsAny(indexField, words));
}
// Register results filter
query.registerResultsFilter(elem -> {
for (Condition cond : originQuery.conditions()) {
Object key = cond.isRelation() ? ((Relation) cond).key() : null;
if (key instanceof Id && indexFields.contains(key)) {
// This is an index field of search index
Id field = (Id) key;
String propValue = elem.<String>getPropertyValue(field);
String fvalue = (String) originQuery.userpropValue(field);
if (this.matchSearchIndexWords(propValue, fvalue)) {
continue;
}
return false;
}
if (!cond.test(elem)) {
return false;
}
}
return true;
});
return query;
}
private boolean matchSearchIndexWords(String propValue, String fieldValue) {
Set<String> propValues = this.segmentWords(propValue);
Set<String> words = this.segmentWords(fieldValue);
return CollectionUtil.hasIntersection(propValues, words);
}
private Set<String> segmentWords(String text) {
return this.textAnalyzer.segment(text);
}
private boolean needIndexForLabel() {
return !this.store().features().supportsQueryByLabel();
}
private static Set<IndexLabel> matchSingleOrCompositeIndex(
ConditionQuery query,
Set<IndexLabel> indexLabels) {
@ -1354,7 +1358,8 @@ public class GraphIndexTransaction extends AbstractTransaction {
continue;
}
// Query and delete index equals element id
for (Iterator<BackendEntry> it = tx.query(q); it.hasNext();) {
Iterator<BackendEntry> it = tx.query(q).iterator();
while (it.hasNext()) {
BackendEntry entry = it.next();
HugeIndex index = serializer.readIndex(graph(), q, entry);
if (index.elementIds().contains(element.id())) {

View File

@ -23,6 +23,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
@ -380,7 +381,7 @@ public class GraphTransaction extends IndexableTransaction {
}
@Override
public Iterator<BackendEntry> query(Query query) {
public QueryResults query(Query query) {
if (!(query instanceof ConditionQuery)) {
return super.query(query);
}
@ -402,7 +403,7 @@ public class GraphTransaction extends IndexableTransaction {
}
}
return !queries.empty() ? queries.fetch() : Collections.emptyIterator();
return !queries.empty() ? queries.fetch() : QueryResults.empty();
}
@Watched(prefix = "graph")
@ -503,7 +504,7 @@ public class GraphTransaction extends IndexableTransaction {
public Iterator<Vertex> queryVertices(Object... vertexIds) {
// NOTE: allowed duplicated vertices if query by duplicated ids
List<Id> ids = InsertionOrderUtil.newList();
Map<Id, Vertex> vertices = InsertionOrderUtil.newMap();
Map<Id, HugeVertex> vertices = new HashMap<>(vertexIds.length);
IdQuery query = new IdQuery(HugeType.VERTEX);
for (Object vertexId : vertexIds) {
@ -517,7 +518,7 @@ public class GraphTransaction extends IndexableTransaction {
// Found from local tx
vertices.put(vertex.id(), vertex);
} else {
// Prepare query from backend store
// Prepare to query from backend store
query.query(id);
}
ids.add(id);
@ -525,11 +526,16 @@ public class GraphTransaction extends IndexableTransaction {
if (!query.empty()) {
// Query from backend store
Iterator<HugeVertex> it = this.queryVerticesFromBackend(query);
while (it.hasNext()) {
HugeVertex vertex = it.next();
vertices.put(vertex.id(), vertex);
if (vertices.isEmpty() && query.ids().size() == ids.size()) {
// Sort at the lower layer and return directly
Iterator<HugeVertex> it = this.queryVerticesFromBackend(query);
@SuppressWarnings({ "unchecked", "rawtypes" })
Iterator<Vertex> r = (Iterator) it;
return r;
}
query.mustSortByInput(false);
Iterator<HugeVertex> it = this.queryVerticesFromBackend(query);
QueryResults.fillMap(it, vertices);
}
return new MapperIterator<>(ids.iterator(), id -> {
@ -579,13 +585,20 @@ public class GraphTransaction extends IndexableTransaction {
protected Iterator<HugeVertex> queryVerticesFromBackend(Query query) {
assert query.resultType().isVertex();
Iterator<BackendEntry> entries = this.query(query);
QueryResults results = this.query(query);
Iterator<BackendEntry> entries = results.iterator();
return new MapperIterator<>(entries, entry -> {
Iterator<HugeVertex> vertices = new MapperIterator<>(entries, entry -> {
HugeVertex vertex = this.serializer.readVertex(graph(), entry);
assert vertex != null;
return vertex;
});
if (!this.store().features().supportsQuerySortByInputIds()) {
// There is no id in BackendEntry, so sort after deserialization
vertices = results.keepInputOrderIfNeeded(vertices);
}
return vertices;
}
@Watched(prefix = "graph")
@ -638,7 +651,7 @@ public class GraphTransaction extends IndexableTransaction {
public Iterator<Edge> queryEdges(Object... edgeIds) {
// NOTE: allowed duplicated edges if query by duplicated ids
List<Id> ids = InsertionOrderUtil.newList();
Map<Id, Edge> edges = InsertionOrderUtil.newMap();
Map<Id, HugeEdge> edges = new HashMap<>(edgeIds.length);
IdQuery query = new IdQuery(HugeType.EDGE);
for (Object edgeId : edgeIds) {
@ -652,7 +665,7 @@ public class GraphTransaction extends IndexableTransaction {
// Found from local tx
edges.put(edge.id(), edge);
} else {
// Prepare query from backend store
// Prepare to query from backend store
query.query(id);
}
ids.add(id);
@ -660,11 +673,16 @@ public class GraphTransaction extends IndexableTransaction {
if (!query.empty()) {
// Query from backend store
Iterator<HugeEdge> it = this.queryEdgesFromBackend(query);
while (it.hasNext()) {
HugeEdge edge = it.next();
edges.put(edge.id(), edge);
if (edges.isEmpty() && query.ids().size() == ids.size()) {
// Sort at the lower layer and return directly
Iterator<HugeEdge> it = this.queryEdgesFromBackend(query);
@SuppressWarnings({ "unchecked", "rawtypes" })
Iterator<Edge> r = (Iterator) it;
return r;
}
query.mustSortByInput(false);
Iterator<HugeEdge> it = this.queryEdgesFromBackend(query);
QueryResults.fillMap(it, edges);
}
return new MapperIterator<>(ids.iterator(), id -> {
@ -731,9 +749,10 @@ public class GraphTransaction extends IndexableTransaction {
protected Iterator<HugeEdge> queryEdgesFromBackend(Query query) {
assert query.resultType().isEdge();
Iterator<BackendEntry> entries = this.query(query);
QueryResults results = this.query(query);
Iterator<BackendEntry> entries = results.iterator();
return new FlatMapperIterator<>(entries, entry -> {
Iterator<HugeEdge> edges = new FlatMapperIterator<>(entries, entry -> {
// Edges are in a vertex
HugeVertex vertex = this.serializer.readVertex(graph(), entry);
assert vertex != null;
@ -743,6 +762,12 @@ public class GraphTransaction extends IndexableTransaction {
// Copy to avoid ConcurrentModificationException when removing edge
return ImmutableList.copyOf(vertex.getEdges()).iterator();
});
if (!this.store().features().supportsQuerySortByInputIds()) {
// There is no id in BackendEntry, so sort after deserialization
edges = results.keepInputOrderIfNeeded(edges);
}
return edges;
}
@Watched(prefix = "graph")

View File

@ -19,7 +19,6 @@
package com.baidu.hugegraph.backend.tx;
import java.util.Collections;
import java.util.Iterator;
import com.baidu.hugegraph.HugeGraph;
@ -67,7 +66,7 @@ public class SchemaIndexTransaction extends AbstractTransaction {
@Watched(prefix = "index")
@Override
public Iterator<BackendEntry> query(Query query) {
public QueryResults query(Query query) {
if (query instanceof ConditionQuery) {
ConditionQuery q = (ConditionQuery) query;
if (q.allSysprop() && q.conditions().size() == 1 &&
@ -79,7 +78,7 @@ public class SchemaIndexTransaction extends AbstractTransaction {
}
@Watched(prefix = "index")
private Iterator<BackendEntry> queryByName(ConditionQuery query) {
private QueryResults queryByName(ConditionQuery query) {
if (!this.needIndexForName()) {
return super.query(query);
}
@ -93,7 +92,7 @@ public class SchemaIndexTransaction extends AbstractTransaction {
indexQuery.eq(HugeKeys.FIELD_VALUES, name);
indexQuery.eq(HugeKeys.INDEX_LABEL_ID, il.id());
Iterator<BackendEntry> entries = super.query(indexQuery);
Iterator<BackendEntry> entries = super.query(indexQuery).iterator();
IdQuery idQuery = new IdQuery(query.resultType(), query);
while (entries.hasNext()) {
HugeIndex index = this.serializer.readIndex(graph(), indexQuery,
@ -101,7 +100,7 @@ public class SchemaIndexTransaction extends AbstractTransaction {
idQuery.query(index.elementIds());
}
if (idQuery.ids().isEmpty()) {
return Collections.emptyIterator();
return QueryResults.empty();
}
assert idQuery.ids().size() == 1 : idQuery.ids();
return super.query(idQuery);

View File

@ -307,7 +307,7 @@ public class SchemaTransaction extends IndexableTransaction {
this.beforeRead();
ConditionQuery query = new ConditionQuery(type);
query.eq(HugeKeys.NAME, name);
Iterator<BackendEntry> iter = this.indexTx.query(query);
Iterator<BackendEntry> iter = this.indexTx.query(query).iterator();
this.afterRead();
if (iter.hasNext()) {
T schema = this.deserialize(iter.next(), type);
@ -320,7 +320,7 @@ public class SchemaTransaction extends IndexableTransaction {
protected <T extends SchemaElement> List<T> getAllSchema(HugeType type) {
Query query = new Query(type);
Iterator<BackendEntry> entries = this.query(query);
Iterator<BackendEntry> entries = this.query(query).iterator();
List<T> result = new ArrayList<>();
while (entries.hasNext()) {

View File

@ -43,6 +43,8 @@ public class IndexLabel extends SchemaElement {
public IndexLabel(final HugeGraph graph, Id id, String name) {
super(graph, id, name);
this.baseType = HugeType.SYS_SCHEMA;
this.baseValue = null;
this.indexType = IndexType.SECONDARY;
this.indexFields = new ArrayList<>();
}
@ -86,6 +88,8 @@ public class IndexLabel extends SchemaElement {
return HugeType.VERTEX;
case EDGE_LABEL:
return HugeType.EDGE;
case SYS_SCHEMA:
return HugeType.SYS_SCHEMA;
default:
throw new AssertionError(String.format(
"Query type of index label is either '%s' or '%s', " +

View File

@ -67,6 +67,10 @@ public abstract class SchemaElement implements Namifiable, Typifiable,
return this.id;
}
public long longId() {
return this.id.asLong();
}
@Override
public String name() {
return this.name;

View File

@ -44,13 +44,14 @@ import com.baidu.hugegraph.schema.PropertyKey;
import com.baidu.hugegraph.schema.SchemaLabel;
import com.baidu.hugegraph.schema.VertexLabel;
import com.baidu.hugegraph.type.HugeType;
import com.baidu.hugegraph.type.Idfiable;
import com.baidu.hugegraph.type.define.Cardinality;
import com.baidu.hugegraph.type.define.HugeKeys;
import com.baidu.hugegraph.util.CollectionUtil;
import com.baidu.hugegraph.util.E;
import com.google.common.collect.ImmutableMap;
public abstract class HugeElement implements Element, GraphType {
public abstract class HugeElement implements Element, GraphType, Idfiable {
private static final Map<Id, HugeProperty<?>> EMPTY = ImmutableMap.of();
private static final int MAX_PROPERTIES = BytesBuffer.UINT16_MAX;

View File

@ -69,11 +69,11 @@ public class HugeIndex implements GraphType {
}
public Id id() {
return formatIndexId(type(), this.indexLabel(), this.fieldValues());
return formatIndexId(type(), this.indexLabelId(), this.fieldValues());
}
public Id hashId() {
return formatIndexHashId(type(), this.indexLabel(), this.fieldValues());
return formatIndexHashId(type(), this.indexLabelId(), this.fieldValues());
}
public Object fieldValues() {
@ -84,10 +84,14 @@ public class HugeIndex implements GraphType {
this.fieldValues = fieldValues;
}
public Id indexLabel() {
public Id indexLabelId() {
return this.indexLabel.id();
}
public IndexLabel indexLabel() {
return this.indexLabel;
}
public Id elementId() {
E.checkState(this.elementIds.size() == 1,
"Expect one element id, actual %s",

View File

@ -0,0 +1,27 @@
/*
* 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.type;
import com.baidu.hugegraph.backend.id.Id;
public interface Idfiable {
public Id id();
}

View File

@ -55,6 +55,11 @@ public class HbaseFeatures implements BackendFeatures {
return true;
}
@Override
public boolean supportsQuerySortByInputIds() {
return true;
}
@Override
public boolean supportsQueryWithOrderBy() {
return true;

View File

@ -75,6 +75,11 @@ public class MysqlFeatures implements BackendFeatures {
return true;
}
@Override
public boolean supportsQuerySortByInputIds() {
return false;
}
@Override
public boolean supportsDeleteEdgeByLabel() {
return true;

View File

@ -43,6 +43,7 @@ import com.baidu.hugegraph.backend.store.TableDefine;
import com.baidu.hugegraph.backend.store.mysql.MysqlEntryIterator.PagePosition;
import com.baidu.hugegraph.backend.store.mysql.MysqlSessions.Session;
import com.baidu.hugegraph.exception.NotFoundException;
import com.baidu.hugegraph.exception.NotSupportException;
import com.baidu.hugegraph.iterator.ExtendableIterator;
import com.baidu.hugegraph.type.define.HugeKeys;
import com.baidu.hugegraph.util.Log;
@ -480,7 +481,7 @@ public abstract class MysqlTable
case CONTAINS_KEY:
case SCAN:
default:
throw new AssertionError("Unsupported relation: " + relation);
throw new NotSupportException("relation '%s'", relation);
}
return sql;
}

View File

@ -59,6 +59,11 @@ public class PaloFeatures implements BackendFeatures {
return true;
}
@Override
public boolean supportsQuerySortByInputIds() {
return false;
}
@Override
public boolean supportsQueryWithContains() {
return false;

View File

@ -38,14 +38,14 @@ public class PostgresqlSerializer extends MysqlSerializer {
* meaningful for deletion of index data in secondary/range index.
*/
if (index.fieldValues() == null && index.elementIds().size() == 0) {
entry.column(HugeKeys.INDEX_LABEL_ID, index.indexLabel().asLong());
entry.column(HugeKeys.INDEX_LABEL_ID, index.indexLabel().longId());
} else {
Object value = index.fieldValues();
if (value != null && value.equals("\u0000")) {
value = Strings.EMPTY;
}
entry.column(HugeKeys.FIELD_VALUES, value);
entry.column(HugeKeys.INDEX_LABEL_ID, index.indexLabel().asLong());
entry.column(HugeKeys.INDEX_LABEL_ID, index.indexLabel().longId());
entry.column(HugeKeys.ELEMENT_IDS,
IdUtil.writeString(index.elementId()));
entry.subId(index.elementId());

View File

@ -77,6 +77,11 @@ public class RocksDBFeatures implements BackendFeatures {
return true;
}
@Override
public boolean supportsQuerySortByInputIds() {
return true;
}
@Override
public boolean supportsDeleteEdgeByLabel() {
// No index in RocksDB

View File

@ -2448,6 +2448,85 @@ public class VertexCoreTest extends BaseCoreTest {
Assert.assertEquals(0, vertices.size());
}
@Test
public void testQueryByTextContainsPropertyOrderByMatchedCount() {
HugeGraph graph = graph();
graph.schema().indexLabel("authorByLived").onV("author")
.search().by("lived").create();
graph.addVertex(T.label, "author", "id", 1, "name", "Tank", "age", 16,
"lived", "Beijing");
graph.addVertex(T.label, "author", "id", 2, "name", "Dim", "age", 40,
"lived", "Shenzhen area");
graph.addVertex(T.label, "author", "id", 3, "name", "Tom", "age", 19,
"lived", "New York Bay");
graph.addVertex(T.label, "author", "id", 4, "name", "Jason", "age", 20,
"lived", "Tokyo Bay");
graph.addVertex(T.label, "author", "id", 5, "name", "James", "age", 62,
"lived", "San Francisco Bay Area");
graph.tx().commit();
List<Vertex> vertices = graph.traversal().V()
.hasLabel("author")
.has("lived", Text.contains("Bay Area"))
.toList();
Assert.assertEquals(4, vertices.size());
Assert.assertEquals("James", vertices.get(0).value("name"));
Assert.assertEquals("Tom", vertices.get(1).value("name"));
Assert.assertEquals("Jason", vertices.get(2).value("name"));
Assert.assertEquals("Dim", vertices.get(3).value("name"));
assertContains(vertices,
T.label, "author", "id", 2, "name", "Dim",
"age", 40, "lived", "Shenzhen area");
assertContains(vertices,
T.label, "author", "id", 3, "name", "Tom",
"age", 19, "lived", "New York Bay");
assertContains(vertices,
T.label, "author", "id", 4, "name", "Jason",
"age", 20, "lived", "Tokyo Bay");
assertContains(vertices,
T.label, "author", "id", 5, "name", "James",
"age", 62, "lived", "San Francisco Bay Area");
}
@Test
public void testQueryByTextContainsPropertyOrderByMatchedCountWithPaging() {
Assume.assumeTrue("Not support paging",
storeFeatures().supportsQueryByPage());
HugeGraph graph = graph();
graph.schema().indexLabel("authorByLived").onV("author")
.search().by("lived").create();
graph.addVertex(T.label, "author", "id", 1, "name", "Tank", "age", 16,
"lived", "Beijing");
graph.addVertex(T.label, "author", "id", 2, "name", "Dim", "age", 40,
"lived", "Shenzhen area");
graph.addVertex(T.label, "author", "id", 3, "name", "Tom", "age", 19,
"lived", "New York Bay");
graph.addVertex(T.label, "author", "id", 4, "name", "Jason", "age", 20,
"lived", "Tokyo Bay");
graph.addVertex(T.label, "author", "id", 5, "name", "James", "age", 62,
"lived", "San Francisco Bay Area");
graph.tx().commit();
List<Vertex> vertices = graph.traversal().V()
.hasLabel("author")
.has("lived", Text.contains("Bay Area"))
.has("~page", "").limit(2)
.toList();
Assert.assertEquals(2, vertices.size());
assertContains(vertices,
T.label, "author", "id", 3, "name", "Tom",
"age", 19, "lived", "New York Bay");
assertContains(vertices,
T.label, "author", "id", 4, "name", "Jason",
"age", 20, "lived", "Tokyo Bay");
}
@Test
public void testQueryByTextContainsPropertyWithLeftIndex() {
HugeGraph graph = graph();
@ -3364,9 +3443,10 @@ public class VertexCoreTest extends BaseCoreTest {
schema.indexLabel("userByNameCityAge")
.onV("user").by("name", "city", "age")
.unique().create();
Vertex v = graph().addVertex(T.label, "user", "name", "Tom",
"city", "Beijing", "age", 18);
graph().addVertex(T.label, "user", "name", "Tom",
"city", "Beijing", "age", 18);
graph().tx().commit();
// Nullable properties
graph().addVertex(T.label, "user", "name", "Tom", "city", "Beijing");
graph().tx().commit();

View File

@ -45,33 +45,33 @@ public class QueryTest {
Query query = new Query(HugeType.VERTEX);
Assert.assertEquals("Query for VERTEX", query.toString());
query.page("page1");
Assert.assertEquals("Query for VERTEX page page1", query.toString());
query.page("p1");
Assert.assertEquals("Query for VERTEX page 'p1'", query.toString());
query = new Query(HugeType.VERTEX);
query.limit(10L);
Assert.assertEquals("Query for VERTEX limit 10", query.toString());
query = new Query(HugeType.VERTEX);
query.page("page2");
query.page("p2");
query.limit(10L);
Assert.assertEquals("Query for VERTEX page page2, limit 10",
Assert.assertEquals("Query for VERTEX page 'p2', limit 10",
query.toString());
query = new Query(HugeType.VERTEX);
query.page("page3");
query.page("p3");
query.offset(100L);
query.limit(10L);
Assert.assertEquals("Query for VERTEX page page3, offset 100, limit 10",
Assert.assertEquals("Query for VERTEX page 'p3', offset 100, limit 10",
query.toString());
query = new Query(HugeType.VERTEX);
query.page("page4");
query.page("");
query.offset(100L);
query.limit(10L);
query.order(HugeKeys.NAME, Order.ASC);
query.order(HugeKeys.FIELDS, Order.DESC);
Assert.assertEquals("Query for VERTEX page page4, offset 100, " +
Assert.assertEquals("Query for VERTEX page '', offset 100, " +
"limit 10, order by {NAME=ASC, FIELDS=DESC}",
query.toString());
}