Compare commits

...

4 Commits

Author SHA1 Message Date
imbajin 81c209d3ea
fix: shell parameters are not counted correctly (#1178)
Due to the lack of "", the empty param is not considered a legal parameter

PS: After the modification, it seems there are at least five params. Should also adjust the logic in `hugegraph-server.sh`? Keep it unchanged now, maybe adjust them on master/0,11 branch is better.

Fix #1172
2020-09-16 16:50:10 +08:00
Jermy Li ed70e00c2e
cherry-pick master bug fix(2020-6-18) for v0.10.4 (#1047)
* Fix mysql backend openWithoutDB ssl-mode not work (#842)
* fix bug of missing offset with index query (#866)
* allow system async task in gremlin context (#892)
* sm allow cassandra backend creating thread as needed while execute cql (#896)
* fix: Invalid limit 10000000, must be <= capacity (#950)
* Fix gremlin-console can't work (#1027)

Change-Id: Ice222d809e4161f1d4d1f04822bef1c5af380a10

Co-authored-by: Linary <liningrui@vip.qq.com>
Co-authored-by: zhoney <zhangyi51@baidu.com>
2020-06-19 21:15:37 +08:00
Jermy Li 55166c7460 cherry-pick master bug fix for v0.10.4 (#785)
* Start service should fail quickly when occured exception (#748)

Change-Id: I36803f2dcc1ae19046cfb209d67a1008df135b21

* allow rest api to query vertices & edges with multi properties by paging (#759)

implemented: #658
Change-Id: Ic803bc5d11bbd77d8d2b8b7c3cc247cdd5d7781e

* fix TableNotFoundException when truncate HBase store (#771)

fixed: #770

Change-Id: Ibb74acc04018378a11b2d3304ea9e1437fc5879b

* fix: Requested permits (0) must be positive (#773)

Change-Id: I43ab6b0614e86464f2ce9824fda63df1f6dbd2f9
fix: #760

* Fix start.hugegraph.sh timeout when config authentication (#761)

Change-Id: Ic22531cead13f3698791070a044d66eacb1c26ee

* fix connect failed of mysql backend when using studio gremlin (#765)

fixed: #758

Change-Id: Ia52ea1588b1b4ecef882732cfbb8606ceb4bea30
2019-12-09 10:58:18 +08:00
liningrui 6fdbebf6c0 HugeGraph-1358: Release 0.10.4
Change-Id: Ie159f4ae7c996dffbce1226db49c3857be5744fc
2019-11-08 14:26:33 +08:00
56 changed files with 720 additions and 327 deletions

View File

@ -1,8 +1,8 @@
# HugeGraph
[![License](https://img.shields.io/badge/license-Apache%202-0E78BA.svg)](https://www.apache.org/licenses/LICENSE-2.0.html)
[![Build Status](https://travis-ci.org/hugegraph/hugegraph.svg?branch=master)](https://travis-ci.org/hugegraph/hugegraph)
[![codecov](https://codecov.io/gh/hugegraph/hugegraph/branch/master/graph/badge.svg)](https://codecov.io/gh/hugegraph/hugegraph)
[![Build Status](https://travis-ci.org/hugegraph/hugegraph.svg?branch=release-0.10)](https://travis-ci.org/hugegraph/hugegraph)
[![codecov](https://codecov.io/gh/hugegraph/hugegraph/branch/release-0.10/graph/badge.svg)](https://codecov.io/gh/hugegraph/hugegraph)
HugeGraph is a fast-speed and highly-scalable [graph database](https://en.wikipedia.org/wiki/Graph_database). Billions of vertices and edges can be easily stored into and queried from HugeGraph due to its excellent OLTP ability. As compliance to [Apache TinkerPop 3](https://tinkerpop.apache.org/) framework, various complicated graph queries can be accomplished through [Gremlin](https://tinkerpop.apache.org/gremlin.html)(a powerful graph traversal language).

View File

@ -39,9 +39,9 @@ import com.baidu.hugegraph.metrics.MetricsUtil;
import com.baidu.hugegraph.server.RestServer;
import com.baidu.hugegraph.type.HugeType;
import com.baidu.hugegraph.util.E;
import com.baidu.hugegraph.util.JsonUtil;
import com.baidu.hugegraph.util.Log;
import com.codahale.metrics.Meter;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.ImmutableMap;
public class API {
@ -172,13 +172,15 @@ public class API {
if (properties == null || properties.isEmpty()) {
return ImmutableMap.of();
}
ObjectMapper mapper = new ObjectMapper();
Map<String, Object> props = null;
try {
props = mapper.readValue(properties, Map.class);
props = JsonUtil.fromJson(properties, Map.class);
} catch (Exception ignored) {}
// If properties is the string "null", props will be null
E.checkArgument(props != null, "Invalid request with none properties");
E.checkArgument(props != null,
"Invalid request with properties: %s", properties);
return props;
}

View File

@ -276,13 +276,9 @@ public class EdgeAPI extends BatchAPI {
Map<String, Object> props = parseProperties(properties);
if (page != null) {
E.checkArgument(vertexId == null && direction == null &&
offset == 0,
E.checkArgument(offset == 0,
"Not support querying edges based on paging " +
"and [vertex, direction, offset] together");
E.checkArgument(props.size() <= 1,
"Not support querying edges based on paging " +
"and more than one property");
"and offset together");
}
Id vertex = VertexAPI.checkAndParseVertexId(vertexId);

View File

@ -230,9 +230,6 @@ public class VertexAPI extends BatchAPI {
E.checkArgument(offset == 0,
"Not support querying vertices based on paging " +
"and offset together");
E.checkArgument(props.size() <= 1,
"Not support querying vertices based on paging " +
"and more than one property");
}
HugeGraph g = graph(manager, graph);

View File

@ -209,21 +209,27 @@ public class CassandraSessionPool extends BackendSessionPool {
} catch (InvalidQueryException ignored) {}
}
@Override
public void open() {
assert this.session == null;
this.session = cluster().connect(keyspace());
this.opened = true;
}
@Override
public boolean opened() {
return this.session != null;
if (this.opened && this.session == null) {
this.tryOpen();
}
return this.opened && this.session != null;
}
@Override
public boolean closed() {
if (this.session == null) {
this.tryOpen();
if (!this.opened || this.session == null) {
return true;
}
return this.session == null ? true : this.session.isClosed();
return this.session.isClosed();
}
@Override
@ -233,6 +239,7 @@ public class CassandraSessionPool extends BackendSessionPool {
return;
}
this.session.close();
this.session = null;
}
@Override

View File

@ -123,7 +123,8 @@ public abstract class CassandraStore
this.store);
}
if (this.sessions.opened()) {
assert this.sessions != null;
if (!this.sessions.closed()) {
// TODO: maybe we should throw an exception here instead of ignore
LOG.debug("Store {} has been opened before", this.store);
this.sessions.useSession();
@ -169,7 +170,7 @@ public abstract class CassandraStore
@Override
public boolean opened() {
this.checkClusterConnected();
return !this.sessions.session().closed();
return this.sessions.session().opened();
}
@Override
@ -259,15 +260,16 @@ public abstract class CassandraStore
public void init() {
this.checkClusterConnected();
// Create keyspace if needed
if (!this.existsKeyspace()) {
this.initKeyspace();
}
if (this.sessions.session().opened()) {
// Session has ever been opened.
LOG.warn("Session has ever been opened(exist keyspace '{}' before)",
this.keyspace);
} else {
// Create keyspace if needed
if (!this.existsKeyspace()) {
this.initKeyspace();
}
// Open session explicitly to get the exception when it fails
this.sessions.session().open();
}
@ -280,13 +282,16 @@ public abstract class CassandraStore
}
@Override
public void clear() {
public void clear(boolean clearSpace) {
this.checkClusterConnected();
if (this.existsKeyspace()) {
this.checkOpened();
this.clearTables();
this.clearKeyspace();
if (!clearSpace) {
this.checkOpened();
this.clearTables();
} else {
this.clearKeyspace();
}
}
LOG.debug("Store cleared: {}", this.store);

View File

@ -96,7 +96,7 @@ public abstract class CassandraTable
Query query) {
ExtendableIterator<BackendEntry> rs = new ExtendableIterator<>();
if (query.limit() == 0 && query.limit() != Query.NO_LIMIT) {
if (query.limit() == 0L && !query.nolimit()) {
LOG.debug("Return empty result(limit=0) for query {}", query);
return rs;
}
@ -159,7 +159,7 @@ public abstract class CassandraTable
}
protected void setPageState(Query query, List<Select> selects) {
if (query.limit() == Query.NO_LIMIT) {
if (query.nolimit()) {
return;
}
for (Select select : selects) {

View File

@ -82,8 +82,8 @@ public class CachedBackendStore implements BackendStore {
}
@Override
public void clear() {
this.store.clear();
public void clear(boolean clearSpace) {
this.store.clear(clearSpace);
}
@Override

View File

@ -26,18 +26,42 @@ import com.baidu.hugegraph.util.E;
public class IdHolderList extends ArrayList<IdHolder> {
private static final IdHolderList EMPTY_P = new IdHolderList(true);
private static final IdHolderList EMPTY_NP = new IdHolderList(false);
private static final long serialVersionUID = -738694176552424990L;
private final boolean paging;
private final boolean needSkipOffset;
public static IdHolderList empty(boolean paging) {
IdHolderList empty = paging ? EMPTY_P : EMPTY_NP;
empty.clear();
return empty;
}
public IdHolderList(boolean paging) {
this(paging, true);
}
public IdHolderList(boolean paging, boolean needSkipOffset) {
this.paging = paging;
this.needSkipOffset = needSkipOffset;
}
public boolean paging() {
return this.paging;
}
public boolean needSkipOffset() {
return this.needSkipOffset;
}
public boolean sameParameters(IdHolderList other) {
return this.paging == other.paging &&
this.needSkipOffset == other.needSkipOffset;
}
@Override
public boolean add(IdHolder holder) {
E.checkArgument(this.paging == holder.paging(),

View File

@ -62,11 +62,12 @@ public final class QueryList {
return this.fetcher;
}
public void add(List<IdHolder> holders) {
public void add(IdHolderList holders) {
// IdHolderList is results of one index query, the query is flattened
if (!this.parent.paging()) {
for (QueryHolder q : this.queries) {
if (q instanceof IndexQuery) {
((IndexQuery) q).holders.addAll(holders);
((IndexQuery) q).merge(holders);
return;
}
}
@ -124,7 +125,6 @@ public final class QueryList {
return query.iterator(offset - current, pageInfo.page(), pageSize);
}
@SuppressWarnings("unused")
private static Set<Id> limit(Set<Id> ids, Query query) {
long fromIndex = query.offset();
E.checkArgument(fromIndex <= Integer.MAX_VALUE,
@ -134,11 +134,11 @@ public final class QueryList {
if (query.offset() >= ids.size()) {
return ImmutableSet.of();
}
if (query.limit() == Query.NO_LIMIT && query.offset() == 0) {
if (query.nolimit() && query.offset() == 0) {
return ids;
}
long toIndex = query.offset() + query.limit();
if (query.limit() == Query.NO_LIMIT || toIndex > ids.size()) {
long toIndex = query.total();
if (query.nolimit() || toIndex > ids.size()) {
toIndex = ids.size();
}
assert fromIndex < ids.size();
@ -191,14 +191,13 @@ public final class QueryList {
Query query = this.query.copy();
query.page(page);
// Not set limit to pageSize due to PageEntryIterator.remaining
if (this.query.limit() == Query.NO_LIMIT) {
if (this.query.nolimit()) {
query.limit(pageSize);
}
QueryResults results = fetcher().apply(query);
QueryResults rs = fetcher().apply(query);
// Must iterate all entries before get the next page
return new PageIterator(results.list().iterator(),
results.queries(),
PageInfo.pageState(results.iterator()));
return new PageIterator(rs.list().iterator(), rs.queries(),
PageInfo.pageState(rs.iterator()));
}
@Override
@ -212,11 +211,21 @@ public final class QueryList {
*/
private class IndexQuery implements QueryHolder {
// Actual is an instance of IdHolderList
private final List<IdHolder> holders;
// An IdHolder each sub-query
private final IdHolderList holders;
// To skip the offset in parent query
private long offsetToSkip;
public IndexQuery(List<IdHolder> holders) {
public IndexQuery(IdHolderList holders) {
this.holders = holders;
this.offsetToSkip = this.holders.needSkipOffset() ?
parent().offset() : -1L;
}
public void merge(IdHolderList holders) {
E.checkState(holders.sameParameters(this.holders),
"Can't merge IdHolderList with different parameters");
this.holders.addAll(holders);
}
@Override
@ -229,18 +238,23 @@ public final class QueryList {
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()) {
Query parent = parent();
if (this.offsetToSkip > 0L) {
// Skip offset when needed
this.offsetToSkip -= ids.size();
ids = limit(ids, parent);
} else if (!parent.nolimit() && ids.size() > parent.total()) {
/*
* 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());
ids = CollectionUtil.subSet(ids, 0, (int) parent.total());
}
IdQuery query = new IdQuery(parent(), ids);
if (ids.isEmpty()) {
return null;
}
IdQuery query = new IdQuery(parent, ids);
return fetcher().apply(query);
}
@ -252,8 +266,8 @@ public final class QueryList {
return PageIterator.EMPTY;
}
IdQuery query = new IdQuery(parent(), pageIds.ids());
QueryResults results = fetcher().apply(query);
return new PageIterator(results.iterator(), results.queries(),
QueryResults rs = fetcher().apply(query);
return new PageIterator(rs.iterator(), rs.queries(),
pageIds.pageState());
}

View File

@ -156,6 +156,10 @@ public class Query implements Cloneable {
this.limit = limit;
}
public boolean nolimit() {
return this.limit() == NO_LIMIT;
}
public boolean reachLimit(long count) {
long limit = this.limit();
if (limit == NO_LIMIT) {
@ -170,7 +174,7 @@ public class Query implements Cloneable {
* @param start the range start, include it
* @param end the range end, exclude it
*/
public void range(long start, long end) {
public long range(long start, long end) {
// Update offset
long offset = this.offset();
start = Math.max(start, offset);
@ -178,7 +182,7 @@ public class Query implements Cloneable {
// Update limit
if (end != -1L) {
if (this.limit() != Query.NO_LIMIT) {
if (!this.nolimit()) {
end = Math.min(end, offset + this.limit());
} else {
assert end < Query.NO_LIMIT;
@ -190,6 +194,7 @@ public class Query implements Cloneable {
// Keep the origin limit
assert this.limit() <= Query.NO_LIMIT;
}
return this.limit;
}
public String page() {

View File

@ -178,8 +178,8 @@ public class QueryResults {
return (Iterator<T>) EMPTY_ITERATOR;
}
private static class EmptyIterator<T> implements Iterator<T>,
Metadatable {
private static class EmptyIterator<T> implements Iterator<T>, Metadatable {
@Override
public Object metadata(String meta, Object... args) {
return null;

View File

@ -112,7 +112,12 @@ public abstract class AbstractBackendStoreProvider
public void clear() throws BackendException {
this.checkOpened();
for (BackendStore store : this.stores.values()) {
store.clear();
// Just clear tables of store, not clear space
store.clear(false);
}
for (BackendStore store : this.stores.values()) {
// Only clear space of store
store.clear(true);
}
this.notifyAndWaitEvent(Events.STORE_CLEAR);

View File

@ -26,12 +26,14 @@ import com.baidu.hugegraph.backend.store.BackendStore.TxState;
*/
public abstract class BackendSession {
protected boolean opened;
private int refs;
private TxState txState;
private final long created;
private long updated;
public BackendSession() {
this.opened = true;
this.refs = 1;
this.txState = TxState.CLEAN;
this.created = System.currentTimeMillis();
@ -50,10 +52,9 @@ public abstract class BackendSession {
this.updated = System.currentTimeMillis();
}
public abstract void open();
public abstract void close();
public abstract boolean closed();
public abstract Object commit();
public abstract void rollback();
@ -64,6 +65,18 @@ public abstract class BackendSession {
// pass
}
protected void reset() {
// pass
}
public boolean opened() {
return this.opened;
}
public boolean closed() {
return !this.opened;
}
protected int attach() {
return ++this.refs;
}

View File

@ -19,6 +19,8 @@
package com.baidu.hugegraph.backend.store;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
@ -37,12 +39,14 @@ public abstract class BackendSessionPool {
private final String name;
private final ThreadLocal<BackendSession> threadLocalSession;
private final AtomicInteger sessionCount;
private final Map<Long, BackendSession> sessions;
public BackendSessionPool(HugeConfig config, String name) {
this.config = config;
this.name = name;
this.threadLocalSession = new ThreadLocal<>();
this.sessionCount = new AtomicInteger(0);
this.sessions = new ConcurrentHashMap<>();
}
public HugeConfig config() {
@ -55,6 +59,8 @@ public abstract class BackendSessionPool {
session = this.newSession();
assert session != null;
this.threadLocalSession.set(session);
assert !this.sessions.containsKey(Thread.currentThread().getId());
this.sessions.put(Thread.currentThread().getId(), session);
int sessionCount = this.sessionCount.incrementAndGet();
LOG.debug("Now(after connect({})) session count is: {}",
this, sessionCount);
@ -113,9 +119,18 @@ public abstract class BackendSessionPool {
throw e;
}
this.threadLocalSession.remove();
assert this.sessions.containsKey(Thread.currentThread().getId());
this.sessions.remove(Thread.currentThread().getId());
return Pair.of(this.sessionCount.decrementAndGet(), ref);
}
protected void forceResetSessions() {
for (BackendSession session : this.sessions.values()) {
session.reset();
}
}
public void close() {
Pair<Integer, Integer> result = Pair.of(-1, -1);
try {

View File

@ -46,7 +46,7 @@ public interface BackendStore {
// Initialize/clear database
public void init();
public void clear();
public void clear(boolean clearSpace);
public boolean initialized();
// Delete all data of database (keep table structure)

View File

@ -177,7 +177,7 @@ public abstract class InMemoryDBStore
}
@Override
public void clear() {
public void clear(boolean clearSpace) {
for (InMemoryDBTable table : this.tables()) {
table.clear(null);
}
@ -263,9 +263,9 @@ public abstract class InMemoryDBStore
}
@Override
public void clear() {
public void clear(boolean clearSpace) {
this.counter.reset();
super.clear();
super.clear(clearSpace);
}
@Override

View File

@ -147,8 +147,7 @@ public class InMemoryDBTable extends BackendTable<BackendSession,
}
iterator = this.skipOffset(iterator, query.offset());
if (query.limit() != Query.NO_LIMIT &&
query.offset() + query.limit() < rs.size()) {
if (!query.nolimit() && query.total() < rs.size()) {
iterator = this.dropTails(iterator, query.limit());
}
return iterator;

View File

@ -154,8 +154,7 @@ public abstract class AbstractTransaction implements Transaction {
RateLimiter rateLimiter = this.graph.rateLimiter();
if (rateLimiter != null) {
int size = this.mutationSize();
assert size > 0;
double time = rateLimiter.acquire(size);
double time = size > 0 ? rateLimiter.acquire(size) : 0.0;
if (time > 0) {
LOG.debug("Waited for {}s to mutate {} item(s)", time, size);
}

View File

@ -325,7 +325,7 @@ public class GraphIndexTransaction extends AbstractTransaction {
* @return converted id query
*/
@Watched(prefix = "index")
public List<IdHolder> queryIndex(ConditionQuery query) {
public IdHolderList queryIndex(ConditionQuery query) {
// Index query must have been flattened in Graph tx
query.checkFlattened();
@ -355,7 +355,7 @@ public class GraphIndexTransaction extends AbstractTransaction {
}
@Watched(prefix = "index")
private List<IdHolder> queryByLabel(ConditionQuery query) {
private IdHolderList queryByLabel(ConditionQuery query) {
HugeType queryType = query.resultType();
IndexLabel il = IndexLabel.label(queryType);
Id label = query.condition(HugeKeys.LABEL);
@ -390,13 +390,15 @@ public class GraphIndexTransaction extends AbstractTransaction {
indexQuery.capacity(query.capacity());
IdHolder idHolder = this.doIndexQuery(il, indexQuery);
List<IdHolder> holders = new IdHolderList(query.paging());
// NOTE: the backend itself will skip the offset
IdHolderList holders = new IdHolderList(query.paging(), false);
holders.add(idHolder);
return holders;
}
@Watched(prefix = "index")
private List<IdHolder> queryByUserprop(ConditionQuery query) {
private IdHolderList queryByUserprop(ConditionQuery query) {
// Get user applied label or collect all qualified labels with
// related index labels
Set<MatchedIndex> indexes = this.collectMatchedIndexes(query);
@ -406,12 +408,12 @@ public class GraphIndexTransaction extends AbstractTransaction {
}
// Value type of Condition not matched
boolean paging = query.paging();
if (!validQueryConditionValues(this.graph(), query)) {
return ImmutableList.of();
return IdHolderList.empty(paging);
}
// Do index query
boolean paging = query.paging();
IdHolderList holders = new IdHolderList(paging);
long idsSize = 0;
for (MatchedIndex index : indexes) {
@ -430,6 +432,12 @@ public class GraphIndexTransaction extends AbstractTransaction {
holders.add(holder);
}
/*
* Finish early if records exceeds required.
* NOTE: need to skip the offset if offset > 0, but can't handle
* it here because the query may a sub-query after flatten,
* so the offset will be handle in QueryList.IndexQuery
*/
idsSize += holders.idsSize();
if (query.reachLimit(idsSize)) {
break;

View File

@ -45,7 +45,7 @@ import com.baidu.hugegraph.backend.BackendException;
import com.baidu.hugegraph.backend.id.EdgeId;
import com.baidu.hugegraph.backend.id.Id;
import com.baidu.hugegraph.backend.id.SplicingIdGenerator;
import com.baidu.hugegraph.backend.page.IdHolder;
import com.baidu.hugegraph.backend.page.IdHolderList;
import com.baidu.hugegraph.backend.page.PageInfo;
import com.baidu.hugegraph.backend.page.QueryList;
import com.baidu.hugegraph.backend.query.Condition;
@ -553,8 +553,7 @@ public class GraphTransaction extends IndexableTransaction {
}
public Iterator<Vertex> queryVertices(Query query) {
E.checkArgument(this.removedVertices.isEmpty() ||
query.limit() == Query.NO_LIMIT,
E.checkArgument(this.removedVertices.isEmpty() || query.nolimit(),
"It's not allowed to query with limit when " +
"there are uncommitted delete records.");
@ -700,8 +699,7 @@ public class GraphTransaction extends IndexableTransaction {
}
public Iterator<Edge> queryEdges(Query query) {
E.checkArgument(this.removedEdges.isEmpty() ||
query.limit() == Query.NO_LIMIT,
E.checkArgument(this.removedEdges.isEmpty() || query.nolimit(),
"It's not allowed to query with limit when " +
"there are uncommitted delete records.");
@ -1103,7 +1101,7 @@ public class GraphTransaction extends IndexableTransaction {
return null;
}
private List<IdHolder> indexQuery(ConditionQuery query) {
private IdHolderList indexQuery(ConditionQuery query) {
/*
* Optimize by index-query
* It will return a list of id (maybe empty) if success,

View File

@ -409,6 +409,17 @@ public class HugeGraphSONModule extends TinkerPopJacksonModule {
generator.writeEndObject();
}
@Override
public void serializeWithType(HugeVertex value, JsonGenerator generator,
SerializerProvider provider,
TypeSerializer typeSer)
throws IOException {
WritableTypeId typeId = typeSer.typeId(value, JsonToken.VALUE_STRING);
typeSer.writeTypePrefix(generator, typeId);
this.serialize(value, generator, provider);
typeSer.writeTypeSuffix(generator, typeId);
}
}
private static class HugeEdgeSerializer
@ -441,6 +452,17 @@ public class HugeGraphSONModule extends TinkerPopJacksonModule {
generator.writeEndObject();
}
@Override
public void serializeWithType(HugeEdge value, JsonGenerator generator,
SerializerProvider provider,
TypeSerializer typeSer)
throws IOException {
WritableTypeId typeId = typeSer.typeId(value, JsonToken.VALUE_STRING);
typeSer.writeTypePrefix(generator, typeId);
this.serialize(value, generator, provider);
typeSer.writeTypeSuffix(generator, typeId);
}
}
private static class ShardSerializer extends StdSerializer<Shard> {

View File

@ -29,13 +29,14 @@ import org.apache.tinkerpop.gremlin.jsr223.DefaultImportCustomizer;
import org.apache.tinkerpop.gremlin.jsr223.ImportCustomizer;
import com.baidu.hugegraph.HugeException;
import com.baidu.hugegraph.HugeFactory;
import com.baidu.hugegraph.util.ReflectionUtil;
import com.google.common.reflect.ClassPath;
public class HugeGraphGremlinPlugin extends AbstractGremlinPlugin {
private static final String PACKAGE = "com.baidu.hugegraph";
private static final String NAME = "com.baidu.hugegraph";
private static final String PACKAGE = "com.baidu.hugegraph.type.define";
private static final String NAME = "HugeGraph";
private static final HugeGraphGremlinPlugin instance;
private static final ImportCustomizer imports;
@ -54,6 +55,9 @@ public class HugeGraphGremlinPlugin extends AbstractGremlinPlugin {
@SuppressWarnings("rawtypes")
Set<Class> classes = new HashSet<>();
classInfos.forEachRemaining(classInfo -> classes.add(classInfo.load()));
// Add entrance class: graph = HugeFactory.open("hugegraph.properties")
classes.add(HugeFactory.class);
imports = DefaultImportCustomizer.build()
.addClassImports(classes)
.create();

View File

@ -22,12 +22,19 @@ package com.baidu.hugegraph.security;
import java.io.FileDescriptor;
import java.net.InetAddress;
import java.security.Permission;
import java.util.Map;
import java.util.Set;
import org.slf4j.Logger;
import com.baidu.hugegraph.util.Log;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
public class HugeSecurityManager extends SecurityManager {
private static final String USER_DIR = System.getProperty("user.dir");
private static final String GREMLIN_SERVER_WORKER = "gremlin-server-exec";
private static final String TASK_WORKER = "task-worker";
private static final Set<String> GREMLIN_EXECUTOR_CLASS = ImmutableSet.of(
@ -42,7 +49,8 @@ public class HugeSecurityManager extends SecurityManager {
"groovy.lang.GroovyClassLoader",
"sun.reflect.DelegatingClassLoader",
"org.codehaus.groovy.reflection.SunClassLoader",
"org.codehaus.groovy.runtime.callsite.CallSiteClassLoader"
"org.codehaus.groovy.runtime.callsite.CallSiteClassLoader",
"org.apache.hadoop.hbase.util.DynamicClassLoader"
);
private static final Set<String> CAFFEINE_CLASSES = ImmutableSet.of(
@ -51,15 +59,49 @@ public class HugeSecurityManager extends SecurityManager {
private static final Set<String> WHITE_SYSTEM_PROPERTYS = ImmutableSet.of(
"line.separator",
"file.separator"
"file.separator",
"socksProxyHost", // MySQL
"file.encoding" // PostgreSQL
);
private static final Map<String, Set<String>> ASYNC_TASKS = ImmutableMap.of(
// Fixed https://github.com/hugegraph/hugegraph/pull/892#issue-387202362
"com.baidu.hugegraph.backend.tx.SchemaTransaction",
ImmutableSet.of("removeVertexLabel", "removeEdgeLabel",
"removeIndexLabel", "rebuildIndex"),
"com.baidu.hugegraph.backend.tx.GraphIndexTransaction",
ImmutableSet.of("asyncRemoveIndexLeft")
);
private static final Map<String, Set<String>> BACKEND_SOCKET = ImmutableMap.of(
// Fixed #758
"com.baidu.hugegraph.backend.store.mysql.MysqlStore",
ImmutableSet.of("open", "init", "clear", "opened", "initialized")
);
private static final Map<String, Set<String>> BACKEND_THREAD = ImmutableMap.of(
// Fixed #758
"com.baidu.hugegraph.backend.store.cassandra.CassandraStore",
ImmutableSet.of("open", "opened", "init"),
// Fixed https://github.com/hugegraph/hugegraph/pull/892#issuecomment-598545072
"com.datastax.driver.core.AbstractSession",
ImmutableSet.of("execute")
);
private static final Set<String> HBASE_CLASSES = ImmutableSet.of(
// Fixed #758
"com.baidu.hugegraph.backend.store.hbase.HbaseStore",
"com.baidu.hugegraph.backend.store.hbase.HbaseStore$HbaseSchemaStore",
"com.baidu.hugegraph.backend.store.hbase.HbaseStore$HbaseGraphStore",
"com.baidu.hugegraph.backend.store.hbase.HbaseSessions$RowIterator"
);
@Override
public void checkPermission(Permission permission) {
if (DENIED_PERMISSIONS.contains(permission.getName()) &&
callFromGremlin()) {
throw new SecurityException(
"Not allowed to access denied permission via Gremlin");
throw newSecurityException(
"Not allowed to access denied permission via Gremlin");
}
}
@ -67,16 +109,16 @@ public class HugeSecurityManager extends SecurityManager {
public void checkPermission(Permission permission, Object context) {
if (DENIED_PERMISSIONS.contains(permission.getName()) &&
callFromGremlin()) {
throw new SecurityException(
"Not allowed to access denied permission via Gremlin");
throw newSecurityException(
"Not allowed to access denied permission via Gremlin");
}
}
@Override
public void checkCreateClassLoader() {
if (!callFromAcceptClassLoaders() && callFromGremlin()) {
throw new SecurityException(
"Not allowed to create class loader via Gremlin");
throw newSecurityException(
"Not allowed to create class loader via Gremlin");
}
super.checkCreateClassLoader();
}
@ -84,26 +126,30 @@ public class HugeSecurityManager extends SecurityManager {
@Override
public void checkLink(String lib) {
if (callFromGremlin()) {
throw new SecurityException(
"Not allowed to link library via Gremlin");
throw newSecurityException(
"Not allowed to link library via Gremlin");
}
super.checkLink(lib);
}
@Override
public void checkAccess(Thread thread) {
if (callFromGremlin() && !callFromCaffeine()) {
throw new SecurityException(
"Not allowed to access thread via Gremlin");
if (callFromGremlin() && !callFromCaffeine() &&
!callFromAsyncTasks() && !callFromEventHubNotify() &&
!callFromBackendThread() && !callFromBackendHbase()) {
throw newSecurityException(
"Not allowed to access thread via Gremlin");
}
super.checkAccess(thread);
}
@Override
public void checkAccess(ThreadGroup threadGroup) {
if (callFromGremlin() && !callFromCaffeine()) {
throw new SecurityException(
"Not allowed to access thread group via Gremlin");
if (callFromGremlin() && !callFromCaffeine() &&
!callFromAsyncTasks() && !callFromEventHubNotify() &&
!callFromBackendThread() && !callFromBackendHbase()) {
throw newSecurityException(
"Not allowed to access thread group via Gremlin");
}
super.checkAccess(threadGroup);
}
@ -111,8 +157,8 @@ public class HugeSecurityManager extends SecurityManager {
@Override
public void checkExit(int status) {
if (callFromGremlin()) {
throw new SecurityException(
"Not allowed to call System.exit() via Gremlin");
throw newSecurityException(
"Not allowed to call System.exit() via Gremlin");
}
super.checkExit(status);
}
@ -120,24 +166,26 @@ public class HugeSecurityManager extends SecurityManager {
@Override
public void checkExec(String cmd) {
if (callFromGremlin()) {
throw new SecurityException(
"Not allowed to execute command via Gremlin");
throw newSecurityException(
"Not allowed to execute command via Gremlin");
}
super.checkExec(cmd);
}
@Override
public void checkRead(FileDescriptor fd) {
if (callFromGremlin()) {
throw new SecurityException("Not allowed to read fd via Gremlin");
if (callFromGremlin() && !callFromBackendSocket()) {
throw newSecurityException("Not allowed to read fd via Gremlin");
}
super.checkRead(fd);
}
@Override
public void checkRead(String file) {
if (callFromGremlin() && !callFromCaffeine()) {
throw new SecurityException("Not allowed to read file via Gremlin");
if (callFromGremlin() && !callFromCaffeine() &&
!readGroovyInCurrentDir(file) && !callFromBackendHbase()) {
throw newSecurityException(
"Not allowed to read file via Gremlin: %s", file);
}
super.checkRead(file);
}
@ -145,15 +193,16 @@ public class HugeSecurityManager extends SecurityManager {
@Override
public void checkRead(String file, Object context) {
if (callFromGremlin()) {
throw new SecurityException("Not allowed to read file via Gremlin");
throw newSecurityException(
"Not allowed to read file via Gremlin: %s", file);
}
super.checkRead(file, context);
}
@Override
public void checkWrite(FileDescriptor fd) {
if (callFromGremlin()) {
throw new SecurityException("Not allowed to write fd via Gremlin");
if (callFromGremlin() && !callFromBackendSocket()) {
throw newSecurityException("Not allowed to write fd via Gremlin");
}
super.checkWrite(fd);
}
@ -161,8 +210,7 @@ public class HugeSecurityManager extends SecurityManager {
@Override
public void checkWrite(String file) {
if (callFromGremlin()) {
throw new SecurityException(
"Not allowed to write file via Gremlin");
throw newSecurityException("Not allowed to write file via Gremlin");
}
super.checkWrite(file);
}
@ -170,8 +218,8 @@ public class HugeSecurityManager extends SecurityManager {
@Override
public void checkDelete(String file) {
if (callFromGremlin()) {
throw new SecurityException(
"Not allowed to delete file via Gremlin");
throw newSecurityException(
"Not allowed to delete file via Gremlin");
}
super.checkDelete(file);
}
@ -179,8 +227,8 @@ public class HugeSecurityManager extends SecurityManager {
@Override
public void checkListen(int port) {
if (callFromGremlin()) {
throw new SecurityException(
"Not allowed to listen socket via Gremlin");
throw newSecurityException(
"Not allowed to listen socket via Gremlin");
}
super.checkListen(port);
}
@ -188,17 +236,18 @@ public class HugeSecurityManager extends SecurityManager {
@Override
public void checkAccept(String host, int port) {
if (callFromGremlin()) {
throw new SecurityException(
"Not allowed to accept socket via Gremlin");
throw newSecurityException(
"Not allowed to accept socket via Gremlin");
}
super.checkAccept(host, port);
}
@Override
public void checkConnect(String host, int port) {
if (callFromGremlin()) {
throw new SecurityException(
"Not allowed to connect socket via Gremlin");
if (callFromGremlin() && !callFromBackendSocket() &&
!callFromBackendHbase()) {
throw newSecurityException(
"Not allowed to connect socket via Gremlin");
}
super.checkConnect(host, port);
}
@ -206,8 +255,8 @@ public class HugeSecurityManager extends SecurityManager {
@Override
public void checkConnect(String host, int port, Object context) {
if (callFromGremlin()) {
throw new SecurityException(
"Not allowed to connect socket via Gremlin");
throw newSecurityException(
"Not allowed to connect socket via Gremlin");
}
super.checkConnect(host, port, context);
}
@ -215,7 +264,7 @@ public class HugeSecurityManager extends SecurityManager {
@Override
public void checkMulticast(InetAddress maddr) {
if (callFromGremlin()) {
throw new SecurityException("Not allowed to multicast via Gremlin");
throw newSecurityException("Not allowed to multicast via Gremlin");
}
super.checkMulticast(maddr);
}
@ -224,7 +273,7 @@ public class HugeSecurityManager extends SecurityManager {
@SuppressWarnings("deprecation")
public void checkMulticast(InetAddress maddr, byte ttl) {
if (callFromGremlin()) {
throw new SecurityException("Not allowed to multicast via Gremlin");
throw newSecurityException("Not allowed to multicast via Gremlin");
}
super.checkMulticast(maddr, ttl);
}
@ -232,8 +281,8 @@ public class HugeSecurityManager extends SecurityManager {
@Override
public void checkSetFactory() {
if (callFromGremlin()) {
throw new SecurityException(
"Not allowed to set socket factory via Gremlin");
throw newSecurityException(
"Not allowed to set socket factory via Gremlin");
}
super.checkSetFactory();
}
@ -241,8 +290,8 @@ public class HugeSecurityManager extends SecurityManager {
@Override
public void checkPropertiesAccess() {
if (callFromGremlin()) {
throw new SecurityException(
"Not allowed to access system properties via Gremlin");
throw newSecurityException(
"Not allowed to access system properties via Gremlin");
}
super.checkPropertiesAccess();
}
@ -250,10 +299,9 @@ public class HugeSecurityManager extends SecurityManager {
@Override
public void checkPropertyAccess(String key) {
if (!callFromAcceptClassLoaders() && callFromGremlin() &&
!WHITE_SYSTEM_PROPERTYS.contains(key)) {
throw new SecurityException(String.format(
"Not allowed to access system property(%s) via Gremlin",
key));
!WHITE_SYSTEM_PROPERTYS.contains(key) && !callFromBackendHbase()) {
throw newSecurityException(
"Not allowed to access system property(%s) via Gremlin", key);
}
super.checkPropertyAccess(key);
}
@ -261,7 +309,7 @@ public class HugeSecurityManager extends SecurityManager {
@Override
public void checkPrintJobAccess() {
if (callFromGremlin()) {
throw new SecurityException("Not allowed to print job via Gremlin");
throw newSecurityException("Not allowed to print job via Gremlin");
}
super.checkPrintJobAccess();
}
@ -270,8 +318,8 @@ public class HugeSecurityManager extends SecurityManager {
@SuppressWarnings("deprecation")
public void checkSystemClipboardAccess() {
if (callFromGremlin()) {
throw new SecurityException(
"Not allowed to access system clipboard via Gremlin");
throw newSecurityException(
"Not allowed to access system clipboard via Gremlin");
}
super.checkSystemClipboardAccess();
}
@ -309,6 +357,29 @@ public class HugeSecurityManager extends SecurityManager {
super.checkAwtEventQueueAccess();
}
private static SecurityException newSecurityException(String message,
Object... args) {
if (args.length > 0) {
message = String.format(message, args);
}
/*
* use dynamic logger here because "static final logger" can't be
* initialized: the logger is not initialized when HugeSecurityManager
* class is loaded
*/
Logger log = Log.logger(HugeSecurityManager.class);
log.warn("SecurityException: {}", message);
return new SecurityException(message);
}
private static boolean readGroovyInCurrentDir(String file) {
if (USER_DIR != null && file != null && file.startsWith(USER_DIR)
&& (file.endsWith(".class") || file.endsWith(".groovy"))) {
return true;
}
return false;
}
private static boolean callFromGremlin() {
return callFromWorkerWithClass(GREMLIN_EXECUTOR_CLASS);
}
@ -321,6 +392,32 @@ public class HugeSecurityManager extends SecurityManager {
return callFromWorkerWithClass(CAFFEINE_CLASSES);
}
private static boolean callFromBackendSocket() {
// Fixed issue #758
return callFromMethods(BACKEND_SOCKET);
}
private static boolean callFromBackendThread() {
// Fixed issue #758
return callFromMethods(BACKEND_THREAD);
}
private static boolean callFromEventHubNotify() {
// Fixed issue #758
// notify() will create thread when submit task to executor
return callFromMethod("com.baidu.hugegraph.event.EventHub", "notify");
}
private static boolean callFromAsyncTasks() {
// Async tasks will create thread when submitted to executor
return callFromMethods(ASYNC_TASKS);
}
private static boolean callFromBackendHbase() {
// TODO: remove this unsafe entrance
return callFromWorkerWithClass(HBASE_CLASSES);
}
private static boolean callFromWorkerWithClass(Set<String> classes) {
Thread curThread = Thread.currentThread();
if (curThread.getName().startsWith(GREMLIN_SERVER_WORKER) ||
@ -336,7 +433,19 @@ public class HugeSecurityManager extends SecurityManager {
return false;
}
@SuppressWarnings("unused")
private static boolean callFromMethods(Map<String, Set<String>> methods) {
Thread curThread = Thread.currentThread();
StackTraceElement[] elements = curThread.getStackTrace();
for (StackTraceElement element : elements) {
Set<String> clazzMethods = methods.get(element.getClassName());
if (clazzMethods != null &&
clazzMethods.contains(element.getMethodName())) {
return true;
}
}
return false;
}
private static boolean callFromMethod(String clazz, String method) {
Thread curThread = Thread.currentThread();
StackTraceElement[] elements = curThread.getStackTrace();

View File

@ -50,7 +50,7 @@ public final class HugeGraphStep<S, E extends Element>
// Store limit/order-by
private final Query queryInfo = new Query(HugeType.UNKNOWN);
private Iterator<E> lastTimeResults = null;
private Iterator<E> lastTimeResults = QueryResults.emptyIterator();
public HugeGraphStep(final GraphStep<S, E> originGraphStep) {
super(originGraphStep.getTraversal(),

View File

@ -38,6 +38,7 @@ import com.baidu.hugegraph.HugeGraph;
import com.baidu.hugegraph.backend.id.Id;
import com.baidu.hugegraph.backend.query.ConditionQuery;
import com.baidu.hugegraph.backend.query.Query;
import com.baidu.hugegraph.backend.query.QueryResults;
import com.baidu.hugegraph.backend.tx.GraphTransaction;
import com.baidu.hugegraph.type.define.Directions;
import com.baidu.hugegraph.util.Log;
@ -54,7 +55,7 @@ public final class HugeVertexStep<E extends Element>
// Store limit/order-by
private final Query queryInfo = new Query(null);
private Iterator<E> lastTimeResults = null;
private Iterator<E> lastTimeResults = QueryResults.emptyIterator();
public HugeVertexStep(final VertexStep<E> originVertexStep) {
super(originVertexStep.getTraversal(),

View File

@ -50,8 +50,7 @@ public interface QueryHolder extends HasContainerHolder, Metadatable {
}
public default long setRange(long start, long end) {
this.queryInfo().range(start, end);
return this.queryInfo().limit();
return this.queryInfo().range(start, end);
}
public default void setPage(String page) {

View File

@ -570,9 +570,9 @@ public final class TraversalUtil {
private static <V> V validPredicateValue(V value, PropertyKey pkey) {
V validValue = pkey.convValue(value, false);
E.checkArgumentNotNull(validValue,
"Invalid data type of query value, " +
"expect '%s', actual '%s'",
pkey.dataType().clazz(),
"Invalid data type of query value '%s', " +
"expect '%s' for '%s', actual '%s'",
value, pkey.dataType().clazz(), pkey.name(),
value == null ? null : value.getClass());
return validValue;
}

View File

@ -57,10 +57,10 @@ check_port "$REST_SERVER_URL"
echo "Starting HugeGraphServer..."
if [ -n "$VERBOSE" ]; then
"$BIN"/hugegraph-server.sh "$TOP"/conf/gremlin-server.yaml \
"$TOP"/conf/rest-server.properties "$OPEN_SECURITY_CHECK" $USER_OPTION $GC_OPTION &
"$TOP"/conf/rest-server.properties "$OPEN_SECURITY_CHECK" "$USER_OPTION" "$GC_OPTION" &
else
"$BIN"/hugegraph-server.sh "$TOP"/conf/gremlin-server.yaml \
"$TOP"/conf/rest-server.properties "$OPEN_SECURITY_CHECK" $USER_OPTION $GC_OPTION >/dev/null 2>&1 &
"$TOP"/conf/rest-server.properties "$OPEN_SECURITY_CHECK" "$USER_OPTION" "$GC_OPTION" >/dev/null 2>&1 &
fi
PID="$!"
@ -69,7 +69,7 @@ echo "$PID" > $PID_FILE
trap 'kill $PID; exit' SIGHUP SIGINT SIGQUIT SIGTERM
wait_for_startup 'HugeGraphServer' "$REST_SERVER_URL/graphs" $SERVER_STARTUP_TIMEOUT_S || {
wait_for_startup ${PID} 'HugeGraphServer' "$REST_SERVER_URL/graphs" ${SERVER_STARTUP_TIMEOUT_S} || {
echo "See $TOP/logs/hugegraph-server.log for HugeGraphServer log output." >&2
exit 1
}

View File

@ -107,9 +107,10 @@ function crontab_remove() {
# wait_for_startup friendly_name host port timeout_s
function wait_for_startup() {
local server_name="$1"
local server_url="$2"
local timeout_s="$3"
local pid="$1"
local server_name="$2"
local server_url="$3"
local timeout_s="$4"
local now_s=`date '+%s'`
local stop_s=$(( $now_s + $timeout_s ))
@ -119,8 +120,14 @@ function wait_for_startup() {
echo -n "Connecting to $server_name ($server_url)"
while [ $now_s -le $stop_s ]; do
echo -n .
process_status "$server_name" "$pid" >/dev/null
if [ $? -eq 1 ]; then
echo "Starting $server_name failed"
return 1
fi
status=`curl -o /dev/null -s -w %{http_code} $server_url`
if [ $status -eq 200 ]; then
if [[ $status -eq 200 || $status -eq 401 ]]; then
echo "OK"
return 0
fi

View File

@ -1,8 +1,9 @@
hosts: [localhost]
port: 8182
serializer: {
className: org.apache.tinkerpop.gremlin.driver.ser.GryoMessageSerializerV1d0,
className: org.apache.tinkerpop.gremlin.driver.ser.GraphSONMessageSerializerV1d0,
config: {
serializeResultToString: false
serializeResultToString: false,
ioRegistries: [com.baidu.hugegraph.io.HugeGraphIoRegistry]
}
}

View File

@ -1,4 +1,4 @@
# host and port of gremlin server
# host and port of gremlin server, need to be consistent with host and port in rest-server.properties
#host: 127.0.0.1
#port: 8182
@ -31,12 +31,6 @@ scriptEngines: {
}
}
serializers:
- { className: org.apache.tinkerpop.gremlin.driver.ser.GryoLiteMessageSerializerV1d0,
config: {
serializeResultToString: false,
ioRegistries: [com.baidu.hugegraph.io.HugeGraphIoRegistry]
}
}
- { className: org.apache.tinkerpop.gremlin.driver.ser.GraphBinaryMessageSerializerV1,
config: {
serializeResultToString: false,

View File

@ -21,6 +21,9 @@ serializer=binary
store=hugegraph
search.text_analyzer=jieba
search.text_analyzer_mode=INDEX
# rocksdb backend config
#rocksdb.data_path=/path/to/disk
#rocksdb.wal_path=/path/to/disk

View File

@ -1,3 +1,9 @@
hosts: [localhost]
port: 8182
serializer: { className: org.apache.tinkerpop.gremlin.driver.ser.GryoMessageSerializerV1d0, config: { ioRegistries: [com.baidu.hugegraph.graphdb.tinkerpop.hugegraphIoRegistry] }}
serializer: {
className: org.apache.tinkerpop.gremlin.driver.ser.GraphSONMessageSerializerV1d0,
config: {
serializeResultToString: false,
ioRegistries: [com.baidu.hugegraph.io.HugeGraphIoRegistry]
}
}

View File

@ -1,9 +1,9 @@
hosts: [localhost]
port: 8182
serializer: {
className: org.apache.tinkerpop.gremlin.driver.ser.GryoMessageSerializerV1d0,
className: org.apache.tinkerpop.gremlin.driver.ser.GraphSONMessageSerializerV1d0,
config: {
serializeResultToString: true,
serializeResultToString: false,
ioRegistries: [com.baidu.hugegraph.io.HugeGraphIoRegistry]
}
}
}

View File

@ -1,5 +1,7 @@
# bind url
restserver.url=http://127.0.0.1:8080
# gremlin server url, need to be consistent with host and port in gremlin-server.yaml
#gremlinserver.url=http://127.0.0.1:8182
# graphs list with pair NAME:CONF_PATH
graphs=[hugegraph:conf/hugegraph.properties]

View File

@ -1,8 +1,4 @@
com.baidu.hugegraph.plugin.HugeGraphGremlinPlugin
org.apache.tinkerpop.gremlin.console.plugin.DriverGremlinPlugin
org.apache.tinkerpop.gremlin.console.groovy.plugin.DriverGremlinPlugin
org.apache.tinkerpop.gremlin.tinkergraph.groovy.plugin.TinkerGraphGremlinPlugin
org.apache.tinkerpop.gremlin.hadoop.groovy.plugin.HadoopGremlinPlugin
org.apache.tinkerpop.gremlin.giraph.groovy.plugin.GiraphGremlinPlugin
org.apache.tinkerpop.gremlin.spark.groovy.plugin.SparkGremlinPlugin
org.apache.tinkerpop.gremlin.console.groovy.plugin.UtilitiesGremlinPlugin
org.apache.tinkerpop.gremlin.console.jsr223.DriverGremlinPlugin
org.apache.tinkerpop.gremlin.console.jsr223.UtilitiesGremlinPlugin
org.apache.tinkerpop.gremlin.tinkergraph.jsr223.TinkerGraphGremlinPlugin

View File

@ -4,6 +4,10 @@ set -ev
TRAVIS_DIR=`dirname $0`
if [ ! -d $HOME/downloads ]; then
mkdir $HOME/downloads
fi
if [[ "$BACKEND" == "cassandra" || "$BACKEND" == "scylladb" ]]; then
$TRAVIS_DIR/install-cassandra.sh
elif [ "$BACKEND" == "hbase" ]; then

View File

@ -3,9 +3,11 @@
set -ev
TRAVIS_DIR=`dirname $0`
VERSION=`mvn help:evaluate -Dexpression=project.version -q -DforceStdout`
SERVER_DIR=hugegraph-$VERSION
mvn package -DskipTests
$TRAVIS_DIR/start-server.sh
mvn test -P api-test,$BACKEND
$TRAVIS_DIR/start-server.sh $SERVER_DIR
mvn test -P api-test,$BACKEND || (cat $SERVER_DIR/logs/hugegraph-server.log && exit 1)
$TRAVIS_DIR/build-report.sh
$TRAVIS_DIR/stop-server.sh

View File

@ -4,10 +4,11 @@ set -ev
HOME_DIR=`pwd`
TRAVIS_DIR=`dirname $0`
VERSION=`mvn help:evaluate -Dexpression=project.version -q -DforceStdout`
BASE_DIR=hugegraph-$VERSION
BASE_DIR=$1
BIN=$BASE_DIR/bin
CONF=$BASE_DIR/conf/hugegraph.properties
REST_CONF=$BASE_DIR/conf/rest-server.properties
GREMLIN_CONF=$BASE_DIR/conf/gremlin-server.yaml
# PostgreSQL configurations
POSTGRESQL_DRIVER=org.postgresql.Driver
@ -32,6 +33,13 @@ if [ "$BACKEND" == "postgresql" ]; then
sed -i "s/#jdbc.username=.*/jdbc.username=$POSTGRESQL_USERNAME/" $CONF
fi
# Set timeout for hbase
if [ "$BACKEND" == "hbase" ]; then
sed -i '$arestserver.request_timeout=200' $REST_CONF
sed -i '$agremlinserver.timeout=200' $REST_CONF
sed -i 's/scriptEvaluationTimeout.*/scriptEvaluationTimeout: 200000/' $GREMLIN_CONF
fi
# Append schema.sync_deletion=true to config file
echo "schema.sync_deletion=true" >> $CONF

View File

@ -20,6 +20,7 @@
package com.baidu.hugegraph.backend.store.hbase;
import static com.baidu.hugegraph.config.OptionChecker.disallowEmpty;
import static com.baidu.hugegraph.config.OptionChecker.positiveInt;
import static com.baidu.hugegraph.config.OptionChecker.rangeInt;
import com.baidu.hugegraph.config.ConfigOption;
@ -80,4 +81,12 @@ public class HbaseOptions extends OptionHolder {
rangeInt(1, 1000),
64
);
public static final ConfigOption<Long> TRUNCATE_TIMEOUT =
new ConfigOption<>(
"hbase.truncate_timeout",
"The timeout in seconds of waiting for store truncate.",
positiveInt(),
30L
);
}

View File

@ -115,6 +115,7 @@ public class HbaseSessions extends BackendSessionPool {
@Override
protected synchronized boolean opened() {
// NOTE: isClosed() seems to always return true even if not connected
return this.hbase != null && !this.hbase.isClosed();
}
@ -205,7 +206,7 @@ public class HbaseSessions extends BackendSessionPool {
} catch (TableNotEnabledException ignored) {
// pass
}
return admin.truncateTableAsync(tableName, false);
return admin.truncateTableAsync(tableName, true);
}
}
@ -232,11 +233,9 @@ public class HbaseSessions extends BackendSessionPool {
*/
public final class Session extends BackendSession {
private boolean closed;
private final Map<String, List<Row>> batch;
public Session() {
this.closed = false;
this.batch = new HashMap<>();
}
@ -272,15 +271,20 @@ public class HbaseSessions extends BackendSessionPool {
}
}
@Override
public void open() {
this.opened = true;
}
@Override
public void close() {
assert this.closeable();
this.closed = true;
this.opened = false;
}
@Override
public boolean closed() {
return this.closed;
return !this.opened || !HbaseSessions.this.opened();
}
/**

View File

@ -24,6 +24,10 @@ import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors;
import org.apache.hadoop.hbase.NamespaceExistException;
@ -128,8 +132,8 @@ public abstract class HbaseStore extends AbstractBackendStore<Session> {
this.sessions = new HbaseSessions(config, this.namespace, this.store);
}
// NOTE: seems to always return true even if not connected
if (this.sessions.opened()) {
assert this.sessions != null;
if (!this.sessions.closed()) {
LOG.debug("Store {} has been opened before", this.store);
this.sessions.useSession();
return;
@ -162,7 +166,7 @@ public abstract class HbaseStore extends AbstractBackendStore<Session> {
@Override
public boolean opened() {
this.checkConnectionOpened();
return !this.sessions.session().closed();
return this.sessions.session().opened();
}
@Override
@ -243,7 +247,7 @@ public abstract class HbaseStore extends AbstractBackendStore<Session> {
}
@Override
public void clear() {
public void clear(boolean clearSpace) {
this.checkConnectionOpened();
// Return if not exists namespace
@ -257,29 +261,34 @@ public abstract class HbaseStore extends AbstractBackendStore<Session> {
e, this.namespace);
}
// Drop tables
for (String table : this.tableNames()) {
try {
this.sessions.dropTable(table);
} catch (TableNotFoundException e) {
continue;
} catch (IOException e) {
throw new BackendException("Failed to drop table '%s' for '%s'",
e, table, this.store);
if (!clearSpace) {
// Drop tables
for (String table : this.tableNames()) {
try {
this.sessions.dropTable(table);
} catch (TableNotFoundException e) {
LOG.warn("The table '{}' for '{}' does not exist " +
"when trying to drop", table, this.store);
} catch (IOException e) {
throw new BackendException(
"Failed to drop table '%s' for '%s'",
e, table, this.store);
}
}
}
// Drop namespace
try {
this.sessions.dropNamespace();
} catch (IOException e) {
String notEmpty = "Only empty namespaces can be removed";
if (e.getCause().getMessage().contains(notEmpty)) {
LOG.debug("Can't drop namespace '{}': {}", this.namespace, e);
} else {
throw new BackendException(
"Failed to drop namespace '%s' for '%s'",
e, this.namespace, this.store);
} else {
// Drop namespace
try {
this.sessions.dropNamespace();
} catch (IOException e) {
String notEmpty = "Only empty namespaces can be removed";
if (e.getCause().getMessage().contains(notEmpty)) {
LOG.debug("Can't drop namespace '{}': {}",
this.namespace, e);
} else {
throw new BackendException(
"Failed to drop namespace '%s' for '%s'",
e, this.namespace, this.store);
}
}
}
@ -310,14 +319,25 @@ public abstract class HbaseStore extends AbstractBackendStore<Session> {
this.checkOpened();
// Truncate tables
for (String table : this.tableNames()) {
try {
this.sessions.truncateTable(table);
} catch (IOException e) {
throw new BackendException(
"Failed to truncate table '%s' for '%s'",
e, table, this.store);
List<String> tables = this.tableNames();
Map<String, Future<Void>> futures = new HashMap<>(tables.size());
String currentTable = null;
try {
for (String table : tables) {
currentTable = table;
futures.put(table, this.sessions.truncateTable(table));
}
long timeout = this.sessions.config()
.get(HbaseOptions.TRUNCATE_TIMEOUT);
for (Map.Entry<String, Future<Void>> entry : futures.entrySet()) {
currentTable = entry.getKey();
entry.getValue().get(timeout, TimeUnit.SECONDS);
}
} catch (IOException | InterruptedException |
ExecutionException | TimeoutException e) {
throw new BackendException(
"Failed to truncate table '%s' for '%s'",
e, currentTable, this.store);
}
LOG.debug("Store truncated: {}", this.store);

View File

@ -123,7 +123,7 @@ public class HbaseTable extends BackendTable<Session, BackendEntry> {
@Override
public Iterator<BackendEntry> query(Session session, Query query) {
if (query.limit() == 0 && query.limit() != Query.NO_LIMIT) {
if (query.limit() == 0L && !query.nolimit()) {
LOG.debug("Return empty result(limit=0) for query {}", query);
return ImmutableList.<BackendEntry>of().iterator();
}

View File

@ -125,7 +125,7 @@ public class MysqlSessions extends BackendSessionPool {
if (e.getCause() instanceof SocketTimeoutException) {
LOG.warn("Drop database '{}' timeout", this.database());
} else {
throw new BackendException("Failed to drop database '%s'",
throw new BackendException("Failed to drop database '%s'", e,
this.database());
}
}
@ -156,6 +156,11 @@ public class MysqlSessions extends BackendSessionPool {
}
}
public void resetConnections() {
// Close the under layer connections owned by each thread
this.forceResetSessions();
}
protected String buildCreateDatabase(String database) {
return String.format("CREATE DATABASE IF NOT EXISTS %s " +
"DEFAULT CHARSET utf8 COLLATE utf8_bin;",
@ -178,15 +183,11 @@ public class MysqlSessions extends BackendSessionPool {
* Connect DB without specified database
*/
protected Connection openWithoutDB(int timeout) {
String jdbcUrl = this.config.get(MysqlOptions.JDBC_URL);
String url = new URIBuilder().setPath(jdbcUrl)
.setParameter("socketTimeout",
String.valueOf(timeout))
.toString();
String url = this.buildUri(false, false, false, timeout);
try {
return this.connect(url);
} catch (SQLException e) {
throw new BackendException("Failed to access %s", jdbcUrl);
throw new BackendException("Failed to access %s", e, url);
}
}
@ -194,20 +195,11 @@ public class MysqlSessions extends BackendSessionPool {
* Connect DB with specified database, but won't auto reconnect
*/
protected Connection openWithDB(int timeout) {
String jdbcUrl = this.config.get(MysqlOptions.JDBC_URL);
if (jdbcUrl.endsWith("/")) {
jdbcUrl = String.format("%s%s", jdbcUrl, this.database());
} else {
jdbcUrl = String.format("%s/%s", jdbcUrl, this.database());
}
String url = new URIBuilder().setPath(jdbcUrl)
.setParameter("socketTimeout",
String.valueOf(timeout))
.toString();
String url = this.buildUri(false, true, false, timeout);
try {
return this.connect(url);
} catch (SQLException e) {
throw new BackendException("Failed to access %s", jdbcUrl);
throw new BackendException("Failed to access %s", e, url);
}
}
@ -215,27 +207,39 @@ public class MysqlSessions extends BackendSessionPool {
* Connect DB with specified database
*/
private Connection open(boolean autoReconnect) throws SQLException {
String url = this.buildUri(true, true, autoReconnect, null);
return this.connect(url);
}
protected String buildUri(boolean withConnParams, boolean withDB,
boolean autoReconnect, Integer timeout) {
String url = this.config.get(MysqlOptions.JDBC_URL);
if (url.endsWith("/")) {
url = String.format("%s%s", url, this.database());
} else {
url = String.format("%s/%s", url, this.database());
if (withDB) {
if (url.endsWith("/")) {
url = String.format("%s%s", url, this.database());
} else {
url = String.format("%s/%s", url, this.database());
}
}
int maxTimes = this.config.get(MysqlOptions.JDBC_RECONNECT_MAX_TIMES);
int interval = this.config.get(MysqlOptions.JDBC_RECONNECT_INTERVAL);
String sslMode = this.config.get(MysqlOptions.JDBC_SSL_MODE);
URIBuilder uriBuilder = this.newConnectionURIBuilder();
uriBuilder.setPath(url)
.setParameter("useSSL", sslMode)
.setParameter("characterEncoding", "utf-8")
.setParameter("rewriteBatchedStatements", "true")
.setParameter("useServerPrepStmts", "false")
.setParameter("autoReconnect", String.valueOf(autoReconnect))
.setParameter("maxReconnects", String.valueOf(maxTimes))
.setParameter("initialTimeout", String.valueOf(interval));
return this.connect(uriBuilder.toString());
URIBuilder builder = this.newConnectionURIBuilder();
builder.setPath(url).setParameter("useSSL", sslMode);
if (withConnParams) {
builder.setParameter("characterEncoding", "utf-8")
.setParameter("rewriteBatchedStatements", "true")
.setParameter("useServerPrepStmts", "false")
.setParameter("autoReconnect", String.valueOf(autoReconnect))
.setParameter("maxReconnects", String.valueOf(maxTimes))
.setParameter("initialTimeout", String.valueOf(interval));
}
if (timeout != null) {
builder.setParameter("socketTimeout", String.valueOf(timeout));
}
return builder.toString();
}
protected URIBuilder newConnectionURIBuilder() {
@ -260,26 +264,36 @@ public class MysqlSessions extends BackendSessionPool {
private Connection conn;
private Map<String, PreparedStatement> statements;
private boolean opened;
private int count;
public Session() {
this.conn = null;
this.statements = new HashMap<>();
this.opened = false;
this.count = 0;
try {
this.open();
} catch (SQLException ignored) {
// Ignore
}
}
public HugeConfig config() {
return MysqlSessions.this.config();
}
public void open() throws SQLException {
@Override
public void open() {
try {
this.doOpen();
} catch (SQLException e) {
throw new BackendException("Failed to open connection", e);
}
}
private void tryOpen() {
try {
this.doOpen();
} catch (SQLException ignored) {
// Ignore
}
}
private void doOpen() throws SQLException {
if (this.conn != null && !this.conn.isClosed()) {
return;
}
@ -294,6 +308,11 @@ public class MysqlSessions extends BackendSessionPool {
return;
}
this.opened = false;
this.doClose();
}
private void doClose() {
SQLException exception = null;
for (PreparedStatement statement : this.statements.values()) {
try {
@ -302,23 +321,42 @@ public class MysqlSessions extends BackendSessionPool {
exception = e;
}
}
this.statements.clear();
try {
this.conn.close();
} catch (SQLException e) {
exception = e;
} finally {
this.conn = null;
}
this.opened = false;
if (exception != null) {
throw new BackendException("Failed to close connection",
exception);
}
}
@Override
public boolean opened() {
if (this.opened && this.conn == null) {
// Reconnect if the connection is reset
tryOpen();
}
return this.opened && this.conn != null;
}
@Override
public boolean closed() {
return !this.opened;
if (!this.opened || this.conn == null) {
return true;
}
try {
return this.conn.isClosed();
} catch (SQLException ignored) {
// Assume closed here
return true;
}
}
public void clear() {
@ -398,6 +436,19 @@ public class MysqlSessions extends BackendSessionPool {
}
}
@Override
protected void reset() {
// NOTE: this method may be called by other threads
if (this.conn == null) {
return;
}
try {
this.doClose();
} catch (Exception e) {
LOG.warn("Failed to reset connection", e);
}
}
public ResultSet select(String sql) throws SQLException {
assert this.conn.getAutoCommit();
return this.conn.createStatement().executeQuery(sql);

View File

@ -147,7 +147,7 @@ public abstract class MysqlStore extends AbstractBackendStore<Session> {
@Override
public boolean opened() {
this.checkClusterConnected();
return !this.sessions.session().closed();
return this.sessions.session().opened();
}
@Override
@ -157,7 +157,7 @@ public abstract class MysqlStore extends AbstractBackendStore<Session> {
try {
// Open a new session connected with specified database
this.sessions.session().open();
} catch (SQLException e) {
} catch (Exception e) {
throw new BackendException("Failed to connect database '%s'",
this.database);
}
@ -168,14 +168,28 @@ public abstract class MysqlStore extends AbstractBackendStore<Session> {
}
@Override
public void clear() {
public void clear(boolean clearSpace) {
// Check connected
this.checkClusterConnected();
if (this.sessions.existsDatabase()) {
this.checkOpened();
this.clearTables();
this.sessions.dropDatabase();
if (!clearSpace) {
this.checkOpened();
this.clearTables();
/*
* Disconnect connections for following database drop.
* Connections will be auto reconnected if not drop database
* in next step, but never do this operation because database
* might be blocked in mysql or throw 'terminating' exception.
* we can't resetConnections() when dropDatabase(), because
* there are 3 stores(schema,system,graph), which are shared
* one database, other stores may keep connected with the
* database when one store doing clear(clearSpace=false).
*/
this.sessions.resetConnections();
} else {
this.sessions.dropDatabase();
}
}
LOG.debug("Store cleared: {}", this.store);

View File

@ -19,14 +19,10 @@
package com.baidu.hugegraph.backend.store.mysql;
import java.util.Iterator;
import com.baidu.hugegraph.backend.BackendException;
import com.baidu.hugegraph.backend.store.AbstractBackendStoreProvider;
import com.baidu.hugegraph.backend.store.BackendStore;
import com.baidu.hugegraph.backend.store.mysql.MysqlStore.MysqlGraphStore;
import com.baidu.hugegraph.backend.store.mysql.MysqlStore.MysqlSchemaStore;
import com.baidu.hugegraph.util.Events;
public class MysqlStoreProvider extends AbstractBackendStoreProvider {
@ -44,20 +40,6 @@ public class MysqlStoreProvider extends AbstractBackendStoreProvider {
return new MysqlGraphStore(this, this.database(), store);
}
@Override
public void clear() throws BackendException {
this.checkOpened();
/*
* We should drop database once only with stores(schema/graph/system),
* otherwise it will easily lead to blocking when drop tables
*/
Iterator<BackendStore> iter = this.stores.values().iterator();
if (iter.hasNext()) {
iter.next().clear();
}
this.notifyAndWaitEvent(Events.STORE_CLEAR);
}
@Override
public String type() {
return "mysql";

View File

@ -296,7 +296,7 @@ public abstract class MysqlTable
public Iterator<BackendEntry> query(Session session, Query query) {
ExtendableIterator<BackendEntry> rs = new ExtendableIterator<>();
if (query.limit() == 0 && query.limit() != Query.NO_LIMIT) {
if (query.limit() == 0L && !query.nolimit()) {
LOG.debug("Return empty result(limit=0) for query {}", query);
return rs;
}
@ -344,7 +344,7 @@ public abstract class MysqlTable
}
if (query.paging()) {
this.wrapPage(selection, query);
} else if (query.limit() != Query.NO_LIMIT || query.offset() > 0) {
} else if (!query.nolimit() || query.offset() > 0) {
this.wrapOffset(selection, query);
}
}
@ -512,7 +512,7 @@ public abstract class MysqlTable
select.append(this.orderByKeys());
if (query.limit() != Query.NO_LIMIT) {
if (!query.nolimit()) {
// Fetch `limit + 1` rows for judging whether reached the last page
select.append(" limit ");
select.append(query.limit() + 1);

View File

@ -536,14 +536,10 @@ public class RocksDBStdSessions extends RocksDBSessions {
*/
private final class StdSession extends RocksDBSessions.Session {
private boolean closed;
private WriteBatch batch;
private WriteOptions writeOptions;
public StdSession(HugeConfig conf) {
this.closed = false;
boolean bulkload = conf.get(RocksDBOptions.BULKLOAD_MODE);
this.batch = new WriteBatch();
this.writeOptions = new WriteOptions();
@ -551,15 +547,20 @@ public class RocksDBStdSessions extends RocksDBSessions {
//this.writeOptions.setSync(false);
}
@Override
public void open() {
this.opened = true;
}
@Override
public void close() {
assert this.closeable();
this.closed = true;
this.opened = false;
}
@Override
public boolean closed() {
return this.closed;
return !this.opened || !RocksDBStdSessions.this.opened();
}
/**

View File

@ -333,7 +333,7 @@ public abstract class RocksDBStore extends AbstractBackendStore<Session> {
@Override
public boolean opened() {
this.checkDbOpened();
return !this.sessions.session().closed();
return this.sessions.session().opened();
}
@Override
@ -412,14 +412,15 @@ public abstract class RocksDBStore extends AbstractBackendStore<Session> {
}
@Override
public synchronized void clear() {
public synchronized void clear(boolean clearSpace) {
this.checkDbOpened();
// Drop tables with main disk
for (String table : this.tableNames()) {
this.dropTable(this.sessions, table);
}
// Drop table with optimized disk
// Drop tables with optimized disk
Map<String, RocksDBSessions> tableDBMap = this.tableDBMapping();
for (Map.Entry<String, RocksDBSessions> e : tableDBMap.entrySet()) {
this.dropTable(e.getValue(), e.getKey());
@ -461,7 +462,7 @@ public abstract class RocksDBStore extends AbstractBackendStore<Session> {
public synchronized void truncate() {
this.checkOpened();
this.clear();
this.clear(false);
this.init();
LOG.debug("Store truncated: {}", this.store);

View File

@ -113,7 +113,7 @@ public class RocksDBTable extends BackendTable<Session, BackendEntry> {
@Override
public Iterator<BackendEntry> query(Session session, Query query) {
if (query.limit() == 0 && query.limit() != Query.NO_LIMIT) {
if (query.limit() == 0L && !query.nolimit()) {
LOG.debug("Return empty result(limit=0) for query {}", query);
return ImmutableList.<BackendEntry>of().iterator();
}

View File

@ -172,23 +172,21 @@ public class RocksDBSstSessions extends RocksDBSessions {
*/
private final class SstSession extends Session {
private boolean closed;
private Map<String, Changes> batch;
public SstSession() {
this.closed = false;
this.batch = new HashMap<>();
}
@Override
public void open() {
this.opened = true;
}
@Override
public void close() {
assert this.closeable();
this.closed = true;
}
@Override
public boolean closed() {
return this.closed;
this.opened = false;
}
/**

View File

@ -39,7 +39,7 @@ public class GremlinApiTest extends BaseApiTest {
+ "\"bindings\":{},"
+ "\"language\":\"gremlin-groovy\","
+ "\"aliases\":{\"g\":\"__g_hugegraph\"}}";
Assert.assertEquals(200, client().post(path, body).getStatus());
assertResponseStatus(200, client().post(path, body));
}
@Test
@ -49,4 +49,63 @@ public class GremlinApiTest extends BaseApiTest {
Response r = client().get(path, params);
Assert.assertEquals(r.readEntity(String.class), 200, r.getStatus());
}
@Test
public void testScript() {
String bodyTemplate = "{"
+ "\"gremlin\":\"%s\","
+ "\"bindings\":{},"
+ "\"language\":\"gremlin-groovy\","
+ "\"aliases\":{\"g\":\"__g_hugegraph\"}}";
String script = "schema=hugegraph.schema();"
+ "schema.propertyKey('name').asText().ifNotExist().create();"
+ "schema.propertyKey('age').asInt().ifNotExist().create();"
+ "schema.propertyKey('city').asUUID().ifNotExist().create();"
+ "schema.propertyKey('lang').asText().ifNotExist().create();"
+ "schema.propertyKey('date').asText().ifNotExist().create();"
+ "schema.propertyKey('price').asInt().ifNotExist().create();"
+ "person=schema.vertexLabel('person').properties('name','age','city').useCustomizeUuidId().ifNotExist().create();"
+ "knows=schema.edgeLabel('knows').sourceLabel('person').targetLabel('person').properties('date').ifNotExist().create();"
+ "marko=hugegraph.addVertex(T.id, '835e1153928149578691cf79258e90eb', T.label,'person','name','marko','age',29,'city','135e1153928149578691cf79258e90eb');"
+ "vadas=hugegraph.addVertex(T.id, '935e1153928149578691cf79258e90eb', T.label,'person','name','vadas','age',27,'city','235e1153928149578691cf79258e90eb');"
+ "marko.addEdge('knows',vadas,'date','20160110');";
String body = String.format(bodyTemplate, script);
assertResponseStatus(200, client().post(path, body));
String queryV = "g.V()";
body = String.format(bodyTemplate, queryV);
assertResponseStatus(200, client().post(path, body));
String queryE = "g.E()";
body = String.format(bodyTemplate, queryE);
assertResponseStatus(200, client().post(path, body));
}
@Test
public void testClearAndInit() {
String body = "{"
+ "\"gremlin\":\"hugegraph.clearBackend()\","
+ "\"bindings\":{},"
+ "\"language\":\"gremlin-groovy\","
+ "\"aliases\":{\"g\":\"__g_hugegraph\"}}";
assertResponseStatus(200, client().post(path, body));
body = "{"
+ "\"gremlin\":\"hugegraph.initBackend()\","
+ "\"bindings\":{},"
+ "\"language\":\"gremlin-groovy\","
+ "\"aliases\":{\"g\":\"__g_hugegraph\"}}";
assertResponseStatus(200, client().post(path, body));
}
@Test
public void testTruncate() {
String body = "{"
+ "\"gremlin\":\"hugegraph.truncateBackend()\","
+ "\"bindings\":{},"
+ "\"language\":\"gremlin-groovy\","
+ "\"aliases\":{\"g\":\"__g_hugegraph\"}}";
assertResponseStatus(200, client().post(path, body));
}
}

View File

@ -5768,14 +5768,14 @@ public class VertexCoreTest extends BaseCoreTest {
.has("city", "Beijing").toList();
Assert.assertEquals(3, vertices.size());
List<Vertex> vertices1 = graph().traversal().V()
.has("city", "Beijing")
.range(0, 2).toList();
Set<Vertex> vertices1 = graph().traversal().V()
.has("city", "Beijing")
.range(0, 2).toSet();
Assert.assertEquals(2, vertices1.size());
List<Vertex> vertices2 = graph().traversal().V()
.has("city", "Beijing")
.range(2, 3).toList();
Set<Vertex> vertices2 = graph().traversal().V()
.has("city", "Beijing")
.range(2, 3).toSet();
Assert.assertEquals(1, vertices2.size());
vertices1.addAll(vertices2);
@ -5791,13 +5791,13 @@ public class VertexCoreTest extends BaseCoreTest {
List<Vertex> vertices = graph().traversal().V()
.has("age", P.between(5, 22)).toList();
Assert.assertEquals(4, vertices.size());
List<Vertex> vertices1 = graph().traversal().V()
.has("age", P.between(5, 22))
.range(0, 3).toList();
Set<Vertex> vertices1 = graph().traversal().V()
.has("age", P.between(5, 22))
.range(0, 3).toSet();
Assert.assertEquals(3, vertices1.size());
List<Vertex> vertices2 = graph().traversal().V()
.has("age", P.between(5, 22))
.range(3, 4).toList();
Set<Vertex> vertices2 = graph().traversal().V()
.has("age", P.between(5, 22))
.range(3, 4).toSet();
Assert.assertEquals(1, vertices2.size());
vertices1.addAll(vertices2);
@ -5813,19 +5813,19 @@ public class VertexCoreTest extends BaseCoreTest {
.toList();
Assert.assertEquals(5, vertices.size());
List<Vertex> vertices1 = graph().traversal().V()
.hasLabel("person")
.range(0, 3).toList();
Set<Vertex> vertices1 = graph().traversal().V()
.hasLabel("person")
.range(0, 3).toSet();
Assert.assertEquals(3, vertices1.size());
List<Vertex> vertices2 = graph().traversal().V()
.hasLabel("person")
.range(3, 5).toList();
Set<Vertex> vertices2 = graph().traversal().V()
.hasLabel("person")
.range(3, 5).toSet();
Assert.assertEquals(2, vertices2.size());
List<Vertex> vertices3 = graph().traversal().V()
.hasLabel("person")
.limit(4).toList();
Set<Vertex> vertices3 = graph().traversal().V()
.hasLabel("person")
.limit(4).toSet();
Assert.assertEquals(4, vertices3.size());
vertices1.addAll(vertices2);

View File

@ -119,6 +119,14 @@ public class SecurityManagerTest {
String result = runGremlinJob("new FileInputStream(new File(\"\"))");
assertError(result, "Not allowed to read file via Gremlin");
// read file
String pom = System.getProperty("user.dir") + "/a.groovy";
try (FileInputStream fis = new FileInputStream(new File(pom))) {
} catch (IOException ignored) {}
result = runGremlinJob(String.format(
"new FileInputStream(new File(\"%s\"))", pom));
assertError(result, "(No such file or directory)");
// read file fd
@SuppressWarnings({ "unused", "resource" })
FileInputStream fis = new FileInputStream(FileDescriptor.in);
@ -278,7 +286,8 @@ public class SecurityManagerTest {
}
private static void assertError(String result, String message) {
Assert.assertTrue(result, result.endsWith(message));
Assert.assertTrue(result, result.endsWith(message) ||
result.contains(message));
}
private static String runGremlinJob(String gremlin) {