Add syntax for creating unique indexes
Reviewers: mtomic, llugovic Reviewed By: mtomic Subscribers: pullbot Differential Revision: https://phabricator.memgraph.io/D1653
This commit is contained in:
parent
b527b2b4e4
commit
1b6a6c15e5
@ -176,7 +176,9 @@ add_custom_command(OUTPUT ${antlr_opencypher_generated_src}
|
||||
COMMAND
|
||||
java -jar ${CMAKE_SOURCE_DIR}/libs/antlr-4.6-complete.jar -Dlanguage=Cpp -visitor -o ${opencypher_generated} -package antlropencypher ${opencypher_lexer_grammar} ${opencypher_parser_grammar}
|
||||
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}"
|
||||
DEPENDS ${opencypher_lexer_grammar} ${opencypher_parser_grammar})
|
||||
DEPENDS ${opencypher_lexer_grammar} ${opencypher_parser_grammar}
|
||||
${opencypher_frontend}/grammar/CypherLexer.g4
|
||||
${opencypher_frontend}/grammar/Cypher.g4)
|
||||
|
||||
# add custom target for generation
|
||||
add_custom_target(generate_opencypher_parser
|
||||
|
@ -2152,6 +2152,35 @@ cpp<#
|
||||
cpp<#)
|
||||
(:serialize :capnp))
|
||||
|
||||
(lcp:define-class create-unique-index (clause)
|
||||
((label "storage::Label" :scope :public)
|
||||
(properties "std::vector<storage::Property>" :scope :public
|
||||
:capnp-save (lcp:capnp-save-vector "storage::capnp::Common" "storage::Property")
|
||||
:capnp-load (lcp:capnp-load-vector "storage::capnp::Common" "storage::Property")))
|
||||
(:public
|
||||
#>cpp
|
||||
CreateUniqueIndex() = default;
|
||||
|
||||
DEFVISITABLE(TreeVisitor<TypedValue>);
|
||||
DEFVISITABLE(HierarchicalTreeVisitor);
|
||||
|
||||
CreateUniqueIndex *Clone(AstStorage &storage) const override {
|
||||
return storage.Create<CreateUniqueIndex>(label_, properties_);
|
||||
}
|
||||
cpp<#)
|
||||
(:protected
|
||||
#>cpp
|
||||
explicit CreateUniqueIndex(int uid) : Clause(uid) {}
|
||||
CreateUniqueIndex(int uid, storage::Label label,
|
||||
const std::vector<storage::Property> &properties)
|
||||
: Clause(uid), label_(label), properties_(properties) {}
|
||||
cpp<#)
|
||||
(:private
|
||||
#>cpp
|
||||
friend class AstStorage;
|
||||
cpp<#)
|
||||
(:serialize :capnp))
|
||||
|
||||
(lcp:define-class auth-query (clause)
|
||||
((action "Action" :scope :public)
|
||||
(user "std::string" :scope :public)
|
||||
|
@ -61,6 +61,7 @@ class RemoveLabels;
|
||||
class Merge;
|
||||
class Unwind;
|
||||
class CreateIndex;
|
||||
class CreateUniqueIndex;
|
||||
class AuthQuery;
|
||||
class CreateStream;
|
||||
class DropStream;
|
||||
@ -83,9 +84,9 @@ using TreeCompositeVisitor = ::utils::CompositeVisitor<
|
||||
|
||||
using TreeLeafVisitor =
|
||||
::utils::LeafVisitor<Identifier, PrimitiveLiteral, ParameterLookup,
|
||||
CreateIndex, AuthQuery, CreateStream, DropStream,
|
||||
ShowStreams, StartStopStream, StartStopAllStreams,
|
||||
TestStream>;
|
||||
CreateIndex, CreateUniqueIndex, AuthQuery,
|
||||
CreateStream, DropStream, ShowStreams, StartStopStream,
|
||||
StartStopAllStreams, TestStream>;
|
||||
|
||||
class HierarchicalTreeVisitor : public TreeCompositeVisitor,
|
||||
public TreeLeafVisitor {
|
||||
@ -108,7 +109,8 @@ using TreeVisitor = ::utils::Visitor<
|
||||
Aggregation, Function, Reduce, Extract, All, Single, ParameterLookup,
|
||||
Create, Match, Return, With, Pattern, NodeAtom, EdgeAtom, Delete, Where,
|
||||
SetProperty, SetProperties, SetLabels, RemoveProperty, RemoveLabels, Merge,
|
||||
Unwind, Identifier, PrimitiveLiteral, CreateIndex, AuthQuery, CreateStream,
|
||||
DropStream, ShowStreams, StartStopStream, StartStopAllStreams, TestStream>;
|
||||
Unwind, Identifier, PrimitiveLiteral, CreateIndex, CreateUniqueIndex,
|
||||
AuthQuery, CreateStream, DropStream, ShowStreams, StartStopStream,
|
||||
StartStopAllStreams, TestStream>;
|
||||
|
||||
} // namespace query
|
||||
|
@ -60,10 +60,15 @@ antlrcpp::Any CypherMainVisitor::visitCypherQuery(
|
||||
antlrcpp::Any CypherMainVisitor::visitIndexQuery(
|
||||
MemgraphCypher::IndexQueryContext *ctx) {
|
||||
query_ = storage_.query();
|
||||
DCHECK(ctx->createIndex()) << "Expected CREATE INDEX";
|
||||
query_->single_query_ = storage_.Create<SingleQuery>();
|
||||
query_->single_query_->clauses_.emplace_back(
|
||||
ctx->createIndex()->accept(this).as<CreateIndex *>());
|
||||
if (ctx->createIndex()) {
|
||||
query_->single_query_->clauses_.emplace_back(
|
||||
ctx->createIndex()->accept(this).as<CreateIndex *>());
|
||||
} else {
|
||||
DCHECK(ctx->createUniqueIndex()) << "Expected CREATE UNIQUE INDEX";
|
||||
query_->single_query_->clauses_.emplace_back(
|
||||
ctx->createUniqueIndex()->accept(this).as<CreateUniqueIndex *>());
|
||||
}
|
||||
return query_;
|
||||
}
|
||||
|
||||
@ -173,7 +178,8 @@ antlrcpp::Any CypherMainVisitor::visitSingleQuery(
|
||||
throw SemanticException("RETURN can't be put before WITH.");
|
||||
}
|
||||
has_update = has_return = has_optional_match = false;
|
||||
} else if (dynamic_cast<CreateIndex *>(clause)) {
|
||||
} else if (dynamic_cast<CreateIndex *>(clause) ||
|
||||
dynamic_cast<CreateUniqueIndex *>(clause)) {
|
||||
// If there is CreateIndex clause then there shouldn't be anything else.
|
||||
if (single_query->clauses_.size() != 1U) {
|
||||
throw SemanticException(
|
||||
@ -271,6 +277,22 @@ antlrcpp::Any CypherMainVisitor::visitCreateIndex(
|
||||
dba_->Label(ctx->labelName()->accept(this)), key.second);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return CreateUniqueIndex*
|
||||
*/
|
||||
antlrcpp::Any CypherMainVisitor::visitCreateUniqueIndex(
|
||||
MemgraphCypher::CreateUniqueIndexContext *ctx) {
|
||||
std::vector<storage::Property> properties;
|
||||
properties.reserve(ctx->propertyKeyName().size());
|
||||
for (const auto &prop_name : ctx->propertyKeyName()) {
|
||||
std::pair<std::string, storage::Property> name_key =
|
||||
prop_name->accept(this);
|
||||
properties.push_back(name_key.second);
|
||||
}
|
||||
return storage_.Create<CreateUniqueIndex>(
|
||||
dba_->Label(ctx->labelName()->accept(this)), properties);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return std::string
|
||||
*/
|
||||
|
@ -219,6 +219,12 @@ class CypherMainVisitor : public antlropencypher::MemgraphCypherBaseVisitor {
|
||||
antlrcpp::Any visitCreateIndex(
|
||||
MemgraphCypher::CreateIndexContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return CreateUniqueIndex*
|
||||
*/
|
||||
antlrcpp::Any visitCreateUniqueIndex(
|
||||
MemgraphCypher::CreateUniqueIndexContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return AuthQuery*
|
||||
*/
|
||||
|
@ -32,7 +32,7 @@ explainQuery : EXPLAIN cypherQuery ;
|
||||
|
||||
cypherQuery : singleQuery ( cypherUnion )* ;
|
||||
|
||||
indexQuery : createIndex ;
|
||||
indexQuery : createIndex | createUniqueIndex;
|
||||
|
||||
singleQuery : clause ( clause )* ;
|
||||
|
||||
@ -274,6 +274,8 @@ integerLiteral : DecimalLiteral
|
||||
|
||||
createIndex : CREATE INDEX ON ':' labelName '(' propertyKeyName ')' ;
|
||||
|
||||
createUniqueIndex : CREATE UNIQUE INDEX ON ':' labelName '(' propertyKeyName ( ',' propertyKeyName )* ')' ;
|
||||
|
||||
doubleLiteral : FloatingLiteral ;
|
||||
|
||||
cypherKeyword : ALL
|
||||
@ -324,6 +326,7 @@ cypherKeyword : ALL
|
||||
| THEN
|
||||
| TRUE
|
||||
| UNION
|
||||
| UNIQUE
|
||||
| UNWIND
|
||||
| WHEN
|
||||
| WHERE
|
||||
|
@ -116,6 +116,7 @@ STARTS : S T A R T S ;
|
||||
THEN : T H E N ;
|
||||
TRUE : T R U E ;
|
||||
UNION : U N I O N ;
|
||||
UNIQUE : U N I Q U E ;
|
||||
UNWIND : U N W I N D ;
|
||||
WHEN : W H E N ;
|
||||
WHERE : W H E R E ;
|
||||
|
@ -54,6 +54,12 @@ class PrivilegeExtractor : public HierarchicalTreeVisitor {
|
||||
AddPrivilege(AuthQuery::Privilege::INDEX);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Visit(CreateUniqueIndex &) override {
|
||||
AddPrivilege(AuthQuery::Privilege::INDEX);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Visit(AuthQuery &) override {
|
||||
AddPrivilege(AuthQuery::Privilege::AUTH);
|
||||
return true;
|
||||
|
@ -220,6 +220,8 @@ bool SymbolGenerator::PostVisit(Match &) {
|
||||
|
||||
bool SymbolGenerator::Visit(CreateIndex &) { return true; }
|
||||
|
||||
bool SymbolGenerator::Visit(CreateUniqueIndex &) { return true; }
|
||||
|
||||
bool SymbolGenerator::Visit(AuthQuery &) { return true; }
|
||||
|
||||
bool SymbolGenerator::Visit(CreateStream &) { return true; }
|
||||
|
@ -47,6 +47,7 @@ class SymbolGenerator : public HierarchicalTreeVisitor {
|
||||
bool PreVisit(Match &) override;
|
||||
bool PostVisit(Match &) override;
|
||||
bool Visit(CreateIndex &) override;
|
||||
bool Visit(CreateUniqueIndex &) override;
|
||||
bool Visit(AuthQuery &) override;
|
||||
bool Visit(CreateStream &) override;
|
||||
bool Visit(DropStream &) override;
|
||||
|
@ -90,7 +90,7 @@ const trie::Trie kKeywords = {
|
||||
"reduce", "user", "password", "alter", "drop", "stream",
|
||||
"streams", "load", "data", "kafka", "transform", "batch",
|
||||
"interval", "show", "start", "stop", "size", "topic",
|
||||
"test", "explain"};
|
||||
"test", "unique", "explain"};
|
||||
|
||||
// Unicode codepoints that are allowed at the start of the unescaped name.
|
||||
const std::bitset<kBitsetSize> kUnescapedNameAllowedStarts(std::string(
|
||||
|
@ -56,6 +56,7 @@ class ExpressionEvaluator : public TreeVisitor<TypedValue> {
|
||||
BLOCK_VISIT(Merge);
|
||||
BLOCK_VISIT(Unwind);
|
||||
BLOCK_VISIT(CreateIndex);
|
||||
BLOCK_VISIT(CreateUniqueIndex);
|
||||
BLOCK_VISIT(AuthQuery);
|
||||
BLOCK_VISIT(CreateStream);
|
||||
BLOCK_VISIT(DropStream);
|
||||
|
@ -3016,8 +3016,10 @@ void Distinct::DistinctCursor::Reset() {
|
||||
seen_rows_.clear();
|
||||
}
|
||||
|
||||
CreateIndex::CreateIndex(storage::Label label, storage::Property property)
|
||||
: label_(label), property_(property) {}
|
||||
CreateIndex::CreateIndex(storage::Label label,
|
||||
const std::vector<storage::Property> &properties,
|
||||
bool is_unique)
|
||||
: label_(label), properties_(properties), is_unique_(is_unique) {}
|
||||
|
||||
bool CreateIndex::Accept(HierarchicalLogicalOperatorVisitor &visitor) {
|
||||
return visitor.Visit(*this);
|
||||
@ -3035,10 +3037,15 @@ class CreateIndexCursor : public Cursor {
|
||||
if (ctx.in_explicit_transaction_) {
|
||||
throw IndexInMulticommandTxException();
|
||||
}
|
||||
try {
|
||||
db_.BuildIndex(self_.label_, self_.property_);
|
||||
} catch (const database::IndexExistsException &) {
|
||||
// Ignore creating an existing index.
|
||||
if (self_.is_unique_) {
|
||||
throw utils::NotYetImplemented("CREATE UNIQUE INDEX");
|
||||
} else {
|
||||
try {
|
||||
CHECK(self_.properties_.size() == 1U);
|
||||
db_.BuildIndex(self_.label_, self_.properties_[0]);
|
||||
} catch (const database::IndexExistsException &) {
|
||||
// Ignore creating an existing index.
|
||||
}
|
||||
}
|
||||
ctx.is_index_created_ = did_create_ = true;
|
||||
return true;
|
||||
|
@ -266,7 +266,7 @@ can serve as inputs to others and thus a sequence of operations is formed.")
|
||||
(format
|
||||
nil
|
||||
"[helper](const auto &reader) {
|
||||
return static_cast<~A>(Load(&helper->ast_storage, reader,
|
||||
return static_cast<~A>(Load(&helper->ast_storage, reader,
|
||||
&helper->loaded_ast_uids));
|
||||
}"
|
||||
ast-type)))
|
||||
@ -2246,18 +2246,23 @@ This implementation maintains input ordering.")
|
||||
|
||||
(lcp:define-class create-index (logical-operator)
|
||||
((label "storage::Label" :scope :public)
|
||||
(property "storage::Property" :scope :public))
|
||||
(properties "std::vector<storage::Property>" :scope :public
|
||||
:capnp-save (lcp:capnp-save-vector "storage::capnp::Common" "storage::Property")
|
||||
:capnp-load (lcp:capnp-load-vector "storage::capnp::Common" "storage::Property"))
|
||||
(is-unique :bool :scope :public))
|
||||
(:documentation
|
||||
"Creates an index for a combination of label and a property.
|
||||
|
||||
This operator takes no input and it shouldn't serve as an input to any
|
||||
operator. Pulling from the cursor of this operator will create an index in
|
||||
the database for the vertices which have the given label and property. In
|
||||
case the index already exists, nothing happens.")
|
||||
case the index already exists, nothing happens. When is_unique is set to true,
|
||||
then a unique index will be created instead of a regular one.")
|
||||
(:public
|
||||
#>cpp
|
||||
CreateIndex() {}
|
||||
CreateIndex(storage::Label label, storage::Property property);
|
||||
CreateIndex(storage::Label label, const std::vector<storage::Property> &properties,
|
||||
bool is_unique);
|
||||
bool Accept(HierarchicalLogicalOperatorVisitor &visitor) override;
|
||||
std::unique_ptr<Cursor> MakeCursor(
|
||||
database::GraphDbAccessor &db) const override;
|
||||
|
@ -53,6 +53,7 @@ class UsedSymbolsCollector : public HierarchicalTreeVisitor {
|
||||
bool Visit(PrimitiveLiteral &) override { return true; }
|
||||
bool Visit(ParameterLookup &) override { return true; }
|
||||
bool Visit(query::CreateIndex &) override { return true; }
|
||||
bool Visit(query::CreateUniqueIndex &) override { return true; }
|
||||
bool Visit(query::AuthQuery &) override { return true; }
|
||||
bool Visit(query::CreateStream &) override { return true; }
|
||||
bool Visit(query::DropStream &) override { return true; }
|
||||
|
@ -402,6 +402,11 @@ class ReturnBodyContext : public HierarchicalTreeVisitor {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Visit(query::CreateUniqueIndex &) override {
|
||||
has_aggregation_.emplace_back(false);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Visit(query::AuthQuery &) override {
|
||||
has_aggregation_.emplace_back(false);
|
||||
return true;
|
||||
|
@ -181,7 +181,13 @@ class RuleBasedPlanner {
|
||||
dynamic_cast<query::CreateIndex *>(clause)) {
|
||||
DCHECK(!input_op) << "Unexpected operator before CreateIndex";
|
||||
input_op = std::make_unique<plan::CreateIndex>(
|
||||
create_index->label_, create_index->property_);
|
||||
create_index->label_,
|
||||
std::vector<storage::Property>{create_index->property_}, false);
|
||||
} else if (auto *create_index =
|
||||
dynamic_cast<query::CreateUniqueIndex *>(clause)) {
|
||||
DCHECK(!input_op) << "Unexpected operator before CreateIndex";
|
||||
input_op = std::make_unique<plan::CreateIndex>(
|
||||
create_index->label_, create_index->properties_, true);
|
||||
} else if (auto *auth_query =
|
||||
dynamic_cast<query::AuthQuery *>(clause)) {
|
||||
DCHECK(!input_op) << "Unexpected operator before AuthQuery";
|
||||
@ -189,8 +195,7 @@ class RuleBasedPlanner {
|
||||
input_op = std::make_unique<plan::AuthHandler>(
|
||||
auth_query->action_, auth_query->user_, auth_query->role_,
|
||||
auth_query->user_or_role_, auth_query->password_,
|
||||
auth_query->privileges_,
|
||||
symbol_table.CreateSymbol("user", false),
|
||||
auth_query->privileges_, symbol_table.CreateSymbol("user", false),
|
||||
symbol_table.CreateSymbol("role", false),
|
||||
symbol_table.CreateSymbol("privilege", false),
|
||||
symbol_table.CreateSymbol("effective", false),
|
||||
@ -444,14 +449,13 @@ class RuleBasedPlanner {
|
||||
// we have to remove them manually because no other filter-extraction
|
||||
// will ever bind them again.
|
||||
filters.erase(
|
||||
std::remove_if(filters.begin(), filters.end(),
|
||||
[
|
||||
e = filter_lambda.inner_edge_symbol,
|
||||
n = filter_lambda.inner_node_symbol
|
||||
](FilterInfo & fi) {
|
||||
return utils::Contains(fi.used_symbols, e) ||
|
||||
utils::Contains(fi.used_symbols, n);
|
||||
}),
|
||||
std::remove_if(
|
||||
filters.begin(), filters.end(),
|
||||
[e = filter_lambda.inner_edge_symbol,
|
||||
n = filter_lambda.inner_node_symbol](FilterInfo &fi) {
|
||||
return utils::Contains(fi.used_symbols, e) ||
|
||||
utils::Contains(fi.used_symbols, n);
|
||||
}),
|
||||
filters.end());
|
||||
// Unbind the temporarily bound inner symbols for filtering.
|
||||
bound_symbols.erase(filter_lambda.inner_edge_symbol);
|
||||
|
@ -1564,6 +1564,42 @@ TYPED_TEST(CypherMainVisitorTest, CreateIndex) {
|
||||
ast_generator.db_accessor_->Property("slavko"));
|
||||
}
|
||||
|
||||
TYPED_TEST(CypherMainVisitorTest, CreateUniqueIndex) {
|
||||
TypeParam ast_generator("Create unIqUe InDeX oN :mirko(slavko, pero)");
|
||||
auto *query = ast_generator.query_;
|
||||
ASSERT_TRUE(query->single_query_);
|
||||
auto *single_query = query->single_query_;
|
||||
ASSERT_EQ(single_query->clauses_.size(), 1U);
|
||||
auto *create_index =
|
||||
dynamic_cast<CreateUniqueIndex *>(single_query->clauses_[0]);
|
||||
ASSERT_TRUE(create_index);
|
||||
ASSERT_EQ(create_index->label_, ast_generator.db_accessor_->Label("mirko"));
|
||||
std::vector<storage::Property> expected_properties{
|
||||
ast_generator.db_accessor_->Property("slavko"),
|
||||
ast_generator.db_accessor_->Property("pero")};
|
||||
ASSERT_EQ(create_index->properties_, expected_properties);
|
||||
}
|
||||
|
||||
TYPED_TEST(CypherMainVisitorTest, CreateUniqueIndexWithoutProperties) {
|
||||
EXPECT_THROW(TypeParam ast_generator("Create unIqUe InDeX oN :mirko()"),
|
||||
SyntaxException);
|
||||
}
|
||||
|
||||
TYPED_TEST(CypherMainVisitorTest, CreateUniqueIndexWithSingleProperty) {
|
||||
TypeParam ast_generator("Create unIqUe InDeX oN :mirko(slavko)");
|
||||
auto *query = ast_generator.query_;
|
||||
ASSERT_TRUE(query->single_query_);
|
||||
auto *single_query = query->single_query_;
|
||||
ASSERT_EQ(single_query->clauses_.size(), 1U);
|
||||
auto *create_index =
|
||||
dynamic_cast<CreateUniqueIndex *>(single_query->clauses_[0]);
|
||||
ASSERT_TRUE(create_index);
|
||||
ASSERT_EQ(create_index->label_, ast_generator.db_accessor_->Label("mirko"));
|
||||
std::vector<storage::Property> expected_properties{
|
||||
ast_generator.db_accessor_->Property("slavko")};
|
||||
ASSERT_EQ(create_index->properties_, expected_properties);
|
||||
}
|
||||
|
||||
TYPED_TEST(CypherMainVisitorTest, ReturnAll) {
|
||||
{ EXPECT_THROW(TypeParam("RETURN all(x in [1,2,3])"), SyntaxException); }
|
||||
{
|
||||
|
@ -363,7 +363,9 @@ class ExpectCreateIndex : public OpChecker<CreateIndex> {
|
||||
|
||||
void ExpectOp(CreateIndex &create_index, const SymbolTable &) override {
|
||||
EXPECT_EQ(create_index.label_, label_);
|
||||
EXPECT_EQ(create_index.property_, property_);
|
||||
EXPECT_EQ(create_index.properties_,
|
||||
std::vector<storage::Property>{property_});
|
||||
EXPECT_FALSE(create_index.is_unique_);
|
||||
}
|
||||
|
||||
private:
|
||||
|
@ -958,12 +958,30 @@ TEST(QueryPlan, CreateIndex) {
|
||||
auto label = dba->Label("label");
|
||||
auto property = dba->Property("property");
|
||||
EXPECT_FALSE(dba->LabelPropertyIndexExists(label, property));
|
||||
auto create_index = std::make_shared<plan::CreateIndex>(label, property);
|
||||
auto create_index = std::make_shared<plan::CreateIndex>(
|
||||
label, std::vector<storage::Property>{property}, false);
|
||||
SymbolTable symbol_table;
|
||||
EXPECT_EQ(PullAll(create_index, *dba, symbol_table), 1);
|
||||
EXPECT_TRUE(dba->LabelPropertyIndexExists(label, property));
|
||||
}
|
||||
|
||||
TEST(QueryPlan, CreateUniqueIndex) {
|
||||
// CREATE UNIQUE INDEX ON :Label(prop1, prop2)
|
||||
database::GraphDb db;
|
||||
auto dba = db.Access();
|
||||
auto label = dba->Label("label");
|
||||
auto prop1 = dba->Property("prop1");
|
||||
auto prop2 = dba->Property("prop2");
|
||||
std::vector<storage::Property> properties{prop1, prop2};
|
||||
auto create_index =
|
||||
std::make_shared<plan::CreateIndex>(label, properties, true);
|
||||
SymbolTable symbol_table;
|
||||
EXPECT_THROW(PullAll(create_index, *dba, symbol_table),
|
||||
utils::NotYetImplemented);
|
||||
// TODO: Check unique index created
|
||||
// EXPECT_EQ(PullAll(create_index, *dba, symbol_table), 1);
|
||||
}
|
||||
|
||||
TEST(QueryPlan, DeleteSetProperty) {
|
||||
database::GraphDb db;
|
||||
auto dba_ptr = db.Access();
|
||||
|
Loading…
Reference in New Issue
Block a user