From 5aeaad198bba8954eb4cf724b9df8c01ca6e3e68 Mon Sep 17 00:00:00 2001
From: Antonio Andelic <antonio2368@users.noreply.github.com>
Date: Thu, 10 Feb 2022 10:30:14 +0100
Subject: [PATCH] Define SHOW VERSION query (#265)

---
 src/query/exceptions.hpp                      |  7 +++++++
 src/query/frontend/ast/ast.lcp                |  8 ++++++++
 src/query/frontend/ast/ast_visitor.hpp        |  9 +++++----
 .../frontend/ast/cypher_main_visitor.cpp      |  6 ++++++
 .../frontend/ast/cypher_main_visitor.hpp      |  5 +++++
 .../opencypher/grammar/MemgraphCypher.g4      |  6 ++++++
 .../opencypher/grammar/MemgraphCypherLexer.g4 |  1 +
 .../frontend/semantic/required_privileges.cpp |  2 ++
 .../frontend/stripped_lexer_constants.hpp     |  3 ++-
 src/query/interpreter.cpp                     | 20 +++++++++++++++++++
 tests/unit/cypher_main_visitor.cpp            |  9 +++++++++
 tests/unit/query_required_privileges.cpp      |  7 ++++++-
 12 files changed, 77 insertions(+), 6 deletions(-)

diff --git a/src/query/exceptions.hpp b/src/query/exceptions.hpp
index f0881de94..885e7eebb 100644
--- a/src/query/exceptions.hpp
+++ b/src/query/exceptions.hpp
@@ -217,4 +217,11 @@ class SettingConfigInMulticommandTxException final : public QueryException {
   SettingConfigInMulticommandTxException()
       : QueryException("Settings cannot be changed or fetched in multicommand transactions.") {}
 };
+
+class VersionInfoInMulticommandTxException : public QueryException {
+ public:
+  VersionInfoInMulticommandTxException()
+      : QueryException("Version info query not allowed in multicommand transactions.") {}
+};
+
 }  // namespace query
diff --git a/src/query/frontend/ast/ast.lcp b/src/query/frontend/ast/ast.lcp
index 167fca065..b08833c87 100644
--- a/src/query/frontend/ast/ast.lcp
+++ b/src/query/frontend/ast/ast.lcp
@@ -2615,4 +2615,12 @@ cpp<#
   (:serialize (:slk))
   (:clone))
 
+(lcp:define-class version-query (query) ()
+  (:public
+    #>cpp
+    DEFVISITABLE(QueryVisitor<void>);
+    cpp<#)
+  (:serialize (:slk))
+  (:clone))
+
 (lcp:pop-namespace) ;; namespace query
diff --git a/src/query/frontend/ast/ast_visitor.hpp b/src/query/frontend/ast/ast_visitor.hpp
index 40c70ae8a..022af65ef 100644
--- a/src/query/frontend/ast/ast_visitor.hpp
+++ b/src/query/frontend/ast/ast_visitor.hpp
@@ -92,6 +92,7 @@ class IsolationLevelQuery;
 class CreateSnapshotQuery;
 class StreamQuery;
 class SettingQuery;
+class VersionQuery;
 
 using TreeCompositeVisitor = ::utils::CompositeVisitor<
     SingleQuery, CypherUnion, NamedExpression, OrOperator, XorOperator, AndOperator, NotOperator, AdditionOperator,
@@ -123,9 +124,9 @@ class ExpressionVisitor
           None, ParameterLookup, Identifier, PrimitiveLiteral, RegexMatch> {};
 
 template <class TResult>
-class QueryVisitor
-    : public ::utils::Visitor<TResult, CypherQuery, ExplainQuery, ProfileQuery, IndexQuery, AuthQuery, InfoQuery,
-                              ConstraintQuery, DumpQuery, ReplicationQuery, LockPathQuery, FreeMemoryQuery,
-                              TriggerQuery, IsolationLevelQuery, CreateSnapshotQuery, StreamQuery, SettingQuery> {};
+class QueryVisitor : public ::utils::Visitor<TResult, CypherQuery, ExplainQuery, ProfileQuery, IndexQuery, AuthQuery,
+                                             InfoQuery, ConstraintQuery, DumpQuery, ReplicationQuery, LockPathQuery,
+                                             FreeMemoryQuery, TriggerQuery, IsolationLevelQuery, CreateSnapshotQuery,
+                                             StreamQuery, SettingQuery, VersionQuery> {};
 
 }  // namespace query
diff --git a/src/query/frontend/ast/cypher_main_visitor.cpp b/src/query/frontend/ast/cypher_main_visitor.cpp
index b80bc7278..ab2e02cc6 100644
--- a/src/query/frontend/ast/cypher_main_visitor.cpp
+++ b/src/query/frontend/ast/cypher_main_visitor.cpp
@@ -870,6 +870,12 @@ antlrcpp::Any CypherMainVisitor::visitShowSettings(MemgraphCypher::ShowSettingsC
   return setting_query;
 }
 
+antlrcpp::Any CypherMainVisitor::visitVersionQuery(MemgraphCypher::VersionQueryContext * /*ctx*/) {
+  auto *version_query = storage_->Create<VersionQuery>();
+  query_ = version_query;
+  return version_query;
+}
+
 antlrcpp::Any CypherMainVisitor::visitCypherUnion(MemgraphCypher::CypherUnionContext *ctx) {
   bool distinct = !ctx->ALL();
   auto *cypher_union = storage_->Create<CypherUnion>(distinct);
diff --git a/src/query/frontend/ast/cypher_main_visitor.hpp b/src/query/frontend/ast/cypher_main_visitor.hpp
index 431b58480..5b36dc45b 100644
--- a/src/query/frontend/ast/cypher_main_visitor.hpp
+++ b/src/query/frontend/ast/cypher_main_visitor.hpp
@@ -359,6 +359,11 @@ class CypherMainVisitor : public antlropencypher::MemgraphCypherBaseVisitor {
    */
   antlrcpp::Any visitShowSettings(MemgraphCypher::ShowSettingsContext *ctx) override;
 
+  /**
+   * @return VersionQuery*
+   */
+  antlrcpp::Any visitVersionQuery(MemgraphCypher::VersionQueryContext *ctx) override;
+
   /**
    * @return CypherUnion*
    */
diff --git a/src/query/frontend/opencypher/grammar/MemgraphCypher.g4 b/src/query/frontend/opencypher/grammar/MemgraphCypher.g4
index d91bb42e8..be9497e20 100644
--- a/src/query/frontend/opencypher/grammar/MemgraphCypher.g4
+++ b/src/query/frontend/opencypher/grammar/MemgraphCypher.g4
@@ -54,6 +54,7 @@ memgraphCypherKeyword : cypherKeyword
                       | HEADER
                       | IDENTIFIED
                       | ISOLATION
+                      | KAFKA
                       | LEVEL
                       | LOAD
                       | LOCK
@@ -62,6 +63,7 @@ memgraphCypherKeyword : cypherKeyword
                       | NEXT
                       | NO
                       | PASSWORD
+                      | PULSAR
                       | PORT
                       | PRIVILEGES
                       | READ
@@ -94,6 +96,7 @@ memgraphCypherKeyword : cypherKeyword
                       | UPDATE
                       | USER
                       | USERS
+                      | VERSION
                       ;
 
 symbolicName : UnescapedSymbolicName
@@ -117,6 +120,7 @@ query : cypherQuery
       | createSnapshotQuery
       | streamQuery
       | settingQuery
+      | versionQuery
       ;
 
 authQuery : createRole
@@ -353,3 +357,5 @@ setSetting : SET DATABASE SETTING settingName TO settingValue ;
 showSetting : SHOW DATABASE SETTING settingName ;
 
 showSettings : SHOW DATABASE SETTINGS ;
+
+versionQuery : SHOW VERSION ;
diff --git a/src/query/frontend/opencypher/grammar/MemgraphCypherLexer.g4 b/src/query/frontend/opencypher/grammar/MemgraphCypherLexer.g4
index 50556df8f..e678a73e9 100644
--- a/src/query/frontend/opencypher/grammar/MemgraphCypherLexer.g4
+++ b/src/query/frontend/opencypher/grammar/MemgraphCypherLexer.g4
@@ -109,3 +109,4 @@ UNLOCK              : U N L O C K ;
 UPDATE              : U P D A T E ;
 USER                : U S E R ;
 USERS               : U S E R S ;
+VERSION             : V E R S I O N ;
diff --git a/src/query/frontend/semantic/required_privileges.cpp b/src/query/frontend/semantic/required_privileges.cpp
index 349f09fc6..6e61dfc2c 100644
--- a/src/query/frontend/semantic/required_privileges.cpp
+++ b/src/query/frontend/semantic/required_privileges.cpp
@@ -76,6 +76,8 @@ class PrivilegeExtractor : public QueryVisitor<void>, public HierarchicalTreeVis
 
   void Visit(SettingQuery & /*setting_query*/) override { AddPrivilege(AuthQuery::Privilege::CONFIG); }
 
+  void Visit(VersionQuery & /*version_query*/) override { AddPrivilege(AuthQuery::Privilege::STATS); }
+
   bool PreVisit(Create & /*unused*/) override {
     AddPrivilege(AuthQuery::Privilege::CREATE);
     return false;
diff --git a/src/query/frontend/stripped_lexer_constants.hpp b/src/query/frontend/stripped_lexer_constants.hpp
index ff28498b1..39b765971 100644
--- a/src/query/frontend/stripped_lexer_constants.hpp
+++ b/src/query/frontend/stripped_lexer_constants.hpp
@@ -202,7 +202,8 @@ const trie::Trie kKeywords = {"union",
                               "bootstrap_servers",
                               "kafka",
                               "pulsar",
-                              "service_url"};
+                              "service_url",
+                              "version"};
 
 // Unicode codepoints that are allowed at the start of the unescaped name.
 const std::bitset<kBitsetSize> kUnescapedNameAllowedStarts(
diff --git a/src/query/interpreter.cpp b/src/query/interpreter.cpp
index 18c3089a9..e119e352b 100644
--- a/src/query/interpreter.cpp
+++ b/src/query/interpreter.cpp
@@ -1725,6 +1725,24 @@ PreparedQuery PrepareSettingQuery(ParsedQuery parsed_query, const bool in_explic
   // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks)
 }
 
+PreparedQuery PrepareVersionQuery(ParsedQuery parsed_query, const bool in_explicit_transaction) {
+  if (in_explicit_transaction) {
+    throw VersionInfoInMulticommandTxException();
+  }
+
+  return PreparedQuery{{"version"},
+                       std::move(parsed_query.required_privileges),
+                       [](AnyStream *stream, std::optional<int> /*n*/) {
+                         std::vector<TypedValue> version_value;
+                         version_value.reserve(1);
+
+                         version_value.emplace_back(gflags::VersionString());
+                         stream->Result(version_value);
+                         return QueryHandlerResult::COMMIT;
+                       },
+                       RWType::NONE};
+}
+
 PreparedQuery PrepareInfoQuery(ParsedQuery parsed_query, bool in_explicit_transaction,
                                std::map<std::string, TypedValue> *summary, InterpreterContext *interpreter_context,
                                storage::Storage *db, utils::MemoryResource *execution_memory) {
@@ -2128,6 +2146,8 @@ Interpreter::PrepareResult Interpreter::Prepare(const std::string &query_string,
           PrepareCreateSnapshotQuery(std::move(parsed_query), in_explicit_transaction_, interpreter_context_);
     } else if (utils::Downcast<SettingQuery>(parsed_query.query)) {
       prepared_query = PrepareSettingQuery(std::move(parsed_query), in_explicit_transaction_, &*execution_db_accessor_);
+    } else if (utils::Downcast<VersionQuery>(parsed_query.query)) {
+      prepared_query = PrepareVersionQuery(std::move(parsed_query), in_explicit_transaction_);
     } else {
       LOG_FATAL("Should not get here -- unknown query type!");
     }
diff --git a/tests/unit/cypher_main_visitor.cpp b/tests/unit/cypher_main_visitor.cpp
index c3fd82a23..6d1d469c8 100644
--- a/tests/unit/cypher_main_visitor.cpp
+++ b/tests/unit/cypher_main_visitor.cpp
@@ -4060,3 +4060,12 @@ TEST_P(CypherMainVisitorTest, SettingQuery) {
   validate_setting_query("SET DATABASE SETTING 'setting' TO 'value'", SettingQuery::Action::SET_SETTING,
                          TypedValue{"setting"}, TypedValue{"value"});
 }
+
+TEST_P(CypherMainVisitorTest, VersionQuery) {
+  auto &ast_generator = *GetParam();
+
+  TestInvalidQuery("SHOW VERION", ast_generator);
+  TestInvalidQuery("SHOW VER", ast_generator);
+  TestInvalidQuery("SHOW VERSIONS", ast_generator);
+  ASSERT_NO_THROW(ast_generator.ParseQuery("SHOW VERSION"));
+}
diff --git a/tests/unit/query_required_privileges.cpp b/tests/unit/query_required_privileges.cpp
index 314b52ec3..aea519ce4 100644
--- a/tests/unit/query_required_privileges.cpp
+++ b/tests/unit/query_required_privileges.cpp
@@ -1,4 +1,4 @@
-// Copyright 2021 Memgraph Ltd.
+// Copyright 2022 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -186,3 +186,8 @@ TEST_F(TestPrivilegeExtractor, SettingQuery) {
   auto *query = storage.Create<SettingQuery>();
   EXPECT_THAT(GetRequiredPrivileges(query), UnorderedElementsAre(AuthQuery::Privilege::CONFIG));
 }
+
+TEST_F(TestPrivilegeExtractor, ShowVersion) {
+  auto *query = storage.Create<VersionQuery>();
+  EXPECT_THAT(GetRequiredPrivileges(query), UnorderedElementsAre(AuthQuery::Privilege::STATS));
+}