From eb4d23c5043bbbae5e2a4a946e42e908cafabc96 Mon Sep 17 00:00:00 2001
From: jbajic <jure.bajic@memgraph.com>
Date: Mon, 23 May 2022 13:56:33 +0200
Subject: [PATCH] Implement SchemaTypes

---
 src/storage/v2/schemas.cpp |  46 +++++++-----
 src/storage/v2/schemas.hpp | 139 +++++++++++++++++++++++++++++++++----
 src/storage/v2/storage.cpp |   6 +-
 src/storage/v2/storage.hpp |   6 ++
 4 files changed, 164 insertions(+), 33 deletions(-)

diff --git a/src/storage/v2/schemas.cpp b/src/storage/v2/schemas.cpp
index e92894a96..b74ca698e 100644
--- a/src/storage/v2/schemas.cpp
+++ b/src/storage/v2/schemas.cpp
@@ -9,7 +9,6 @@
 // by the Apache License, Version 2.0, included in the file
 // licenses/APL.txt.
 
-#include <tuple>
 #include <unordered_map>
 #include <utility>
 #include <vector>
@@ -19,40 +18,53 @@
 
 namespace memgraph::storage {
 
-Schemas::CreationStatus Schemas::CreateSchema(
-    const LabelId label, const std::vector<std::pair<PropertyId, PropertyValue::Type>> &property_types) {
-  auto res = schemas_.insert({label, property_types}).second;
+SchemaViolation::SchemaViolation(ValidationStatus status, LabelId label) : status{status}, label{label} {}
+
+SchemaViolation::SchemaViolation(ValidationStatus status, LabelId label, SchemaType violated_type)
+    : status{status}, label{label}, violated_type{violated_type} {}
+
+SchemaViolation::SchemaViolation(ValidationStatus status, LabelId label, SchemaType violated_type,
+                                 PropertyValue violated_property_value)
+    : status{status}, label{label}, violated_type{violated_type}, violated_property_value{violated_property_value} {}
+
+Schemas::CreationStatus Schemas::CreateSchema(const LabelId primary_label,
+                                              const std::vector<SchemaType> &schemas_types) {
+  auto res = schemas_.insert({primary_label, schemas_types}).second;
   return res ? Schemas::CreationStatus::SUCCESS : Schemas::CreationStatus::FAIL;
 }
 
-Schemas::DeletionStatus Schemas::DeleteSchema(const LabelId label) {
-  auto res = schemas_.erase(label);
+Schemas::DeletionStatus Schemas::DeleteSchema(const LabelId primary_label) {
+  auto res = schemas_.erase(primary_label);
   return res != 0 ? Schemas::DeletionStatus::SUCCESS : Schemas::DeletionStatus::FAIL;
 }
 
-Schemas::ValidationStatus Schemas::ValidateVertex(const LabelId primary_label, const Vertex &vertex) {
+[[nodiscard]] std::optional<SchemaViolation> Schemas::ValidateVertex(const LabelId primary_label,
+                                                                     const Vertex &vertex) {
+  // TODO Check for multiple defined primary labels
+  std::vector<std::pair<SchemaType, PropertyValue>> type_violations;
   if (!schemas_.contains(primary_label)) {
-    return Schemas::ValidationStatus::NO_SCHEMA_DEFINED_FOR_LABEL;
+    return SchemaViolation(SchemaViolation::ValidationStatus::NO_SCHEMA_DEFINED_FOR_LABEL, primary_label);
   }
   if (!utils::Contains(vertex.labels, primary_label)) {
-    return Schemas::ValidationStatus::VERTEX_HAS_NO_PRIMARY_LABEL;
+    return SchemaViolation(SchemaViolation::ValidationStatus::VERTEX_HAS_NO_PRIMARY_LABEL, primary_label);
   }
 
-  for (auto &[property_id, property_type] : schemas_[primary_label]) {
-    // Property existence check
-    if (!vertex.properties.HasProperty(property_id)) {
-      return Schemas::ValidationStatus::VERTEX_HAS_NO_PROPERTY;
+  for (auto &schema_type : schemas_[primary_label]) {
+    if (!vertex.properties.HasProperty(schema_type.property_id)) {
+      return SchemaViolation(SchemaViolation::ValidationStatus::VERTEX_HAS_NO_PROPERTY, primary_label, schema_type);
     }
     // Property type check
-    if (vertex.properties.GetProperty(property_id).type() != property_type) {
-      return Schemas::ValidationStatus::VERTEX_PROPERTY_WRONG_TYPE;
+    //  TODO Can this be replaced with just property id check?
+    if (auto vertex_property = vertex.properties.GetProperty(schema_type.property_id);
+        PropertyValueTypeToSchemaType(vertex_property) != schema_type.type) {
+      return SchemaViolation(SchemaViolation::ValidationStatus::VERTEX_PROPERTY_WRONG_TYPE, primary_label, schema_type,
+                             vertex_property);
     }
   }
-
   // TODO after the introduction of vertex hashing introduce check for vertex
   // primary key uniqueness
 
-  return Schemas::ValidationStatus::SUCCESS;
+  return std::nullopt;
 }
 
 Schemas::SchemasList Schemas::ListSchemas() const {
diff --git a/src/storage/v2/schemas.hpp b/src/storage/v2/schemas.hpp
index e2d3da3d3..7d55f5cc2 100644
--- a/src/storage/v2/schemas.hpp
+++ b/src/storage/v2/schemas.hpp
@@ -11,25 +11,61 @@
 
 #pragma once
 
+#include <memory>
+#include <optional>
 #include <unordered_map>
 #include <utility>
 #include <vector>
 
+#include "storage/v2/id_types.hpp"
 #include "storage/v2/indices.hpp"
 #include "storage/v2/property_value.hpp"
+#include "storage/v2/temporal.hpp"
 #include "storage/v2/transaction.hpp"
 #include "storage/v2/vertex.hpp"
 #include "utils/result.hpp"
 
 namespace memgraph::storage {
 
-///
+class SchemaViolationException : public utils::BasicException {
+  using utils::BasicException::BasicException;
+};
+
+struct SchemaType {
+  enum class Type : uint8_t { Bool, Int, Double, String, List, Map, Date, LocalTime, LocalDateTime, Duration };
+
+  Type type;
+  PropertyId property_id;
+  std::shared_ptr<SchemaType> nested_value;
+};
+
+struct SchemaViolation {
+  enum class ValidationStatus : uint8_t {
+    VERTEX_HAS_NO_PRIMARY_LABEL,
+    VERTEX_HAS_NO_PROPERTY,
+    NO_SCHEMA_DEFINED_FOR_LABEL,
+    VERTEX_PROPERTY_WRONG_TYPE
+  };
+
+  SchemaViolation(ValidationStatus status, LabelId label);
+
+  SchemaViolation(ValidationStatus status, LabelId label, SchemaType violated_type);
+
+  SchemaViolation(ValidationStatus status, LabelId label, SchemaType violated_type,
+                  PropertyValue violated_property_value);
+
+  ValidationStatus status;
+  LabelId label;
+  std::optional<SchemaType> violated_type;
+  std::optional<PropertyValue> violated_property_value;
+};
+
 /// Structure that represents a collection of schemas
 /// Schema can be mapped under only one label => primary label
 class Schemas {
  public:
-  using SchemasStructure = std::unordered_map<LabelId, std::vector<std::pair<PropertyId, PropertyValue::Type>>>;
-  using SchemasList = std::vector<std::pair<LabelId, std::vector<std::pair<PropertyId, PropertyValue::Type>>>>;
+  using SchemasStructure = std::unordered_map<LabelId, std::vector<SchemaType>>;
+  using SchemasList = std::vector<std::pair<LabelId, std::vector<SchemaType>>>;
 
   Schemas() = default;
   Schemas(const Schemas &) = delete;
@@ -40,21 +76,12 @@ class Schemas {
 
   enum class CreationStatus : uint8_t { SUCCESS, FAIL };
   enum class DeletionStatus : uint8_t { SUCCESS, FAIL };
-  enum class ValidationStatus : uint8_t {
-    SUCCESS,
-    VERTEX_DELETED,
-    VERTEX_HAS_NO_PRIMARY_LABEL,
-    VERTEX_HAS_NO_PROPERTY,
-    NO_SCHEMA_DEFINED_FOR_LABEL,
-    VERTEX_PROPERTY_WRONG_TYPE
-  };
 
-  CreationStatus CreateSchema(LabelId label,
-                              const std::vector<std::pair<PropertyId, PropertyValue::Type>> &property_types);
+  CreationStatus CreateSchema(LabelId label, const std::vector<SchemaType> &schemas_types);
 
   DeletionStatus DeleteSchema(LabelId label);
 
-  ValidationStatus ValidateVertex(LabelId primary_label, const Vertex &vertex);
+  std::optional<SchemaViolation> ValidateVertex(LabelId primary_label, const Vertex &vertex);
 
   SchemasList ListSchemas() const;
 
@@ -62,4 +89,88 @@ class Schemas {
   SchemasStructure schemas_;
 };
 
+inline std::optional<SchemaType::Type> PropertyValueTypeToSchemaType(const PropertyValue &property_value) {
+  switch (property_value.type()) {
+    case PropertyValue::Type::Bool: {
+      return SchemaType::Type::Bool;
+    }
+    case PropertyValue::Type::Int: {
+      return SchemaType::Type::Int;
+    }
+    case PropertyValue::Type::Double: {
+      return SchemaType::Type::Double;
+    }
+    case PropertyValue::Type::String: {
+      return SchemaType::Type::String;
+    }
+    case PropertyValue::Type::List: {
+      return SchemaType::Type::List;
+    }
+    case PropertyValue::Type::Map: {
+      return SchemaType::Type::Map;
+    }
+    case PropertyValue::Type::TemporalData: {
+      switch (property_value.ValueTemporalData().type) {
+        case TemporalType::Date: {
+          return SchemaType::Type::Date;
+        }
+        case TemporalType::LocalDateTime: {
+          return SchemaType::Type::LocalDateTime;
+        }
+        case TemporalType::LocalTime: {
+          return SchemaType::Type::LocalTime;
+        }
+        case TemporalType::Duration: {
+          return SchemaType::Type::Duration;
+        }
+      }
+    }
+    default: {
+      return std::nullopt;
+    }
+  }
+}
+
+inline std::string SchemaTypeToString(const SchemaType::Type type) {
+  switch (type) {
+    case SchemaType::Type::Bool: {
+      return "Bool";
+    }
+    case SchemaType::Type::Int: {
+      return "Integer";
+    }
+    case SchemaType::Type::Double: {
+      return "Double";
+    }
+    case SchemaType::Type::String: {
+      return "String";
+    }
+    case SchemaType::Type::List: {
+      return "List";
+    }
+    case SchemaType::Type::Map: {
+      return "Map";
+    }
+    case SchemaType::Type::Date: {
+      return "Date";
+    }
+    case SchemaType::Type::LocalTime: {
+      return "LocalTime";
+    }
+    case SchemaType::Type::LocalDateTime: {
+      return "LocalDateTime";
+    }
+    case SchemaType::Type::Duration: {
+      return "Duration";
+    }
+  }
+}
+
+inline std::string PropertyTypeToString(const PropertyValue &property_value) {
+  if (const auto schema_type = PropertyValueTypeToSchemaType(property_value); schema_type) {
+    return SchemaTypeToString(*schema_type);
+  }
+  return "Unknown";
+}
+
 }  // namespace memgraph::storage
diff --git a/src/storage/v2/storage.cpp b/src/storage/v2/storage.cpp
index f5064fadc..4b6e46709 100644
--- a/src/storage/v2/storage.cpp
+++ b/src/storage/v2/storage.cpp
@@ -28,6 +28,7 @@
 #include "storage/v2/indices.hpp"
 #include "storage/v2/mvcc.hpp"
 #include "storage/v2/replication/config.hpp"
+#include "storage/v2/schemas.hpp"
 #include "storage/v2/transaction.hpp"
 #include "storage/v2/vertex_accessor.hpp"
 #include "utils/file.hpp"
@@ -456,12 +457,13 @@ VertexAccessor Storage::Accessor::CreateVertex() {
   OOMExceptionEnabler oom_exception;
   auto gid = storage_->vertex_id_.fetch_add(1, std::memory_order_acq_rel);
   auto acc = storage_->vertices_.access();
-  auto delta = CreateDeleteObjectDelta(&transaction_);
+  auto *delta = CreateDeleteObjectDelta(&transaction_);
   auto [it, inserted] = acc.insert(Vertex{storage::Gid::FromUint(gid), delta});
   MG_ASSERT(inserted, "The vertex must be inserted here!");
   MG_ASSERT(it != acc.end(), "Invalid Vertex accessor!");
+
   delta->prev.Set(&*it);
-  return VertexAccessor(&*it, &transaction_, &storage_->indices_, &storage_->constraints_, config_);
+  return {&*it, &transaction_, &storage_->indices_, &storage_->constraints_, config_};
 }
 
 VertexAccessor Storage::Accessor::CreateVertex(storage::Gid gid) {
diff --git a/src/storage/v2/storage.hpp b/src/storage/v2/storage.hpp
index 7e55b22ca..1f38697a5 100644
--- a/src/storage/v2/storage.hpp
+++ b/src/storage/v2/storage.hpp
@@ -414,6 +414,12 @@ class Storage final {
 
   ConstraintsInfo ListAllConstraints() const;
 
+  /// @throw std::bad_alloc
+  bool CreateSchema(LabelId primary_label, std::vector<SchemaType> &schemas_types);
+
+  /// @throw std::bad_alloc
+  bool DeleteSchema(LabelId primary_label);
+
   SchemasInfo ListAllSchemas() const;
 
   StorageInfo GetInfo() const;