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:
Teon Banek 2018-10-12 11:20:53 +02:00
parent b527b2b4e4
commit 1b6a6c15e5
20 changed files with 188 additions and 35 deletions

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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
*/

View File

@ -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*
*/

View File

@ -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

View File

@ -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 ;

View File

@ -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;

View File

@ -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; }

View File

@ -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;

View File

@ -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(

View File

@ -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);

View File

@ -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;

View File

@ -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;

View File

@ -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; }

View File

@ -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;

View File

@ -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);

View File

@ -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); }
{

View File

@ -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:

View File

@ -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();