;; 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
;; License, and you may not use this file except in compliance with the Business Source License.
;;
;; As of the Change Date specified in that file, in accordance with
;; the Business Source License, use of this software will be governed
;; by the Apache License, Version 2.0, included in the file
;; licenses/APL.txt.

#>cpp
#pragma once

#include <memory>
#include <unordered_map>
#include <variant>
#include <vector>

#include "query/frontend/ast/ast_visitor.hpp"
#include "query/frontend/semantic/symbol.hpp"
#include "query/interpret/awesome_memgraph_functions.hpp"
#include "query/typed_value.hpp"
#include "storage/v2/property_value.hpp"
#include "utils/typeinfo.hpp"

cpp<#

(lcp:namespace memgraph)
(lcp:namespace query)

(defun slk-save-ast-pointer (member)
  #>cpp
  query::SaveAstPointer(self.${member}, builder);
  cpp<#)

(defun slk-load-ast-pointer (type)
  (lambda (member)
    #>cpp
    self->${member} = query::LoadAstPointer<query::${type}>(storage, reader);
    cpp<#))

(defun slk-save-ast-vector (member)
  #>cpp
  size_t size = self.${member}.size();
  slk::Save(size, builder);
  for (const auto *val : self.${member}) {
    query::SaveAstPointer(val, builder);
  }
  cpp<#)

(defun slk-load-ast-vector (type)
  (lambda (member)
    #>cpp
    size_t size = 0;
    slk::Load(&size, reader);
    self->${member}.resize(size);
    for (size_t i = 0; i < size; ++i) {
      self->${member}[i] = query::LoadAstPointer<query::${type}>(storage, reader);
    }
    cpp<#))

(defun slk-save-property-map (member)
  #>cpp
  size_t size = self.${member}.size();
  slk::Save(size, builder);
  for (const auto &entry : self.${member}) {
    slk::Save(entry.first, builder);
    query::SaveAstPointer(entry.second, builder);
  }
  cpp<#)

(defun slk-load-property-map (member)
  #>cpp
  size_t size = 0;
  slk::Load(&size, reader);
  for (size_t i = 0; i < size; ++i) {
    query::PropertyIx key;
    slk::Load(&key, reader, storage);
    auto *value = query::LoadAstPointer<query::Expression>(storage, reader);
    self->${member}.emplace(key, value);
  }
  cpp<#)

(defun clone-property-map (source dest)
  #>cpp
  for (const auto &entry : ${source}) {
    PropertyIx key = storage->GetPropertyIx(entry.first.name);
    ${dest}[key] = entry.second->Clone(storage);
  }
  cpp<#)

(defun slk-save-expression-map (member)
  #>cpp
  size_t size = self.${member}.size();
  slk::Save(size, builder);
  for (const auto &entry : self.${member}) {
    query::SaveAstPointer(entry.first, builder);
    query::SaveAstPointer(entry.second, builder);
  }
  cpp<#)

(defun slk-load-expression-map (member)
  #>cpp
  size_t size = 0;
  slk::Load(&size, reader);
  for (size_t i = 0; i < size; ++i) {
    auto *key = query::LoadAstPointer<query::Expression>(storage, reader);
    auto *value = query::LoadAstPointer<query::Expression>(storage, reader);
    self->${member}.emplace(key, value);
  }
  cpp<#)

(defun clone-expression-map (source dest)
  #>cpp
  for (const auto &[key, value] : ${source}) {
    ${dest}[key->Clone(storage)] = value->Clone(storage);
  }
  cpp<#)

(defun slk-load-name-ix (name-type)
  (lambda (member)
    #>cpp
    self->${member} = storage->Get${name-type}Ix(self->name).ix;
    cpp<#))

(defun clone-name-ix-vector (name-type)
  (lambda (source dest)
    #>cpp
    ${dest}.resize(${source}.size());
    for (auto i = 0; i < ${dest}.size(); ++i) {
      ${dest}[i] = storage->Get${name-type}Ix(${source}[i].name);
    }
    cpp<#))

;; The following index structs serve as a decoupling point of AST from
;; concrete database types. All the names are collected in AstStorage, and can
;; be indexed through these instances. This means that we can create a vector
;; of concrete database types in the same order as all of the names and use the
;; same index to get the correct behaviour. Additionally, each index is
;; accompanied with the duplicated name found at the same index. The primary
;; reason for this duplication is simplifying the Clone and serialization API.
;; When an old index is being cloned or deserialized into a new AstStorage, we
;; request the new `ix` from the new AstStorage for the same `name`. If we
;; didn't do this, we would have to duplicate the old storage, which would
;; require having access to that storage. This in turn would complicate the
;; client code.
(lcp:define-struct label-ix ()
  ((name "std::string")
   (ix :int64_t
       :dont-save t
       :slk-load (slk-load-name-ix "Label")))
  (:serialize (:slk :load-args '((storage "query::AstStorage *")))))

(lcp:define-struct property-ix ()
  ((name "std::string")
   (ix :int64_t
       :dont-save t
       :slk-load (slk-load-name-ix "Property")))
  (:serialize (:slk :load-args '((storage "query::AstStorage *")))))

(lcp:define-struct edge-type-ix ()
  ((name "std::string")
   (ix :int64_t
       :dont-save t
       :slk-load (slk-load-name-ix "EdgeType")))
  (:serialize (:slk :load-args '((storage "query::AstStorage *")))))

#>cpp
inline bool operator==(const LabelIx &a, const LabelIx &b) {
  return a.ix == b.ix && a.name == b.name;
}

inline bool operator!=(const LabelIx &a, const LabelIx &b) { return !(a == b); }

inline bool operator==(const PropertyIx &a, const PropertyIx &b) {
  return a.ix == b.ix && a.name == b.name;
}

inline bool operator!=(const PropertyIx &a, const PropertyIx &b) {
  return !(a == b);
}

inline bool operator==(const EdgeTypeIx &a, const EdgeTypeIx &b) {
  return a.ix == b.ix && a.name == b.name;
}

inline bool operator!=(const EdgeTypeIx &a, const EdgeTypeIx &b) {
  return !(a == b);
}
cpp<#

(lcp:pop-namespace) ;; namespace query
(lcp:pop-namespace) ;; namespace memgraph

#>cpp
namespace std {

template <>
struct hash<memgraph::query::LabelIx> {
  size_t operator()(const memgraph::query::LabelIx &label) const { return label.ix; }
};

template <>
struct hash<memgraph::query::PropertyIx> {
  size_t operator()(const memgraph::query::PropertyIx &prop) const { return prop.ix; }
};

template <>
struct hash<memgraph::query::EdgeTypeIx> {
  size_t operator()(const memgraph::query::EdgeTypeIx &edge_type) const {
    return edge_type.ix;
  }
};

}  // namespace std
cpp<#

(lcp:namespace memgraph)
(lcp:namespace query)

#>cpp

class Tree;

// It would be better to call this AstTree, but we already have a class Tree,
// which could be renamed to Node or AstTreeNode, but we also have a class
// called NodeAtom...
class AstStorage {
 public:
  AstStorage() = default;
  AstStorage(const AstStorage &) = delete;
  AstStorage &operator=(const AstStorage &) = delete;
  AstStorage(AstStorage &&) = default;
  AstStorage &operator=(AstStorage &&) = default;

  template <typename T, typename... Args>
  T *Create(Args &&... args) {
    T *ptr = new T(std::forward<Args>(args)...);
    std::unique_ptr<T> tmp(ptr);
    storage_.emplace_back(std::move(tmp));
    return ptr;
  }

  LabelIx GetLabelIx(const std::string &name) {
    return LabelIx{name, FindOrAddName(name, &labels_)};
  }

  PropertyIx GetPropertyIx(const std::string &name) {
    return PropertyIx{name, FindOrAddName(name, &properties_)};
  }

  EdgeTypeIx GetEdgeTypeIx(const std::string &name) {
    return EdgeTypeIx{name, FindOrAddName(name, &edge_types_)};
  }

  std::vector<std::string> labels_;
  std::vector<std::string> edge_types_;
  std::vector<std::string> properties_;

  // Public only for serialization access
  std::vector<std::unique_ptr<Tree>> storage_;

 private:
  int64_t FindOrAddName(const std::string &name,
                        std::vector<std::string> *names) {
    for (int64_t i = 0; i < names->size(); ++i) {
      if ((*names)[i] == name) {
        return i;
      }
    }
    names->push_back(name);
    return names->size() - 1;
  }
};
cpp<#

(lcp:define-class tree ()
  ()
  (:abstractp t)
  (:public
    #>cpp
    Tree() = default;
    virtual ~Tree() {}
    cpp<#)
  (:private
    #>cpp
    friend class AstStorage;
    cpp<#)
  (:serialize (:slk :load-args '((storage "query::AstStorage *"))))
  (:clone :return-type (lambda (typename)
                         (format nil "~A*" typename))
          :args '((storage "AstStorage *"))
          :init-object (lambda (var typename)
                         (format nil "~A* ~A = storage->Create<~A>();"
                                 typename var typename))))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Expressions
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(lcp:define-class expression (tree "::utils::Visitable<HierarchicalTreeVisitor>"
                                   "::utils::Visitable<ExpressionVisitor<TypedValue>>"
                                   "::utils::Visitable<ExpressionVisitor<TypedValue*>>"
                                   "::utils::Visitable<ExpressionVisitor<void>>")
  ()
  (:abstractp t)
  (:public
    #>cpp
    using utils::Visitable<HierarchicalTreeVisitor>::Accept;
    using utils::Visitable<ExpressionVisitor<TypedValue>>::Accept;
    using utils::Visitable<ExpressionVisitor<TypedValue*>>::Accept;
    using utils::Visitable<ExpressionVisitor<void>>::Accept;

    Expression() = default;
    cpp<#)
  (:private
    #>cpp
    friend class AstStorage;
    cpp<#)
  (:serialize (:slk :ignore-other-base-classes t))
  (:clone :ignore-other-base-classes t)
  (:type-info :ignore-other-base-classes t))

(lcp:define-class where (tree "::utils::Visitable<HierarchicalTreeVisitor>")
  ((expression "Expression *" :initval "nullptr" :scope :public
               :slk-save #'slk-save-ast-pointer
               :slk-load (slk-load-ast-pointer "Expression")))
  (:public
    #>cpp
    using utils::Visitable<HierarchicalTreeVisitor>::Accept;

    Where() = default;

    bool Accept(HierarchicalTreeVisitor &visitor) override {
      if (visitor.PreVisit(*this)) {
        expression_->Accept(visitor);
      }
      return visitor.PostVisit(*this);
    }
    cpp<#)
  (:protected
    #>cpp
    explicit Where(Expression *expression) : expression_(expression) {}
    cpp<#)
  (:private
    #>cpp
    friend class AstStorage;
    cpp<#)
  (:serialize (:slk :ignore-other-base-classes t))
  (:clone :ignore-other-base-classes t)
  (:type-info :ignore-other-base-classes t))

(lcp:define-class binary-operator (expression)
  ((expression1 "Expression *" :initval "nullptr" :scope :public
                :slk-save #'slk-save-ast-pointer
                :slk-load (slk-load-ast-pointer "Expression"))
   (expression2 "Expression *" :initval "nullptr" :scope :public
                :slk-save #'slk-save-ast-pointer
                :slk-load (slk-load-ast-pointer "Expression")))
  (:abstractp t)
  (:public
    #>cpp
    BinaryOperator() = default;
    cpp<#)
  (:protected
    #>cpp
    BinaryOperator(Expression *expression1, Expression *expression2)
        : expression1_(expression1), expression2_(expression2) {}
    cpp<#)
  (:private
    #>cpp
    friend class AstStorage;
    cpp<#)
  (:serialize (:slk))
  (:clone))

(lcp:define-class unary-operator (expression)
  ((expression "Expression *" :initval "nullptr" :scope :public
               :slk-save #'slk-save-ast-pointer
               :slk-load (slk-load-ast-pointer "Expression")))
  (:abstractp t)
  (:public
    #>cpp
    UnaryOperator() = default;
    cpp<#)
  (:protected
    #>cpp
    explicit UnaryOperator(Expression *expression) : expression_(expression) {}
    cpp<#)
  (:private
    #>cpp
    friend class AstStorage;
    cpp<#)
  (:serialize (:slk))
  (:clone))

(macrolet ((define-binary-operators ()
             `(lcp:cpp-list
               ,@(loop for op in
                       '(or-operator xor-operator and-operator addition-operator
                         subtraction-operator multiplication-operator division-operator
                         mod-operator not-equal-operator equal-operator less-operator
                         greater-operator less-equal-operator greater-equal-operator
                         in-list-operator subscript-operator)
                       collecting
                       `(lcp:define-class ,op (binary-operator)
                          ()
                          (:public
                            #>cpp
                            DEFVISITABLE(ExpressionVisitor<TypedValue>);
                            DEFVISITABLE(ExpressionVisitor<TypedValue*>);
                            DEFVISITABLE(ExpressionVisitor<void>);
                            bool Accept(HierarchicalTreeVisitor &visitor) override {
                              if (visitor.PreVisit(*this)) {
                                expression1_->Accept(visitor) && expression2_->Accept(visitor);
                              }
                              return visitor.PostVisit(*this);
                            }
                            cpp<#)
                          (:protected
                            #>cpp
                            using BinaryOperator::BinaryOperator;
                            cpp<#)
                          (:private
                            #>cpp
                            friend class AstStorage;
                            cpp<#)
                          (:serialize (:slk))
                          (:clone))))))
  (define-binary-operators))

(macrolet ((define-unary-operators ()
             `(lcp:cpp-list
               ,@(loop for op in
                       '(not-operator unary-plus-operator
                         unary-minus-operator is-null-operator)
                       collecting
                       `(lcp:define-class ,op (unary-operator)
                          ()
                          (:public
                            #>cpp
                            DEFVISITABLE(ExpressionVisitor<TypedValue>);
                            DEFVISITABLE(ExpressionVisitor<TypedValue*>);
                            DEFVISITABLE(ExpressionVisitor<void>);
                            bool Accept(HierarchicalTreeVisitor &visitor) override {
                              if (visitor.PreVisit(*this)) {
                                expression_->Accept(visitor);
                              }
                              return visitor.PostVisit(*this);
                            }
                            cpp<#)
                          (:protected
                            #>cpp
                            using UnaryOperator::UnaryOperator;
                            cpp<#)
                          (:private
                            #>cpp
                            friend class AstStorage;
                            cpp<#)
                          (:serialize (:slk))
                          (:clone))))))
  (define-unary-operators))

(lcp:define-class aggregation (binary-operator)
  ((op "Op" :scope :public)
   (symbol-pos :int32_t :initval -1 :scope :public
               :documentation "Symbol table position of the symbol this Aggregation is mapped to.")
    (distinct :bool :initval "false" :scope :public))
  (:public
    (lcp:define-enum op
      (count min max sum avg collect-list collect-map project)
      (:serialize))
    #>cpp
    Aggregation() = default;

    static const constexpr char *const kCount = "COUNT";
    static const constexpr char *const kMin = "MIN";
    static const constexpr char *const kMax = "MAX";
    static const constexpr char *const kSum = "SUM";
    static const constexpr char *const kAvg = "AVG";
    static const constexpr char *const kCollect = "COLLECT";
    static const constexpr char *const kProject = "PROJECT";

    static std::string OpToString(Op op) {
      const char *op_strings[] = {kCount, kMin,     kMax,    kSum,
                                  kAvg,   kCollect, kCollect, kProject};
      return op_strings[static_cast<int>(op)];
    }

    DEFVISITABLE(ExpressionVisitor<TypedValue>);
    DEFVISITABLE(ExpressionVisitor<TypedValue*>);
    DEFVISITABLE(ExpressionVisitor<void>);
    bool Accept(HierarchicalTreeVisitor &visitor) override {
      if (visitor.PreVisit(*this)) {
        if (expression1_) expression1_->Accept(visitor);
        if (expression2_) expression2_->Accept(visitor);
      }
      return visitor.PostVisit(*this);
    }

    Aggregation *MapTo(const Symbol &symbol) {
      symbol_pos_ = symbol.position();
      return this;
    }
    cpp<#)
  (:protected
    #>cpp
    // Use only for serialization.
    explicit Aggregation(Op op) : op_(op) {}

    /// Aggregation's first expression is the value being aggregated. The second
    /// expression is the key used only in COLLECT_MAP.
    Aggregation(Expression *expression1, Expression *expression2, Op op, bool distinct)
        : BinaryOperator(expression1, expression2), op_(op), distinct_(distinct) {
      // COUNT without expression denotes COUNT(*) in cypher.
      DMG_ASSERT(expression1 || op == Aggregation::Op::COUNT,
                 "All aggregations, except COUNT require expression");
      DMG_ASSERT((expression2 == nullptr) ^ (op == Aggregation::Op::COLLECT_MAP),
                 "The second expression is obligatory in COLLECT_MAP and "
                 "invalid otherwise");
    }
    cpp<#)
  (:private
    #>cpp
    friend class AstStorage;
    cpp<#)
  (:serialize (:slk))
  (:clone))

(lcp:define-class list-slicing-operator (expression)
  ((list "Expression *" :initval "nullptr" :scope :public
         :slk-save #'slk-save-ast-pointer
         :slk-load (slk-load-ast-pointer "Expression"))
   (lower-bound "Expression *" :initval "nullptr" :scope :public
                :slk-save #'slk-save-ast-pointer
                :slk-load (slk-load-ast-pointer "Expression"))
   (upper-bound "Expression *" :initval "nullptr" :scope :public
                :slk-save #'slk-save-ast-pointer
                :slk-load (slk-load-ast-pointer "Expression")))
  (:public
    #>cpp
    ListSlicingOperator() = default;

    DEFVISITABLE(ExpressionVisitor<TypedValue>);
    DEFVISITABLE(ExpressionVisitor<TypedValue*>);
    DEFVISITABLE(ExpressionVisitor<void>);
    bool Accept(HierarchicalTreeVisitor &visitor) override {
      if (visitor.PreVisit(*this)) {
        bool cont = list_->Accept(visitor);
        if (cont && lower_bound_) {
          cont = lower_bound_->Accept(visitor);
        }
        if (cont && upper_bound_) {
          upper_bound_->Accept(visitor);
        }
      }
      return visitor.PostVisit(*this);
    }
   cpp<#)
  (:protected
    #>cpp
    ListSlicingOperator(Expression *list, Expression *lower_bound,
                        Expression *upper_bound)
        : list_(list), lower_bound_(lower_bound), upper_bound_(upper_bound) {}
    cpp<#)
  (:private
    #>cpp
    friend class AstStorage;
    cpp<#)
  (:serialize (:slk))
  (:clone))

(lcp:define-class if-operator (expression)
  ((condition "Expression *" :scope :public
              :slk-save #'slk-save-ast-pointer
              :slk-load (slk-load-ast-pointer "Expression")
              :documentation "None of the expressions should be nullptr. If there is no else_expression, you should make it null PrimitiveLiteral.")
   (then-expression "Expression *" :scope :public
                    :slk-save #'slk-save-ast-pointer
                    :slk-load (slk-load-ast-pointer "Expression"))
   (else-expression "Expression *" :scope :public
                    :slk-save #'slk-save-ast-pointer
                    :slk-load (slk-load-ast-pointer "Expression")))
  (:public
    #>cpp
    IfOperator() = default;

    DEFVISITABLE(ExpressionVisitor<TypedValue>);
    DEFVISITABLE(ExpressionVisitor<TypedValue*>);
    DEFVISITABLE(ExpressionVisitor<void>);
    bool Accept(HierarchicalTreeVisitor &visitor) override {
      if (visitor.PreVisit(*this)) {
        condition_->Accept(visitor) && then_expression_->Accept(visitor) &&
            else_expression_->Accept(visitor);
      }
      return visitor.PostVisit(*this);
    }
    cpp<#)
  (:protected
    #>cpp
    IfOperator(Expression *condition, Expression *then_expression,
               Expression *else_expression)
        : condition_(condition),
          then_expression_(then_expression),
          else_expression_(else_expression) {}
    cpp<#)
  (:private
    #>cpp
    friend class AstStorage;
    cpp<#)
  (:serialize (:slk))
  (:clone))

(lcp:define-class base-literal (expression)
  ()
  (:abstractp t)
  (:public
    #>cpp
    BaseLiteral() = default;
    cpp<#)
  (:private
    #>cpp
    friend class AstStorage;
    cpp<#)
  (:serialize (:slk))
  (:clone))

(lcp:define-class primitive-literal (base-literal)
  ((value "::storage::PropertyValue" :scope :public)
   (token-position :int32_t :scope :public :initval -1
                   :documentation "This field contains token position of literal used to create PrimitiveLiteral object. If PrimitiveLiteral object is not created from query, leave its value at -1."))
  (:public
    #>cpp
    PrimitiveLiteral() = default;

    DEFVISITABLE(ExpressionVisitor<TypedValue>);
    DEFVISITABLE(ExpressionVisitor<TypedValue*>);
    DEFVISITABLE(ExpressionVisitor<void>);
    DEFVISITABLE(HierarchicalTreeVisitor);
    cpp<#)
  (:private
    #>cpp
    friend class AstStorage;
    cpp<#)
  (:protected
    #>cpp
    template <typename T>
    explicit PrimitiveLiteral(T value) : value_(value) {}
    template <typename T>
    PrimitiveLiteral(T value, int token_position)
        : value_(value), token_position_(token_position) {}
    cpp<#)
  (:serialize (:slk))
  (:clone))

(lcp:define-class list-literal (base-literal)
  ((elements "std::vector<Expression *>"
             :scope :public
             :slk-save #'slk-save-ast-vector
             :slk-load (slk-load-ast-vector "Expression")))
  (:public
    #>cpp
    ListLiteral() = default;

    DEFVISITABLE(ExpressionVisitor<TypedValue>);
    DEFVISITABLE(ExpressionVisitor<TypedValue*>);
    DEFVISITABLE(ExpressionVisitor<void>);
    bool Accept(HierarchicalTreeVisitor &visitor) override {
      if (visitor.PreVisit(*this)) {
        for (auto expr_ptr : elements_)
          if (!expr_ptr->Accept(visitor)) break;
      }
      return visitor.PostVisit(*this);
    }
    cpp<#)
  (:protected
    #>cpp
    explicit ListLiteral(const std::vector<Expression *> &elements)
        : elements_(elements) {}
    cpp<#)
  (:private
    #>cpp
    friend class AstStorage;
    cpp<#)
  (:serialize (:slk))
  (:clone))

(lcp:define-class map-literal (base-literal)
  ((elements "std::unordered_map<PropertyIx, Expression *>"
             :slk-save #'slk-save-property-map
             :slk-load #'slk-load-property-map
             :clone #'clone-property-map
             :scope :public))
  (:public
    #>cpp
    MapLiteral() = default;

    DEFVISITABLE(ExpressionVisitor<TypedValue>);
    DEFVISITABLE(ExpressionVisitor<TypedValue*>);
    DEFVISITABLE(ExpressionVisitor<void>);
    bool Accept(HierarchicalTreeVisitor &visitor) override {
      if (visitor.PreVisit(*this)) {
        for (auto pair : elements_)
          if (!pair.second->Accept(visitor)) break;
      }
      return visitor.PostVisit(*this);
    }
    cpp<#)
  (:protected
    #>cpp
    explicit MapLiteral(
        const std::unordered_map<PropertyIx, Expression *> &elements)
        : elements_(elements) {}
    cpp<#)
  (:private
    #>cpp
    friend class AstStorage;
    cpp<#)
  (:serialize (:slk))
  (:clone))

(lcp:define-class identifier (expression)
  ((name "std::string" :scope :public)
   (user-declared :bool :initval "true" :scope :public)
   (symbol-pos :int32_t :initval -1 :scope :public
               :documentation "Symbol table position of the symbol this Identifier is mapped to."))
  (:public
    #>cpp
    Identifier() = default;

    DEFVISITABLE(ExpressionVisitor<TypedValue>);
    DEFVISITABLE(ExpressionVisitor<TypedValue*>);
    DEFVISITABLE(ExpressionVisitor<void>);
    DEFVISITABLE(HierarchicalTreeVisitor);

    Identifier *MapTo(const Symbol &symbol) {
      symbol_pos_ = symbol.position();
      return this;
    }

    explicit Identifier(const std::string &name) : name_(name) {}
    Identifier(const std::string &name, bool user_declared)
        : name_(name), user_declared_(user_declared) {}
    cpp<#)
  (:private
    #>cpp
    friend class AstStorage;
    cpp<#)
  (:serialize (:slk))
  (:clone))

(lcp:define-class property-lookup (expression)
  ((expression "Expression *" :initval "nullptr" :scope :public
               :slk-save #'slk-save-ast-pointer
               :slk-load (slk-load-ast-pointer "Expression"))
   (property "PropertyIx" :scope :public
             :slk-load (lambda (member)
                        #>cpp
                        slk::Load(&self->${member}, reader, storage);
                        cpp<#)
             :clone (lambda (source dest)
                      #>cpp
                      ${dest} = storage->GetPropertyIx(${source}.name);
                      cpp<#)))
  (:public
    #>cpp
    PropertyLookup() = default;

    DEFVISITABLE(ExpressionVisitor<TypedValue>);
    DEFVISITABLE(ExpressionVisitor<TypedValue*>);
    DEFVISITABLE(ExpressionVisitor<void>);
    bool Accept(HierarchicalTreeVisitor &visitor) override {
      if (visitor.PreVisit(*this)) {
        expression_->Accept(visitor);
      }
      return visitor.PostVisit(*this);
    }
    cpp<#)
  (:protected
    #>cpp
    PropertyLookup(Expression *expression, PropertyIx property)
        : expression_(expression), property_(property) {}
    cpp<#)
  (:private
    #>cpp
    friend class AstStorage;
    cpp<#)
  (:serialize (:slk))
  (:clone))

(lcp:define-class labels-test (expression)
  ((expression "Expression *" :initval "nullptr" :scope :public
               :slk-save #'slk-save-ast-pointer
               :slk-load (slk-load-ast-pointer "Expression"))
   (labels "std::vector<LabelIx>" :scope :public
           :slk-load (lambda (member)
                      #>cpp
                      size_t size = 0;
                      slk::Load(&size, reader);
                      self->${member}.resize(size);
                      for (size_t i = 0; i < size; ++i) {
                        slk::Load(&self->${member}[i], reader, storage);
                      }
                      cpp<#)
           :clone (clone-name-ix-vector "Label")))
  (:public
    #>cpp
    LabelsTest() = default;

    DEFVISITABLE(ExpressionVisitor<TypedValue>);
    DEFVISITABLE(ExpressionVisitor<TypedValue*>);
    DEFVISITABLE(ExpressionVisitor<void>);
    bool Accept(HierarchicalTreeVisitor &visitor) override {
      if (visitor.PreVisit(*this)) {
        expression_->Accept(visitor);
      }
      return visitor.PostVisit(*this);
    }
    cpp<#)
  (:protected
    #>cpp
    LabelsTest(Expression *expression, const std::vector<LabelIx> &labels)
        : expression_(expression), labels_(labels) {}
    cpp<#)
  (:private
    #>cpp
    friend class AstStorage;
    cpp<#)
  (:serialize (:slk))
  (:clone))

(lcp:define-class function (expression)
  ((arguments "std::vector<Expression *>"
              :scope :public
              :slk-save #'slk-save-ast-vector
              :slk-load (slk-load-ast-vector "Expression"))
   (function-name "std::string" :scope :public)
   (function "std::function<TypedValue(const TypedValue *, int64_t,
                                       const FunctionContext &)>"
             :scope :public
             :dont-save t
             :clone :copy
             :slk-load (lambda (member)
                        #>cpp
                        self->${member} = query::NameToFunction(self->function_name_);
                        cpp<#)))
  (:public
    #>cpp
    Function() = default;

    DEFVISITABLE(ExpressionVisitor<TypedValue>);
    DEFVISITABLE(ExpressionVisitor<TypedValue*>);
    DEFVISITABLE(ExpressionVisitor<void>);
    bool Accept(HierarchicalTreeVisitor &visitor) override {
      if (visitor.PreVisit(*this)) {
        for (auto *argument : arguments_) {
          if (!argument->Accept(visitor)) break;
        }
      }
      return visitor.PostVisit(*this);
    }
    cpp<#)
  (:protected
    #>cpp
    Function(const std::string &function_name,
             const std::vector<Expression *> &arguments)
        : arguments_(arguments),
          function_name_(function_name),
          function_(NameToFunction(function_name_)) {
      if (!function_) {
        throw SemanticException("Function '{}' doesn't exist.", function_name);
      }
    }
    cpp<#)
  (:private
    #>cpp
    friend class AstStorage;
    cpp<#)
  (:serialize (:slk))
  (:clone))

(lcp:define-class reduce (expression)
  ((accumulator "Identifier *" :initval "nullptr" :scope :public
                :slk-save #'slk-save-ast-pointer
                :slk-load (slk-load-ast-pointer "Identifier")
                :documentation "Identifier for the accumulating variable")
   (initializer "Expression *" :initval "nullptr" :scope :public
                :slk-save #'slk-save-ast-pointer
                :slk-load (slk-load-ast-pointer "Expression")
                :documentation "Expression which produces the initial accumulator value.")
   (identifier "Identifier *" :initval "nullptr" :scope :public
               :slk-save #'slk-save-ast-pointer
               :slk-load (slk-load-ast-pointer "Identifier")
               :documentation "Identifier for the list element.")
   (list "Expression *" :initval "nullptr" :scope :public
         :slk-save #'slk-save-ast-pointer
         :slk-load (slk-load-ast-pointer "Expression")
         :documentation "Expression which produces a list to be reduced.")
   (expression "Expression *" :initval "nullptr" :scope :public
               :slk-save #'slk-save-ast-pointer
               :slk-load (slk-load-ast-pointer "Expression")
               :documentation "Expression which does the reduction, i.e. produces the new accumulator value."))
  (:public
    #>cpp
    Reduce() = default;

    DEFVISITABLE(ExpressionVisitor<TypedValue>);
    DEFVISITABLE(ExpressionVisitor<TypedValue*>);
    DEFVISITABLE(ExpressionVisitor<void>);
    bool Accept(HierarchicalTreeVisitor &visitor) override {
      if (visitor.PreVisit(*this)) {
        accumulator_->Accept(visitor) && initializer_->Accept(visitor) &&
            identifier_->Accept(visitor) && list_->Accept(visitor) &&
            expression_->Accept(visitor);
      }
      return visitor.PostVisit(*this);
    }
    cpp<#)
  (:protected
    #>cpp
    Reduce(Identifier *accumulator, Expression *initializer, Identifier *identifier,
           Expression *list, Expression *expression)
        : accumulator_(accumulator),
          initializer_(initializer),
          identifier_(identifier),
          list_(list),
          expression_(expression) {}
    cpp<#)
  (:private
    #>cpp
    friend class AstStorage;
    cpp<#)
  (:serialize (:slk))
  (:clone))

(lcp:define-class coalesce (expression)
  ((expressions "std::vector<Expression *>"
                :scope :public
                :slk-save #'slk-save-ast-vector
                :slk-load (slk-load-ast-vector "Expression")
                :documentation "A list of expressions to evaluate. None of the expressions should be nullptr."))
  (:public
   #>cpp
   Coalesce() = default;

   DEFVISITABLE(ExpressionVisitor<TypedValue>);
   DEFVISITABLE(ExpressionVisitor<TypedValue*>);
   DEFVISITABLE(ExpressionVisitor<void>);
   bool Accept(HierarchicalTreeVisitor &visitor) override {
     if (visitor.PreVisit(*this)) {
       for (auto *expr : expressions_) {
         if (!expr->Accept(visitor)) break;
       }
     }
     return visitor.PostVisit(*this);
   }
   cpp<#
   )
  (:private
   #>cpp
   explicit Coalesce(const std::vector<Expression *> &expressions)
       : expressions_(expressions) {}

   friend class AstStorage;
   cpp<#)
  (:serialize (:slk))
  (:clone))

(lcp:define-class extract (expression)
  ((identifier "Identifier *" :initval "nullptr" :scope :public
               :slk-save #'slk-save-ast-pointer
               :slk-load (slk-load-ast-pointer "Identifier")
               :documentation "Identifier for the list element.")
   (list "Expression *" :initval "nullptr" :scope :public
         :slk-save #'slk-save-ast-pointer
         :slk-load (slk-load-ast-pointer "Expression")
         :documentation "Expression which produces a list which will be extracted.")
   (expression "Expression *" :initval "nullptr" :scope :public
               :slk-save #'slk-save-ast-pointer
               :slk-load (slk-load-ast-pointer "Expression")
               :documentation "Expression which produces the new value for list element."))
  (:public
    #>cpp
    Extract() = default;

    DEFVISITABLE(ExpressionVisitor<TypedValue>);
    DEFVISITABLE(ExpressionVisitor<TypedValue*>);
    DEFVISITABLE(ExpressionVisitor<void>);
    bool Accept(HierarchicalTreeVisitor &visitor) override {
      if (visitor.PreVisit(*this)) {
        identifier_->Accept(visitor) && list_->Accept(visitor) &&
            expression_->Accept(visitor);
      }
      return visitor.PostVisit(*this);
    }
    cpp<#)
  (:protected
    #>cpp
    Extract(Identifier *identifier, Expression *list, Expression *expression)
        : identifier_(identifier), list_(list), expression_(expression) {}
    cpp<#)
  (:private
    #>cpp
    friend class AstStorage;
    cpp<#)
  (:serialize (:slk))
  (:clone))

(lcp:define-class all (expression)
  ((identifier "Identifier *" :initval "nullptr" :scope :public
               :slk-save #'slk-save-ast-pointer
               :slk-load (slk-load-ast-pointer "Identifier"))
   (list-expression "Expression *" :initval "nullptr" :scope :public
                    :slk-save #'slk-save-ast-pointer
                    :slk-load (slk-load-ast-pointer "Expression"))
   (where "Where *" :initval "nullptr" :scope :public
          :slk-save #'slk-save-ast-pointer
          :slk-load (slk-load-ast-pointer "Where")))
  (:public
    #>cpp
    All() = default;

    DEFVISITABLE(ExpressionVisitor<TypedValue>);
    DEFVISITABLE(ExpressionVisitor<TypedValue*>);
    DEFVISITABLE(ExpressionVisitor<void>);
    bool Accept(HierarchicalTreeVisitor &visitor) override {
      if (visitor.PreVisit(*this)) {
        identifier_->Accept(visitor) && list_expression_->Accept(visitor) &&
            where_->Accept(visitor);
      }
      return visitor.PostVisit(*this);
    }
    cpp<#)
  (:protected
    #>cpp
    All(Identifier *identifier, Expression *list_expression, Where *where)
        : identifier_(identifier),
          list_expression_(list_expression),
          where_(where) {}
    cpp<#)
  (:private
    #>cpp
    friend class AstStorage;
    cpp<#)
  (:serialize (:slk))
  (:clone))

;; TODO: This is pretty much copy pasted from All. Consider merging Reduce,
;; All, Any, None and Single into something like a higher-order function call
;; which takes a list argument and a function which is applied on list elements.
(lcp:define-class single (expression)
  ((identifier "Identifier *" :initval "nullptr" :scope :public
               :slk-save #'slk-save-ast-pointer
               :slk-load (slk-load-ast-pointer "Identifier"))
   (list-expression "Expression *" :initval "nullptr" :scope :public
                    :slk-save #'slk-save-ast-pointer
                    :slk-load (slk-load-ast-pointer "Expression"))
   (where "Where *" :initval "nullptr" :scope :public
          :slk-save #'slk-save-ast-pointer
          :slk-load (slk-load-ast-pointer "Where")))
  (:public
    #>cpp
    Single() = default;

    DEFVISITABLE(ExpressionVisitor<TypedValue>);
    DEFVISITABLE(ExpressionVisitor<TypedValue*>);
    DEFVISITABLE(ExpressionVisitor<void>);
    bool Accept(HierarchicalTreeVisitor &visitor) override {
      if (visitor.PreVisit(*this)) {
        identifier_->Accept(visitor) && list_expression_->Accept(visitor) &&
            where_->Accept(visitor);
      }
      return visitor.PostVisit(*this);
    }
    cpp<#)
  (:protected
    #>cpp
    Single(Identifier *identifier, Expression *list_expression, Where *where)
        : identifier_(identifier),
          list_expression_(list_expression),
          where_(where) {}
    cpp<#)
  (:private
    #>cpp
    friend class AstStorage;
    cpp<#)
  (:serialize (:slk))
  (:clone))

;; TODO: This is pretty much copy pasted from All. Consider merging Reduce,
;; All, Any, None and Single into something like a higher-order function call
;; which takes a list argument and a function which is applied on list elements.
(lcp:define-class any (expression)
  ((identifier "Identifier *" :initval "nullptr" :scope :public
               :slk-save #'slk-save-ast-pointer
               :slk-load (slk-load-ast-pointer "Identifier"))
   (list-expression "Expression *" :initval "nullptr" :scope :public
                    :slk-save #'slk-save-ast-pointer
                    :slk-load (slk-load-ast-pointer "Expression"))
   (where "Where *" :initval "nullptr" :scope :public
          :slk-save #'slk-save-ast-pointer
          :slk-load (slk-load-ast-pointer "Where")))
  (:public
    #>cpp
    Any() = default;

    DEFVISITABLE(ExpressionVisitor<TypedValue>);
    DEFVISITABLE(ExpressionVisitor<TypedValue*>);
    DEFVISITABLE(ExpressionVisitor<void>);
    bool Accept(HierarchicalTreeVisitor &visitor) override {
      if (visitor.PreVisit(*this)) {
        identifier_->Accept(visitor) && list_expression_->Accept(visitor) &&
            where_->Accept(visitor);
      }
      return visitor.PostVisit(*this);
    }
    cpp<#)
  (:protected
    #>cpp
    Any(Identifier *identifier, Expression *list_expression, Where *where)
        : identifier_(identifier),
          list_expression_(list_expression),
          where_(where) {}
    cpp<#)
  (:private
    #>cpp
    friend class AstStorage;
    cpp<#)
  (:serialize (:slk))
  (:clone))

;; TODO: This is pretty much copy pasted from All. Consider merging Reduce,
;; All, Any, None and Single into something like a higher-order function call
;; which takes a list argument and a function which is applied on list elements.
(lcp:define-class none (expression)
  ((identifier "Identifier *" :initval "nullptr" :scope :public
               :slk-save #'slk-save-ast-pointer
               :slk-load (slk-load-ast-pointer "Identifier"))
   (list-expression "Expression *" :initval "nullptr" :scope :public
                    :slk-save #'slk-save-ast-pointer
                    :slk-load (slk-load-ast-pointer "Expression"))
   (where "Where *" :initval "nullptr" :scope :public
          :slk-save #'slk-save-ast-pointer
          :slk-load (slk-load-ast-pointer "Where")))
  (:public
    #>cpp
    None() = default;

    DEFVISITABLE(ExpressionVisitor<TypedValue>);
    DEFVISITABLE(ExpressionVisitor<TypedValue*>);
    DEFVISITABLE(ExpressionVisitor<void>);
    bool Accept(HierarchicalTreeVisitor &visitor) override {
      if (visitor.PreVisit(*this)) {
        identifier_->Accept(visitor) && list_expression_->Accept(visitor) &&
            where_->Accept(visitor);
      }
      return visitor.PostVisit(*this);
    }
    cpp<#)
  (:protected
    #>cpp
    None(Identifier *identifier, Expression *list_expression, Where *where)
        : identifier_(identifier),
          list_expression_(list_expression),
          where_(where) {}
    cpp<#)
  (:private
    #>cpp
    friend class AstStorage;
    cpp<#)
  (:serialize (:slk))
  (:clone))

(lcp:define-class parameter-lookup (expression)
  ((token-position :int32_t :initval -1 :scope :public
                   :documentation "This field contains token position of *literal* used to create ParameterLookup object. If ParameterLookup object is not created from a literal leave this value at -1."))
  (:public
    #>cpp
    ParameterLookup() = default;

    DEFVISITABLE(ExpressionVisitor<TypedValue>);
    DEFVISITABLE(ExpressionVisitor<TypedValue*>);
    DEFVISITABLE(ExpressionVisitor<void>);
    DEFVISITABLE(HierarchicalTreeVisitor);
    cpp<#)
  (:protected
    #>cpp
    explicit ParameterLookup(int token_position)
        : token_position_(token_position) {}
    cpp<#)
  (:private
    #>cpp
    friend class AstStorage;
    cpp<#)
  (:serialize (:slk))
  (:clone))

(lcp:define-class regex-match (expression)
  ((string-expr "Expression *" :scope :public
                :slk-save #'slk-save-ast-pointer
                :slk-load (slk-load-ast-pointer "Expression"))
   (regex "Expression *" :scope :public
                :slk-save #'slk-save-ast-pointer
                :slk-load (slk-load-ast-pointer "Expression")))
  (:public
    #>cpp
    RegexMatch() = default;

    DEFVISITABLE(ExpressionVisitor<TypedValue>);
    DEFVISITABLE(ExpressionVisitor<TypedValue*>);
    DEFVISITABLE(ExpressionVisitor<void>);
    bool Accept(HierarchicalTreeVisitor &visitor) override {
      if (visitor.PreVisit(*this)) {
        string_expr_->Accept(visitor) && regex_->Accept(visitor);
      }
      return visitor.PostVisit(*this);
    }
    cpp<#)
  (:private
    #>cpp
    friend class AstStorage;
    RegexMatch(Expression *string_expr, Expression *regex)
        : string_expr_(string_expr), regex_(regex) {}
    cpp<#)
  (:serialize (:slk))
  (:clone))

(lcp:define-class named-expression (tree "::utils::Visitable<HierarchicalTreeVisitor>"
                                         "::utils::Visitable<ExpressionVisitor<TypedValue>>"
                                         "::utils::Visitable<ExpressionVisitor<TypedValue*>>"
                                         "::utils::Visitable<ExpressionVisitor<void>>")
  ((name "std::string" :scope :public)
   (expression "Expression *" :initval "nullptr" :scope :public
               :slk-save #'slk-save-ast-pointer
               :slk-load (slk-load-ast-pointer "Expression"))
   (token-position :int32_t :initval -1 :scope :public
                   :documentation "This field contains token position of first token in named expression used to create name_. If NamedExpression object is not created from query or it is aliased leave this value at -1.")
   (symbol-pos :int32_t :initval -1 :scope :public
               :documentation "Symbol table position of the symbol this NamedExpression is mapped to.")
   (is-aliased :bool :initval "false" :scope :public
               :documentation "True if the variable is aliased"))
  (:public
    #>cpp
    using utils::Visitable<ExpressionVisitor<TypedValue>>::Accept;
    using utils::Visitable<ExpressionVisitor<void>>::Accept;
    using utils::Visitable<HierarchicalTreeVisitor>::Accept;

    NamedExpression() = default;

    DEFVISITABLE(ExpressionVisitor<TypedValue>);
    DEFVISITABLE(ExpressionVisitor<TypedValue*>);
    DEFVISITABLE(ExpressionVisitor<void>);
    bool Accept(HierarchicalTreeVisitor &visitor) override {
      if (visitor.PreVisit(*this)) {
        expression_->Accept(visitor);
      }
      return visitor.PostVisit(*this);
    }

    NamedExpression *MapTo(const Symbol &symbol) {
      symbol_pos_ = symbol.position();
      return this;
    }
    cpp<#)
  (:protected
    #>cpp
    explicit NamedExpression(const std::string &name) : name_(name) {}
    NamedExpression(const std::string &name, Expression *expression)
        : name_(name), expression_(expression) {}
    NamedExpression(const std::string &name, Expression *expression,
                    int token_position)
        : name_(name), expression_(expression), token_position_(token_position) {}
    cpp<#)
  (:private
    #>cpp
    friend class AstStorage;
    cpp<#)
  (:serialize (:slk :ignore-other-base-classes t))
  (:clone :ignore-other-base-classes t)
  (:type-info :ignore-other-base-classes t))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; END Expressions
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(lcp:define-class pattern-atom (tree "::utils::Visitable<HierarchicalTreeVisitor>")
  ((identifier "Identifier *" :initval "nullptr" :scope :public
               :slk-save #'slk-save-ast-pointer
               :slk-load (slk-load-ast-pointer "Identifier")))
  (:abstractp t)
  (:public
    #>cpp
    using utils::Visitable<HierarchicalTreeVisitor>::Accept;

    PatternAtom() = default;
    cpp<#)
  (:protected
    #>cpp
    explicit PatternAtom(Identifier *identifier) : identifier_(identifier) {}
    cpp<#)
  (:private
    #>cpp
    friend class AstStorage;
    cpp<#)
  (:serialize (:slk :ignore-other-base-classes t))
  (:clone :ignore-other-base-classes t)
  (:type-info :ignore-other-base-classes t))

(defun clone-variant-properties (source destination)
  #>cpp
    if (const auto *properties = std::get_if<std::unordered_map<PropertyIx, Expression *>>(&${source})) {
      auto &new_obj_properties = std::get<std::unordered_map<PropertyIx, Expression *>>(${destination});
      for (const auto &[property, value_expression] : *properties) {
        PropertyIx key = storage->GetPropertyIx(property.name);
        new_obj_properties[key] = value_expression->Clone(storage);
      }
    } else {
      ${destination} = std::get<ParameterLookup *>(${source})->Clone(storage);
    }
  cpp<#)

(lcp:define-class node-atom (pattern-atom)
  ((labels "std::vector<LabelIx>" :scope :public
           :slk-load (lambda (member)
                      #>cpp
                      size_t size = 0;
                      slk::Load(&size, reader);
                      self->${member}.resize(size);
                      for (size_t i = 0; i < size; ++i) {
                        slk::Load(&self->${member}[i], reader, storage);
                      }
                      cpp<#)
           :clone (clone-name-ix-vector "Label"))
   (properties "std::variant<std::unordered_map<PropertyIx, Expression *>, ParameterLookup*>"
              :clone #'clone-variant-properties
              :scope :public))
  (:public
    #>cpp
    bool Accept(HierarchicalTreeVisitor &visitor) override {
      if (visitor.PreVisit(*this)) {
        if (auto* properties = std::get_if<std::unordered_map<PropertyIx, Expression *>>(&properties_)) {
          bool cont = identifier_->Accept(visitor);
          for (auto &property : *properties) {
            if (cont) {
              cont = property.second->Accept(visitor);
            }
          }
        } else {
          std::get<ParameterLookup*>(properties_)->Accept(visitor);
        }
      }
      return visitor.PostVisit(*this);
    }
    cpp<#)
  (:protected
    #>cpp
    using PatternAtom::PatternAtom;
    cpp<#)
  (:private
    #>cpp
    friend class AstStorage;
    cpp<#)
  (:serialize (:slk))
  (:clone))

(lcp:define-class edge-atom (pattern-atom)
  ((type "Type" :initval "Type::SINGLE" :scope :public)
   (direction "Direction" :initval "Direction::BOTH" :scope :public)
   (edge-types "std::vector<EdgeTypeIx>" :scope :public
               :slk-load (lambda (member)
                          #>cpp
                          size_t size = 0;
                          slk::Load(&size, reader);
                          self->${member}.resize(size);
                          for (size_t i = 0; i < size; ++i) {
                            slk::Load(&self->${member}[i], reader, storage);
                          }
                          cpp<#)
               :clone (clone-name-ix-vector "EdgeType"))
   (properties "std::variant<std::unordered_map<PropertyIx, Expression *>, ParameterLookup*>"
               :scope :public
               :slk-save #'slk-save-property-map
               :slk-load #'slk-load-property-map
               :clone #'clone-variant-properties)
   (lower-bound "Expression *" :initval "nullptr" :scope :public
                :slk-save #'slk-save-ast-pointer
                :slk-load (slk-load-ast-pointer "Expression")
                :documentation "Evaluates to lower bound in variable length expands.")
   (upper-bound "Expression *" :initval "nullptr" :scope :public
                :slk-save #'slk-save-ast-pointer
                :slk-load (slk-load-ast-pointer "Expression")
                :documentation "Evaluated to upper bound in variable length expands.")
   (filter-lambda "Lambda" :scope :public
                  :documentation "Filter lambda for variable length expands. Can have an empty expression, but identifiers must be valid, because an optimization pass may inline other expressions into this lambda."
                  :slk-load (lambda (member)
                              #>cpp
                              slk::Load(&self->${member}, reader, storage);
                              cpp<#))
   (weight-lambda "Lambda" :scope :public
                  :documentation "Used in weighted shortest path. It must have valid expressions and identifiers. In all other expand types, it is empty."
                  :slk-load (lambda (member)
                              #>cpp
                              slk::Load(&self->${member}, reader, storage);
                              cpp<#))
   (total-weight "Identifier *" :initval "nullptr" :scope :public
                 :slk-save #'slk-save-ast-pointer
                 :slk-load (slk-load-ast-pointer "Identifier")
                 :documentation "Variable where the total weight for weighted shortest path will be stored."))
  (:public
    (lcp:define-enum type
                     (single depth-first breadth-first weighted-shortest-path all-shortest-paths)
                     (:serialize))
    (lcp:define-enum direction
                     (in out both)
                     (:serialize))
    (lcp:define-struct lambda ()
      ((inner-edge "Identifier *" :initval "nullptr"
                   :slk-save #'slk-save-ast-pointer
                   :slk-load (slk-load-ast-pointer "Identifier")
                   :documentation "Argument identifier for the edge currently being traversed.")
       (inner-node "Identifier *" :initval "nullptr"
                   :slk-save #'slk-save-ast-pointer
                   :slk-load (slk-load-ast-pointer "Identifier")
                   :documentation "Argument identifier for the destination node of the edge.")
       (expression "Expression *" :initval "nullptr"
                   :slk-save #'slk-save-ast-pointer
                   :slk-load (slk-load-ast-pointer "Expression")
                   :documentation "Evaluates the result of the lambda."))
      (:documentation "Lambda for use in filtering or weight calculation during variable expand.")
      (:serialize (:slk :load-args '((storage "query::AstStorage *"))))
      (:clone :args '((storage "AstStorage *"))))
    #>cpp
    bool Accept(HierarchicalTreeVisitor &visitor) override {
      if (visitor.PreVisit(*this)) {
        bool cont = identifier_->Accept(visitor);
        if (auto *properties = std::get_if<std::unordered_map<query::PropertyIx, query::Expression *>>(&properties_)) {
          for (auto &property : *properties) {
            if (cont) {
              cont = property.second->Accept(visitor);
            }
          }
        } else {
          std::get<ParameterLookup *>(properties_)->Accept(visitor);
        }
        if (cont && lower_bound_) {
          cont = lower_bound_->Accept(visitor);
        }
        if (cont && upper_bound_) {
          cont = upper_bound_->Accept(visitor);
        }
        if (cont && total_weight_) {
          total_weight_->Accept(visitor);
        }
      }
      return visitor.PostVisit(*this);
    }

    bool IsVariable() const {
      switch (type_) {
        case Type::DEPTH_FIRST:
        case Type::BREADTH_FIRST:
        case Type::WEIGHTED_SHORTEST_PATH:
        case Type::ALL_SHORTEST_PATHS:
          return true;
        case Type::SINGLE:
          return false;
      }
    }
    cpp<#)
  (:protected
    #>cpp
    using PatternAtom::PatternAtom;
    EdgeAtom(Identifier *identifier, Type type, Direction direction)
        : PatternAtom(identifier), type_(type), direction_(direction) {}

    // Creates an edge atom for a SINGLE expansion with the given .
    EdgeAtom(Identifier *identifier, Type type, Direction direction,
             const std::vector<EdgeTypeIx> &edge_types)
        : PatternAtom(identifier),
          type_(type),
          direction_(direction),
          edge_types_(edge_types) {}
    cpp<#)
  (:private
    #>cpp
    friend class AstStorage;
    cpp<#)
  (:serialize (:slk))
  (:clone))

(lcp:define-class pattern (tree "::utils::Visitable<HierarchicalTreeVisitor>")
  ((identifier "Identifier *" :initval "nullptr" :scope :public
               :slk-save #'slk-save-ast-pointer
               :slk-load (slk-load-ast-pointer "Identifier"))
   (atoms "std::vector<PatternAtom *>"
          :scope :public
          :slk-save #'slk-save-ast-vector
          :slk-load (slk-load-ast-vector "PatternAtom")))
  (:public
    #>cpp
    using utils::Visitable<HierarchicalTreeVisitor>::Accept;

    Pattern() = default;

    bool Accept(HierarchicalTreeVisitor &visitor) override {
      if (visitor.PreVisit(*this)) {
        bool cont = identifier_->Accept(visitor);
        for (auto &part : atoms_) {
          if (cont) {
            cont = part->Accept(visitor);
          }
        }
      }
      return visitor.PostVisit(*this);
    }
    cpp<#)
  (:private
    #>cpp
    friend class AstStorage;
    cpp<#)
  (:serialize (:slk :ignore-other-base-classes t))
  (:clone :ignore-other-base-classes t)
  (:type-info :ignore-other-base-classes t))

(lcp:define-class clause (tree "::utils::Visitable<HierarchicalTreeVisitor>")
  ()
  (:abstractp t)
  (:public
    #>cpp
    using utils::Visitable<HierarchicalTreeVisitor>::Accept;

    Clause() = default;
    cpp<#)
  (:private
    #>cpp
    friend class AstStorage;
    cpp<#)
  (:serialize (:slk :ignore-other-base-classes t))
  (:clone :ignore-other-base-classes t)
  (:type-info :ignore-other-base-classes t))

(lcp:define-class single-query (tree "::utils::Visitable<HierarchicalTreeVisitor>")
  ((clauses "std::vector<Clause *>"
            :scope :public
            :slk-save #'slk-save-ast-vector
            :slk-load (slk-load-ast-vector "Clause")))
  (:public
    #>cpp
    using utils::Visitable<HierarchicalTreeVisitor>::Accept;

    SingleQuery() = default;

    bool Accept(HierarchicalTreeVisitor &visitor) override {
      if (visitor.PreVisit(*this)) {
        for (auto &clause : clauses_) {
          if (!clause->Accept(visitor)) break;
        }
      }
      return visitor.PostVisit(*this);
    }
    cpp<#)
  (:private
    #>cpp
    friend class AstStorage;
    cpp<#)
  (:serialize (:slk :ignore-other-base-classes t))
  (:clone :ignore-other-base-classes t)
  (:type-info :ignore-other-base-classes t))

(lcp:define-class cypher-union (tree "::utils::Visitable<HierarchicalTreeVisitor>")
  ((single-query "SingleQuery *" :initval "nullptr" :scope :public
                 :slk-save #'slk-save-ast-pointer
                 :slk-load (slk-load-ast-pointer "SingleQuery"))
   (distinct :bool :initval "false" :scope :public)
   (union-symbols "std::vector<Symbol>" :scope :public
                  :documentation "Holds symbols that are created during symbol generation phase. These symbols are used when UNION/UNION ALL combines single query results."))
  (:public
    #>cpp
    using utils::Visitable<HierarchicalTreeVisitor>::Accept;

    CypherUnion() = default;

    bool Accept(HierarchicalTreeVisitor &visitor) override {
      if (visitor.PreVisit(*this)) {
        single_query_->Accept(visitor);
      }
      return visitor.PostVisit(*this);
    }
    cpp<#)
  (:protected
    #>cpp
    explicit CypherUnion(bool distinct) : distinct_(distinct) {}
    CypherUnion(bool distinct, SingleQuery *single_query,
                std::vector<Symbol> union_symbols)
        : single_query_(single_query),
          distinct_(distinct),
          union_symbols_(union_symbols) {}
    cpp<#)
  (:private
    #>cpp
    friend class AstStorage;
    cpp<#)
  (:serialize (:slk :ignore-other-base-classes t))
  (:clone :ignore-other-base-classes t)
  (:type-info :ignore-other-base-classes t))

(lcp:define-class query (tree "::utils::Visitable<QueryVisitor<void>>")
  ()
  (:abstractp t)
  (:public
    #>cpp
    using utils::Visitable<QueryVisitor<void>>::Accept;

    Query() = default;
    cpp<#)
  (:private
    #>cpp
    friend class AstStorage;
    cpp<#)
  (:serialize (:slk :ignore-other-base-classes t))
  (:clone :ignore-other-base-classes t)
  (:type-info :ignore-other-base-classes t))

(lcp:define-class cypher-query (query "::utils::Visitable<HierarchicalTreeVisitor>")
  ((single-query "SingleQuery *" :initval "nullptr" :scope :public
                 :slk-save #'slk-save-ast-pointer
                 :slk-load (slk-load-ast-pointer "SingleQuery")
                 :documentation "First and potentially only query.")
   (cypher-unions "std::vector<CypherUnion *>"
                  :scope :public
                  :slk-save #'slk-save-ast-vector
                  :slk-load (slk-load-ast-vector "CypherUnion")
                  :documentation "Contains remaining queries that should form and union with `single_query_`.")
   (memory-limit "Expression *" :initval "nullptr" :scope :public
                 :slk-save #'slk-save-ast-pointer
                 :slk-load (slk-load-ast-pointer "Expression"))
   (memory-scale "size_t" :initval "1024U" :scope :public))
  (:public
    #>cpp
    CypherQuery() = default;

    DEFVISITABLE(QueryVisitor<void>);

    bool Accept(HierarchicalTreeVisitor &visitor) override {
      if (visitor.PreVisit(*this)) {
        single_query_->Accept(visitor);
        for (auto *cypher_union : cypher_unions_) {
          cypher_union->Accept(visitor);
        }
      }

      return visitor.PostVisit(*this);
    }
    cpp<#)
  (:private
    #>cpp
    friend class AstStorage;
    cpp<#)
  (:serialize (:slk :ignore-other-base-classes t))
  (:clone :ignore-other-base-classes t)
  (:type-info :ignore-other-base-classes t))

(lcp:define-class explain-query (query)
  ((cypher-query "CypherQuery *" :initval "nullptr" :scope :public
                 :slk-save #'slk-save-ast-pointer
                 :slk-load (slk-load-ast-pointer "CypherQuery")
                 :documentation "The CypherQuery to explain."))
  (:public
    #>cpp
    ExplainQuery() = default;

    DEFVISITABLE(QueryVisitor<void>);
    cpp<#)
  (:private
    #>cpp
    friend class AstStorage;
    cpp<#)
  (:serialize (:slk))
  (:clone))

(lcp:define-class profile-query (query)
  ((cypher-query "CypherQuery *"
                 :initval "nullptr"
                 :scope :public
                 :slk-save #'slk-save-ast-pointer
                 :slk-load (slk-load-ast-pointer "CypherQuery")
                 :documentation "The CypherQuery to profile."))
  (:public
   #>cpp
   ProfileQuery() = default;

   DEFVISITABLE(QueryVisitor<void>);
   cpp<#)
  (:private
   #>cpp
   friend class AstStorage;
   cpp<#)
  (:serialize (:slk))
  (:clone))

(lcp:define-class index-query (query)
  ((action "Action" :scope :public)
   (label "LabelIx" :scope :public
          :slk-load (lambda (member)
                     #>cpp
                     slk::Load(&self->${member}, reader, storage);
                     cpp<#)
          :clone (lambda (source dest)
                   #>cpp
                   ${dest} = storage->GetLabelIx(${source}.name);
                   cpp<#))
   (properties "std::vector<PropertyIx>" :scope :public
               :slk-load (lambda (member)
                          #>cpp
                          size_t size = 0;
                          slk::Load(&size, reader);
                          self->${member}.resize(size);
                          for (size_t i = 0; i < size; ++i) {
                            slk::Load(&self->${member}[i], reader, storage);
                          }
                          cpp<#)
               :clone (clone-name-ix-vector "Property")))
  (:public
   (lcp:define-enum action
       (create drop)
     (:serialize))

    #>cpp
    IndexQuery() = default;

    DEFVISITABLE(QueryVisitor<void>);
  cpp<#)
  (:protected
    #>cpp
    IndexQuery(Action action, LabelIx label, std::vector<PropertyIx> properties)
        : action_(action), label_(label), properties_(properties) {}
    cpp<#)
  (:private
    #>cpp
    friend class AstStorage;
    cpp<#)
  (:serialize (:slk))
  (:clone))

(lcp:define-class create (clause)
  ((patterns "std::vector<Pattern *>"
             :scope :public
             :slk-save #'slk-save-ast-vector
             :slk-load (slk-load-ast-vector "Pattern")))
  (:public
    #>cpp
    Create() = default;

    bool Accept(HierarchicalTreeVisitor &visitor) override {
      if (visitor.PreVisit(*this)) {
        for (auto &pattern : patterns_) {
          if (!pattern->Accept(visitor)) break;
        }
      }
      return visitor.PostVisit(*this);
    }
    cpp<#)
  (:protected
    #>cpp
    explicit Create(std::vector<Pattern *> patterns) : patterns_(patterns) {}
    cpp<#)
  (:private
    #>cpp
    friend class AstStorage;
    cpp<#)
  (:serialize (:slk))
  (:clone))

(lcp:define-class call-procedure (clause)
  ((procedure-name "std::string" :scope :public)
   (arguments "std::vector<Expression *>"
              :scope :public
              :slk-save #'slk-save-ast-vector
              :slk-load (slk-load-ast-vector "Expression"))
   (result-fields "std::vector<std::string>" :scope :public)
   (result-identifiers "std::vector<Identifier *>"
                       :scope :public
                       :slk-save #'slk-save-ast-vector
                       :slk-load (slk-load-ast-vector "Identifier"))
   (memory-limit "Expression *" :initval "nullptr" :scope :public
                 :slk-save #'slk-save-ast-pointer
                 :slk-load (slk-load-ast-pointer "Expression"))
   (memory-scale "size_t" :initval "1024U" :scope :public)
   (is_write :bool  :scope :public))
  (:public
    #>cpp
    CallProcedure() = default;

    bool Accept(HierarchicalTreeVisitor &visitor) override {
      if (visitor.PreVisit(*this)) {
        bool cont = true;
        for (auto &arg : arguments_) {
          if (!arg->Accept(visitor)) {
            cont = false;
            break;
          }
        }
        if (cont) {
          for (auto &ident : result_identifiers_) {
            if (!ident->Accept(visitor)) {
              cont = false;
              break;
            }
          }
        }
      }
      return visitor.PostVisit(*this);
    }
    cpp<#)
  (:private
    #>cpp
    friend class AstStorage;
    cpp<#)
  (:serialize (:slk))
  (:clone))

(lcp:define-class match (clause)
  ((patterns "std::vector<Pattern *>"
             :scope :public
             :slk-save #'slk-save-ast-vector
             :slk-load (slk-load-ast-vector "Pattern"))
   (where "Where *" :initval "nullptr" :scope :public
          :slk-save #'slk-save-ast-pointer
          :slk-load (slk-load-ast-pointer "Where"))
   (optional :bool :initval "false" :scope :public))
  (:public
    #>cpp
    Match() = default;

    bool Accept(HierarchicalTreeVisitor &visitor) override {
      if (visitor.PreVisit(*this)) {
        bool cont = true;
        for (auto &pattern : patterns_) {
          if (!pattern->Accept(visitor)) {
            cont = false;
            break;
          }
        }
        if (cont && where_) {
          where_->Accept(visitor);
        }
      }
      return visitor.PostVisit(*this);
    }
    cpp<#)
  (:protected
    #>cpp
    explicit Match(bool optional) : optional_(optional) {}
    Match(bool optional, Where *where, std::vector<Pattern *> patterns)
        : patterns_(patterns), where_(where), optional_(optional) {}
    cpp<#)
  (:private
    #>cpp
    friend class AstStorage;
    cpp<#)
  (:serialize (:slk))
  (:clone))

(lcp:define-enum ordering
    (asc desc)
  (:documentation "Defines the order for sorting values (ascending or descending).")
  (:serialize))

(lcp:define-struct sort-item ()
  ((ordering "Ordering" :scope :public)
   (expression "Expression *"
               :slk-save #'slk-save-ast-pointer
               :slk-load (slk-load-ast-pointer "Expression")))
  (:serialize (:slk :load-args '((storage "query::AstStorage *"))))
  (:clone :args '((storage "AstStorage *"))))

(lcp:define-struct return-body ()
  ((distinct :bool :initval "false"
             :documentation "True if distinct results should be produced.")
   (all-identifiers :bool :initval "false"
                    :documentation "True if asterisk was found in the return body.")
   (named-expressions "std::vector<NamedExpression *>"
                      :slk-save #'slk-save-ast-vector
                      :slk-load (slk-load-ast-vector "NamedExpression")
                      :documentation "Expressions which are used to produce results.")
   (order-by "std::vector<SortItem>"
             :slk-load (lambda (member)
                         #>cpp
                         size_t size = 0;
                         slk::Load(&size, reader);
                         self->${member}.resize(size);
                         for (size_t i = 0; i < size; ++i) {
                           slk::Load(&self->${member}[i], reader, storage);
                         }
                         cpp<#)
             :documentation "Expressions used for ordering the results.")
   (skip "Expression *" :initval "nullptr"
         :slk-save #'slk-save-ast-pointer
         :slk-load (slk-load-ast-pointer "Expression")
         :documentation "Optional expression on how many results to skip.")
   (limit "Expression *" :initval "nullptr"
          :slk-save #'slk-save-ast-pointer
          :slk-load (slk-load-ast-pointer "Expression")
          :documentation "Optional expression on how many results to produce."))
  (:documentation "Contents common to @c Return and @c With clauses.")
  (:serialize (:slk :load-args '((storage "query::AstStorage *"))))
  (:clone :args '((storage "AstStorage *"))))

(lcp:define-class return (clause)
  ((body "ReturnBody" :scope :public
         :slk-load (lambda (member)
                     #>cpp
                     slk::Load(&self->${member}, reader, storage);
                     cpp<#)))
  (:public
    #>cpp
    Return() = default;

    bool Accept(HierarchicalTreeVisitor &visitor) override {
      if (visitor.PreVisit(*this)) {
        bool cont = true;
        for (auto &expr : body_.named_expressions) {
          if (!expr->Accept(visitor)) {
            cont = false;
            break;
          }
        }
        if (cont) {
          for (auto &order_by : body_.order_by) {
            if (!order_by.expression->Accept(visitor)) {
              cont = false;
              break;
            }
          }
        }
        if (cont && body_.skip) cont = body_.skip->Accept(visitor);
        if (cont && body_.limit) cont = body_.limit->Accept(visitor);
      }
      return visitor.PostVisit(*this);
    }
    cpp<#)
  (:protected
    #>cpp
    explicit Return(ReturnBody &body) : body_(body) {}
    cpp<#)
  (:private
    #>cpp
    friend class AstStorage;
    cpp<#)
  (:serialize (:slk))
  (:clone))

(lcp:define-class with (clause)
  ((body "ReturnBody" :scope :public
         :slk-load (lambda (member)
                     #>cpp
                     slk::Load(&self->${member}, reader, storage);
                     cpp<#))
   (where "Where *" :initval "nullptr" :scope :public
          :slk-save #'slk-save-ast-pointer
          :slk-load (slk-load-ast-pointer "Where")))
  (:public
    #>cpp
    With() = default;

    bool Accept(HierarchicalTreeVisitor &visitor) override {
      if (visitor.PreVisit(*this)) {
        bool cont = true;
        for (auto &expr : body_.named_expressions) {
          if (!expr->Accept(visitor)) {
            cont = false;
            break;
          }
        }
        if (cont) {
          for (auto &order_by : body_.order_by) {
            if (!order_by.expression->Accept(visitor)) {
              cont = false;
              break;
            }
          }
        }
        if (cont && where_) cont = where_->Accept(visitor);
        if (cont && body_.skip) cont = body_.skip->Accept(visitor);
        if (cont && body_.limit) cont = body_.limit->Accept(visitor);
      }
      return visitor.PostVisit(*this);
    }
    cpp<#)
  (:protected
    #>cpp
    With(ReturnBody &body, Where *where) : body_(body), where_(where) {}
    cpp<#)
  (:private
    #>cpp
    friend class AstStorage;
    cpp<#)
  (:serialize (:slk))
  (:clone))

(lcp:define-class delete (clause)
  ((expressions "std::vector<Expression *>"
                :slk-save #'slk-save-ast-vector
                :slk-load (slk-load-ast-vector "Expression")
                :scope :public)
   (detach :bool :initval "false" :scope :public))
  (:public
    #>cpp
    Delete() = default;

    bool Accept(HierarchicalTreeVisitor &visitor) override {
      if (visitor.PreVisit(*this)) {
        for (auto &expr : expressions_) {
          if (!expr->Accept(visitor)) break;
        }
      }
      return visitor.PostVisit(*this);
    }
    cpp<#)
  (:protected
    #>cpp
    Delete(bool detach, std::vector<Expression *> expressions)
        : expressions_(expressions), detach_(detach) {}
    cpp<#)
  (:private
    #>cpp
    friend class AstStorage;
    cpp<#)
  (:serialize (:slk))
  (:clone))

(lcp:define-class set-property (clause)
  ((property-lookup "PropertyLookup *" :initval "nullptr" :scope :public
                    :slk-save #'slk-save-ast-pointer
                    :slk-load (slk-load-ast-pointer "PropertyLookup"))
   (expression "Expression *" :initval "nullptr" :scope :public
               :slk-save #'slk-save-ast-pointer
               :slk-load (slk-load-ast-pointer "Expression")))
  (:public
    #>cpp
    SetProperty() = default;

    bool Accept(HierarchicalTreeVisitor &visitor) override {
      if (visitor.PreVisit(*this)) {
        property_lookup_->Accept(visitor) && expression_->Accept(visitor);
      }
      return visitor.PostVisit(*this);
    }
    cpp<#)
  (:protected
    #>cpp
    SetProperty(PropertyLookup *property_lookup, Expression *expression)
        : property_lookup_(property_lookup), expression_(expression) {}
    cpp<#)
  (:private
    #>cpp
    friend class AstStorage;
    cpp<#)
  (:serialize (:slk))
  (:clone))

(lcp:define-class set-properties (clause)
  ((identifier "Identifier *" :initval "nullptr" :scope :public
               :slk-save #'slk-save-ast-pointer
               :slk-load (slk-load-ast-pointer "Identifier"))
   (expression "Expression *" :initval "nullptr" :scope :public
               :slk-save #'slk-save-ast-pointer
               :slk-load (slk-load-ast-pointer "Expression"))
   (update :bool :initval "false" :scope :public))
  (:public
    #>cpp
    SetProperties() = default;

    bool Accept(HierarchicalTreeVisitor &visitor) override {
      if (visitor.PreVisit(*this)) {
        identifier_->Accept(visitor) && expression_->Accept(visitor);
      }
      return visitor.PostVisit(*this);
    }
    cpp<#)
  (:protected
    #>cpp
    SetProperties(Identifier *identifier, Expression *expression,
                  bool update = false)
        : identifier_(identifier), expression_(expression), update_(update) {}
    cpp<#)
  (:private
    #>cpp
    friend class AstStorage;
    cpp<#)
  (:serialize (:slk))
  (:clone))

(lcp:define-class set-labels (clause)
  ((identifier "Identifier *" :initval "nullptr" :scope :public
               :slk-save #'slk-save-ast-pointer
               :slk-load (slk-load-ast-pointer "Identifier"))
   (labels "std::vector<LabelIx>" :scope :public
           :slk-load (lambda (member)
                      #>cpp
                      size_t size = 0;
                      slk::Load(&size, reader);
                      self->${member}.resize(size);
                      for (size_t i = 0; i < size; ++i) {
                        slk::Load(&self->${member}[i], reader, storage);
                      }
                      cpp<#)
           :clone (clone-name-ix-vector "Label")))
  (:public
    #>cpp
    SetLabels() = default;

    bool Accept(HierarchicalTreeVisitor &visitor) override {
      if (visitor.PreVisit(*this)) {
        identifier_->Accept(visitor);
      }
      return visitor.PostVisit(*this);
    }
    cpp<#)
  (:protected
    #>cpp
    SetLabels(Identifier *identifier, const std::vector<LabelIx> &labels)
        : identifier_(identifier), labels_(labels) {}
    cpp<#)
  (:private
    #>cpp
    friend class AstStorage;
    cpp<#)
  (:serialize (:slk))
  (:clone))

(lcp:define-class remove-property (clause)
  ((property-lookup "PropertyLookup *" :initval "nullptr" :scope :public
                    :slk-save #'slk-save-ast-pointer
                    :slk-load (slk-load-ast-pointer "PropertyLookup")))
  (:public
    #>cpp
    RemoveProperty() = default;

    bool Accept(HierarchicalTreeVisitor &visitor) override {
      if (visitor.PreVisit(*this)) {
        property_lookup_->Accept(visitor);
      }
      return visitor.PostVisit(*this);
    }
    cpp<#)
  (:protected
    #>cpp
    explicit RemoveProperty(PropertyLookup *property_lookup)
        : property_lookup_(property_lookup) {}
    cpp<#)
  (:private
    #>cpp
    friend class AstStorage;
    cpp<#)
  (:serialize (:slk))
  (:clone))

(lcp:define-class remove-labels (clause)
  ((identifier "Identifier *" :initval "nullptr" :scope :public
               :slk-save #'slk-save-ast-pointer
               :slk-load (slk-load-ast-pointer "Identifier"))
   (labels "std::vector<LabelIx>" :scope :public
           :slk-load (lambda (member)
                      #>cpp
                      size_t size = 0;
                      slk::Load(&size, reader);
                      self->${member}.resize(size);
                      for (size_t i = 0; i < size; ++i) {
                        slk::Load(&self->${member}[i], reader, storage);
                      }
                      cpp<#)
           :clone (clone-name-ix-vector "Label")))
  (:public
    #>cpp
    RemoveLabels() = default;

    bool Accept(HierarchicalTreeVisitor &visitor) override {
      if (visitor.PreVisit(*this)) {
        identifier_->Accept(visitor);
      }
      return visitor.PostVisit(*this);
    }
    cpp<#)
  (:protected
    #>cpp
    RemoveLabels(Identifier *identifier, const std::vector<LabelIx> &labels)
        : identifier_(identifier), labels_(labels) {}
    cpp<#)
  (:private
    #>cpp
    friend class AstStorage;
    cpp<#)
  (:serialize (:slk))
  (:clone))

(lcp:define-class merge (clause)
  ((pattern "Pattern *" :initval "nullptr" :scope :public
            :slk-save #'slk-save-ast-pointer
            :slk-load (slk-load-ast-pointer "Pattern"))
   (on-match "std::vector<Clause *>"
             :scope :public
             :slk-save #'slk-save-ast-vector
             :slk-load (slk-load-ast-vector "Clause"))
   (on-create "std::vector<Clause *>"
              :scope :public
              :slk-save #'slk-save-ast-vector
              :slk-load (slk-load-ast-vector "Clause")))
  (:public
    #>cpp
    Merge() = default;

    bool Accept(HierarchicalTreeVisitor &visitor) override {
      if (visitor.PreVisit(*this)) {
        bool cont = pattern_->Accept(visitor);
        if (cont) {
          for (auto &set : on_match_) {
            if (!set->Accept(visitor)) {
              cont = false;
              break;
            }
          }
        }
        if (cont) {
          for (auto &set : on_create_) {
            if (!set->Accept(visitor)) {
              cont = false;
              break;
            }
          }
        }
      }
      return visitor.PostVisit(*this);
    }
    cpp<#)
  (:protected
    #>cpp
    Merge(Pattern *pattern, std::vector<Clause *> on_match,
          std::vector<Clause *> on_create)
        : pattern_(pattern), on_match_(on_match), on_create_(on_create) {}
    cpp<#)
  (:private
    #>cpp
    friend class AstStorage;
    cpp<#)
  (:serialize (:slk))
  (:clone))

(lcp:define-class unwind (clause)
  ((named-expression "NamedExpression *" :initval "nullptr" :scope :public
                     :slk-save #'slk-save-ast-pointer
                     :slk-load (slk-load-ast-pointer "NamedExpression")))
  (:public
    #>cpp
    Unwind() = default;

    bool Accept(HierarchicalTreeVisitor &visitor) override {
      if (visitor.PreVisit(*this)) {
        named_expression_->Accept(visitor);
      }
      return visitor.PostVisit(*this);
    }
    cpp<#)
  (:protected
    #>cpp
    explicit Unwind(NamedExpression *named_expression)
        : named_expression_(named_expression) {
      DMG_ASSERT(named_expression, "Unwind cannot take nullptr for named_expression");
    }
    cpp<#)
  (:private
    #>cpp
    friend class AstStorage;
    cpp<#)
  (:serialize (:slk))
  (:clone))


(lcp:define-class auth-query (query)
  ((action "Action" :scope :public)
   (user "std::string" :scope :public)
   (role "std::string" :scope :public)
   (user-or-role "std::string" :scope :public)
   (password "Expression *" :initval "nullptr" :scope :public
             :slk-save #'slk-save-ast-pointer
             :slk-load (slk-load-ast-pointer "Expression"))
   (privileges "std::vector<Privilege>" :scope :public)
   (label-privileges "std::vector<std::unordered_map<FineGrainedPrivilege, std::vector<std::string>>>" :scope :public)
   (edge-type-privileges "std::vector<std::unordered_map<FineGrainedPrivilege, std::vector<std::string>>>" :scope :public))
  (:public
    (lcp:define-enum action
        (create-role drop-role show-roles create-user set-password drop-user
         show-users set-role clear-role grant-privilege deny-privilege
         revoke-privilege show-privileges show-role-for-user
         show-users-for-role)
      (:serialize))
    (lcp:define-enum privilege
        (create delete match merge set remove index stats auth constraint
         dump replication durability read_file free_memory trigger config stream module_read module_write
         websocket transaction_management)
      (:serialize))
    (lcp:define-enum fine-grained-privilege
        (nothing read update create_delete)
      (:serialize))
    #>cpp
    AuthQuery() = default;

    DEFVISITABLE(QueryVisitor<void>);
    cpp<#)
  (:protected
    #>cpp
    AuthQuery(Action action, std::string user, std::string role,
              std::string user_or_role, Expression *password,
              std::vector<Privilege> privileges,
              std::vector<std::unordered_map<FineGrainedPrivilege, std::vector<std::string>>> label_privileges,
              std::vector<std::unordered_map<FineGrainedPrivilege, std::vector<std::string>>> edge_type_privileges)
        : action_(action),
          user_(user),
          role_(role),
          user_or_role_(user_or_role),
          password_(password),
          privileges_(privileges),
          label_privileges_(label_privileges),
          edge_type_privileges_(edge_type_privileges) {}
    cpp<#)
  (:private
    #>cpp
    friend class AstStorage;
    cpp<#)
  (:serialize (:slk))
  (:clone))

;; TODO: Generate this via LCP
#>cpp
/// Constant that holds all available privileges.
const std::vector<AuthQuery::Privilege> kPrivilegesAll = {
    AuthQuery::Privilege::CREATE,     AuthQuery::Privilege::DELETE,
    AuthQuery::Privilege::MATCH,      AuthQuery::Privilege::MERGE,
    AuthQuery::Privilege::SET,        AuthQuery::Privilege::REMOVE,
    AuthQuery::Privilege::INDEX,      AuthQuery::Privilege::STATS,
    AuthQuery::Privilege::AUTH,
    AuthQuery::Privilege::CONSTRAINT, AuthQuery::Privilege::DUMP,
    AuthQuery::Privilege::REPLICATION,
    AuthQuery::Privilege::READ_FILE,
    AuthQuery::Privilege::DURABILITY,
    AuthQuery::Privilege::FREE_MEMORY, AuthQuery::Privilege::TRIGGER,
    AuthQuery::Privilege::CONFIG,      AuthQuery::Privilege::STREAM,
    AuthQuery::Privilege::MODULE_READ, AuthQuery::Privilege::MODULE_WRITE,
    AuthQuery::Privilege::WEBSOCKET,   AuthQuery::Privilege::TRANSACTION_MANAGEMENT};
cpp<#

(lcp:define-class info-query (query)
  ((info-type "InfoType" :scope :public))
  (:public
    (lcp:define-enum info-type
        (storage index constraint)
      (:serialize))

    #>cpp
    DEFVISITABLE(QueryVisitor<void>);
    cpp<#)
  (:serialize (:slk))
  (:clone))

(lcp:define-struct constraint ()
  ((type "Type")
   (label "LabelIx" :scope :public
          :slk-load (lambda (member)
                     #>cpp
                     slk::Load(&self->${member}, reader, storage);
                     cpp<#)
          :clone (lambda (source dest)
                   #>cpp
                   ${dest} = storage->GetLabelIx(${source}.name);
                   cpp<#))
   (properties "std::vector<PropertyIx>" :scope :public
               :slk-load (lambda (member)
                          #>cpp
                          size_t size = 0;
                          slk::Load(&size, reader);
                          self->${member}.resize(size);
                          for (size_t i = 0; i < size; ++i) {
                            slk::Load(&self->${member}[i], reader, storage);
                          }
                          cpp<#)
               :clone (clone-name-ix-vector "Property")))
  (:public
    (lcp:define-enum type (exists unique node-key)
      (:serialize (:lcp))))
  (:serialize (:slk :load-args '((storage "query::AstStorage *"))))
  (:clone :args '((storage "AstStorage *"))))

(lcp:define-class constraint-query (query)
  ((action-type "ActionType" :scope :public)
   (constraint "Constraint" :scope :public
               :slk-load (lambda (member)
                           #>cpp
                           slk::Load(&self->${member}, reader, storage);
                           cpp<#)))
  (:public
    (lcp:define-enum action-type
        (create drop)
      (:serialize))

    #>cpp
    DEFVISITABLE(QueryVisitor<void>);
    cpp<#)
  (:serialize (:slk))
  (:clone))

(lcp:define-class dump-query (query) ()
  (:public
    #>cpp
    DEFVISITABLE(QueryVisitor<void>);
    cpp<#)
  (:serialize (:slk))
  (:clone))

(lcp:define-class analyze-graph-query (query)
  ((action "Action" :scope :public)
  (labels "std::vector<std::string>" :scope :public))

  (:public
    (lcp:define-enum action
        (analyze delete)
      (:serialize))
    #>cpp
    AnalyzeGraphQuery() = default;

    DEFVISITABLE(QueryVisitor<void>);
    cpp<#)
  (:private
    #>cpp
    friend class AstStorage;
    cpp<#)
  (:serialize (:slk))
  (:clone))

(lcp:define-class replication-query (query)
  ((action "Action" :scope :public)
   (role "ReplicationRole" :scope :public)
   (replica_name "std::string" :scope :public)
   (socket_address "Expression *" :initval "nullptr" :scope :public
             :slk-save #'slk-save-ast-pointer
             :slk-load (slk-load-ast-pointer "Expression"))
   (port "Expression *" :initval "nullptr" :scope :public
             :slk-save #'slk-save-ast-pointer
             :slk-load (slk-load-ast-pointer "Expression"))
   (sync_mode "SyncMode" :scope :public))

  (:public
    (lcp:define-enum action
        (set-replication-role show-replication-role register-replica
         drop-replica show-replicas)
      (:serialize))
    (lcp:define-enum replication-role
        (main replica)
      (:serialize))
    (lcp:define-enum sync-mode
        (sync async)
      (:serialize))
    (lcp:define-enum replica-state
        (ready replicating recovery invalid)
      (:serialize))
    #>cpp
    ReplicationQuery() = default;

    DEFVISITABLE(QueryVisitor<void>);
    cpp<#)
  (:private
    #>cpp
    friend class AstStorage;
    cpp<#)
  (:serialize (:slk))
  (:clone))

(lcp:define-class lock-path-query (query)
  ((action "Action" :scope :public))

  (:public
    (lcp:define-enum action
      (lock-path unlock-path)
      (:serialize))
    #>cpp
    LockPathQuery() = default;

    DEFVISITABLE(QueryVisitor<void>);
    cpp<#)
  (:private
    #>cpp
    friend class AstStorage;
    cpp<#)
  (:serialize (:slk))
  (:clone))

(lcp:define-class load-csv (clause)
  ((file "Expression *" :scope :public)
   (with_header "bool" :scope :public)
   (ignore_bad "bool" :scope :public)
   (delimiter "Expression *" :initval "nullptr" :scope :public)
   (quote "Expression *" :initval "nullptr" :scope :public)
   (row_var "Identifier *" :initval "nullptr" :scope :public
                     :slk-save #'slk-save-ast-pointer
                     :slk-load (slk-load-ast-pointer "Identifier")))

  (:public
    #>cpp
    LoadCsv() = default;

    bool Accept(HierarchicalTreeVisitor &visitor) override {
      if (visitor.PreVisit(*this)) {
        row_var_->Accept(visitor);
      }
      return visitor.PostVisit(*this);
    }
    cpp<#)
  (:protected
    #>cpp
    explicit LoadCsv(Expression *file, bool with_header, bool ignore_bad, Expression *delimiter,
                     Expression* quote, Identifier* row_var)
        : file_(file),
          with_header_(with_header),
          ignore_bad_(ignore_bad),
          delimiter_(delimiter),
          quote_(quote),
          row_var_(row_var) {
      DMG_ASSERT(row_var, "LoadCsv cannot take nullptr for identifier");
    }
    cpp<#)
  (:private
    #>cpp
    friend class AstStorage;
    cpp<#)
  (:serialize (:slk))
  (:clone))

(lcp:define-class free-memory-query (query) ()
  (:public
    #>cpp
    DEFVISITABLE(QueryVisitor<void>);
    cpp<#)
  (:serialize (:slk))
  (:clone))

(lcp:define-class trigger-query (query)
  ((action "Action" :scope :public)
   (event_type "EventType" :scope :public)
   (trigger_name "std::string" :scope :public)
   (before_commit "bool" :scope :public)
   (statement "std::string" :scope :public))

  (:public
    (lcp:define-enum action
        (create-trigger drop-trigger show-triggers)
      (:serialize))
    (lcp:define-enum event-type
        (any vertex_create edge_create create vertex_delete edge_delete delete vertex_update edge_update update)
      (:serialize))
    #>cpp
    TriggerQuery() = default;

    DEFVISITABLE(QueryVisitor<void>);
    cpp<#)
  (:private
    #>cpp
    friend class AstStorage;
    cpp<#)
  (:serialize (:slk))
  (:clone))

(lcp:define-class isolation-level-query (query)
  ((isolation_level "IsolationLevel" :scope :public)
   (isolation_level_scope "IsolationLevelScope" :scope :public))

  (:public
    (lcp:define-enum isolation-level
        (snapshot-isolation read-committed read-uncommitted)
      (:serialize))
    (lcp:define-enum isolation-level-scope
        (next session global)
      (:serialize))
    #>cpp
    IsolationLevelQuery() = default;

    DEFVISITABLE(QueryVisitor<void>);
    cpp<#)
  (:private
    #>cpp
    friend class AstStorage;
    cpp<#)
  (:serialize (:slk))
  (:clone))

(lcp:define-class create-snapshot-query (query) ()
  (:public
    #>cpp
    DEFVISITABLE(QueryVisitor<void>);
    cpp<#)
  (:serialize (:slk))
  (:clone))

(defun clone-variant-topic-names (source destination)
  #>cpp
    if (auto *topic_expression = std::get_if<Expression*>(&${source})) {
      if (*topic_expression == nullptr) {
        ${destination} = nullptr;
      } else {
        ${destination} = (*topic_expression)->Clone(storage);
      }
    } else {
      ${destination} = std::get<std::vector<std::string>>(${source});
    }
  cpp<#)

(lcp:define-class stream-query (query)
  ((action "Action" :scope :public)
   (type "Type" :scope :public)
   (stream_name "std::string" :scope :public)

   (batch_limit "Expression *" :initval "nullptr" :scope :public
             :slk-save #'slk-save-ast-pointer
             :slk-load (slk-load-ast-pointer "Expression"))
   (timeout "Expression *" :initval "nullptr" :scope :public
             :slk-save #'slk-save-ast-pointer
             :slk-load (slk-load-ast-pointer "Expression"))

   (transform_name "std::string" :scope :public)
   (batch_interval "Expression *" :initval "nullptr" :scope :public
             :slk-save #'slk-save-ast-pointer
             :slk-load (slk-load-ast-pointer "Expression"))
   (batch_size "Expression *" :initval "nullptr" :scope :public
             :slk-save #'slk-save-ast-pointer
             :slk-load (slk-load-ast-pointer "Expression"))

   (topic_names "std::variant<Expression*, std::vector<std::string>>" :initval "nullptr"
             :clone #'clone-variant-topic-names
             :scope :public)
   (consumer_group "std::string" :scope :public)
   (bootstrap_servers "Expression *" :initval "nullptr" :scope :public
             :slk-save #'slk-save-ast-pointer
             :slk-load (slk-load-ast-pointer "Expression"))

   (service_url "Expression *" :initval "nullptr" :scope :public
             :slk-save #'slk-save-ast-pointer
             :slk-load (slk-load-ast-pointer "Expression"))

   (configs "std::unordered_map<Expression *, Expression *>" :scope :public
             :slk-save #'slk-save-expression-map
             :slk-load #'slk-load-expression-map
             :clone #'clone-expression-map)

   (credentials "std::unordered_map<Expression *, Expression *>" :scope :public
             :slk-save #'slk-save-expression-map
             :slk-load #'slk-load-expression-map
             :clone #'clone-expression-map))

  (:public
    (lcp:define-enum action
        (create-stream drop-stream start-stream stop-stream start-all-streams stop-all-streams show-streams check-stream)
      (:serialize))
    (lcp:define-enum type
        (kafka pulsar)
      (:serialize))
    #>cpp
    StreamQuery() = default;

    DEFVISITABLE(QueryVisitor<void>);
    cpp<#)
  (:private
    #>cpp
    friend class AstStorage;
    cpp<#)
  (:serialize (:slk))
  (:clone))

(lcp:define-class setting-query (query)
  ((action "Action" :scope :public)
   (setting_name "Expression *" :initval "nullptr" :scope :public)
   (setting_value "Expression *" :initval "nullptr" :scope :public))

  (:public
    (lcp:define-enum action
        (show-setting show-all-settings set-setting)
      (:serialize))
    #>cpp
    SettingQuery() = default;

    DEFVISITABLE(QueryVisitor<void>);
    cpp<#)
  (:private
    #>cpp
    friend class AstStorage;
    cpp<#)
  (:serialize (:slk))
  (:clone))

(lcp:define-class transaction-queue-query (query)
  ((action "Action" :scope :public)
  (transaction_id_list "std::vector<Expression*>" :scope :public))

  (:public
    (lcp:define-enum action
        (show-transactions terminate-transactions)
      (:serialize))
    #>cpp
    TransactionQueueQuery() = default;

    DEFVISITABLE(QueryVisitor<void>);
    cpp<#)
  (:private
    #>cpp
    friend class AstStorage;
    cpp<#)
  (:serialize (:slk))
  (:clone))

(lcp:define-class version-query (query) ()
  (:public
    #>cpp
    DEFVISITABLE(QueryVisitor<void>);
    cpp<#)
  (:serialize (:slk))
  (:clone))

(lcp:define-class foreach (clause)
  ((named_expression "NamedExpression *" :initval "nullptr" :scope :public
          :slk-save #'slk-save-ast-pointer
          :slk-load (slk-load-ast-pointer "Expression"))
   (clauses "std::vector<Clause *>"
          :scope :public
          :slk-save #'slk-save-ast-vector
          :slk-load (slk-load-ast-vector "Clause")))
  (:public
    #>cpp
    Foreach() = default;

    bool Accept(HierarchicalTreeVisitor &visitor) override {
      if (visitor.PreVisit(*this)) {
          named_expression_->Accept(visitor);
          for (auto &clause : clauses_) {
            clause->Accept(visitor);
          }
      }
      return visitor.PostVisit(*this);
    }
    cpp<#)
  (:protected
    #>cpp
    Foreach(NamedExpression *expression, std::vector<Clause *> clauses)
        : named_expression_(expression), clauses_(clauses) {}
    cpp<#)
  (:private
    #>cpp
    friend class AstStorage;
    cpp<#)
  (:serialize (:slk))
  (:clone))

  (lcp:define-class show-config-query (query) ()
  (:public
    #>cpp
    DEFVISITABLE(QueryVisitor<void>);
    cpp<#)
  (:serialize (:slk))
  (:clone))

(lcp:define-class exists (expression)
  ((pattern "Pattern *" :initval "nullptr" :scope :public
             :slk-save #'slk-save-ast-pointer
             :slk-load (slk-load-ast-pointer "Pattern"))
   (symbol-pos :int32_t :initval -1 :scope :public
               :documentation "Symbol table position of the symbol this Aggregation is mapped to."))

  (:public
    #>cpp
    Exists() = default;

    DEFVISITABLE(ExpressionVisitor<TypedValue>);
    DEFVISITABLE(ExpressionVisitor<TypedValue*>);
    DEFVISITABLE(ExpressionVisitor<void>);
    bool Accept(HierarchicalTreeVisitor &visitor) override {
      if (visitor.PreVisit(*this)) {
        pattern_->Accept(visitor);
      }
      return visitor.PostVisit(*this);
    }
    Exists *MapTo(const Symbol &symbol) {
      symbol_pos_ = symbol.position();
      return this;
    }
    cpp<#)
  (:protected
    #>cpp
    Exists(Pattern * pattern) : pattern_(pattern) {}
    cpp<#)
  (:private
    #>cpp
    friend class AstStorage;
    cpp<#)
  (:serialize (:slk))
  (:clone))

(lcp:define-class call-subquery (clause)
  ((cypher-query "CypherQuery *" :scope :public
          :slk-save #'slk-save-ast-pointer
          :slk-load (slk-load-ast-pointer "CypherQuery")))
  (:public
    #>cpp
    CallSubquery() = default;

    bool Accept(HierarchicalTreeVisitor &visitor) override {
      if (visitor.PreVisit(*this)) {
        cypher_query_->Accept(visitor);
      }
      return visitor.PostVisit(*this);
    }

    cpp<#)
  (:private
    #>cpp
    friend class AstStorage;
    cpp<#)
  (:serialize (:slk))
  (:clone))

(lcp:pop-namespace) ;; namespace query
(lcp:pop-namespace) ;; namespace memgraph
