Merge branch 'master' into T595-notification-when-sync-replica-down

This commit is contained in:
Josip Mrden 2023-03-07 00:30:58 +01:00
commit d4194ff393
55 changed files with 2269 additions and 310 deletions

View File

@ -160,6 +160,23 @@ jobs:
name: debian-11-platform name: debian-11-platform
path: build/output/debian-11/memgraph*.deb path: build/output/debian-11/memgraph*.deb
fedora-36:
runs-on: [self-hosted, DockerMgBuild, X64]
timeout-minutes: 60
steps:
- name: "Set up repository"
uses: actions/checkout@v3
with:
fetch-depth: 0 # Required because of release/get_version.py
- name: "Build package"
run: |
./release/package/run.sh package fedora-36
- name: "Upload package"
uses: actions/upload-artifact@v3
with:
name: fedora-36
path: build/output/fedora-36/memgraph*.rpm
debian-11-arm: debian-11-arm:
runs-on: [self-hosted, DockerMgBuild, ARM64, strange] runs-on: [self-hosted, DockerMgBuild, ARM64, strange]
timeout-minutes: 60 timeout-minutes: 60
@ -177,8 +194,8 @@ jobs:
name: debian-11-arm name: debian-11-arm
path: build/output/debian-11-arm/memgraph*.deb path: build/output/debian-11-arm/memgraph*.deb
fedora-36: ubuntu-2204-arm:
runs-on: [self-hosted, DockerMgBuild, X64] runs-on: [self-hosted, DockerMgBuild, ARM64, strange]
timeout-minutes: 60 timeout-minutes: 60
steps: steps:
- name: "Set up repository" - name: "Set up repository"
@ -187,9 +204,9 @@ jobs:
fetch-depth: 0 # Required because of release/get_version.py fetch-depth: 0 # Required because of release/get_version.py
- name: "Build package" - name: "Build package"
run: | run: |
./release/package/run.sh package fedora-36 ./release/package/run.sh package ubuntu-22.04-arm
- name: "Upload package" - name: "Upload package"
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
with: with:
name: fedora-36 name: ubuntu-22.04-arm
path: build/output/fedora-36/memgraph*.rpm path: build/output/ubuntu-22.04-arm/memgraph*.deb

View File

@ -4,10 +4,6 @@
--- ---
<p align="center">
Build modern, graph-based applications on top of your streaming data in minutes.
</p>
<p align="center"> <p align="center">
<a href="https://github.com/memgraph/memgraph/blob/master/licenses/APL.txt"> <a href="https://github.com/memgraph/memgraph/blob/master/licenses/APL.txt">
<img src="https://img.shields.io/badge/license-APL-green" alt="license" title="license"/> <img src="https://img.shields.io/badge/license-APL-green" alt="license" title="license"/>
@ -89,6 +85,7 @@ your browser.
### macOS ### macOS
[![macOS](https://img.shields.io/badge/macOS-Docker-000000?style=for-the-badge&logo=macos&logoColor=F0F0F0)](https://memgraph.com/docs/memgraph/install-memgraph-on-macos-docker) [![macOS](https://img.shields.io/badge/macOS-Docker-000000?style=for-the-badge&logo=macos&logoColor=F0F0F0)](https://memgraph.com/docs/memgraph/install-memgraph-on-macos-docker)
[![macOS](https://img.shields.io/badge/lima-AACF41?style=for-the-badge&logo=macos&logoColor=F0F0F0)](https://memgraph.com/docs/memgraph/install-memgraph-on-ubuntu)
### Linux ### Linux

View File

@ -0,0 +1,96 @@
#!/bin/bash
set -Eeuo pipefail
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
source "$DIR/../util.sh"
check_operating_system "ubuntu-22.04"
check_architecture "arm64" "aarch64"
TOOLCHAIN_BUILD_DEPS=(
coreutils gcc g++ build-essential make # generic build tools
wget # used for archive download
gnupg # used for archive signature verification
tar gzip bzip2 xz-utils unzip # used for archive unpacking
zlib1g-dev # zlib library used for all builds
libexpat1-dev libbabeltrace-dev liblzma-dev python3-dev texinfo # for gdb
libcurl4-openssl-dev # for cmake
libreadline-dev # for cmake and llvm
libffi-dev libxml2-dev # for llvm
curl # snappy
file
git # for thrift
libgmp-dev # for gdb
gperf # for proxygen
libssl-dev
libedit-dev libpcre3-dev automake bison # for swig
)
TOOLCHAIN_RUN_DEPS=(
make # generic build tools
tar gzip bzip2 xz-utils # used for archive unpacking
zlib1g # zlib library used for all builds
libexpat1 libbabeltrace1 liblzma5 python3 # for gdb
libcurl4 # for cmake
libreadline8 # for cmake and llvm
libffi7 libxml2 # for llvm
libssl-dev # for libevent
)
MEMGRAPH_BUILD_DEPS=(
git # source code control
make pkg-config # build system
curl wget # for downloading libs
uuid-dev default-jre-headless # required by antlr
libreadline-dev # for memgraph console
libpython3-dev python3-dev # for query modules
libssl-dev
libseccomp-dev
netcat # tests are using nc to wait for memgraph
python3 python3-virtualenv python3-pip # for qa, macro_benchmark and stress tests
python3-yaml # for the configuration generator
libcurl4-openssl-dev # mg-requests
sbcl # for custom Lisp C++ preprocessing
doxygen graphviz # source documentation generators
mono-runtime mono-mcs zip unzip default-jdk-headless # for driver tests
dotnet-sdk-6.0 golang nodejs npm
autoconf # for jemalloc code generation
libtool # for protobuf code generation
)
list() {
echo "$1"
}
check() {
check_all_dpkg "$1"
}
install() {
cd "$DIR"
apt update
# If GitHub Actions runner is installed, append LANG to the environment.
# Python related tests doesn't work the LANG export.
if [ -d "/home/gh/actions-runner" ]; then
echo "LANG=en_US.utf8" >> /home/gh/actions-runner/.env
else
echo "NOTE: export LANG=en_US.utf8"
fi
apt install -y wget
for pkg in $1; do
if [ "$pkg" == dotnet-sdk-6.0 ]; then
if ! dpkg -s dotnet-sdk-6.0 2>/dev/null >/dev/null; then
wget -nv https://packages.microsoft.com/config/ubuntu/22.04/packages-microsoft-prod.deb -O packages-microsoft-prod.deb
dpkg -i packages-microsoft-prod.deb
apt-get update
apt-get install -y apt-transport-https dotnet-sdk-6.0
fi
continue
fi
apt install -y "$pkg"
done
}
deps=$2"[*]"
"$1" "${!deps}"

View File

@ -51,11 +51,19 @@ CPPCHECK_VERSION=2.6
LLVM_VERSION=13.0.0 LLVM_VERSION=13.0.0
SWIG_VERSION=4.0.2 # used only for LLVM compilation SWIG_VERSION=4.0.2 # used only for LLVM compilation
# Check for the dependencies. # Set the right env script
echo "ALL BUILD PACKAGES: $($DIR/../os/$DISTRO.sh list TOOLCHAIN_BUILD_DEPS)" ENV_SCRIPT="$DIR/../os/$DISTRO.sh"
$DIR/../os/$DISTRO.sh check TOOLCHAIN_BUILD_DEPS if [[ "$for_arm" = true ]]; then
echo "ALL RUN PACKAGES: $($DIR/../os/$DISTRO.sh list TOOLCHAIN_RUN_DEPS)" ENV_SCRIPT="$DIR/../os/$DISTRO-arm.sh"
$DIR/../os/$DISTRO.sh check TOOLCHAIN_RUN_DEPS fi
# Check for the toolchain build dependencies.
echo "ALL BUILD PACKAGES: $(${ENV_SCRIPT} list TOOLCHAIN_BUILD_DEPS)"
${ENV_SCRIPT} check TOOLCHAIN_BUILD_DEPS
# Check for the toolchain run dependencies.
echo "ALL RUN PACKAGES: $(${ENV_SCRIPT} list TOOLCHAIN_RUN_DEPS)"
${ENV_SCRIPT} check TOOLCHAIN_RUN_DEPS
# check installation directory # check installation directory
NAME=toolchain-v$TOOLCHAIN_VERSION NAME=toolchain-v$TOOLCHAIN_VERSION
@ -622,7 +630,7 @@ In order to be able to run all of these tools you should install the following
packages: packages:
\`\`\` \`\`\`
$($DIR/../os/$DISTRO.sh list TOOLCHAIN_RUN_DEPS) $($DIR/../os/$ENV_SCRIPT.sh list TOOLCHAIN_RUN_DEPS)
\`\`\` \`\`\`
## Usage ## Usage

View File

@ -19,13 +19,16 @@ function architecture() {
} }
check_architecture() { check_architecture() {
local ARCH=$(architecture)
for arch in "$@"; do for arch in "$@"; do
if [ "$(architecture)" = "$arch" ]; then if [ "${ARCH}" = "$arch" ]; then
echo "The right architecture!" echo "The right architecture!"
return 0 return 0
fi fi
done done
echo "Not the right architecture!" echo "Not the right architecture!"
echo "Expected: $@"
echo "Actual: ${ARCH}"
exit 1 exit 1
} }

View File

@ -0,0 +1,11 @@
version: "3"
services:
debian-11-arm:
build:
context: debian-11-arm
container_name: "mgbuild_debian-11-arm"
ubuntu-2204-arm:
build:
context: ubuntu-22.04-arm
container_name: "mgbuild_ubuntu-22.04-arm"

View File

@ -3,7 +3,7 @@
set -Eeuo pipefail set -Eeuo pipefail
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
SUPPORTED_OS=(centos-7 centos-9 debian-10 debian-11 ubuntu-18.04 ubuntu-20.04 ubuntu-22.04 debian-11-arm fedora-36) SUPPORTED_OS=(centos-7 centos-9 debian-10 debian-11 ubuntu-18.04 ubuntu-20.04 ubuntu-22.04 debian-11-arm fedora-36 ubuntu-22.04-arm)
PROJECT_ROOT="$SCRIPT_DIR/../.." PROJECT_ROOT="$SCRIPT_DIR/../.."
TOOLCHAIN_VERSION="toolchain-v4" TOOLCHAIN_VERSION="toolchain-v4"
ACTIVATE_TOOLCHAIN="source /opt/${TOOLCHAIN_VERSION}/activate" ACTIVATE_TOOLCHAIN="source /opt/${TOOLCHAIN_VERSION}/activate"
@ -76,7 +76,7 @@ make_package () {
docker exec "$build_container" bash -c "cd /memgraph && git config --global --add safe.directory '*'" docker exec "$build_container" bash -c "cd /memgraph && git config --global --add safe.directory '*'"
docker exec "$build_container" bash -c "cd /memgraph && $ACTIVATE_TOOLCHAIN && ./init" docker exec "$build_container" bash -c "cd /memgraph && $ACTIVATE_TOOLCHAIN && ./init"
docker exec "$build_container" bash -c "cd $container_build_dir && rm -rf ./*" docker exec "$build_container" bash -c "cd $container_build_dir && rm -rf ./*"
if [[ "$os" == "debian-11-arm" ]]; then if [[ "$os" =~ "-arm" ]]; then
docker exec "$build_container" bash -c "cd $container_build_dir && $ACTIVATE_TOOLCHAIN && cmake -DCMAKE_BUILD_TYPE=release -DMG_ARCH="ARM64" $telemetry_id_override_flag .." docker exec "$build_container" bash -c "cd $container_build_dir && $ACTIVATE_TOOLCHAIN && cmake -DCMAKE_BUILD_TYPE=release -DMG_ARCH="ARM64" $telemetry_id_override_flag .."
else else
docker exec "$build_container" bash -c "cd $container_build_dir && $ACTIVATE_TOOLCHAIN && cmake -DCMAKE_BUILD_TYPE=release $telemetry_id_override_flag .." docker exec "$build_container" bash -c "cd $container_build_dir && $ACTIVATE_TOOLCHAIN && cmake -DCMAKE_BUILD_TYPE=release $telemetry_id_override_flag .."

View File

@ -0,0 +1,17 @@
FROM ubuntu:22.04
ARG TOOLCHAIN_VERSION
# Stops tzdata interactive configuration.
ENV DEBIAN_FRONTEND=noninteractive
RUN apt update && apt install -y \
ca-certificates wget git
# Do NOT be smart here and clean the cache because the container is used in the
# stateful context.
RUN wget -q https://s3-eu-west-1.amazonaws.com/deps.memgraph.io/${TOOLCHAIN_VERSION}/${TOOLCHAIN_VERSION}-binaries-ubuntu-22.04-arm64.tar.gz \
-O ${TOOLCHAIN_VERSION}-binaries-ubuntu-22.04-arm64.tar.gz \
&& tar xzvf ${TOOLCHAIN_VERSION}-binaries-ubuntu-22.04-arm64.tar.gz -C /opt
ENTRYPOINT ["sleep", "infinity"]

View File

@ -1,4 +1,4 @@
// Copyright 2022 Memgraph Ltd. // Copyright 2023 Memgraph Ltd.
// //
// Use of this software is governed by the Business Source License // 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 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@ -107,5 +107,37 @@ storage::PropertyValue PropsSetChecked(T *record, const storage::PropertyId &key
} }
} }
template <typename T>
concept AccessorWithInitProperties = requires(T accessor,
const std::map<storage::PropertyId, storage::PropertyValue> &properties) {
{ accessor.InitProperties(properties) } -> std::same_as<storage::Result<bool>>;
};
/// Set property `values` mapped with given `key` on a `record`.
///
/// @throw QueryRuntimeException if value cannot be set as a property value
template <AccessorWithInitProperties T>
bool MultiPropsInitChecked(T *record, std::map<storage::PropertyId, storage::PropertyValue> &properties) {
try {
auto maybe_values = record->InitProperties(properties);
if (maybe_values.HasError()) {
switch (maybe_values.GetError()) {
case storage::Error::SERIALIZATION_ERROR:
throw TransactionSerializationException();
case storage::Error::DELETED_OBJECT:
throw QueryRuntimeException("Trying to set properties on a deleted object.");
case storage::Error::PROPERTIES_DISABLED:
throw QueryRuntimeException("Can't set property because properties on edges are disabled.");
case storage::Error::VERTEX_HAS_EDGES:
case storage::Error::NONEXISTENT_OBJECT:
throw QueryRuntimeException("Unexpected error when setting a property.");
}
}
return std::move(*maybe_values);
} catch (const TypedValueException &) {
throw QueryRuntimeException("Cannot set properties.");
}
}
int64_t QueryTimestamp(); int64_t QueryTimestamp();
} // namespace memgraph::query } // namespace memgraph::query

View File

@ -1,4 +1,4 @@
// Copyright 2022 Memgraph Ltd. // Copyright 2023 Memgraph Ltd.
// //
// Use of this software is governed by the Business Source License // 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 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@ -71,6 +71,10 @@ class EdgeAccessor final {
return impl_.SetProperty(key, value); return impl_.SetProperty(key, value);
} }
storage::Result<bool> InitProperties(const std::map<storage::PropertyId, storage::PropertyValue> &properties) {
return impl_.InitProperties(properties);
}
storage::Result<storage::PropertyValue> RemoveProperty(storage::PropertyId key) { storage::Result<storage::PropertyValue> RemoveProperty(storage::PropertyId key) {
return SetProperty(key, storage::PropertyValue()); return SetProperty(key, storage::PropertyValue());
} }
@ -125,6 +129,10 @@ class VertexAccessor final {
return impl_.SetProperty(key, value); return impl_.SetProperty(key, value);
} }
storage::Result<bool> InitProperties(const std::map<storage::PropertyId, storage::PropertyValue> &properties) {
return impl_.InitProperties(properties);
}
storage::Result<storage::PropertyValue> RemoveProperty(storage::PropertyId key) { storage::Result<storage::PropertyValue> RemoveProperty(storage::PropertyId key) {
return SetProperty(key, storage::PropertyValue()); return SetProperty(key, storage::PropertyValue());
} }

View File

@ -2711,5 +2711,41 @@ cpp<#
(:serialize (:slk)) (:serialize (:slk))
(:clone)) (: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:pop-namespace) ;; namespace query (lcp:pop-namespace) ;; namespace query
(lcp:pop-namespace) ;; namespace memgraph (lcp:pop-namespace) ;; namespace memgraph

View File

@ -1,4 +1,4 @@
// Copyright 2022 Memgraph Ltd. // Copyright 2023 Memgraph Ltd.
// //
// Use of this software is governed by the Business Source License // 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 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@ -95,6 +95,7 @@ class SettingQuery;
class VersionQuery; class VersionQuery;
class Foreach; class Foreach;
class ShowConfigQuery; class ShowConfigQuery;
class Exists;
using TreeCompositeVisitor = utils::CompositeVisitor< using TreeCompositeVisitor = utils::CompositeVisitor<
SingleQuery, CypherUnion, NamedExpression, OrOperator, XorOperator, AndOperator, NotOperator, AdditionOperator, SingleQuery, CypherUnion, NamedExpression, OrOperator, XorOperator, AndOperator, NotOperator, AdditionOperator,
@ -103,7 +104,7 @@ using TreeCompositeVisitor = utils::CompositeVisitor<
ListSlicingOperator, IfOperator, UnaryPlusOperator, UnaryMinusOperator, IsNullOperator, ListLiteral, MapLiteral, ListSlicingOperator, IfOperator, UnaryPlusOperator, UnaryMinusOperator, IsNullOperator, ListLiteral, MapLiteral,
PropertyLookup, LabelsTest, Aggregation, Function, Reduce, Coalesce, Extract, All, Single, Any, None, CallProcedure, PropertyLookup, LabelsTest, Aggregation, Function, Reduce, Coalesce, Extract, All, Single, Any, None, CallProcedure,
Create, Match, Return, With, Pattern, NodeAtom, EdgeAtom, Delete, Where, SetProperty, SetProperties, SetLabels, Create, Match, Return, With, Pattern, NodeAtom, EdgeAtom, Delete, Where, SetProperty, SetProperties, SetLabels,
RemoveProperty, RemoveLabels, Merge, Unwind, RegexMatch, LoadCsv, Foreach>; RemoveProperty, RemoveLabels, Merge, Unwind, RegexMatch, LoadCsv, Foreach, Exists>;
using TreeLeafVisitor = utils::LeafVisitor<Identifier, PrimitiveLiteral, ParameterLookup>; using TreeLeafVisitor = utils::LeafVisitor<Identifier, PrimitiveLiteral, ParameterLookup>;
@ -123,7 +124,7 @@ class ExpressionVisitor
LessOperator, GreaterOperator, LessEqualOperator, GreaterEqualOperator, InListOperator, SubscriptOperator, LessOperator, GreaterOperator, LessEqualOperator, GreaterEqualOperator, InListOperator, SubscriptOperator,
ListSlicingOperator, IfOperator, UnaryPlusOperator, UnaryMinusOperator, IsNullOperator, ListLiteral, ListSlicingOperator, IfOperator, UnaryPlusOperator, UnaryMinusOperator, IsNullOperator, ListLiteral,
MapLiteral, PropertyLookup, LabelsTest, Aggregation, Function, Reduce, Coalesce, Extract, All, Single, Any, MapLiteral, PropertyLookup, LabelsTest, Aggregation, Function, Reduce, Coalesce, Extract, All, Single, Any,
None, ParameterLookup, Identifier, PrimitiveLiteral, RegexMatch> {}; None, ParameterLookup, Identifier, PrimitiveLiteral, RegexMatch, Exists> {};
template <class TResult> template <class TResult>
class QueryVisitor : public utils::Visitor<TResult, CypherQuery, ExplainQuery, ProfileQuery, IndexQuery, AuthQuery, class QueryVisitor : public utils::Visitor<TResult, CypherQuery, ExplainQuery, ProfileQuery, IndexQuery, AuthQuery,

View File

@ -1,4 +1,4 @@
// Copyright 2022 Memgraph Ltd. // Copyright 2023 Memgraph Ltd.
// //
// Use of this software is governed by the Business Source License // 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 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@ -2160,7 +2160,10 @@ antlrcpp::Any CypherMainVisitor::visitAtom(MemgraphCypher::AtomContext *ctx) {
auto *list = std::any_cast<Expression *>(ctx->extractExpression()->idInColl()->expression()->accept(this)); auto *list = std::any_cast<Expression *>(ctx->extractExpression()->idInColl()->expression()->accept(this));
auto *expr = std::any_cast<Expression *>(ctx->extractExpression()->expression()->accept(this)); auto *expr = std::any_cast<Expression *>(ctx->extractExpression()->expression()->accept(this));
return static_cast<Expression *>(storage_->Create<Extract>(ident, list, expr)); return static_cast<Expression *>(storage_->Create<Extract>(ident, list, expr));
} else if (ctx->existsExpression()) {
return std::any_cast<Expression *>(ctx->existsExpression()->accept(this));
} }
// TODO: Implement this. We don't support comprehensions, filtering... at // TODO: Implement this. We don't support comprehensions, filtering... at
// the moment. // the moment.
throw utils::NotYetImplemented("atom expression '{}'", ctx->getText()); throw utils::NotYetImplemented("atom expression '{}'", ctx->getText());
@ -2204,6 +2207,17 @@ antlrcpp::Any CypherMainVisitor::visitLiteral(MemgraphCypher::LiteralContext *ct
return visitChildren(ctx); return visitChildren(ctx);
} }
antlrcpp::Any CypherMainVisitor::visitExistsExpression(MemgraphCypher::ExistsExpressionContext *ctx) {
auto *exists = storage_->Create<Exists>();
exists->pattern_ = std::any_cast<Pattern *>(ctx->patternPart()->accept(this));
if (exists->pattern_->identifier_) {
throw SyntaxException("Identifiers are not supported in exists(...).");
}
return static_cast<Expression *>(exists);
}
antlrcpp::Any CypherMainVisitor::visitParenthesizedExpression(MemgraphCypher::ParenthesizedExpressionContext *ctx) { antlrcpp::Any CypherMainVisitor::visitParenthesizedExpression(MemgraphCypher::ParenthesizedExpressionContext *ctx) {
return std::any_cast<Expression *>(ctx->expression()->accept(this)); return std::any_cast<Expression *>(ctx->expression()->accept(this));
} }

View File

@ -1,4 +1,4 @@
// Copyright 2022 Memgraph Ltd. // Copyright 2023 Memgraph Ltd.
// //
// Use of this software is governed by the Business Source License // 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 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@ -763,6 +763,11 @@ class CypherMainVisitor : public antlropencypher::MemgraphCypherBaseVisitor {
*/ */
antlrcpp::Any visitParameter(MemgraphCypher::ParameterContext *ctx) override; antlrcpp::Any visitParameter(MemgraphCypher::ParameterContext *ctx) override;
/**
* @return Exists* (Expression)
*/
antlrcpp::Any visitExistsExpression(MemgraphCypher::ExistsExpressionContext *ctx) override;
/** /**
* @return Expression* * @return Expression*
*/ */

View File

@ -1,4 +1,4 @@
// Copyright 2022 Memgraph Ltd. // Copyright 2023 Memgraph Ltd.
// //
// Use of this software is governed by the Business Source License // 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 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@ -60,6 +60,7 @@ class ExpressionPrettyPrinter : public ExpressionVisitor<void> {
void Visit(Reduce &op) override; void Visit(Reduce &op) override;
void Visit(Coalesce &op) override; void Visit(Coalesce &op) override;
void Visit(Extract &op) override; void Visit(Extract &op) override;
void Visit(Exists &op) override;
void Visit(All &op) override; void Visit(All &op) override;
void Visit(Single &op) override; void Visit(Single &op) override;
void Visit(Any &op) override; void Visit(Any &op) override;
@ -264,6 +265,8 @@ void ExpressionPrettyPrinter::Visit(Extract &op) {
PrintOperator(out_, "Extract", op.identifier_, op.list_, op.expression_); PrintOperator(out_, "Extract", op.identifier_, op.list_, op.expression_);
} }
void ExpressionPrettyPrinter::Visit(Exists & /*op*/) { PrintOperator(out_, "Exists", "expression"); }
void ExpressionPrettyPrinter::Visit(All &op) { void ExpressionPrettyPrinter::Visit(All &op) {
PrintOperator(out_, "All", op.identifier_, op.list_expression_, op.where_->expression_); PrintOperator(out_, "All", op.identifier_, op.list_expression_, op.where_->expression_);
} }

View File

@ -236,6 +236,7 @@ atom : literal
| ( ANY '(' filterExpression ')' ) | ( ANY '(' filterExpression ')' )
| ( NONE '(' filterExpression ')' ) | ( NONE '(' filterExpression ')' )
| ( SINGLE '(' filterExpression ')' ) | ( SINGLE '(' filterExpression ')' )
| ( EXISTS '(' existsExpression ')' )
| relationshipsPattern | relationshipsPattern
| parenthesizedExpression | parenthesizedExpression
| functionInvocation | functionInvocation
@ -275,6 +276,8 @@ reduceExpression : accumulator=variable '=' initial=expression ',' idInColl '|'
extractExpression : idInColl '|' expression ; extractExpression : idInColl '|' expression ;
existsExpression : patternPart ;
idInColl : variable IN expression ; idInColl : variable IN expression ;
functionInvocation : functionName '(' ( DISTINCT )? ( expression ( ',' expression )* )? ')' ; functionInvocation : functionName '(' ( DISTINCT )? ( expression ( ',' expression )* )? ')' ;

View File

@ -64,6 +64,11 @@ auto SymbolGenerator::CreateSymbol(const std::string &name, bool user_declared,
return symbol; return symbol;
} }
auto SymbolGenerator::CreateAnonymousSymbol(Symbol::Type /*type*/) {
auto symbol = symbol_table_->CreateAnonymousSymbol();
return symbol;
}
auto SymbolGenerator::GetOrCreateSymbol(const std::string &name, bool user_declared, Symbol::Type type) { auto SymbolGenerator::GetOrCreateSymbol(const std::string &name, bool user_declared, Symbol::Type type) {
// NOLINTNEXTLINE // NOLINTNEXTLINE
for (auto scope = scopes_.rbegin(); scope != scopes_.rend(); ++scope) { for (auto scope = scopes_.rbegin(); scope != scopes_.rend(); ++scope) {
@ -302,6 +307,14 @@ SymbolGenerator::ReturnType SymbolGenerator::Visit(Identifier &ident) {
if (scope.in_skip || scope.in_limit) { if (scope.in_skip || scope.in_limit) {
throw SemanticException("Variables are not allowed in {}.", scope.in_skip ? "SKIP" : "LIMIT"); throw SemanticException("Variables are not allowed in {}.", scope.in_skip ? "SKIP" : "LIMIT");
} }
if (scope.in_exists && (scope.visiting_edge || scope.in_node_atom)) {
auto has_symbol = HasSymbol(ident.name_);
if (!has_symbol && !ConsumePredefinedIdentifier(ident.name_) && ident.user_declared_) {
throw SemanticException("Unbounded variables are not allowed in exists!");
}
}
Symbol symbol; Symbol symbol;
if (scope.in_pattern && !(scope.in_node_atom || scope.visiting_edge)) { if (scope.in_pattern && !(scope.in_node_atom || scope.visiting_edge)) {
// If we are in the pattern, and outside of a node or an edge, the // If we are in the pattern, and outside of a node or an edge, the
@ -328,7 +341,8 @@ SymbolGenerator::ReturnType SymbolGenerator::Visit(Identifier &ident) {
} }
symbol = GetOrCreateSymbol(ident.name_, ident.user_declared_, type); symbol = GetOrCreateSymbol(ident.name_, ident.user_declared_, type);
} else if (scope.in_pattern && !scope.in_pattern_atom_identifier && scope.in_match) { } else if (scope.in_pattern && !scope.in_pattern_atom_identifier && scope.in_match) {
if (scope.in_edge_range && scope.visiting_edge->identifier_->name_ == ident.name_) { if (scope.in_edge_range && scope.visiting_edge && scope.visiting_edge->identifier_ &&
scope.visiting_edge->identifier_->name_ == ident.name_) {
// Prevent variable path bounds to reference the identifier which is bound // Prevent variable path bounds to reference the identifier which is bound
// by the variable path itself. // by the variable path itself.
throw UnboundVariableError(ident.name_); throw UnboundVariableError(ident.name_);
@ -430,6 +444,46 @@ bool SymbolGenerator::PreVisit(Extract &extract) {
return false; return false;
} }
bool SymbolGenerator::PreVisit(Exists &exists) {
auto &scope = scopes_.back();
if (scope.in_set_property) {
throw utils::NotYetImplemented("Set property can not be used with exists, but only during matching!");
}
if (scope.in_with) {
throw utils::NotYetImplemented("WITH can not be used with exists, but only during matching!");
}
scope.in_exists = true;
const auto &symbol = CreateAnonymousSymbol();
exists.MapTo(symbol);
return true;
}
bool SymbolGenerator::PostVisit(Exists & /*exists*/) {
auto &scope = scopes_.back();
scope.in_exists = false;
return true;
}
bool SymbolGenerator::PreVisit(SetProperty & /*set_property*/) {
auto &scope = scopes_.back();
scope.in_set_property = true;
return true;
}
bool SymbolGenerator::PostVisit(SetProperty & /*set_property*/) {
auto &scope = scopes_.back();
scope.in_set_property = false;
return true;
}
// Pattern and its subparts. // Pattern and its subparts.
bool SymbolGenerator::PreVisit(Pattern &pattern) { bool SymbolGenerator::PreVisit(Pattern &pattern) {
@ -439,6 +493,7 @@ bool SymbolGenerator::PreVisit(Pattern &pattern) {
MG_ASSERT(utils::IsSubtype(*pattern.atoms_[0], NodeAtom::kType), "Expected a single NodeAtom in Pattern"); MG_ASSERT(utils::IsSubtype(*pattern.atoms_[0], NodeAtom::kType), "Expected a single NodeAtom in Pattern");
scope.in_create_node = true; scope.in_create_node = true;
} }
return true; return true;
} }

View File

@ -64,6 +64,8 @@ class SymbolGenerator : public HierarchicalTreeVisitor {
bool PostVisit(Match &) override; bool PostVisit(Match &) override;
bool PreVisit(Foreach &) override; bool PreVisit(Foreach &) override;
bool PostVisit(Foreach &) override; bool PostVisit(Foreach &) override;
bool PreVisit(SetProperty & /*set_property*/) override;
bool PostVisit(SetProperty & /*set_property*/) override;
// Expressions // Expressions
ReturnType Visit(Identifier &) override; ReturnType Visit(Identifier &) override;
@ -79,6 +81,8 @@ class SymbolGenerator : public HierarchicalTreeVisitor {
bool PreVisit(None &) override; bool PreVisit(None &) override;
bool PreVisit(Reduce &) override; bool PreVisit(Reduce &) override;
bool PreVisit(Extract &) override; bool PreVisit(Extract &) override;
bool PreVisit(Exists & /*exists*/) override;
bool PostVisit(Exists & /*exists*/) override;
// Pattern and its subparts. // Pattern and its subparts.
bool PreVisit(Pattern &) override; bool PreVisit(Pattern &) override;
@ -113,6 +117,8 @@ class SymbolGenerator : public HierarchicalTreeVisitor {
bool in_where{false}; bool in_where{false};
bool in_match{false}; bool in_match{false};
bool in_foreach{false}; bool in_foreach{false};
bool in_exists{false};
bool in_set_property{false};
// True when visiting a pattern atom (node or edge) identifier, which can be // True when visiting a pattern atom (node or edge) identifier, which can be
// reused or created in the pattern itself. // reused or created in the pattern itself.
bool in_pattern_atom_identifier{false}; bool in_pattern_atom_identifier{false};
@ -143,6 +149,9 @@ class SymbolGenerator : public HierarchicalTreeVisitor {
auto CreateSymbol(const std::string &name, bool user_declared, Symbol::Type type = Symbol::Type::ANY, auto CreateSymbol(const std::string &name, bool user_declared, Symbol::Type type = Symbol::Type::ANY,
int token_position = -1); int token_position = -1);
// Returns a freshly generated anonymous symbol.
auto CreateAnonymousSymbol(Symbol::Type type = Symbol::Type::ANY);
auto GetOrCreateSymbol(const std::string &name, bool user_declared, Symbol::Type type = Symbol::Type::ANY); auto GetOrCreateSymbol(const std::string &name, bool user_declared, Symbol::Type type = Symbol::Type::ANY);
// Returns the symbol by name. If the mapping already exists, checks if the // Returns the symbol by name. If the mapping already exists, checks if the
// types match. Otherwise, returns a new symbol. // types match. Otherwise, returns a new symbol.

View File

@ -1,4 +1,4 @@
// Copyright 2022 Memgraph Ltd. // Copyright 2023 Memgraph Ltd.
// //
// Use of this software is governed by the Business Source License // 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 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@ -51,6 +51,7 @@ class SymbolTable final {
const Symbol &at(const Identifier &ident) const { return table_.at(ident.symbol_pos_); } const Symbol &at(const Identifier &ident) const { return table_.at(ident.symbol_pos_); }
const Symbol &at(const NamedExpression &nexpr) const { return table_.at(nexpr.symbol_pos_); } const Symbol &at(const NamedExpression &nexpr) const { return table_.at(nexpr.symbol_pos_); }
const Symbol &at(const Aggregation &aggr) const { return table_.at(aggr.symbol_pos_); } const Symbol &at(const Aggregation &aggr) const { return table_.at(aggr.symbol_pos_); }
const Symbol &at(const Exists &exists) const { return table_.at(exists.symbol_pos_); }
// TODO: Remove these since members are public // TODO: Remove these since members are public
int32_t max_position() const { return static_cast<int32_t>(table_.size()); } int32_t max_position() const { return static_cast<int32_t>(table_.size()); }

View File

@ -89,6 +89,7 @@ class ReferenceExpressionEvaluator : public ExpressionVisitor<TypedValue *> {
UNSUCCESSFUL_VISIT(None); UNSUCCESSFUL_VISIT(None);
UNSUCCESSFUL_VISIT(ParameterLookup); UNSUCCESSFUL_VISIT(ParameterLookup);
UNSUCCESSFUL_VISIT(RegexMatch); UNSUCCESSFUL_VISIT(RegexMatch);
UNSUCCESSFUL_VISIT(Exists);
private: private:
Frame *frame_; Frame *frame_;
@ -619,6 +620,8 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
return TypedValue(result, ctx_->memory); return TypedValue(result, ctx_->memory);
} }
TypedValue Visit(Exists &exists) override { return TypedValue{frame_->at(symbol_table_->at(exists)), ctx_->memory}; }
TypedValue Visit(All &all) override { TypedValue Visit(All &all) override {
auto list_value = all.list_expression_->Accept(*this); auto list_value = all.list_expression_->Accept(*this);
if (list_value.IsNull()) { if (list_value.IsNull()) {

View File

@ -25,6 +25,7 @@
#include <cppitertools/chain.hpp> #include <cppitertools/chain.hpp>
#include <cppitertools/imap.hpp> #include <cppitertools/imap.hpp>
#include "query/common.hpp"
#include "spdlog/spdlog.h" #include "spdlog/spdlog.h"
#include "license/license.hpp" #include "license/license.hpp"
@ -115,6 +116,7 @@ extern const Event CartesianOperator;
extern const Event CallProcedureOperator; extern const Event CallProcedureOperator;
extern const Event ForeachOperator; extern const Event ForeachOperator;
extern const Event EmptyResultOperator; extern const Event EmptyResultOperator;
extern const Event EvaluatePatternFilterOperator;
} // namespace EventCounter } // namespace EventCounter
namespace memgraph::query::plan { namespace memgraph::query::plan {
@ -208,17 +210,18 @@ VertexAccessor &CreateLocalVertex(const NodeCreationInfo &node_info, Frame *fram
storage::View::NEW); storage::View::NEW);
// TODO: PropsSetChecked allocates a PropertyValue, make it use context.memory // TODO: PropsSetChecked allocates a PropertyValue, make it use context.memory
// when we update PropertyValue with custom allocator. // when we update PropertyValue with custom allocator.
std::map<storage::PropertyId, storage::PropertyValue> properties;
if (const auto *node_info_properties = std::get_if<PropertiesMapList>(&node_info.properties)) { if (const auto *node_info_properties = std::get_if<PropertiesMapList>(&node_info.properties)) {
for (const auto &[key, value_expression] : *node_info_properties) { for (const auto &[key, value_expression] : *node_info_properties) {
PropsSetChecked(&new_node, key, value_expression->Accept(evaluator)); properties.emplace(key, value_expression->Accept(evaluator));
} }
} else { } else {
auto property_map = evaluator.Visit(*std::get<ParameterLookup *>(node_info.properties)); auto property_map = evaluator.Visit(*std::get<ParameterLookup *>(node_info.properties));
for (const auto &[key, value] : property_map.ValueMap()) { for (const auto &[key, value] : property_map.ValueMap()) {
auto property_id = dba.NameToProperty(key); properties.emplace(dba.NameToProperty(key), value);
PropsSetChecked(&new_node, property_id, value);
} }
} }
MultiPropsInitChecked(&new_node, properties);
(*frame)[node_info.symbol] = new_node; (*frame)[node_info.symbol] = new_node;
return (*frame)[node_info.symbol].ValueVertex(); return (*frame)[node_info.symbol].ValueVertex();
@ -299,17 +302,18 @@ EdgeAccessor CreateEdge(const EdgeCreationInfo &edge_info, DbAccessor *dba, Vert
auto maybe_edge = dba->InsertEdge(from, to, edge_info.edge_type); auto maybe_edge = dba->InsertEdge(from, to, edge_info.edge_type);
if (maybe_edge.HasValue()) { if (maybe_edge.HasValue()) {
auto &edge = *maybe_edge; auto &edge = *maybe_edge;
if (const auto *properties = std::get_if<PropertiesMapList>(&edge_info.properties)) { std::map<storage::PropertyId, storage::PropertyValue> properties;
for (const auto &[key, value_expression] : *properties) { if (const auto *edge_info_properties = std::get_if<PropertiesMapList>(&edge_info.properties)) {
PropsSetChecked(&edge, key, value_expression->Accept(*evaluator)); for (const auto &[key, value_expression] : *edge_info_properties) {
properties.emplace(key, value_expression->Accept(*evaluator));
} }
} else { } else {
auto property_map = evaluator->Visit(*std::get<ParameterLookup *>(edge_info.properties)); auto property_map = evaluator->Visit(*std::get<ParameterLookup *>(edge_info.properties));
for (const auto &[key, value] : property_map.ValueMap()) { for (const auto &[key, value] : property_map.ValueMap()) {
auto property_id = dba->NameToProperty(key); properties.emplace(dba->NameToProperty(key), value);
PropsSetChecked(&edge, property_id, value);
} }
} }
if (!properties.empty()) MultiPropsInitChecked(&edge, properties);
(*frame)[edge_info.symbol] = edge; (*frame)[edge_info.symbol] = edge;
} else { } else {
@ -2254,10 +2258,19 @@ std::vector<Symbol> ConstructNamedPath::ModifiedSymbols(const SymbolTable &table
return symbols; return symbols;
} }
Filter::Filter(const std::shared_ptr<LogicalOperator> &input, Expression *expression) Filter::Filter(const std::shared_ptr<LogicalOperator> &input,
: input_(input ? input : std::make_shared<Once>()), expression_(expression) {} const std::vector<std::shared_ptr<LogicalOperator>> &pattern_filters, Expression *expression)
: input_(input ? input : std::make_shared<Once>()), pattern_filters_(pattern_filters), expression_(expression) {}
ACCEPT_WITH_INPUT(Filter) bool Filter::Accept(HierarchicalLogicalOperatorVisitor &visitor) {
if (visitor.PreVisit(*this)) {
input_->Accept(visitor);
for (const auto &pattern_filter : pattern_filters_) {
pattern_filter->Accept(visitor);
}
}
return visitor.PostVisit(*this);
}
UniqueCursorPtr Filter::MakeCursor(utils::MemoryResource *mem) const { UniqueCursorPtr Filter::MakeCursor(utils::MemoryResource *mem) const {
EventCounter::IncrementCounter(EventCounter::FilterOperator); EventCounter::IncrementCounter(EventCounter::FilterOperator);
@ -2267,8 +2280,24 @@ UniqueCursorPtr Filter::MakeCursor(utils::MemoryResource *mem) const {
std::vector<Symbol> Filter::ModifiedSymbols(const SymbolTable &table) const { return input_->ModifiedSymbols(table); } std::vector<Symbol> Filter::ModifiedSymbols(const SymbolTable &table) const { return input_->ModifiedSymbols(table); }
static std::vector<UniqueCursorPtr> MakeCursorVector(const std::vector<std::shared_ptr<LogicalOperator>> &ops,
utils::MemoryResource *mem) {
std::vector<UniqueCursorPtr> cursors;
cursors.reserve(ops.size());
if (!ops.empty()) {
for (const auto &op : ops) {
cursors.push_back(op->MakeCursor(mem));
}
}
return cursors;
}
Filter::FilterCursor::FilterCursor(const Filter &self, utils::MemoryResource *mem) Filter::FilterCursor::FilterCursor(const Filter &self, utils::MemoryResource *mem)
: self_(self), input_cursor_(self_.input_->MakeCursor(mem)) {} : self_(self),
input_cursor_(self_.input_->MakeCursor(mem)),
pattern_filter_cursors_(MakeCursorVector(self_.pattern_filters_, mem)) {}
bool Filter::FilterCursor::Pull(Frame &frame, ExecutionContext &context) { bool Filter::FilterCursor::Pull(Frame &frame, ExecutionContext &context) {
SCOPED_PROFILE_OP("Filter"); SCOPED_PROFILE_OP("Filter");
@ -2278,6 +2307,10 @@ bool Filter::FilterCursor::Pull(Frame &frame, ExecutionContext &context) {
ExpressionEvaluator evaluator(&frame, context.symbol_table, context.evaluation_context, context.db_accessor, ExpressionEvaluator evaluator(&frame, context.symbol_table, context.evaluation_context, context.db_accessor,
storage::View::OLD); storage::View::OLD);
while (input_cursor_->Pull(frame, context)) { while (input_cursor_->Pull(frame, context)) {
for (const auto &pattern_filter_cursor : pattern_filter_cursors_) {
pattern_filter_cursor->Pull(frame, context);
}
if (EvaluateFilter(evaluator, self_.expression_)) return true; if (EvaluateFilter(evaluator, self_.expression_)) return true;
} }
return false; return false;
@ -2287,6 +2320,39 @@ void Filter::FilterCursor::Shutdown() { input_cursor_->Shutdown(); }
void Filter::FilterCursor::Reset() { input_cursor_->Reset(); } void Filter::FilterCursor::Reset() { input_cursor_->Reset(); }
EvaluatePatternFilter::EvaluatePatternFilter(const std::shared_ptr<LogicalOperator> &input, Symbol output_symbol)
: input_(input), output_symbol_(output_symbol) {}
ACCEPT_WITH_INPUT(EvaluatePatternFilter);
UniqueCursorPtr EvaluatePatternFilter::MakeCursor(utils::MemoryResource *mem) const {
EventCounter::IncrementCounter(EventCounter::EvaluatePatternFilterOperator);
return MakeUniqueCursorPtr<EvaluatePatternFilterCursor>(mem, *this, mem);
}
EvaluatePatternFilter::EvaluatePatternFilterCursor::EvaluatePatternFilterCursor(const EvaluatePatternFilter &self,
utils::MemoryResource *mem)
: self_(self), input_cursor_(self_.input_->MakeCursor(mem)) {}
std::vector<Symbol> EvaluatePatternFilter::ModifiedSymbols(const SymbolTable &table) const {
return input_->ModifiedSymbols(table);
}
bool EvaluatePatternFilter::EvaluatePatternFilterCursor::Pull(Frame &frame, ExecutionContext &context) {
SCOPED_PROFILE_OP("EvaluatePatternFilter");
input_cursor_->Reset();
frame[self_.output_symbol_] = TypedValue(input_cursor_->Pull(frame, context), context.evaluation_context.memory);
return true;
}
void EvaluatePatternFilter::EvaluatePatternFilterCursor::Shutdown() { input_cursor_->Shutdown(); }
void EvaluatePatternFilter::EvaluatePatternFilterCursor::Reset() { input_cursor_->Reset(); }
Produce::Produce(const std::shared_ptr<LogicalOperator> &input, const std::vector<NamedExpression *> &named_expressions) Produce::Produce(const std::shared_ptr<LogicalOperator> &input, const std::vector<NamedExpression *> &named_expressions)
: input_(input ? input : std::make_shared<Once>()), named_expressions_(named_expressions) {} : input_(input ? input : std::make_shared<Once>()), named_expressions_(named_expressions) {}

View File

@ -133,6 +133,7 @@ class CallProcedure;
class LoadCsv; class LoadCsv;
class Foreach; class Foreach;
class EmptyResult; class EmptyResult;
class EvaluatePatternFilter;
using LogicalOperatorCompositeVisitor = utils::CompositeVisitor< using LogicalOperatorCompositeVisitor = utils::CompositeVisitor<
Once, CreateNode, CreateExpand, ScanAll, ScanAllByLabel, Once, CreateNode, CreateExpand, ScanAll, ScanAllByLabel,
@ -141,7 +142,7 @@ using LogicalOperatorCompositeVisitor = utils::CompositeVisitor<
Expand, ExpandVariable, ConstructNamedPath, Filter, Produce, Delete, Expand, ExpandVariable, ConstructNamedPath, Filter, Produce, Delete,
SetProperty, SetProperties, SetLabels, RemoveProperty, RemoveLabels, SetProperty, SetProperties, SetLabels, RemoveProperty, RemoveLabels,
EdgeUniquenessFilter, Accumulate, Aggregate, Skip, Limit, OrderBy, Merge, EdgeUniquenessFilter, Accumulate, Aggregate, Skip, Limit, OrderBy, Merge,
Optional, Unwind, Distinct, Union, Cartesian, CallProcedure, LoadCsv, Foreach, EmptyResult>; Optional, Unwind, Distinct, Union, Cartesian, CallProcedure, LoadCsv, Foreach, EmptyResult, EvaluatePatternFilter>;
using LogicalOperatorLeafVisitor = utils::LeafVisitor<Once>; using LogicalOperatorLeafVisitor = utils::LeafVisitor<Once>;
@ -1122,6 +1123,9 @@ pulled.")
((input "std::shared_ptr<LogicalOperator>" :scope :public ((input "std::shared_ptr<LogicalOperator>" :scope :public
:slk-save #'slk-save-operator-pointer :slk-save #'slk-save-operator-pointer
:slk-load #'slk-load-operator-pointer) :slk-load #'slk-load-operator-pointer)
(pattern_filters "std::vector<std::shared_ptr<LogicalOperator>>" :scope :public
:slk-save #'slk-save-ast-vector
:slk-load (slk-load-ast-vector "std::shared_ptr<LogicalOperator>"))
(expression "Expression *" :scope :public (expression "Expression *" :scope :public
:slk-save #'slk-save-ast-pointer :slk-save #'slk-save-ast-pointer
:slk-load (slk-load-ast-pointer "Expression"))) :slk-load (slk-load-ast-pointer "Expression")))
@ -1136,6 +1140,7 @@ a boolean value.")
Filter() {} Filter() {}
Filter(const std::shared_ptr<LogicalOperator> &input_, Filter(const std::shared_ptr<LogicalOperator> &input_,
const std::vector<std::shared_ptr<LogicalOperator>> &pattern_filters_,
Expression *expression_); Expression *expression_);
bool Accept(HierarchicalLogicalOperatorVisitor &visitor) override; bool Accept(HierarchicalLogicalOperatorVisitor &visitor) override;
UniqueCursorPtr MakeCursor(utils::MemoryResource *) const override; UniqueCursorPtr MakeCursor(utils::MemoryResource *) const override;
@ -1159,6 +1164,7 @@ a boolean value.")
private: private:
const Filter &self_; const Filter &self_;
const UniqueCursorPtr input_cursor_; const UniqueCursorPtr input_cursor_;
const std::vector<UniqueCursorPtr> pattern_filter_cursors_;
}; };
cpp<#) cpp<#)
(:serialize (:slk)) (:serialize (:slk))
@ -1777,6 +1783,45 @@ operator's implementation does not expect this.")
(:serialize (:slk)) (:serialize (:slk))
(:clone)) (:clone))
(lcp:define-class evaluate-pattern-filter (logical-operator)
((input "std::shared_ptr<LogicalOperator>" :scope :public
:slk-save #'slk-save-operator-pointer
:slk-load #'slk-load-operator-pointer)
(output-symbol "Symbol" :scope :public))
(:documentation "Applies the pattern filter by putting the value of the input cursor to the frame.")
(:public
#>cpp
EvaluatePatternFilter() {}
EvaluatePatternFilter(const std::shared_ptr<LogicalOperator> &input, Symbol output_symbol);
bool Accept(HierarchicalLogicalOperatorVisitor &visitor) override;
UniqueCursorPtr MakeCursor(utils::MemoryResource *) const override;
std::vector<Symbol> ModifiedSymbols(const SymbolTable &) const override;
bool HasSingleInput() const override { return true; }
std::shared_ptr<LogicalOperator> input() const override { return input_; }
void set_input(std::shared_ptr<LogicalOperator> input) override {
input_ = input;
}
cpp<#)
(:private
#>cpp
class EvaluatePatternFilterCursor : public Cursor {
public:
EvaluatePatternFilterCursor(const EvaluatePatternFilter &, utils::MemoryResource *);
bool Pull(Frame &, ExecutionContext &) override;
void Shutdown() override;
void Reset() override;
private:
const EvaluatePatternFilter &self_;
UniqueCursorPtr input_cursor_;
};
cpp<#)
(:serialize (:slk))
(:clone))
(lcp:define-class limit (logical-operator) (lcp:define-class limit (logical-operator)
((input "std::shared_ptr<LogicalOperator>" :scope :public ((input "std::shared_ptr<LogicalOperator>" :scope :public
:slk-save #'slk-save-operator-pointer :slk-save #'slk-save-operator-pointer

View File

@ -1,4 +1,4 @@
// Copyright 2022 Memgraph Ltd. // Copyright 2023 Memgraph Ltd.
// //
// Use of this software is governed by the Business Source License // 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 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@ -84,56 +84,6 @@ std::vector<Expansion> NormalizePatterns(const SymbolTable &symbol_table, const
return expansions; return expansions;
} }
// Fills the given Matching, by converting the Match patterns to normalized
// representation as Expansions. Filters used in the Match are also collected,
// as well as edge symbols which determine Cyphermorphism. Collecting filters
// will lift them out of a pattern and generate new expressions (just like they
// were in a Where clause).
void AddMatching(const std::vector<Pattern *> &patterns, Where *where, SymbolTable &symbol_table, AstStorage &storage,
Matching &matching) {
auto expansions = NormalizePatterns(symbol_table, patterns);
std::unordered_set<Symbol> edge_symbols;
for (const auto &expansion : expansions) {
// Matching may already have some expansions, so offset our index.
const size_t expansion_ix = matching.expansions.size();
// Map node1 symbol to expansion
const auto &node1_sym = symbol_table.at(*expansion.node1->identifier_);
matching.node_symbol_to_expansions[node1_sym].insert(expansion_ix);
// Add node1 to all symbols.
matching.expansion_symbols.insert(node1_sym);
if (expansion.edge) {
const auto &edge_sym = symbol_table.at(*expansion.edge->identifier_);
// Fill edge symbols for Cyphermorphism.
edge_symbols.insert(edge_sym);
// Map node2 symbol to expansion
const auto &node2_sym = symbol_table.at(*expansion.node2->identifier_);
matching.node_symbol_to_expansions[node2_sym].insert(expansion_ix);
// Add edge and node2 to all symbols
matching.expansion_symbols.insert(edge_sym);
matching.expansion_symbols.insert(node2_sym);
}
matching.expansions.push_back(expansion);
}
if (!edge_symbols.empty()) {
matching.edge_symbols.emplace_back(edge_symbols);
}
for (auto *pattern : patterns) {
matching.filters.CollectPatternFilters(*pattern, symbol_table, storage);
if (pattern->identifier_->user_declared_) {
std::vector<Symbol> path_elements;
for (auto *pattern_atom : pattern->atoms_)
path_elements.emplace_back(symbol_table.at(*pattern_atom->identifier_));
matching.named_paths.emplace(symbol_table.at(*pattern->identifier_), std::move(path_elements));
}
}
if (where) {
matching.filters.CollectWhereFilter(*where, symbol_table);
}
}
void AddMatching(const Match &match, SymbolTable &symbol_table, AstStorage &storage, Matching &matching) {
return AddMatching(match.patterns_, match.where_, symbol_table, storage, matching);
}
auto SplitExpressionOnAnd(Expression *expression) { auto SplitExpressionOnAnd(Expression *expression) {
// TODO: Think about converting all filtering expression into CNF to improve // TODO: Think about converting all filtering expression into CNF to improve
// the granularity of filters which can be stand alone. // the granularity of filters which can be stand alone.
@ -519,6 +469,8 @@ void Filters::AnalyzeAndStoreFilter(Expression *expr, const SymbolTable &symbol_
if (!add_prop_is_not_null_check(is_not_null)) { if (!add_prop_is_not_null_check(is_not_null)) {
all_filters_.emplace_back(make_filter(FilterInfo::Type::Generic)); all_filters_.emplace_back(make_filter(FilterInfo::Type::Generic));
} }
} else if (auto *exists = utils::Downcast<Exists>(expr)) {
all_filters_.emplace_back(make_filter(FilterInfo::Type::Pattern));
} else { } else {
all_filters_.emplace_back(make_filter(FilterInfo::Type::Generic)); all_filters_.emplace_back(make_filter(FilterInfo::Type::Generic));
} }
@ -528,6 +480,78 @@ void Filters::AnalyzeAndStoreFilter(Expression *expr, const SymbolTable &symbol_
// as `expr1 < n.prop AND n.prop < expr2`. // as `expr1 < n.prop AND n.prop < expr2`.
} }
// Fills the given Matching, by converting the Match patterns to normalized
// representation as Expansions. Filters used in the Match are also collected,
// as well as edge symbols which determine Cyphermorphism. Collecting filters
// will lift them out of a pattern and generate new expressions (just like they
// were in a Where clause).
void AddMatching(const std::vector<Pattern *> &patterns, Where *where, SymbolTable &symbol_table, AstStorage &storage,
Matching &matching) {
auto expansions = NormalizePatterns(symbol_table, patterns);
std::unordered_set<Symbol> edge_symbols;
for (const auto &expansion : expansions) {
// Matching may already have some expansions, so offset our index.
const size_t expansion_ix = matching.expansions.size();
// Map node1 symbol to expansion
const auto &node1_sym = symbol_table.at(*expansion.node1->identifier_);
matching.node_symbol_to_expansions[node1_sym].insert(expansion_ix);
// Add node1 to all symbols.
matching.expansion_symbols.insert(node1_sym);
if (expansion.edge) {
const auto &edge_sym = symbol_table.at(*expansion.edge->identifier_);
// Fill edge symbols for Cyphermorphism.
edge_symbols.insert(edge_sym);
// Map node2 symbol to expansion
const auto &node2_sym = symbol_table.at(*expansion.node2->identifier_);
matching.node_symbol_to_expansions[node2_sym].insert(expansion_ix);
// Add edge and node2 to all symbols
matching.expansion_symbols.insert(edge_sym);
matching.expansion_symbols.insert(node2_sym);
}
matching.expansions.push_back(expansion);
}
if (!edge_symbols.empty()) {
matching.edge_symbols.emplace_back(edge_symbols);
}
for (auto *const pattern : patterns) {
matching.filters.CollectPatternFilters(*pattern, symbol_table, storage);
if (pattern->identifier_->user_declared_) {
std::vector<Symbol> path_elements;
for (auto *const pattern_atom : pattern->atoms_)
path_elements.push_back(symbol_table.at(*pattern_atom->identifier_));
matching.named_paths.emplace(symbol_table.at(*pattern->identifier_), std::move(path_elements));
}
}
if (where) {
matching.filters.CollectWhereFilter(*where, symbol_table);
}
}
void AddMatching(const Match &match, SymbolTable &symbol_table, AstStorage &storage, Matching &matching) {
AddMatching(match.patterns_, match.where_, symbol_table, storage, matching);
// If there are any pattern filters, we add those as well
for (auto &filter : matching.filters) {
PatternFilterVisitor visitor(symbol_table, storage);
filter.expression->Accept(visitor);
filter.matchings = visitor.getMatchings();
}
}
void PatternFilterVisitor::Visit(Exists &op) {
std::vector<Pattern *> patterns;
patterns.push_back(op.pattern_);
FilterMatching filter_matching;
AddMatching(patterns, nullptr, symbol_table_, storage_, filter_matching);
filter_matching.type = PatternFilterType::EXISTS;
filter_matching.symbol = std::make_optional<Symbol>(symbol_table_.at(op));
matchings_.push_back(std::move(filter_matching));
}
static void ParseForeach(query::Foreach &foreach, SingleQueryPart &query_part, AstStorage &storage, static void ParseForeach(query::Foreach &foreach, SingleQueryPart &query_part, AstStorage &storage,
SymbolTable &symbol_table) { SymbolTable &symbol_table) {
for (auto *clause : foreach.clauses_) { for (auto *clause : foreach.clauses_) {

View File

@ -1,4 +1,4 @@
// Copyright 2022 Memgraph Ltd. // Copyright 2023 Memgraph Ltd.
// //
// Use of this software is governed by the Business Source License // 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 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@ -70,7 +70,26 @@ class UsedSymbolsCollector : public HierarchicalTreeVisitor {
} }
bool Visit(Identifier &ident) override { bool Visit(Identifier &ident) override {
symbols_.insert(symbol_table_.at(ident)); if (!in_exists || ident.user_declared_) {
symbols_.insert(symbol_table_.at(ident));
}
return true;
}
bool PreVisit(Exists &exists) override {
in_exists = true;
// We do not visit pattern identifier since we're in exists filter pattern
for (auto &atom : exists.pattern_->atoms_) {
atom->Accept(*this);
}
return false;
}
bool PostVisit(Exists & /*exists*/) override {
in_exists = false;
return true; return true;
} }
@ -79,6 +98,9 @@ class UsedSymbolsCollector : public HierarchicalTreeVisitor {
std::unordered_set<Symbol> symbols_; std::unordered_set<Symbol> symbols_;
const SymbolTable &symbol_table_; const SymbolTable &symbol_table_;
private:
bool in_exists{false};
}; };
/// Normalized representation of a pattern that needs to be matched. /// Normalized representation of a pattern that needs to be matched.
@ -99,6 +121,93 @@ struct Expansion {
NodeAtom *node2 = nullptr; NodeAtom *node2 = nullptr;
}; };
struct FilterMatching;
enum class PatternFilterType { EXISTS };
/// Collects matchings from filters that include patterns
class PatternFilterVisitor : public ExpressionVisitor<void> {
public:
explicit PatternFilterVisitor(SymbolTable &symbol_table, AstStorage &storage)
: symbol_table_(symbol_table), storage_(storage) {}
using ExpressionVisitor<void>::Visit;
// Unary operators
void Visit(NotOperator &op) override { op.expression_->Accept(*this); }
void Visit(IsNullOperator &op) override { op.expression_->Accept(*this); };
void Visit(UnaryPlusOperator &op) override{};
void Visit(UnaryMinusOperator &op) override{};
// Binary operators
void Visit(OrOperator &op) override {
op.expression1_->Accept(*this);
op.expression2_->Accept(*this);
}
void Visit(XorOperator &op) override {
op.expression1_->Accept(*this);
op.expression2_->Accept(*this);
}
void Visit(AndOperator &op) override {
op.expression1_->Accept(*this);
op.expression2_->Accept(*this);
}
void Visit(NotEqualOperator &op) override {
op.expression1_->Accept(*this);
op.expression2_->Accept(*this);
};
void Visit(EqualOperator &op) override {
op.expression1_->Accept(*this);
op.expression2_->Accept(*this);
};
void Visit(InListOperator &op) override {
op.expression1_->Accept(*this);
op.expression2_->Accept(*this);
};
void Visit(AdditionOperator &op) override{};
void Visit(SubtractionOperator &op) override{};
void Visit(MultiplicationOperator &op) override{};
void Visit(DivisionOperator &op) override{};
void Visit(ModOperator &op) override{};
void Visit(LessOperator &op) override{};
void Visit(GreaterOperator &op) override{};
void Visit(LessEqualOperator &op) override{};
void Visit(GreaterEqualOperator &op) override{};
void Visit(SubscriptOperator &op) override{};
// Other
void Visit(ListSlicingOperator &op) override{};
void Visit(IfOperator &op) override{};
void Visit(ListLiteral &op) override{};
void Visit(MapLiteral &op) override{};
void Visit(LabelsTest &op) override{};
void Visit(Aggregation &op) override{};
void Visit(Function &op) override{};
void Visit(Reduce &op) override{};
void Visit(Coalesce &op) override{};
void Visit(Extract &op) override{};
void Visit(Exists &op) override;
void Visit(All &op) override{};
void Visit(Single &op) override{};
void Visit(Any &op) override{};
void Visit(None &op) override{};
void Visit(Identifier &op) override{};
void Visit(PrimitiveLiteral &op) override{};
void Visit(PropertyLookup &op) override{};
void Visit(ParameterLookup &op) override{};
void Visit(NamedExpression &op) override{};
void Visit(RegexMatch &op) override{};
std::vector<FilterMatching> getMatchings() { return matchings_; }
SymbolTable &symbol_table_;
AstStorage &storage_;
private:
/// Collection of matchings in the filter expression being analyzed.
std::vector<FilterMatching> matchings_;
};
/// Stores the symbols and expression used to filter a property. /// Stores the symbols and expression used to filter a property.
class PropertyFilter { class PropertyFilter {
public: public:
@ -153,7 +262,7 @@ struct FilterInfo {
/// applied for labels or a property. Non generic types contain extra /// applied for labels or a property. Non generic types contain extra
/// information which can be used to produce indexed scans of graph /// information which can be used to produce indexed scans of graph
/// elements. /// elements.
enum class Type { Generic, Label, Property, Id }; enum class Type { Generic, Label, Property, Id, Pattern };
Type type; Type type;
/// The original filter expression which must be satisfied. /// The original filter expression which must be satisfied.
@ -166,6 +275,8 @@ struct FilterInfo {
std::optional<PropertyFilter> property_filter; std::optional<PropertyFilter> property_filter;
/// Information for Type::Id filtering. /// Information for Type::Id filtering.
std::optional<IdFilter> id_filter; std::optional<IdFilter> id_filter;
/// Matchings for filters that include patterns
std::vector<FilterMatching> matchings;
}; };
/// Stores information on filters used inside the @c Matching of a @c QueryPart. /// Stores information on filters used inside the @c Matching of a @c QueryPart.
@ -287,6 +398,13 @@ struct Matching {
std::unordered_set<Symbol> expansion_symbols{}; std::unordered_set<Symbol> expansion_symbols{};
}; };
struct FilterMatching : Matching {
/// Type of pattern filter
PatternFilterType type;
/// Symbol for the filter expression
std::optional<Symbol> symbol;
};
/// @brief Represents a read (+ write) part of a query. Parts are split on /// @brief Represents a read (+ write) part of a query. Parts are split on
/// `WITH` clauses. /// `WITH` clauses.
/// ///

View File

@ -1,4 +1,4 @@
// Copyright 2022 Memgraph Ltd. // Copyright 2023 Memgraph Ltd.
// //
// Use of this software is governed by the Business Source License // 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 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@ -14,6 +14,7 @@
#include "query/db_accessor.hpp" #include "query/db_accessor.hpp"
#include "query/frontend/ast/pretty_print.hpp" #include "query/frontend/ast/pretty_print.hpp"
#include "query/plan/operator.hpp"
#include "utils/string.hpp" #include "utils/string.hpp"
namespace memgraph::query::plan { namespace memgraph::query::plan {
@ -148,7 +149,6 @@ bool PlanPrinter::PreVisit(query::plan::Produce &op) {
} }
PRE_VISIT(ConstructNamedPath); PRE_VISIT(ConstructNamedPath);
PRE_VISIT(Filter);
PRE_VISIT(SetProperty); PRE_VISIT(SetProperty);
PRE_VISIT(SetProperties); PRE_VISIT(SetProperties);
PRE_VISIT(SetLabels); PRE_VISIT(SetLabels);
@ -157,6 +157,7 @@ PRE_VISIT(RemoveLabels);
PRE_VISIT(EdgeUniquenessFilter); PRE_VISIT(EdgeUniquenessFilter);
PRE_VISIT(Accumulate); PRE_VISIT(Accumulate);
PRE_VISIT(EmptyResult); PRE_VISIT(EmptyResult);
PRE_VISIT(EvaluatePatternFilter);
bool PlanPrinter::PreVisit(query::plan::Aggregate &op) { bool PlanPrinter::PreVisit(query::plan::Aggregate &op) {
WithPrintLn([&](auto &out) { WithPrintLn([&](auto &out) {
@ -251,6 +252,15 @@ bool PlanPrinter::PreVisit(query::plan::Foreach &op) {
op.input_->Accept(*this); op.input_->Accept(*this);
return false; return false;
} }
bool PlanPrinter::PreVisit(query::plan::Filter &op) {
WithPrintLn([](auto &out) { out << "* Filter"; });
for (const auto &pattern_filter : op.pattern_filters_) {
Branch(*pattern_filter);
}
op.input_->Accept(*this);
return false;
}
#undef PRE_VISIT #undef PRE_VISIT
bool PlanPrinter::DefaultPreVisit() { bool PlanPrinter::DefaultPreVisit() {
@ -589,6 +599,13 @@ bool PlanToJsonVisitor::PreVisit(Filter &op) {
op.input_->Accept(*this); op.input_->Accept(*this);
self["input"] = PopOutput(); self["input"] = PopOutput();
for (auto pattern_idx = 0; pattern_idx < op.pattern_filters_.size(); pattern_idx++) {
auto pattern_filter_key = "pattern_filter" + std::to_string(pattern_idx + 1);
op.pattern_filters_[pattern_idx]->Accept(*this);
self[pattern_filter_key] = PopOutput();
}
output_ = std::move(self); output_ = std::move(self);
return false; return false;
} }
@ -908,6 +925,7 @@ bool PlanToJsonVisitor::PreVisit(Cartesian &op) {
output_ = std::move(self); output_ = std::move(self);
return false; return false;
} }
bool PlanToJsonVisitor::PreVisit(Foreach &op) { bool PlanToJsonVisitor::PreVisit(Foreach &op) {
json self; json self;
self["name"] = "Foreach"; self["name"] = "Foreach";
@ -924,6 +942,18 @@ bool PlanToJsonVisitor::PreVisit(Foreach &op) {
return false; return false;
} }
bool PlanToJsonVisitor::PreVisit(EvaluatePatternFilter &op) {
json self;
self["name"] = "EvaluatePatternFilter";
self["output_symbol"] = ToJson(op.output_symbol_);
op.input_->Accept(*this);
self["input"] = PopOutput();
output_ = std::move(self);
return false;
}
} // namespace impl } // namespace impl
} // namespace memgraph::query::plan } // namespace memgraph::query::plan

View File

@ -1,4 +1,4 @@
// Copyright 2022 Memgraph Ltd. // Copyright 2023 Memgraph Ltd.
// //
// Use of this software is governed by the Business Source License // 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 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@ -74,6 +74,7 @@ class PlanPrinter : public virtual HierarchicalLogicalOperatorVisitor {
bool PreVisit(ConstructNamedPath &) override; bool PreVisit(ConstructNamedPath &) override;
bool PreVisit(Filter &) override; bool PreVisit(Filter &) override;
bool PreVisit(EvaluatePatternFilter & /*unused*/) override;
bool PreVisit(EdgeUniquenessFilter &) override; bool PreVisit(EdgeUniquenessFilter &) override;
bool PreVisit(Merge &) override; bool PreVisit(Merge &) override;
@ -186,6 +187,7 @@ class PlanToJsonVisitor : public virtual HierarchicalLogicalOperatorVisitor {
bool PreVisit(Optional &) override; bool PreVisit(Optional &) override;
bool PreVisit(Filter &) override; bool PreVisit(Filter &) override;
bool PreVisit(EvaluatePatternFilter & /*op*/) override;
bool PreVisit(EdgeUniquenessFilter &) override; bool PreVisit(EdgeUniquenessFilter &) override;
bool PreVisit(Cartesian &) override; bool PreVisit(Cartesian &) override;

View File

@ -454,6 +454,16 @@ class IndexLookupRewriter final : public HierarchicalLogicalOperatorVisitor {
return true; return true;
} }
bool PreVisit(EvaluatePatternFilter &op) override {
prev_ops_.push_back(&op);
return true;
}
bool PostVisit(EvaluatePatternFilter & /*op*/) override {
prev_ops_.pop_back();
return true;
}
std::shared_ptr<LogicalOperator> new_root_; std::shared_ptr<LogicalOperator> new_root_;
private: private:

View File

@ -1,4 +1,4 @@
// Copyright 2022 Memgraph Ltd. // Copyright 2023 Memgraph Ltd.
// //
// Use of this software is governed by the Business Source License // 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 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@ -25,15 +25,6 @@ namespace memgraph::query::plan {
namespace { namespace {
bool HasBoundFilterSymbols(const std::unordered_set<Symbol> &bound_symbols, const FilterInfo &filter) {
for (const auto &symbol : filter.used_symbols) {
if (bound_symbols.find(symbol) == bound_symbols.end()) {
return false;
}
}
return true;
}
// Ast tree visitor which collects the context for a return body. // Ast tree visitor which collects the context for a return body.
// The return body of WITH and RETURN clauses consists of: // The return body of WITH and RETURN clauses consists of:
// //
@ -495,7 +486,8 @@ std::unique_ptr<LogicalOperator> GenReturnBody(std::unique_ptr<LogicalOperator>
// Where may see new symbols so it comes after we generate Produce and in // Where may see new symbols so it comes after we generate Produce and in
// general, comes after any OrderBy, Skip or Limit. // general, comes after any OrderBy, Skip or Limit.
if (body.where()) { if (body.where()) {
last_op = std::make_unique<Filter>(std::move(last_op), body.where()->expression_); last_op = std::make_unique<Filter>(std::move(last_op), std::vector<std::shared_ptr<LogicalOperator>>{},
body.where()->expression_);
} }
return last_op; return last_op;
} }
@ -504,6 +496,12 @@ std::unique_ptr<LogicalOperator> GenReturnBody(std::unique_ptr<LogicalOperator>
namespace impl { namespace impl {
bool HasBoundFilterSymbols(const std::unordered_set<Symbol> &bound_symbols, const FilterInfo &filter) {
return std::ranges::all_of(
filter.used_symbols.begin(), filter.used_symbols.end(),
[&bound_symbols](const auto &symbol) { return bound_symbols.find(symbol) != bound_symbols.end(); });
}
Expression *ExtractFilters(const std::unordered_set<Symbol> &bound_symbols, Filters &filters, AstStorage &storage) { Expression *ExtractFilters(const std::unordered_set<Symbol> &bound_symbols, Filters &filters, AstStorage &storage) {
Expression *filter_expr = nullptr; Expression *filter_expr = nullptr;
for (auto filters_it = filters.begin(); filters_it != filters.end();) { for (auto filters_it = filters.begin(); filters_it != filters.end();) {
@ -517,16 +515,6 @@ Expression *ExtractFilters(const std::unordered_set<Symbol> &bound_symbols, Filt
return filter_expr; return filter_expr;
} }
std::unique_ptr<LogicalOperator> GenFilters(std::unique_ptr<LogicalOperator> last_op,
const std::unordered_set<Symbol> &bound_symbols, Filters &filters,
AstStorage &storage) {
auto *filter_expr = ExtractFilters(bound_symbols, filters, storage);
if (filter_expr) {
last_op = std::make_unique<Filter>(std::move(last_op), filter_expr);
}
return last_op;
}
std::unique_ptr<LogicalOperator> GenNamedPaths(std::unique_ptr<LogicalOperator> last_op, std::unique_ptr<LogicalOperator> GenNamedPaths(std::unique_ptr<LogicalOperator> last_op,
std::unordered_set<Symbol> &bound_symbols, std::unordered_set<Symbol> &bound_symbols,
std::unordered_map<Symbol, std::vector<Symbol>> &named_paths) { std::unordered_map<Symbol, std::vector<Symbol>> &named_paths) {

View File

@ -81,8 +81,8 @@ namespace impl {
// removed from `Filters`. // removed from `Filters`.
Expression *ExtractFilters(const std::unordered_set<Symbol> &, Filters &, AstStorage &); Expression *ExtractFilters(const std::unordered_set<Symbol> &, Filters &, AstStorage &);
std::unique_ptr<LogicalOperator> GenFilters(std::unique_ptr<LogicalOperator>, const std::unordered_set<Symbol> &, /// Checks if the filters has all the bound symbols to be included in the current part of the query
Filters &, AstStorage &); bool HasBoundFilterSymbols(const std::unordered_set<Symbol> &bound_symbols, const FilterInfo &filter);
/// Utility function for iterating pattern atoms and accumulating a result. /// Utility function for iterating pattern atoms and accumulating a result.
/// ///
@ -398,119 +398,11 @@ class RuleBasedPlanner {
// Try to generate any filters even before the 1st match operator. This // Try to generate any filters even before the 1st match operator. This
// optimizes the optional match which filters only on symbols bound in // optimizes the optional match which filters only on symbols bound in
// regular match. // regular match.
auto last_op = impl::GenFilters(std::move(input_op), bound_symbols, filters, storage); auto last_op = GenFilters(std::move(input_op), bound_symbols, filters, storage, symbol_table);
for (const auto &expansion : matching.expansions) {
const auto &node1_symbol = symbol_table.at(*expansion.node1->identifier_);
if (bound_symbols.insert(node1_symbol).second) {
// We have just bound this symbol, so generate ScanAll which fills it.
last_op = std::make_unique<ScanAll>(std::move(last_op), node1_symbol, match_context.view);
match_context.new_symbols.emplace_back(node1_symbol);
last_op = impl::GenFilters(std::move(last_op), bound_symbols, filters, storage);
last_op = impl::GenNamedPaths(std::move(last_op), bound_symbols, named_paths);
last_op = impl::GenFilters(std::move(last_op), bound_symbols, filters, storage);
}
// We have an edge, so generate Expand.
if (expansion.edge) {
auto *edge = expansion.edge;
// If the expand symbols were already bound, then we need to indicate
// that they exist. The Expand will then check whether the pattern holds
// instead of writing the expansion to symbols.
const auto &node_symbol = symbol_table.at(*expansion.node2->identifier_);
auto existing_node = utils::Contains(bound_symbols, node_symbol);
const auto &edge_symbol = symbol_table.at(*edge->identifier_);
MG_ASSERT(!utils::Contains(bound_symbols, edge_symbol), "Existing edges are not supported");
std::vector<storage::EdgeTypeId> edge_types;
edge_types.reserve(edge->edge_types_.size());
for (const auto &type : edge->edge_types_) {
edge_types.push_back(GetEdgeType(type));
}
if (edge->IsVariable()) {
std::optional<ExpansionLambda> weight_lambda;
std::optional<Symbol> total_weight;
if (edge->type_ == EdgeAtom::Type::WEIGHTED_SHORTEST_PATH || last_op = HandleExpansion(std::move(last_op), matching, symbol_table, storage, bound_symbols,
edge->type_ == EdgeAtom::Type::ALL_SHORTEST_PATHS) { match_context.new_symbols, named_paths, filters, match_context.view);
weight_lambda.emplace(ExpansionLambda{symbol_table.at(*edge->weight_lambda_.inner_edge),
symbol_table.at(*edge->weight_lambda_.inner_node),
edge->weight_lambda_.expression});
total_weight.emplace(symbol_table.at(*edge->total_weight_));
}
ExpansionLambda filter_lambda;
filter_lambda.inner_edge_symbol = symbol_table.at(*edge->filter_lambda_.inner_edge);
filter_lambda.inner_node_symbol = symbol_table.at(*edge->filter_lambda_.inner_node);
{
// Bind the inner edge and node symbols so they're available for
// inline filtering in ExpandVariable.
bool inner_edge_bound = bound_symbols.insert(filter_lambda.inner_edge_symbol).second;
bool inner_node_bound = bound_symbols.insert(filter_lambda.inner_node_symbol).second;
MG_ASSERT(inner_edge_bound && inner_node_bound, "An inner edge and node can't be bound from before");
}
// Join regular filters with lambda filter expression, so that they
// are done inline together. Semantic analysis should guarantee that
// lambda filtering uses bound symbols.
filter_lambda.expression = impl::BoolJoin<AndOperator>(
storage, impl::ExtractFilters(bound_symbols, filters, storage), edge->filter_lambda_.expression);
// At this point it's possible we have leftover filters for inline
// filtering (they use the inner symbols. If they were not collected,
// we have to remove them manually because no other filter-extraction
// will ever bind them again.
filters.erase(std::remove_if(
filters.begin(), filters.end(),
[e = filter_lambda.inner_edge_symbol, n = filter_lambda.inner_node_symbol](FilterInfo &fi) {
return utils::Contains(fi.used_symbols, e) || utils::Contains(fi.used_symbols, n);
}),
filters.end());
// Unbind the temporarily bound inner symbols for filtering.
bound_symbols.erase(filter_lambda.inner_edge_symbol);
bound_symbols.erase(filter_lambda.inner_node_symbol);
if (total_weight) {
bound_symbols.insert(*total_weight);
}
// TODO: Pass weight lambda.
MG_ASSERT(match_context.view == storage::View::OLD,
"ExpandVariable should only be planned with storage::View::OLD");
last_op = std::make_unique<ExpandVariable>(std::move(last_op), node1_symbol, node_symbol, edge_symbol,
edge->type_, expansion.direction, edge_types, expansion.is_flipped,
edge->lower_bound_, edge->upper_bound_, existing_node,
filter_lambda, weight_lambda, total_weight);
} else {
last_op = std::make_unique<Expand>(std::move(last_op), node1_symbol, node_symbol, edge_symbol,
expansion.direction, edge_types, existing_node, match_context.view);
}
// Bind the expanded edge and node.
bound_symbols.insert(edge_symbol);
match_context.new_symbols.emplace_back(edge_symbol);
if (bound_symbols.insert(node_symbol).second) {
match_context.new_symbols.emplace_back(node_symbol);
}
// Ensure Cyphermorphism (different edge symbols always map to
// different edges).
for (const auto &edge_symbols : matching.edge_symbols) {
if (edge_symbols.find(edge_symbol) == edge_symbols.end()) {
continue;
}
std::vector<Symbol> other_symbols;
for (const auto &symbol : edge_symbols) {
if (symbol == edge_symbol || bound_symbols.find(symbol) == bound_symbols.end()) {
continue;
}
other_symbols.push_back(symbol);
}
if (!other_symbols.empty()) {
last_op = std::make_unique<EdgeUniquenessFilter>(std::move(last_op), edge_symbol, other_symbols);
}
}
last_op = impl::GenFilters(std::move(last_op), bound_symbols, filters, storage);
last_op = impl::GenNamedPaths(std::move(last_op), bound_symbols, named_paths);
last_op = impl::GenFilters(std::move(last_op), bound_symbols, filters, storage);
}
}
MG_ASSERT(named_paths.empty(), "Expected to generate all named paths"); MG_ASSERT(named_paths.empty(), "Expected to generate all named paths");
// We bound all named path symbols, so just add them to new_symbols. // We bound all named path symbols, so just add them to new_symbols.
for (const auto &named_path : matching.named_paths) { for (const auto &named_path : matching.named_paths) {
@ -547,6 +439,143 @@ class RuleBasedPlanner {
return std::make_unique<plan::Merge>(std::move(input_op), std::move(on_match), std::move(on_create)); return std::make_unique<plan::Merge>(std::move(input_op), std::move(on_match), std::move(on_create));
} }
std::unique_ptr<LogicalOperator> HandleExpansion(std::unique_ptr<LogicalOperator> last_op, const Matching &matching,
const SymbolTable &symbol_table, AstStorage &storage,
std::unordered_set<Symbol> &bound_symbols,
std::vector<Symbol> &new_symbols,
std::unordered_map<Symbol, std::vector<Symbol>> &named_paths,
Filters &filters, storage::View view) {
for (const auto &expansion : matching.expansions) {
const auto &node1_symbol = symbol_table.at(*expansion.node1->identifier_);
if (bound_symbols.insert(node1_symbol).second) {
// We have just bound this symbol, so generate ScanAll which fills it.
last_op = std::make_unique<ScanAll>(std::move(last_op), node1_symbol, view);
new_symbols.emplace_back(node1_symbol);
last_op = GenFilters(std::move(last_op), bound_symbols, filters, storage, symbol_table);
last_op = impl::GenNamedPaths(std::move(last_op), bound_symbols, named_paths);
last_op = GenFilters(std::move(last_op), bound_symbols, filters, storage, symbol_table);
}
if (expansion.edge) {
last_op = GenExpand(std::move(last_op), expansion, symbol_table, bound_symbols, matching, storage, filters,
named_paths, new_symbols, view);
}
}
return last_op;
}
std::unique_ptr<LogicalOperator> GenExpand(std::unique_ptr<LogicalOperator> last_op, const Expansion &expansion,
const SymbolTable &symbol_table, std::unordered_set<Symbol> &bound_symbols,
const Matching &matching, AstStorage &storage, Filters &filters,
std::unordered_map<Symbol, std::vector<Symbol>> &named_paths,
std::vector<Symbol> &new_symbols, storage::View view) {
// If the expand symbols were already bound, then we need to indicate
// that they exist. The Expand will then check whether the pattern holds
// instead of writing the expansion to symbols.
const auto &node1_symbol = symbol_table.at(*expansion.node1->identifier_);
bound_symbols.insert(node1_symbol);
const auto &node_symbol = symbol_table.at(*expansion.node2->identifier_);
auto *edge = expansion.edge;
auto existing_node = utils::Contains(bound_symbols, node_symbol);
const auto &edge_symbol = symbol_table.at(*edge->identifier_);
MG_ASSERT(!utils::Contains(bound_symbols, edge_symbol), "Existing edges are not supported");
std::vector<storage::EdgeTypeId> edge_types;
edge_types.reserve(edge->edge_types_.size());
for (const auto &type : edge->edge_types_) {
edge_types.push_back(GetEdgeType(type));
}
if (edge->IsVariable()) {
std::optional<ExpansionLambda> weight_lambda;
std::optional<Symbol> total_weight;
if (edge->type_ == EdgeAtom::Type::WEIGHTED_SHORTEST_PATH || edge->type_ == EdgeAtom::Type::ALL_SHORTEST_PATHS) {
weight_lambda.emplace(ExpansionLambda{symbol_table.at(*edge->weight_lambda_.inner_edge),
symbol_table.at(*edge->weight_lambda_.inner_node),
edge->weight_lambda_.expression});
total_weight.emplace(symbol_table.at(*edge->total_weight_));
}
ExpansionLambda filter_lambda;
filter_lambda.inner_edge_symbol = symbol_table.at(*edge->filter_lambda_.inner_edge);
filter_lambda.inner_node_symbol = symbol_table.at(*edge->filter_lambda_.inner_node);
{
// Bind the inner edge and node symbols so they're available for
// inline filtering in ExpandVariable.
bool inner_edge_bound = bound_symbols.insert(filter_lambda.inner_edge_symbol).second;
bool inner_node_bound = bound_symbols.insert(filter_lambda.inner_node_symbol).second;
MG_ASSERT(inner_edge_bound && inner_node_bound, "An inner edge and node can't be bound from before");
}
// Join regular filters with lambda filter expression, so that they
// are done inline together. Semantic analysis should guarantee that
// lambda filtering uses bound symbols.
filter_lambda.expression = impl::BoolJoin<AndOperator>(
storage, impl::ExtractFilters(bound_symbols, filters, storage), edge->filter_lambda_.expression);
// At this point it's possible we have leftover filters for inline
// filtering (they use the inner symbols. If they were not collected,
// we have to remove them manually because no other filter-extraction
// will ever bind them again.
filters.erase(
std::remove_if(filters.begin(), filters.end(),
[e = filter_lambda.inner_edge_symbol, n = filter_lambda.inner_node_symbol](FilterInfo &fi) {
return utils::Contains(fi.used_symbols, e) || utils::Contains(fi.used_symbols, n);
}),
filters.end());
// Unbind the temporarily bound inner symbols for filtering.
bound_symbols.erase(filter_lambda.inner_edge_symbol);
bound_symbols.erase(filter_lambda.inner_node_symbol);
if (total_weight) {
bound_symbols.insert(*total_weight);
}
// TODO: Pass weight lambda.
MG_ASSERT(view == storage::View::OLD, "ExpandVariable should only be planned with storage::View::OLD");
last_op = std::make_unique<ExpandVariable>(std::move(last_op), node1_symbol, node_symbol, edge_symbol,
edge->type_, expansion.direction, edge_types, expansion.is_flipped,
edge->lower_bound_, edge->upper_bound_, existing_node, filter_lambda,
weight_lambda, total_weight);
} else {
last_op = std::make_unique<Expand>(std::move(last_op), node1_symbol, node_symbol, edge_symbol,
expansion.direction, edge_types, existing_node, view);
}
// Bind the expanded edge and node.
bound_symbols.insert(edge_symbol);
new_symbols.emplace_back(edge_symbol);
if (bound_symbols.insert(node_symbol).second) {
new_symbols.emplace_back(node_symbol);
}
// Ensure Cyphermorphism (different edge symbols always map to
// different edges).
for (const auto &edge_symbols : matching.edge_symbols) {
if (edge_symbols.find(edge_symbol) == edge_symbols.end()) {
continue;
}
std::vector<Symbol> other_symbols;
for (const auto &symbol : edge_symbols) {
if (symbol == edge_symbol || bound_symbols.find(symbol) == bound_symbols.end()) {
continue;
}
other_symbols.push_back(symbol);
}
if (!other_symbols.empty()) {
last_op = std::make_unique<EdgeUniquenessFilter>(std::move(last_op), edge_symbol, other_symbols);
}
}
last_op = GenFilters(std::move(last_op), bound_symbols, filters, storage, symbol_table);
last_op = impl::GenNamedPaths(std::move(last_op), bound_symbols, named_paths);
last_op = GenFilters(std::move(last_op), bound_symbols, filters, storage, symbol_table);
return last_op;
}
std::unique_ptr<LogicalOperator> HandleForeachClause(query::Foreach *foreach, std::unique_ptr<LogicalOperator> HandleForeachClause(query::Foreach *foreach,
std::unique_ptr<LogicalOperator> input_op, std::unique_ptr<LogicalOperator> input_op,
const SymbolTable &symbol_table, const SymbolTable &symbol_table,
@ -567,6 +596,64 @@ class RuleBasedPlanner {
return std::make_unique<plan::Foreach>(std::move(input_op), std::move(op), foreach->named_expression_->expression_, return std::make_unique<plan::Foreach>(std::move(input_op), std::move(op), foreach->named_expression_->expression_,
symbol); symbol);
} }
std::unique_ptr<LogicalOperator> GenFilters(std::unique_ptr<LogicalOperator> last_op,
const std::unordered_set<Symbol> &bound_symbols, Filters &filters,
AstStorage &storage, const SymbolTable &symbol_table) {
auto pattern_filters = ExtractPatternFilters(filters, symbol_table, storage, bound_symbols);
auto *filter_expr = impl::ExtractFilters(bound_symbols, filters, storage);
if (filter_expr) {
last_op = std::make_unique<Filter>(std::move(last_op), std::move(pattern_filters), filter_expr);
}
return last_op;
}
std::unique_ptr<LogicalOperator> MakeExistsFilter(const FilterMatching &matching, const SymbolTable &symbol_table,
AstStorage &storage,
const std::unordered_set<Symbol> &bound_symbols) {
std::vector<Symbol> once_symbols(bound_symbols.begin(), bound_symbols.end());
std::unique_ptr<LogicalOperator> last_op = std::make_unique<Once>(once_symbols);
std::vector<Symbol> new_symbols;
std::unordered_set<Symbol> expand_symbols(bound_symbols.begin(), bound_symbols.end());
auto filters = matching.filters;
std::unordered_map<Symbol, std::vector<Symbol>> named_paths;
last_op = HandleExpansion(std::move(last_op), matching, symbol_table, storage, expand_symbols, new_symbols,
named_paths, filters, storage::View::OLD);
last_op = std::make_unique<Limit>(std::move(last_op), storage.Create<PrimitiveLiteral>(1));
last_op = std::make_unique<EvaluatePatternFilter>(std::move(last_op), matching.symbol.value());
return last_op;
}
std::vector<std::shared_ptr<LogicalOperator>> ExtractPatternFilters(Filters &filters, const SymbolTable &symbol_table,
AstStorage &storage,
const std::unordered_set<Symbol> &bound_symbols) {
std::vector<std::shared_ptr<LogicalOperator>> operators;
for (const auto &filter : filters) {
for (const auto &matching : filter.matchings) {
if (!impl::HasBoundFilterSymbols(bound_symbols, filter)) {
continue;
}
switch (matching.type) {
case PatternFilterType::EXISTS: {
operators.push_back(MakeExistsFilter(matching, symbol_table, storage, bound_symbols));
break;
}
}
}
}
return operators;
}
}; };
} // namespace memgraph::query::plan } // namespace memgraph::query::plan

View File

@ -1,4 +1,4 @@
// Copyright 2022 Memgraph Ltd. // Copyright 2023 Memgraph Ltd.
// //
// Use of this software is governed by the Business Source License // 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 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@ -155,6 +155,18 @@ auto ExpansionNodes(const std::vector<Expansion> &expansions, const SymbolTable
return nodes; return nodes;
} }
FilterMatching ToFilterMatching(Matching &matching) {
FilterMatching filter_matching;
filter_matching.expansions = matching.expansions;
filter_matching.edge_symbols = matching.edge_symbols;
filter_matching.filters = matching.filters;
filter_matching.node_symbol_to_expansions = matching.node_symbol_to_expansions;
filter_matching.named_paths = matching.named_paths;
filter_matching.expansion_symbols = matching.expansion_symbols;
return filter_matching;
}
} // namespace } // namespace
VaryMatchingStart::VaryMatchingStart(Matching matching, const SymbolTable &symbol_table) VaryMatchingStart::VaryMatchingStart(Matching matching, const SymbolTable &symbol_table)
@ -209,11 +221,31 @@ CartesianProduct<VaryMatchingStart> VaryMultiMatchingStarts(const std::vector<Ma
return MakeCartesianProduct(std::move(variants)); return MakeCartesianProduct(std::move(variants));
} }
CartesianProduct<VaryMatchingStart> VaryFilterMatchingStarts(const Matching &matching,
const SymbolTable &symbol_table) {
auto filter_matchings_cnt = 0;
for (const auto &filter : matching.filters) {
filter_matchings_cnt += static_cast<int>(filter.matchings.size());
}
std::vector<VaryMatchingStart> variants;
variants.reserve(filter_matchings_cnt);
for (const auto &filter : matching.filters) {
for (const auto &filter_matching : filter.matchings) {
variants.emplace_back(filter_matching, symbol_table);
}
}
return MakeCartesianProduct(std::move(variants));
}
VaryQueryPartMatching::VaryQueryPartMatching(SingleQueryPart query_part, const SymbolTable &symbol_table) VaryQueryPartMatching::VaryQueryPartMatching(SingleQueryPart query_part, const SymbolTable &symbol_table)
: query_part_(std::move(query_part)), : query_part_(std::move(query_part)),
matchings_(VaryMatchingStart(query_part_.matching, symbol_table)), matchings_(VaryMatchingStart(query_part_.matching, symbol_table)),
optional_matchings_(VaryMultiMatchingStarts(query_part_.optional_matching, symbol_table)), optional_matchings_(VaryMultiMatchingStarts(query_part_.optional_matching, symbol_table)),
merge_matchings_(VaryMultiMatchingStarts(query_part_.merge_matching, symbol_table)) {} merge_matchings_(VaryMultiMatchingStarts(query_part_.merge_matching, symbol_table)),
filter_matchings_(VaryFilterMatchingStarts(query_part_.matching, symbol_table)) {}
VaryQueryPartMatching::iterator::iterator(const SingleQueryPart &query_part, VaryQueryPartMatching::iterator::iterator(const SingleQueryPart &query_part,
VaryMatchingStart::iterator matchings_begin, VaryMatchingStart::iterator matchings_begin,
@ -221,7 +253,9 @@ VaryQueryPartMatching::iterator::iterator(const SingleQueryPart &query_part,
CartesianProduct<VaryMatchingStart>::iterator optional_begin, CartesianProduct<VaryMatchingStart>::iterator optional_begin,
CartesianProduct<VaryMatchingStart>::iterator optional_end, CartesianProduct<VaryMatchingStart>::iterator optional_end,
CartesianProduct<VaryMatchingStart>::iterator merge_begin, CartesianProduct<VaryMatchingStart>::iterator merge_begin,
CartesianProduct<VaryMatchingStart>::iterator merge_end) CartesianProduct<VaryMatchingStart>::iterator merge_end,
CartesianProduct<VaryMatchingStart>::iterator filter_begin,
CartesianProduct<VaryMatchingStart>::iterator filter_end)
: current_query_part_(query_part), : current_query_part_(query_part),
matchings_it_(matchings_begin), matchings_it_(matchings_begin),
matchings_end_(matchings_end), matchings_end_(matchings_end),
@ -230,7 +264,10 @@ VaryQueryPartMatching::iterator::iterator(const SingleQueryPart &query_part,
optional_end_(optional_end), optional_end_(optional_end),
merge_it_(merge_begin), merge_it_(merge_begin),
merge_begin_(merge_begin), merge_begin_(merge_begin),
merge_end_(merge_end) { merge_end_(merge_end),
filter_it_(filter_begin),
filter_begin_(filter_begin),
filter_end_(filter_end) {
if (matchings_it_ != matchings_end_) { if (matchings_it_ != matchings_end_) {
// Fill the query part with the first variation of matchings // Fill the query part with the first variation of matchings
SetCurrentQueryPart(); SetCurrentQueryPart();
@ -242,26 +279,37 @@ VaryQueryPartMatching::iterator &VaryQueryPartMatching::iterator::operator++() {
// * matchings (m1) and (m2) // * matchings (m1) and (m2)
// * optional matchings (o1) and (o2) // * optional matchings (o1) and (o2)
// * merge matching (g1) // * merge matching (g1)
// * filter matching (f1) and (f2)
// We want to produce parts for: // We want to produce parts for:
// * (m1), (o1), (g1) // * (m1), (o1), (g1), (f1)
// * (m1), (o2), (g1) // * (m1), (o1), (g1), (f2)
// * (m2), (o1), (g1) // * (m1), (o2), (g1), (f1)
// * (m2), (o2), (g1) // * (m1), (o2), (g1), (f2)
// Create variations by changing the merge part first. // * (m2), (o1), (g1), (f1)
if (merge_it_ != merge_end_) ++merge_it_; // * (m2), (o1), (g1), (f2)
// If all merge variations are done, start them from beginning and move to the // * (m2), (o2), (g1), (f1)
// next optional matching variation. // * (m2), (o2), (g1), (f2)
if (merge_it_ == merge_end_) {
// Create variations by changing the filter part first.
if (filter_it_ != filter_end_) ++filter_it_;
// Create variations by changing the merge part.
if (filter_it_ == filter_end_) {
filter_it_ = filter_begin_;
if (merge_it_ != merge_end_) ++merge_it_;
}
// Create variations by changing the optional part.
if (merge_it_ == merge_end_ && filter_it_ == filter_begin_) {
merge_it_ = merge_begin_; merge_it_ = merge_begin_;
if (optional_it_ != optional_end_) ++optional_it_; if (optional_it_ != optional_end_) ++optional_it_;
} }
// If all optional matching variations are done (after exhausting merge
// variations), start them from beginning and move to the next regular if (optional_it_ == optional_end_ && merge_it_ == merge_begin_ && filter_it_ == filter_begin_) {
// matching variation.
if (optional_it_ == optional_end_ && merge_it_ == merge_begin_) {
optional_it_ = optional_begin_; optional_it_ = optional_begin_;
if (matchings_it_ != matchings_end_) ++matchings_it_; if (matchings_it_ != matchings_end_) ++matchings_it_;
} }
// We have reached the end, so return; // We have reached the end, so return;
if (matchings_it_ == matchings_end_) return *this; if (matchings_it_ == matchings_end_) return *this;
// Fill the query part with the new variation of matchings. // Fill the query part with the new variation of matchings.
@ -283,6 +331,28 @@ void VaryQueryPartMatching::iterator::SetCurrentQueryPart() {
if (merge_it_ != merge_end_) { if (merge_it_ != merge_end_) {
current_query_part_.merge_matching = *merge_it_; current_query_part_.merge_matching = *merge_it_;
} }
DMG_ASSERT(filter_it_ != filter_end_ || filter_begin_ == filter_end_,
"Either there are no filter matchings or we can always generate"
"a variation");
auto all_filter_matchings = *filter_it_;
auto all_filter_matchings_idx = 0;
for (auto &filter : current_query_part_.matching.filters) {
auto matchings_size = filter.matchings.size();
std::vector<FilterMatching> new_matchings;
new_matchings.reserve(matchings_size);
for (auto i = 0; i < matchings_size; i++) {
new_matchings.push_back(ToFilterMatching(all_filter_matchings[all_filter_matchings_idx]));
new_matchings[i].symbol = filter.matchings[i].symbol;
new_matchings[i].type = filter.matchings[i].type;
all_filter_matchings_idx++;
}
filter.matchings = std::move(new_matchings);
}
} }
bool VaryQueryPartMatching::iterator::operator==(const iterator &other) const { bool VaryQueryPartMatching::iterator::operator==(const iterator &other) const {
@ -291,7 +361,8 @@ bool VaryQueryPartMatching::iterator::operator==(const iterator &other) const {
// iterators can be at any position. // iterators can be at any position.
return true; return true;
} }
return matchings_it_ == other.matchings_it_ && optional_it_ == other.optional_it_ && merge_it_ == other.merge_it_; return matchings_it_ == other.matchings_it_ && optional_it_ == other.optional_it_ && merge_it_ == other.merge_it_ &&
filter_it_ == other.filter_it_;
} }
} // namespace memgraph::query::plan::impl } // namespace memgraph::query::plan::impl

View File

@ -1,4 +1,4 @@
// Copyright 2022 Memgraph Ltd. // Copyright 2023 Memgraph Ltd.
// //
// Use of this software is governed by the Business Source License // 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 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@ -230,6 +230,8 @@ class VaryMatchingStart {
// Cartesian product of all of them is returned. // Cartesian product of all of them is returned.
CartesianProduct<VaryMatchingStart> VaryMultiMatchingStarts(const std::vector<Matching> &, const SymbolTable &); CartesianProduct<VaryMatchingStart> VaryMultiMatchingStarts(const std::vector<Matching> &, const SymbolTable &);
CartesianProduct<VaryMatchingStart> VaryFilterMatchingStarts(const Matching &matching, const SymbolTable &symbol_table);
// Produces alternative query parts out of a single part by varying how each // Produces alternative query parts out of a single part by varying how each
// graph matching is done. // graph matching is done.
class VaryQueryPartMatching { class VaryQueryPartMatching {
@ -245,6 +247,7 @@ class VaryQueryPartMatching {
typedef const SingleQueryPart *pointer; typedef const SingleQueryPart *pointer;
iterator(const SingleQueryPart &, VaryMatchingStart::iterator, VaryMatchingStart::iterator, iterator(const SingleQueryPart &, VaryMatchingStart::iterator, VaryMatchingStart::iterator,
CartesianProduct<VaryMatchingStart>::iterator, CartesianProduct<VaryMatchingStart>::iterator,
CartesianProduct<VaryMatchingStart>::iterator, CartesianProduct<VaryMatchingStart>::iterator, CartesianProduct<VaryMatchingStart>::iterator, CartesianProduct<VaryMatchingStart>::iterator,
CartesianProduct<VaryMatchingStart>::iterator, CartesianProduct<VaryMatchingStart>::iterator); CartesianProduct<VaryMatchingStart>::iterator, CartesianProduct<VaryMatchingStart>::iterator);
@ -266,15 +269,20 @@ class VaryQueryPartMatching {
CartesianProduct<VaryMatchingStart>::iterator merge_it_; CartesianProduct<VaryMatchingStart>::iterator merge_it_;
CartesianProduct<VaryMatchingStart>::iterator merge_begin_; CartesianProduct<VaryMatchingStart>::iterator merge_begin_;
CartesianProduct<VaryMatchingStart>::iterator merge_end_; CartesianProduct<VaryMatchingStart>::iterator merge_end_;
CartesianProduct<VaryMatchingStart>::iterator filter_it_;
CartesianProduct<VaryMatchingStart>::iterator filter_begin_;
CartesianProduct<VaryMatchingStart>::iterator filter_end_;
}; };
auto begin() { auto begin() {
return iterator(query_part_, matchings_.begin(), matchings_.end(), optional_matchings_.begin(), return iterator(query_part_, matchings_.begin(), matchings_.end(), optional_matchings_.begin(),
optional_matchings_.end(), merge_matchings_.begin(), merge_matchings_.end()); optional_matchings_.end(), merge_matchings_.begin(), merge_matchings_.end(),
filter_matchings_.begin(), filter_matchings_.end());
} }
auto end() { auto end() {
return iterator(query_part_, matchings_.end(), matchings_.end(), optional_matchings_.end(), return iterator(query_part_, matchings_.end(), matchings_.end(), optional_matchings_.end(),
optional_matchings_.end(), merge_matchings_.end(), merge_matchings_.end()); optional_matchings_.end(), merge_matchings_.end(), merge_matchings_.end(), filter_matchings_.end(),
filter_matchings_.end());
} }
private: private:
@ -286,6 +294,7 @@ class VaryQueryPartMatching {
CartesianProduct<VaryMatchingStart> optional_matchings_; CartesianProduct<VaryMatchingStart> optional_matchings_;
// Like optional matching, but for merge matchings. // Like optional matching, but for merge matchings.
CartesianProduct<VaryMatchingStart> merge_matchings_; CartesianProduct<VaryMatchingStart> merge_matchings_;
CartesianProduct<VaryMatchingStart> filter_matchings_;
}; };
} // namespace impl } // namespace impl

View File

@ -1,4 +1,4 @@
// Copyright 2022 Memgraph Ltd. // Copyright 2023 Memgraph Ltd.
// //
// Use of this software is governed by the Business Source License // 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 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@ -146,10 +146,10 @@ struct mgp_date {
mgp_date(const memgraph::utils::Date &date, memgraph::utils::MemoryResource *memory) noexcept mgp_date(const memgraph::utils::Date &date, memgraph::utils::MemoryResource *memory) noexcept
: memory(memory), date(date) {} : memory(memory), date(date) {}
mgp_date(const std::string_view string, memgraph::utils::MemoryResource *memory) noexcept mgp_date(const std::string_view string, memgraph::utils::MemoryResource *memory)
: memory(memory), date(memgraph::utils::ParseDateParameters(string).first) {} : memory(memory), date(memgraph::utils::ParseDateParameters(string).first) {}
mgp_date(const mgp_date_parameters *parameters, memgraph::utils::MemoryResource *memory) noexcept mgp_date(const mgp_date_parameters *parameters, memgraph::utils::MemoryResource *memory)
: memory(memory), date(MapDateParameters(parameters)) {} : memory(memory), date(MapDateParameters(parameters)) {}
mgp_date(const int64_t microseconds, memgraph::utils::MemoryResource *memory) noexcept mgp_date(const int64_t microseconds, memgraph::utils::MemoryResource *memory) noexcept
@ -194,10 +194,10 @@ struct mgp_local_time {
// have everything noexcept here. // have everything noexcept here.
static_assert(std::is_nothrow_copy_constructible_v<memgraph::utils::LocalTime>); static_assert(std::is_nothrow_copy_constructible_v<memgraph::utils::LocalTime>);
mgp_local_time(const std::string_view string, memgraph::utils::MemoryResource *memory) noexcept mgp_local_time(const std::string_view string, memgraph::utils::MemoryResource *memory)
: memory(memory), local_time(memgraph::utils::ParseLocalTimeParameters(string).first) {} : memory(memory), local_time(memgraph::utils::ParseLocalTimeParameters(string).first) {}
mgp_local_time(const mgp_local_time_parameters *parameters, memgraph::utils::MemoryResource *memory) noexcept mgp_local_time(const mgp_local_time_parameters *parameters, memgraph::utils::MemoryResource *memory)
: memory(memory), local_time(MapLocalTimeParameters(parameters)) {} : memory(memory), local_time(MapLocalTimeParameters(parameters)) {}
mgp_local_time(const memgraph::utils::LocalTime &local_time, memgraph::utils::MemoryResource *memory) noexcept mgp_local_time(const memgraph::utils::LocalTime &local_time, memgraph::utils::MemoryResource *memory) noexcept
@ -250,8 +250,7 @@ struct mgp_local_date_time {
mgp_local_date_time(const std::string_view string, memgraph::utils::MemoryResource *memory) noexcept mgp_local_date_time(const std::string_view string, memgraph::utils::MemoryResource *memory) noexcept
: memory(memory), local_date_time(CreateLocalDateTimeFromString(string)) {} : memory(memory), local_date_time(CreateLocalDateTimeFromString(string)) {}
mgp_local_date_time(const mgp_local_date_time_parameters *parameters, mgp_local_date_time(const mgp_local_date_time_parameters *parameters, memgraph::utils::MemoryResource *memory)
memgraph::utils::MemoryResource *memory) noexcept
: memory(memory), : memory(memory),
local_date_time(MapDateParameters(parameters->date_parameters), local_date_time(MapDateParameters(parameters->date_parameters),
MapLocalTimeParameters(parameters->local_time_parameters)) {} MapLocalTimeParameters(parameters->local_time_parameters)) {}
@ -301,7 +300,7 @@ struct mgp_duration {
// have everything noexcept here. // have everything noexcept here.
static_assert(std::is_nothrow_copy_constructible_v<memgraph::utils::Duration>); static_assert(std::is_nothrow_copy_constructible_v<memgraph::utils::Duration>);
mgp_duration(const std::string_view string, memgraph::utils::MemoryResource *memory) noexcept mgp_duration(const std::string_view string, memgraph::utils::MemoryResource *memory)
: memory(memory), duration(memgraph::utils::ParseDurationParameters(string)) {} : memory(memory), duration(memgraph::utils::ParseDurationParameters(string)) {}
mgp_duration(const mgp_duration_parameters *parameters, memgraph::utils::MemoryResource *memory) noexcept mgp_duration(const mgp_duration_parameters *parameters, memgraph::utils::MemoryResource *memory) noexcept

View File

@ -1,4 +1,4 @@
// Copyright 2022 Memgraph Ltd. // Copyright 2023 Memgraph Ltd.
// //
// Use of this software is governed by the Business Source License // 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 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@ -123,6 +123,24 @@ Result<storage::PropertyValue> EdgeAccessor::SetProperty(PropertyId property, co
return std::move(current_value); return std::move(current_value);
} }
Result<bool> EdgeAccessor::InitProperties(const std::map<storage::PropertyId, storage::PropertyValue> &properties) {
utils::MemoryTracker::OutOfMemoryExceptionEnabler oom_exception;
if (!config_.properties_on_edges) return Error::PROPERTIES_DISABLED;
std::lock_guard<utils::SpinLock> guard(edge_.ptr->lock);
if (!PrepareForWrite(transaction_, edge_.ptr)) return Error::SERIALIZATION_ERROR;
if (edge_.ptr->deleted) return Error::DELETED_OBJECT;
if (!edge_.ptr->properties.InitProperties(properties)) return false;
for (const auto &[property, _] : properties) {
CreateAndLinkDelta(transaction_, edge_.ptr, Delta::SetPropertyTag(), property, PropertyValue());
}
return true;
}
Result<std::map<PropertyId, PropertyValue>> EdgeAccessor::ClearProperties() { Result<std::map<PropertyId, PropertyValue>> EdgeAccessor::ClearProperties() {
if (!config_.properties_on_edges) return Error::PROPERTIES_DISABLED; if (!config_.properties_on_edges) return Error::PROPERTIES_DISABLED;

View File

@ -1,4 +1,4 @@
// Copyright 2022 Memgraph Ltd. // Copyright 2023 Memgraph Ltd.
// //
// Use of this software is governed by the Business Source License // 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 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@ -58,6 +58,11 @@ class EdgeAccessor final {
/// @throw std::bad_alloc /// @throw std::bad_alloc
Result<storage::PropertyValue> SetProperty(PropertyId property, const PropertyValue &value); Result<storage::PropertyValue> SetProperty(PropertyId property, const PropertyValue &value);
/// Set property values only if property store is empty. Returns `true` if successully set all values,
/// `false` otherwise.
/// @throw std::bad_alloc
Result<bool> InitProperties(const std::map<storage::PropertyId, storage::PropertyValue> &properties);
/// Remove all properties and return old values for each removed property. /// Remove all properties and return old values for each removed property.
/// @throw std::bad_alloc /// @throw std::bad_alloc
Result<std::map<PropertyId, PropertyValue>> ClearProperties(); Result<std::map<PropertyId, PropertyValue>> ClearProperties();

View File

@ -1,4 +1,4 @@
// Copyright 2022 Memgraph Ltd. // Copyright 2023 Memgraph Ltd.
// //
// Use of this software is governed by the Business Source License // 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 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@ -1053,7 +1053,7 @@ bool PropertyStore::SetProperty(PropertyId property, const PropertyValue &value)
in_local_buffer = true; in_local_buffer = true;
} else { } else {
// Allocate a new external buffer. // Allocate a new external buffer.
auto alloc_data = new uint8_t[property_size_to_power_of_8]; auto *alloc_data = new uint8_t[property_size_to_power_of_8];
auto alloc_size = property_size_to_power_of_8; auto alloc_size = property_size_to_power_of_8;
SetSizeData(buffer_, alloc_size, alloc_data); SetSizeData(buffer_, alloc_size, alloc_data);
@ -1144,6 +1144,64 @@ bool PropertyStore::SetProperty(PropertyId property, const PropertyValue &value)
return !existed; return !existed;
} }
bool PropertyStore::InitProperties(const std::map<storage::PropertyId, storage::PropertyValue> &properties) {
uint64_t size = 0;
uint8_t *data = nullptr;
std::tie(size, data) = GetSizeData(buffer_);
if (size != 0) {
return false;
}
uint64_t property_size = 0;
{
Writer writer;
for (const auto &[property, value] : properties) {
if (value.IsNull()) {
continue;
}
EncodeProperty(&writer, property, value);
property_size = writer.Written();
}
}
auto property_size_to_power_of_8 = ToPowerOf8(property_size);
if (property_size <= sizeof(buffer_) - 1) {
// Use the local buffer.
buffer_[0] = kUseLocalBuffer;
size = sizeof(buffer_) - 1;
data = &buffer_[1];
} else {
// Allocate a new external buffer.
auto *alloc_data = new uint8_t[property_size_to_power_of_8];
auto alloc_size = property_size_to_power_of_8;
SetSizeData(buffer_, alloc_size, alloc_data);
size = alloc_size;
data = alloc_data;
}
// Encode the property into the data buffer.
Writer writer(data, size);
for (const auto &[property, value] : properties) {
if (value.IsNull()) {
continue;
}
MG_ASSERT(EncodeProperty(&writer, property, value), "Invalid database state!");
writer.Written();
}
auto metadata = writer.WriteMetadata();
if (metadata) {
// If there is any space left in the buffer we add a tombstone to
// indicate that there are no more properties to be decoded.
metadata->Set({Type::EMPTY});
}
return true;
}
bool PropertyStore::ClearProperties() { bool PropertyStore::ClearProperties() {
bool in_local_buffer = false; bool in_local_buffer = false;
uint64_t size; uint64_t size;

View File

@ -1,4 +1,4 @@
// Copyright 2022 Memgraph Ltd. // Copyright 2023 Memgraph Ltd.
// //
// Use of this software is governed by the Business Source License // 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 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@ -59,6 +59,12 @@ class PropertyStore {
/// @throw std::bad_alloc /// @throw std::bad_alloc
bool SetProperty(PropertyId property, const PropertyValue &value); bool SetProperty(PropertyId property, const PropertyValue &value);
/// Init property values and return `true` if insertion took place. `false` is
/// returned if there exists property in property store and insertion couldn't take place. The time complexity of this
/// function is O(n).
/// @throw std::bad_alloc
bool InitProperties(const std::map<storage::PropertyId, storage::PropertyValue> &properties);
/// Remove all properties and return `true` if any removal took place. /// Remove all properties and return `true` if any removal took place.
/// `false` is returned if there were no properties to remove. The time /// `false` is returned if there were no properties to remove. The time
/// complexity of this function is O(1). /// complexity of this function is O(1).

View File

@ -109,9 +109,11 @@ void Storage::ReplicationClient::InitializeClient() {
} }
if (branching_point) { if (branching_point) {
spdlog::error( spdlog::error(
"Replica {} cannot be used with this instance. Please start a clean " "You cannot register Replica {} to this Main because at one point "
"instance of Memgraph server on the specified endpoint.", "Replica {} acted as the Main instance. Both the Main and Replica {} "
name_); "now hold unique data. Please resolve data conflicts and start the "
"replication on a clean instance.",
name_, name_, name_);
return; return;
} }

View File

@ -1,4 +1,4 @@
// Copyright 2022 Memgraph Ltd. // Copyright 2023 Memgraph Ltd.
// //
// Use of this software is governed by the Business Source License // 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 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@ -230,6 +230,23 @@ Result<PropertyValue> VertexAccessor::SetProperty(PropertyId property, const Pro
return std::move(current_value); return std::move(current_value);
} }
Result<bool> VertexAccessor::InitProperties(const std::map<storage::PropertyId, storage::PropertyValue> &properties) {
utils::MemoryTracker::OutOfMemoryExceptionEnabler oom_exception;
std::lock_guard<utils::SpinLock> guard(vertex_->lock);
if (!PrepareForWrite(transaction_, vertex_)) return Error::SERIALIZATION_ERROR;
if (vertex_->deleted) return Error::DELETED_OBJECT;
if (!vertex_->properties.InitProperties(properties)) return false;
for (const auto &[property, value] : properties) {
CreateAndLinkDelta(transaction_, vertex_, Delta::SetPropertyTag(), property, PropertyValue());
UpdateOnSetProperty(indices_, property, value, vertex_, *transaction_);
}
return true;
}
Result<std::map<PropertyId, PropertyValue>> VertexAccessor::ClearProperties() { Result<std::map<PropertyId, PropertyValue>> VertexAccessor::ClearProperties() {
std::lock_guard<utils::SpinLock> guard(vertex_->lock); std::lock_guard<utils::SpinLock> guard(vertex_->lock);

View File

@ -1,4 +1,4 @@
// Copyright 2022 Memgraph Ltd. // Copyright 2023 Memgraph Ltd.
// //
// Use of this software is governed by the Business Source License // 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 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@ -68,6 +68,11 @@ class VertexAccessor final {
/// @throw std::bad_alloc /// @throw std::bad_alloc
Result<PropertyValue> SetProperty(PropertyId property, const PropertyValue &value); Result<PropertyValue> SetProperty(PropertyId property, const PropertyValue &value);
/// Set property values only if property store is empty. Returns `true` if successully set all values,
/// `false` otherwise.
/// @throw std::bad_alloc
Result<bool> InitProperties(const std::map<storage::PropertyId, storage::PropertyValue> &properties);
/// Remove all properties and return the values of the removed properties. /// Remove all properties and return the values of the removed properties.
/// @throw std::bad_alloc /// @throw std::bad_alloc
Result<std::map<PropertyId, PropertyValue>> ClearProperties(); Result<std::map<PropertyId, PropertyValue>> ClearProperties();

View File

@ -1,4 +1,4 @@
// Copyright 2022 Memgraph Ltd. // Copyright 2023 Memgraph Ltd.
// //
// Use of this software is governed by the Business Source License // 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 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@ -51,6 +51,7 @@
M(CartesianOperator, "Number of times Cartesian operator was used.") \ M(CartesianOperator, "Number of times Cartesian operator was used.") \
M(CallProcedureOperator, "Number of times CallProcedure operator was used.") \ M(CallProcedureOperator, "Number of times CallProcedure operator was used.") \
M(ForeachOperator, "Number of times Foreach operator was used.") \ M(ForeachOperator, "Number of times Foreach operator was used.") \
M(EvaluatePatternFilterOperator, "Number of times EvaluatePatternFilter operator was used.") \
\ \
M(FailedQuery, "Number of times executing a query failed.") \ M(FailedQuery, "Number of times executing a query failed.") \
M(LabelIndexCreated, "Number of times a label index was created.") \ M(LabelIndexCreated, "Number of times a label index was created.") \

View File

@ -585,12 +585,12 @@ Feature: Functions
""" """
When executing query: When executing query:
""" """
MATCH ()-[r]->() RETURN PROPERTIES(r) AS p MATCH ()-[r]->() RETURN PROPERTIES(r) AS p ORDER BY p.prop;
""" """
Then the result should be: Then the result should be:
| p | | p |
| {b: true} |
| {} | | {} |
| {b: true} |
| {c: 123} | | {c: 123} |
Scenario: Properties test2: Scenario: Properties test2:

View File

@ -0,0 +1,529 @@
Feature: WHERE exists
Scenario: Test exists with empty edge and node specifiers
Given an empty graph
And having executed:
"""
CREATE (:One {prop:1})-[:TYPE]->(:Two)
"""
When executing query:
"""
MATCH (n:One) WHERE exists((n)-[]-()) RETURN n.prop;
"""
Then the result should be:
| n.prop |
| 1 |
Scenario: Test exists with empty edge and node specifiers return 2 entries
Given an empty graph
And having executed:
"""
CREATE (:One {prop:1})-[:TYPE]->(:Two), (:One {prop: 3})-[:TYPE]->(:Two)
"""
When executing query:
"""
MATCH (n:One) WHERE exists((n)-[]-()) RETURN n.prop ORDER BY n.prop;
"""
Then the result should be:
| n.prop |
| 1 |
| 3 |
Scenario: Test exists with edge specifier
Given an empty graph
And having executed:
"""
CREATE (:One {prop:1})-[:TYPE]->(:Two)
"""
When executing query:
"""
MATCH (n:One) WHERE exists((n)-[:TYPE]-()) RETURN n.prop;
"""
Then the result should be:
| n.prop |
| 1 |
Scenario: Test exists with wrong edge specifier
Given an empty graph
And having executed:
"""
CREATE (:One {prop:1})-[:TYPE]->(:Two)
"""
When executing query:
"""
MATCH (n:One) WHERE exists((n)-[:TYPE2]-()) RETURN n.prop;
"""
Then the result should be empty
Scenario: Test exists with correct edge direction
Given an empty graph
And having executed:
"""
CREATE (:One {prop:1})-[:TYPE]->(:Two)
"""
When executing query:
"""
MATCH (n:One) WHERE exists((n)-[:TYPE]->()) RETURN n.prop;
"""
Then the result should be:
| n.prop |
| 1 |
Scenario: Test exists with wrong edge direction
Given an empty graph
And having executed:
"""
CREATE (:One {prop:1})-[:TYPE]->(:Two)
"""
When executing query:
"""
MATCH (n:One) WHERE exists((n)<-[:TYPE]-()) RETURN n.prop;
"""
Then the result should be empty
Scenario: Test exists with destination node label
Given an empty graph
And having executed:
"""
CREATE (:One {prop:1})-[:TYPE]->(:Two)
"""
When executing query:
"""
MATCH (n:One) WHERE exists((n)-[]->(:Two)) RETURN n.prop;
"""
Then the result should be:
| n.prop |
| 1 |
Scenario: Test exists with wrong destination node label
Given an empty graph
And having executed:
"""
CREATE (:One {prop:1})-[:TYPE]->(:Two)
"""
When executing query:
"""
MATCH (n:One) WHERE exists((n)-[]->(:Three)) RETURN n.prop;
"""
Then the result should be empty
Scenario: Test exists with destination node property
Given an empty graph
And having executed:
"""
CREATE (:One {prop:1})-[:TYPE]->(:Two {prop: 2})
"""
When executing query:
"""
MATCH (n:One) WHERE exists((n)-[]->({prop: 2})) RETURN n.prop;
"""
Then the result should be:
| n.prop |
| 1 |
Scenario: Test exists with wrong destination node property
Given an empty graph
And having executed:
"""
CREATE (:One {prop:1})-[:TYPE]->(:Two {prop: 2})
"""
When executing query:
"""
MATCH (n:One) WHERE exists((n)-[]->({prop: 3})) RETURN n.prop;
"""
Then the result should be empty
Scenario: Test exists with edge property
Given an empty graph
And having executed:
"""
CREATE (:One {prop:1})-[:TYPE {prop: 1}]->(:Two {prop: 2})
"""
When executing query:
"""
MATCH (n:One) WHERE exists((n)-[:TYPE {prop: 1}]->()) RETURN n.prop;
"""
Then the result should be:
| n.prop |
| 1 |
Scenario: Test exists with wrong edge property
Given an empty graph
And having executed:
"""
CREATE (:One {prop:1})-[:TYPE {prop: 1}]->(:Two {prop: 2})
"""
When executing query:
"""
MATCH (n:One) WHERE exists((n)-[:TYPE {prop: 2}]->()) RETURN n.prop;
"""
Then the result should be empty
Scenario: Test exists with both edge property and node label property
Given an empty graph
And having executed:
"""
CREATE (:One {prop:1})-[:TYPE {prop: 1}]->(:Two {prop: 2})
"""
When executing query:
"""
MATCH (n:One) WHERE exists((n)-[:TYPE {prop: 1}]->(:Two {prop: 2})) RETURN n.prop;
"""
Then the result should be:
| n.prop |
| 1 |
Scenario: Test exists with correct edge property and wrong node label property
Given an empty graph
And having executed:
"""
CREATE (:One {prop:1})-[:TYPE {prop: 1}]->(:Two {prop: 2})
"""
When executing query:
"""
MATCH (n:One) WHERE exists((n)-[:TYPE {prop: 1}]->(:Two {prop: 3})) RETURN n.prop;
"""
Then the result should be empty
Scenario: Test exists with wrong edge property and correct node label property
Given an empty graph
And having executed:
"""
CREATE (:One {prop:1})-[:TYPE {prop: 1}]->(:Two {prop: 2})
"""
When executing query:
"""
MATCH (n:One) WHERE exists((n)-[:TYPE {prop: 2}]->(:Two {prop:2})) RETURN n.prop;
"""
Then the result should be empty
Scenario: Test exists with wrong edge property and wrong node label property
Given an empty graph
And having executed:
"""
CREATE (:One {prop:1})-[:TYPE {prop: 1}]->(:Two {prop: 2})
"""
When executing query:
"""
MATCH (n:One) WHERE exists((n)-[:TYPE {prop: 2}]->(:Two {prop:3})) RETURN n.prop;
"""
Then the result should be empty
Scenario: Test exists AND exists
Given an empty graph
And having executed:
"""
CREATE (:One {prop:1})-[:TYPE {prop: 1}]->(:Two {prop: 2})
"""
When executing query:
"""
MATCH (n:One) WHERE exists((n)-[:TYPE]->()) AND exists((n)-[]->(:Two)) RETURN n.prop;
"""
Then the result should be:
| n.prop |
| 1 |
Scenario: Test exists OR exists first condition
Given an empty graph
And having executed:
"""
CREATE (:One {prop:1})-[:TYPE {prop: 1}]->(:Two {prop: 2})
"""
When executing query:
"""
MATCH (n:One) WHERE exists((n)-[:TYPE]->()) OR exists((n)-[]->(:Three)) RETURN n.prop;
"""
Then the result should be:
| n.prop |
| 1 |
Scenario: Test exists OR exists second condition
Given an empty graph
And having executed:
"""
CREATE (:One {prop:1})-[:TYPE {prop: 1}]->(:Two {prop: 2})
"""
When executing query:
"""
MATCH (n:One) WHERE exists((n)-[:TYPE2]->()) OR exists((n)-[]->(:Two)) RETURN n.prop;
"""
Then the result should be:
| n.prop |
| 1 |
Scenario: Test exists OR exists fail
Given an empty graph
And having executed:
"""
CREATE (:One {prop:1})-[:TYPE {prop: 1}]->(:Two {prop: 2})
"""
When executing query:
"""
MATCH (n:One) WHERE exists((n)-[:TYPE2]->()) OR exists((n)-[]->(:Three)) RETURN n.prop;
"""
Then the result should be empty
Scenario: Test NOT exists
Given an empty graph
And having executed:
"""
CREATE (:One {prop:1})-[:TYPE {prop: 1}]->(:Two {prop: 2})
"""
When executing query:
"""
MATCH (n:One) WHERE NOT exists((n)-[:TYPE2]->()) RETURN n.prop;
"""
Then the result should be:
| n.prop |
| 1 |
Scenario: Test multi-hop first in sequence
Given an empty graph
And having executed:
"""
CREATE (:One {prop:1})-[:TYPE {prop: 1}]->(:Two {prop: 2})-[:TYPE {prop:2}]->(:Three {prop: 3})
"""
When executing query:
"""
MATCH (n) WHERE exists((n)-[]->()-[]->()) RETURN n.prop;
"""
Then the result should be:
| n.prop |
| 1 |
Scenario: Test multi-hop in middle sequence
Given an empty graph
And having executed:
"""
CREATE (:One {prop:1})-[:TYPE {prop: 1}]->(:Two {prop: 2})-[:TYPE {prop:2}]->(:Three {prop: 3})
"""
When executing query:
"""
MATCH (n) WHERE exists(()-[]->(n)-[]->()) RETURN n.prop;
"""
Then the result should be:
| n.prop |
| 2 |
Scenario: Test multi-hop at the end of the sequence
Given an empty graph
And having executed:
"""
CREATE (:One {prop:1})-[:TYPE {prop: 1}]->(:Two {prop: 2})-[:TYPE {prop:2}]->(:Three {prop: 3})
"""
When executing query:
"""
MATCH (n) WHERE exists(()-[]->()-[]->(n)) RETURN n.prop;
"""
Then the result should be:
| n.prop |
| 3 |
Scenario: Test multi-hop not exists
Given an empty graph
And having executed:
"""
CREATE (:One {prop:1})-[:TYPE {prop: 1}]->(:Two {prop: 2})-[:TYPE {prop:2}]->(:Three {prop: 3})
"""
When executing query:
"""
MATCH (n) WHERE exists(()-[]->(n)<-[]-()) RETURN n.prop;
"""
Then the result should be empty
Scenario: Test multi-hop with filters
Given an empty graph
And having executed:
"""
CREATE (:One {prop:1})-[:TYPE {prop: 1}]->(:Two {prop: 2})-[:TYPE {prop:2}]->(:Three {prop: 3})
"""
When executing query:
"""
MATCH (n) WHERE exists(({prop: 1})-[:TYPE]->(n)-[{prop:2}]->(:Three)) RETURN n.prop;
"""
Then the result should be:
| n.prop |
| 2 |
Scenario: Test multi-hop with wrong filters
Given an empty graph
And having executed:
"""
CREATE (:One {prop:1})-[:TYPE {prop: 1}]->(:Two {prop: 2})-[:TYPE {prop:2}]->(:Three {prop: 3})
"""
When executing query:
"""
MATCH (n) WHERE exists(({prop: 1})-[:TYPE]->(n)-[:TYPE2]->(:Three)) RETURN n.prop;
"""
Then the result should be empty
Scenario: Test node-only hop
Given an empty graph
And having executed:
"""
CREATE (:One {prop:1})-[:TYPE {prop: 1}]->(:Two {prop: 2})-[:TYPE {prop:2}]->(:Three {prop: 3})
"""
When executing query:
"""
MATCH (n) WHERE exists((n)) RETURN n.prop;
"""
Then the result should be:
| n.prop |
| 1 |
| 2 |
| 3 |
Scenario: Test exists with different edge type
Given an empty graph
And having executed:
"""
CREATE (:One {prop:1})-[:TYPE]->(:Two)
"""
When executing query:
"""
MATCH (n:One) WHERE exists((n)-[:TYPE2]->()) RETURN n.prop;
"""
Then the result should be empty
Scenario: Test exists with correct edge type multiple edges
Given an empty graph
And having executed:
"""
CREATE (:One {prop:1})-[:TYPE]->(:Two {prop: 10}), (:One {prop: 2})-[:TYPE]->(:Two {prop: 11});
"""
When executing query:
"""
MATCH (n:Two) WHERE exists((n)<-[:TYPE]-()) RETURN n.prop ORDER BY n.prop;
"""
Then the result should be:
| n.prop |
| 10 |
| 11 |
Scenario: Test exists does not work in WITH clauses
Given an empty graph
And having executed:
"""
CREATE (:One {prop:1})-[:TYPE]->(:Two);
"""
When executing query:
"""
MATCH (n:Two) WITH n WHERE exists((n)<-[:TYPE]-()) RETURN n.prop;
"""
Then an error should be raised
Scenario: Test exists is not null
Given an empty graph
And having executed:
"""
CREATE (:One {prop:1})-[:TYPE]->(:Two);
"""
When executing query:
"""
MATCH (n:One) WHERE exists((n)-[]-()) is not null RETURN n.prop;
"""
Then the result should be:
| n.prop |
| 1 |
Scenario: Test exists is null
Given an empty graph
And having executed:
"""
CREATE (:One {prop:1})-[:TYPE]->(:Two);
"""
When executing query:
"""
MATCH (n:One) WHERE exists((n)-[]-()) is null RETURN n.prop;
"""
Then the result should be empty
Scenario: Test exists equal to true
Given an empty graph
And having executed:
"""
CREATE (:One {prop:1})-[:TYPE]->(:Two);
"""
When executing query:
"""
MATCH (n:One) WHERE exists((n)-[]-()) = true RETURN n.prop;
"""
Then the result should be:
| n.prop |
| 1 |
Scenario: Test exists equal to true
Given an empty graph
And having executed:
"""
CREATE (:One {prop:1})-[:TYPE]->(:Two);
"""
When executing query:
"""
MATCH (n:One) WHERE exists((n)-[]-()) = false RETURN n.prop;
"""
Then the result should be empty
Scenario: Test exists in list
Given an empty graph
And having executed:
"""
CREATE (:One {prop:1})-[:TYPE]->(:Two);
"""
When executing query:
"""
MATCH (n:One) WHERE exists((n)-[]-()) in [true] RETURN n.prop;
"""
Then the result should be:
| n.prop |
| 1 |
Scenario: Test BFS hop
Given an empty graph
And having executed:
"""
CREATE (:One {prop:1})-[:TYPE {prop: 1}]->(:Two {prop: 2})-[:TYPE {prop:2}]->(:Three {prop: 3})
"""
When executing query:
"""
MATCH (n:One) WHERE exists((n)-[*bfs]->(:Three)) RETURN n.prop;
"""
Then the result should be:
| n.prop |
| 1 |
Scenario: Test exists not in list
Given an empty graph
And having executed:
"""
CREATE (:One {prop:1})-[:TYPE]->(:Two);
"""
When executing query:
"""
MATCH (n:One) WHERE exists((n)-[]-()) in [false] RETURN n.prop;
"""
Then the result should be empty
Scenario: Test exists on multihop patterns without results
Given an empty graph
And having executed:
"""
MATCH (n) DETACH DELETE n;
"""
When executing query:
"""
MATCH ()-[]-(m)-[]->(a) WHERE m.prop=1 and a.prop=3 and exists(()-[]->(m)) RETURN m, a;
"""
Then the result should be empty
Scenario: Test exists does not work in SetProperty clauses
Given an empty graph
And having executed:
"""
CREATE (:One {prop:1})-[:TYPE]->(:Two);
"""
When executing query:
"""
MATCH (n:Two) SET n.prop = exists((n)<-[:TYPE]-()) RETURN n.prop;
"""
Then an error should be raised

View File

@ -389,6 +389,10 @@ TEST_F(CppApiTestFixture, TestLocalDateTime) {
auto ldt_1 = mgp::LocalDateTime("2021-10-05T14:15:00"); auto ldt_1 = mgp::LocalDateTime("2021-10-05T14:15:00");
auto ldt_2 = mgp::LocalDateTime(2021, 10, 5, 14, 15, 0, 0, 0); auto ldt_2 = mgp::LocalDateTime(2021, 10, 5, 14, 15, 0, 0, 0);
ASSERT_ANY_THROW(mgp::LocalDateTime(
2021, 10, 0, 14, 15, 0, 0,
0)); // ...10, 0, 14... <- 0 is an illegal value for the `day` parameter; must throw an exception
ASSERT_EQ(ldt_1.Year(), 2021); ASSERT_EQ(ldt_1.Year(), 2021);
ASSERT_EQ(ldt_1.Month(), 10); ASSERT_EQ(ldt_1.Month(), 10);
ASSERT_EQ(ldt_1.Day(), 5); ASSERT_EQ(ldt_1.Day(), 5);

View File

@ -1,4 +1,4 @@
// Copyright 2022 Memgraph Ltd. // Copyright 2023 Memgraph Ltd.
// //
// Use of this software is governed by the Business Source License // 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 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@ -4307,3 +4307,75 @@ TEST_P(CypherMainVisitorTest, Foreach) {
ASSERT_TRUE(dynamic_cast<RemoveProperty *>(*++clauses.begin())); ASSERT_TRUE(dynamic_cast<RemoveProperty *>(*++clauses.begin()));
} }
} }
TEST_P(CypherMainVisitorTest, ExistsThrow) {
auto &ast_generator = *GetParam();
TestInvalidQueryWithMessage<SyntaxException>("MATCH (n) WHERE exists(p=(n)-[]->()) RETURN n;", ast_generator,
"Identifiers are not supported in exists(...).");
}
TEST_P(CypherMainVisitorTest, Exists) {
auto &ast_generator = *GetParam();
{
const auto *query =
dynamic_cast<CypherQuery *>(ast_generator.ParseQuery("MATCH (n) WHERE exists((n)-[]->()) RETURN n;"));
const auto *match = dynamic_cast<Match *>(query->single_query_->clauses_[0]);
const auto *exists = dynamic_cast<Exists *>(match->where_->expression_);
ASSERT_TRUE(exists);
const auto pattern = exists->pattern_;
ASSERT_TRUE(pattern->atoms_.size() == 3);
const auto *node1 = dynamic_cast<NodeAtom *>(pattern->atoms_[0]);
const auto *edge = dynamic_cast<EdgeAtom *>(pattern->atoms_[1]);
const auto *node2 = dynamic_cast<NodeAtom *>(pattern->atoms_[2]);
ASSERT_TRUE(node1);
ASSERT_TRUE(edge);
ASSERT_TRUE(node2);
}
{
const auto *query =
dynamic_cast<CypherQuery *>(ast_generator.ParseQuery("MATCH (n) WHERE exists((n)-[]->()-[]->()) RETURN n;"));
const auto *match = dynamic_cast<Match *>(query->single_query_->clauses_[0]);
const auto *exists = dynamic_cast<Exists *>(match->where_->expression_);
ASSERT_TRUE(exists);
const auto pattern = exists->pattern_;
ASSERT_TRUE(pattern->atoms_.size() == 5);
const auto *node1 = dynamic_cast<NodeAtom *>(pattern->atoms_[0]);
const auto *edge = dynamic_cast<EdgeAtom *>(pattern->atoms_[1]);
const auto *node2 = dynamic_cast<NodeAtom *>(pattern->atoms_[2]);
const auto *edge2 = dynamic_cast<EdgeAtom *>(pattern->atoms_[3]);
const auto *node3 = dynamic_cast<NodeAtom *>(pattern->atoms_[4]);
ASSERT_TRUE(node1);
ASSERT_TRUE(edge);
ASSERT_TRUE(node2);
ASSERT_TRUE(edge2);
ASSERT_TRUE(node3);
}
{
const auto *query = dynamic_cast<CypherQuery *>(ast_generator.ParseQuery("MATCH (n) WHERE exists((n)) RETURN n;"));
const auto *match = dynamic_cast<Match *>(query->single_query_->clauses_[0]);
const auto *exists = dynamic_cast<Exists *>(match->where_->expression_);
ASSERT_TRUE(exists);
const auto pattern = exists->pattern_;
ASSERT_TRUE(pattern->atoms_.size() == 1);
const auto *node = dynamic_cast<NodeAtom *>(pattern->atoms_[0]);
ASSERT_TRUE(node);
}
}

View File

@ -1,4 +1,4 @@
// Copyright 2022 Memgraph Ltd. // Copyright 2023 Memgraph Ltd.
// //
// Use of this software is governed by the Business Source License // 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 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@ -378,7 +378,8 @@ TEST_F(PrintToJsonTest, ConstructNamedPath) {
TEST_F(PrintToJsonTest, Filter) { TEST_F(PrintToJsonTest, Filter) {
std::shared_ptr<LogicalOperator> last_op = std::make_shared<ScanAll>(nullptr, GetSymbol("node1")); std::shared_ptr<LogicalOperator> last_op = std::make_shared<ScanAll>(nullptr, GetSymbol("node1"));
last_op = std::make_shared<Filter>(last_op, EQ(PROPERTY_LOOKUP("node1", dba.NameToProperty("prop")), LITERAL(5))); last_op = std::make_shared<Filter>(last_op, std::vector<std::shared_ptr<LogicalOperator>>{},
EQ(PROPERTY_LOOKUP("node1", dba.NameToProperty("prop")), LITERAL(5)));
Check(last_op.get(), R"sep( Check(last_op.get(), R"sep(
{ {
@ -995,3 +996,55 @@ TEST_F(PrintToJsonTest, Foreach) {
} }
})sep"); })sep");
} }
TEST_F(PrintToJsonTest, Exists) {
Symbol x = GetSymbol("x");
Symbol e = GetSymbol("edge");
Symbol n = GetSymbol("node");
Symbol output = GetSymbol("output_symbol");
std::shared_ptr<LogicalOperator> last_op = std::make_shared<ScanAll>(nullptr, x);
std::shared_ptr<LogicalOperator> expand = std::make_shared<Expand>(
nullptr, x, n, e, memgraph::query::EdgeAtom::Direction::BOTH,
std::vector<memgraph::storage::EdgeTypeId>{dba.NameToEdgeType("EdgeType1")}, false, memgraph::storage::View::OLD);
std::shared_ptr<LogicalOperator> limit = std::make_shared<Limit>(expand, LITERAL(1));
std::shared_ptr<LogicalOperator> evaluate_pattern_filter = std::make_shared<EvaluatePatternFilter>(limit, output);
last_op = std::make_shared<Filter>(
last_op, std::vector<std::shared_ptr<LogicalOperator>>{evaluate_pattern_filter},
EXISTS(PATTERN(NODE("x"), EDGE("edge", memgraph::query::EdgeAtom::Direction::BOTH, {}, false),
NODE("node", std::nullopt, false))));
Check(last_op.get(), R"sep(
{
"expression": "(Exists expression)",
"input": {
"input": {
"name": "Once"
},
"name": "ScanAll",
"output_symbol": "x"
},
"name": "Filter",
"pattern_filter1": {
"input": {
"expression": "1",
"input": {
"direction": "both",
"edge_symbol": "edge",
"edge_types": [
"EdgeType1"
],
"existing_node": false,
"input": {
"name": "Once"
},
"input_symbol": "x",
"name": "Expand",
"node_symbol": "node"
},
"name": "Limit"
},
"name": "EvaluatePatternFilter",
"output_symbol": "output_symbol"
}
})sep");
}

View File

@ -1,4 +1,4 @@
// Copyright 2022 Memgraph Ltd. // Copyright 2023 Memgraph Ltd.
// //
// Use of this software is governed by the Business Source License // 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 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@ -157,13 +157,13 @@ auto GetPropertyLookup(AstStorage &storage, TDbAccessor &, Expression *expr,
/// ///
/// Name is used to create the Identifier which is assigned to the edge. /// Name is used to create the Identifier which is assigned to the edge.
auto GetEdge(AstStorage &storage, const std::string &name, EdgeAtom::Direction dir = EdgeAtom::Direction::BOTH, auto GetEdge(AstStorage &storage, const std::string &name, EdgeAtom::Direction dir = EdgeAtom::Direction::BOTH,
const std::vector<std::string> &edge_types = {}) { const std::vector<std::string> &edge_types = {}, const bool user_declared = true) {
std::vector<EdgeTypeIx> types; std::vector<EdgeTypeIx> types;
types.reserve(edge_types.size()); types.reserve(edge_types.size());
for (const auto &type : edge_types) { for (const auto &type : edge_types) {
types.push_back(storage.GetEdgeTypeIx(type)); types.push_back(storage.GetEdgeTypeIx(type));
} }
return storage.Create<EdgeAtom>(storage.Create<Identifier>(name), EdgeAtom::Type::SINGLE, dir, types); return storage.Create<EdgeAtom>(storage.Create<Identifier>(name, user_declared), EdgeAtom::Type::SINGLE, dir, types);
} }
/// Create a variable length expansion EdgeAtom with given name, direction and /// Create a variable length expansion EdgeAtom with given name, direction and
@ -205,8 +205,9 @@ auto GetEdgeVariable(AstStorage &storage, const std::string &name, EdgeAtom::Typ
/// Create a NodeAtom with given name and label. /// Create a NodeAtom with given name and label.
/// ///
/// Name is used to create the Identifier which is assigned to the node. /// Name is used to create the Identifier which is assigned to the node.
auto GetNode(AstStorage &storage, const std::string &name, std::optional<std::string> label = std::nullopt) { auto GetNode(AstStorage &storage, const std::string &name, std::optional<std::string> label = std::nullopt,
auto node = storage.Create<NodeAtom>(storage.Create<Identifier>(name)); const bool user_declared = true) {
auto node = storage.Create<NodeAtom>(storage.Create<Identifier>(name, user_declared));
if (label) node->labels_.emplace_back(storage.GetLabelIx(*label)); if (label) node->labels_.emplace_back(storage.GetLabelIx(*label));
return node; return node;
} }
@ -586,6 +587,7 @@ auto GetForeach(AstStorage &storage, NamedExpression *named_expr, const std::vec
#define COALESCE(...) storage.Create<memgraph::query::Coalesce>(std::vector<memgraph::query::Expression *>{__VA_ARGS__}) #define COALESCE(...) storage.Create<memgraph::query::Coalesce>(std::vector<memgraph::query::Expression *>{__VA_ARGS__})
#define EXTRACT(variable, list, expr) \ #define EXTRACT(variable, list, expr) \
storage.Create<memgraph::query::Extract>(storage.Create<memgraph::query::Identifier>(variable), list, expr) storage.Create<memgraph::query::Extract>(storage.Create<memgraph::query::Identifier>(variable), list, expr)
#define EXISTS(pattern) storage.Create<memgraph::query::Exists>(pattern)
#define AUTH_QUERY(action, user, role, user_or_role, password, privileges, labels, edgeTypes) \ #define AUTH_QUERY(action, user, role, user_or_role, password, privileges, labels, edgeTypes) \
storage.Create<memgraph::query::AuthQuery>((action), (user), (role), (user_or_role), password, (privileges), \ storage.Create<memgraph::query::AuthQuery>((action), (user), (role), (user_or_role), password, (privileges), \
(labels), (edgeTypes)) (labels), (edgeTypes))

View File

@ -1,4 +1,4 @@
// Copyright 2022 Memgraph Ltd. // Copyright 2023 Memgraph Ltd.
// //
// Use of this software is governed by the Business Source License // 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 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@ -204,7 +204,8 @@ TEST_F(QueryCostEstimator, Foreach) {
EXPECT_COST(OP_COST_PARAM + OP_CARD_PARAM * OP_COST_PARAM); EXPECT_COST(OP_COST_PARAM + OP_CARD_PARAM * OP_COST_PARAM);
TEST_F(QueryCostEstimator, Filter) { TEST_F(QueryCostEstimator, Filter) {
TEST_OP(MakeOp<Filter>(last_op_, Literal(true)), CostParam::kFilter, CardParam::kFilter); TEST_OP(MakeOp<Filter>(last_op_, std::vector<std::shared_ptr<LogicalOperator>>{}, Literal(true)), CostParam::kFilter,
CardParam::kFilter);
} }
TEST_F(QueryCostEstimator, EdgeUniquenessFilter) { TEST_F(QueryCostEstimator, EdgeUniquenessFilter) {

View File

@ -1735,4 +1735,119 @@ TYPED_TEST(TestPlanner, Foreach) {
DeleteListContent(&on_create); DeleteListContent(&on_create);
} }
} }
TYPED_TEST(TestPlanner, Exists) {
AstStorage storage;
FakeDbAccessor dba;
// MATCH (n) WHERE exists((n)-[]-())
{
auto *query = QUERY(SINGLE_QUERY(
MATCH(PATTERN(NODE("n"))),
WHERE(EXISTS(PATTERN(NODE("n"), EDGE("edge", memgraph::query::EdgeAtom::Direction::BOTH, {}, false),
NODE("node", std::nullopt, false)))),
RETURN("n")));
auto symbol_table = memgraph::query::MakeSymbolTable(query);
auto planner = MakePlanner<TypeParam>(&dba, storage, symbol_table, query);
std::list<BaseOpChecker *> pattern_filter{new ExpectExpand(), new ExpectLimit(), new ExpectEvaluatePatternFilter()};
CheckPlan(planner.plan(), symbol_table, ExpectScanAll(),
ExpectFilter(std::vector<std::list<BaseOpChecker *>>{pattern_filter}), ExpectProduce());
DeleteListContent(&pattern_filter);
}
// MATCH (n) WHERE exists((n)-[:TYPE]-(:Two))
{
auto *query = QUERY(SINGLE_QUERY(
MATCH(PATTERN(NODE("n"))),
WHERE(EXISTS(PATTERN(NODE("n"), EDGE("edge", memgraph::query::EdgeAtom::Direction::BOTH, {"TYPE"}, false),
NODE("node", "Two", false)))),
RETURN("n")));
auto symbol_table = memgraph::query::MakeSymbolTable(query);
auto planner = MakePlanner<TypeParam>(&dba, storage, symbol_table, query);
std::list<BaseOpChecker *> pattern_filter{new ExpectExpand(), new ExpectFilter(), new ExpectLimit(),
new ExpectEvaluatePatternFilter()};
CheckPlan(planner.plan(), symbol_table, ExpectScanAll(),
ExpectFilter(std::vector<std::list<BaseOpChecker *>>{pattern_filter}), ExpectProduce());
DeleteListContent(&pattern_filter);
}
// MATCH (n) WHERE exists((n)-[:TYPE]-(:Two)) AND exists((n)-[]-())
{
auto *query = QUERY(SINGLE_QUERY(
MATCH(PATTERN(NODE("n"))),
WHERE(AND(EXISTS(PATTERN(NODE("n"), EDGE("edge", memgraph::query::EdgeAtom::Direction::BOTH, {"TYPE"}, false),
NODE("node", "Two", false))),
EXISTS(PATTERN(NODE("n"), EDGE("edge2", memgraph::query::EdgeAtom::Direction::BOTH, {}, false),
NODE("node2", std::nullopt, false))))),
RETURN("n")));
auto symbol_table = memgraph::query::MakeSymbolTable(query);
auto planner = MakePlanner<TypeParam>(&dba, storage, symbol_table, query);
std::list<BaseOpChecker *> pattern_filter_with_types{new ExpectExpand(), new ExpectFilter(), new ExpectLimit(),
new ExpectEvaluatePatternFilter()};
std::list<BaseOpChecker *> pattern_filter_without_types{new ExpectExpand(), new ExpectLimit(),
new ExpectEvaluatePatternFilter()};
CheckPlan(
planner.plan(), symbol_table, ExpectScanAll(),
ExpectFilter(std::vector<std::list<BaseOpChecker *>>{pattern_filter_without_types, pattern_filter_with_types}),
ExpectProduce());
DeleteListContent(&pattern_filter_with_types);
DeleteListContent(&pattern_filter_without_types);
}
// MATCH (n) WHERE n.prop = 1 AND exists((n)-[:TYPE]-(:Two))
{
auto property = dba.Property("prop");
auto *query = QUERY(SINGLE_QUERY(
MATCH(PATTERN(NODE("n"))),
WHERE(AND(EXISTS(PATTERN(NODE("n"), EDGE("edge", memgraph::query::EdgeAtom::Direction::BOTH, {"TYPE"}, false),
NODE("node", "Two", false))),
PROPERTY_LOOKUP("n", property))),
RETURN("n")));
auto symbol_table = memgraph::query::MakeSymbolTable(query);
auto planner = MakePlanner<TypeParam>(&dba, storage, symbol_table, query);
std::list<BaseOpChecker *> pattern_filter{new ExpectExpand(), new ExpectFilter(), new ExpectLimit(),
new ExpectEvaluatePatternFilter()};
CheckPlan(planner.plan(), symbol_table, ExpectScanAll(),
ExpectFilter(std::vector<std::list<BaseOpChecker *>>{pattern_filter}), ExpectProduce());
DeleteListContent(&pattern_filter);
}
// MATCH (n) WHERE exists((n)-[:TYPE]-(:Two)) OR exists((n)-[]-())
{
auto *query = QUERY(SINGLE_QUERY(
MATCH(PATTERN(NODE("n"))),
WHERE(OR(EXISTS(PATTERN(NODE("n"), EDGE("edge", memgraph::query::EdgeAtom::Direction::BOTH, {"TYPE"}, false),
NODE("node", "Two", false))),
EXISTS(PATTERN(NODE("n"), EDGE("edge2", memgraph::query::EdgeAtom::Direction::BOTH, {}, false),
NODE("node2", std::nullopt, false))))),
RETURN("n")));
auto symbol_table = memgraph::query::MakeSymbolTable(query);
auto planner = MakePlanner<TypeParam>(&dba, storage, symbol_table, query);
std::list<BaseOpChecker *> pattern_filter_with_types{new ExpectExpand(), new ExpectFilter(), new ExpectLimit(),
new ExpectEvaluatePatternFilter()};
std::list<BaseOpChecker *> pattern_filter_without_types{new ExpectExpand(), new ExpectLimit(),
new ExpectEvaluatePatternFilter()};
CheckPlan(
planner.plan(), symbol_table, ExpectScanAll(),
ExpectFilter(std::vector<std::list<BaseOpChecker *>>{pattern_filter_with_types, pattern_filter_without_types}),
ExpectProduce());
DeleteListContent(&pattern_filter_with_types);
DeleteListContent(&pattern_filter_without_types);
}
}
} // namespace } // namespace

View File

@ -1,4 +1,4 @@
// Copyright 2022 Memgraph Ltd. // Copyright 2023 Memgraph Ltd.
// //
// Use of this software is governed by the Business Source License // 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 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@ -64,7 +64,6 @@ class PlanChecker : public virtual HierarchicalLogicalOperatorVisitor {
PRE_VISIT(ScanAllById); PRE_VISIT(ScanAllById);
PRE_VISIT(Expand); PRE_VISIT(Expand);
PRE_VISIT(ExpandVariable); PRE_VISIT(ExpandVariable);
PRE_VISIT(Filter);
PRE_VISIT(ConstructNamedPath); PRE_VISIT(ConstructNamedPath);
PRE_VISIT(EmptyResult); PRE_VISIT(EmptyResult);
PRE_VISIT(Produce); PRE_VISIT(Produce);
@ -79,6 +78,7 @@ class PlanChecker : public virtual HierarchicalLogicalOperatorVisitor {
PRE_VISIT(Skip); PRE_VISIT(Skip);
PRE_VISIT(Limit); PRE_VISIT(Limit);
PRE_VISIT(OrderBy); PRE_VISIT(OrderBy);
PRE_VISIT(EvaluatePatternFilter);
bool PreVisit(Merge &op) override { bool PreVisit(Merge &op) override {
CheckOp(op); CheckOp(op);
op.input()->Accept(*this); op.input()->Accept(*this);
@ -97,6 +97,12 @@ class PlanChecker : public virtual HierarchicalLogicalOperatorVisitor {
return false; return false;
} }
bool PreVisit(Filter &op) override {
CheckOp(op);
op.input()->Accept(*this);
return false;
}
bool Visit(Once &) override { bool Visit(Once &) override {
// Ignore checking Once, it is implicitly at the end. // Ignore checking Once, it is implicitly at the end.
return true; return true;
@ -141,7 +147,6 @@ using ExpectScanAll = OpChecker<ScanAll>;
using ExpectScanAllByLabel = OpChecker<ScanAllByLabel>; using ExpectScanAllByLabel = OpChecker<ScanAllByLabel>;
using ExpectScanAllById = OpChecker<ScanAllById>; using ExpectScanAllById = OpChecker<ScanAllById>;
using ExpectExpand = OpChecker<Expand>; using ExpectExpand = OpChecker<Expand>;
using ExpectFilter = OpChecker<Filter>;
using ExpectConstructNamedPath = OpChecker<ConstructNamedPath>; using ExpectConstructNamedPath = OpChecker<ConstructNamedPath>;
using ExpectProduce = OpChecker<Produce>; using ExpectProduce = OpChecker<Produce>;
using ExpectEmptyResult = OpChecker<EmptyResult>; using ExpectEmptyResult = OpChecker<EmptyResult>;
@ -156,6 +161,23 @@ using ExpectLimit = OpChecker<Limit>;
using ExpectOrderBy = OpChecker<OrderBy>; using ExpectOrderBy = OpChecker<OrderBy>;
using ExpectUnwind = OpChecker<Unwind>; using ExpectUnwind = OpChecker<Unwind>;
using ExpectDistinct = OpChecker<Distinct>; using ExpectDistinct = OpChecker<Distinct>;
using ExpectEvaluatePatternFilter = OpChecker<EvaluatePatternFilter>;
class ExpectFilter : public OpChecker<Filter> {
public:
ExpectFilter(const std::vector<std::list<BaseOpChecker *>> &pattern_filters = {})
: pattern_filters_(pattern_filters) {}
void ExpectOp(Filter &filter, const SymbolTable &symbol_table) override {
for (auto i = 0; i < filter.pattern_filters_.size(); i++) {
PlanChecker check_updates(pattern_filters_[i], symbol_table);
filter.pattern_filters_[i]->Accept(check_updates);
}
}
std::vector<std::list<BaseOpChecker *>> pattern_filters_;
};
class ExpectForeach : public OpChecker<Foreach> { class ExpectForeach : public OpChecker<Foreach> {
public: public:

View File

@ -1,4 +1,4 @@
// Copyright 2022 Memgraph Ltd. // Copyright 2023 Memgraph Ltd.
// //
// Use of this software is governed by the Business Source License // 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 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@ -1532,7 +1532,7 @@ TEST(QueryPlan, NodeFilterSet) {
false, memgraph::storage::View::OLD); false, memgraph::storage::View::OLD);
auto *filter_expr = auto *filter_expr =
EQ(storage.Create<PropertyLookup>(scan_all.node_->identifier_, storage.GetPropertyIx(prop.first)), LITERAL(42)); EQ(storage.Create<PropertyLookup>(scan_all.node_->identifier_, storage.GetPropertyIx(prop.first)), LITERAL(42));
auto node_filter = std::make_shared<Filter>(expand.op_, filter_expr); auto node_filter = std::make_shared<Filter>(expand.op_, std::vector<std::shared_ptr<LogicalOperator>>{}, filter_expr);
// SET n.prop = n.prop + 1 // SET n.prop = n.prop + 1
auto set_prop = PROPERTY_LOOKUP(IDENT("n")->MapTo(scan_all.sym_), prop); auto set_prop = PROPERTY_LOOKUP(IDENT("n")->MapTo(scan_all.sym_), prop);
auto add = ADD(set_prop, LITERAL(1)); auto add = ADD(set_prop, LITERAL(1));
@ -1569,7 +1569,8 @@ TEST(QueryPlan, FilterRemove) {
auto expand = MakeExpand(storage, symbol_table, scan_all.op_, scan_all.sym_, "r", EdgeAtom::Direction::BOTH, {}, "m", auto expand = MakeExpand(storage, symbol_table, scan_all.op_, scan_all.sym_, "r", EdgeAtom::Direction::BOTH, {}, "m",
false, memgraph::storage::View::OLD); false, memgraph::storage::View::OLD);
auto filter_prop = PROPERTY_LOOKUP(IDENT("n")->MapTo(scan_all.sym_), prop); auto filter_prop = PROPERTY_LOOKUP(IDENT("n")->MapTo(scan_all.sym_), prop);
auto filter = std::make_shared<Filter>(expand.op_, LESS(filter_prop, LITERAL(43))); auto filter = std::make_shared<Filter>(expand.op_, std::vector<std::shared_ptr<LogicalOperator>>{},
LESS(filter_prop, LITERAL(43)));
// REMOVE n.prop // REMOVE n.prop
auto rem_prop = PROPERTY_LOOKUP(IDENT("n")->MapTo(scan_all.sym_), prop); auto rem_prop = PROPERTY_LOOKUP(IDENT("n")->MapTo(scan_all.sym_), prop);
auto rem = std::make_shared<plan::RemoveProperty>(filter, prop.second, rem_prop); auto rem = std::make_shared<plan::RemoveProperty>(filter, prop.second, rem_prop);

View File

@ -1,4 +1,4 @@
// Copyright 2022 Memgraph Ltd. // Copyright 2023 Memgraph Ltd.
// //
// Use of this software is governed by the Business Source License // 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 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@ -292,7 +292,7 @@ TEST(QueryPlan, NodeFilterLabelsAndProperties) {
// node filtering // node filtering
auto *filter_expr = AND(storage.Create<LabelsTest>(n.node_->identifier_, n.node_->labels_), auto *filter_expr = AND(storage.Create<LabelsTest>(n.node_->identifier_, n.node_->labels_),
EQ(PROPERTY_LOOKUP(n.node_->identifier_, property), LITERAL(42))); EQ(PROPERTY_LOOKUP(n.node_->identifier_, property), LITERAL(42)));
auto node_filter = std::make_shared<Filter>(n.op_, filter_expr); auto node_filter = std::make_shared<Filter>(n.op_, std::vector<std::shared_ptr<LogicalOperator>>{}, filter_expr);
// make a named expression and a produce // make a named expression and a produce
auto output = NEXPR("x", IDENT("n")->MapTo(n.sym_))->MapTo(symbol_table.CreateSymbol("named_expression_1", true)); auto output = NEXPR("x", IDENT("n")->MapTo(n.sym_))->MapTo(symbol_table.CreateSymbol("named_expression_1", true));
@ -344,7 +344,7 @@ TEST(QueryPlan, NodeFilterMultipleLabels) {
// node filtering // node filtering
auto *filter_expr = storage.Create<LabelsTest>(n.node_->identifier_, n.node_->labels_); auto *filter_expr = storage.Create<LabelsTest>(n.node_->identifier_, n.node_->labels_);
auto node_filter = std::make_shared<Filter>(n.op_, filter_expr); auto node_filter = std::make_shared<Filter>(n.op_, std::vector<std::shared_ptr<LogicalOperator>>{}, filter_expr);
// make a named expression and a produce // make a named expression and a produce
auto output = NEXPR("n", IDENT("n")->MapTo(n.sym_))->MapTo(symbol_table.CreateSymbol("named_expression_1", true)); auto output = NEXPR("n", IDENT("n")->MapTo(n.sym_))->MapTo(symbol_table.CreateSymbol("named_expression_1", true));
@ -679,7 +679,7 @@ class QueryPlanExpandVariable : public testing::Test {
bool is_reverse = false) { bool is_reverse = false) {
auto n_from = MakeScanAll(storage, symbol_table, node_from, input_op); auto n_from = MakeScanAll(storage, symbol_table, node_from, input_op);
auto filter_op = std::make_shared<Filter>( auto filter_op = std::make_shared<Filter>(
n_from.op_, n_from.op_, std::vector<std::shared_ptr<LogicalOperator>>{},
storage.Create<memgraph::query::LabelsTest>( storage.Create<memgraph::query::LabelsTest>(
n_from.node_->identifier_, std::vector<LabelIx>{storage.GetLabelIx(dba.LabelToName(labels[layer]))})); n_from.node_->identifier_, std::vector<LabelIx>{storage.GetLabelIx(dba.LabelToName(labels[layer]))}));
@ -1355,7 +1355,7 @@ TEST_F(QueryPlanExpandVariable, ExpandToSameSymbol) {
auto n_from = ScanAllTuple{node, logical_op, symbol}; auto n_from = ScanAllTuple{node, logical_op, symbol};
auto filter_op = std::make_shared<Filter>( auto filter_op = std::make_shared<Filter>(
n_from.op_, n_from.op_, std::vector<std::shared_ptr<LogicalOperator>>{},
storage.Create<memgraph::query::LabelsTest>( storage.Create<memgraph::query::LabelsTest>(
n_from.node_->identifier_, std::vector<LabelIx>{storage.GetLabelIx(dba.LabelToName(labels[layer]))})); n_from.node_->identifier_, std::vector<LabelIx>{storage.GetLabelIx(dba.LabelToName(labels[layer]))}));
@ -1546,7 +1546,7 @@ TEST_F(QueryPlanExpandVariable, FineGrainedExpandToSameSymbol) {
auto n_from = ScanAllTuple{node, logical_op, symbol}; auto n_from = ScanAllTuple{node, logical_op, symbol};
auto filter_op = std::make_shared<Filter>( auto filter_op = std::make_shared<Filter>(
n_from.op_, n_from.op_, std::vector<std::shared_ptr<LogicalOperator>>{},
storage.Create<memgraph::query::LabelsTest>( storage.Create<memgraph::query::LabelsTest>(
n_from.node_->identifier_, std::vector<LabelIx>{storage.GetLabelIx(dba.LabelToName(labels[layer]))})); n_from.node_->identifier_, std::vector<LabelIx>{storage.GetLabelIx(dba.LabelToName(labels[layer]))}));
@ -1813,7 +1813,8 @@ class QueryPlanExpandWeightedShortestPath : public testing::Test {
auto n = MakeScanAll(storage, symbol_table, "n", existing_node_input ? existing_node_input->op_ : nullptr); auto n = MakeScanAll(storage, symbol_table, "n", existing_node_input ? existing_node_input->op_ : nullptr);
auto last_op = n.op_; auto last_op = n.op_;
if (node_id) { if (node_id) {
last_op = std::make_shared<Filter>(last_op, EQ(PROPERTY_LOOKUP(n.node_->identifier_, prop), LITERAL(*node_id))); last_op = std::make_shared<Filter>(last_op, std::vector<std::shared_ptr<LogicalOperator>>{},
EQ(PROPERTY_LOOKUP(n.node_->identifier_, prop), LITERAL(*node_id)));
} }
auto ident_e = IDENT("e"); auto ident_e = IDENT("e");
@ -1967,8 +1968,9 @@ TEST_F(QueryPlanExpandWeightedShortestPath, ExistingNode) {
// scan the nodes optionally filtering on property value // scan the nodes optionally filtering on property value
auto n0 = MakeScanAll(storage, symbol_table, "n0"); auto n0 = MakeScanAll(storage, symbol_table, "n0");
if (preceeding_node_id) { if (preceeding_node_id) {
auto filter = std::make_shared<Filter>( auto filter =
n0.op_, EQ(PROPERTY_LOOKUP(n0.node_->identifier_, prop), LITERAL(*preceeding_node_id))); std::make_shared<Filter>(n0.op_, std::vector<std::shared_ptr<LogicalOperator>>{},
EQ(PROPERTY_LOOKUP(n0.node_->identifier_, prop), LITERAL(*preceeding_node_id)));
// inject the filter op into the ScanAllTuple. that way the filter // inject the filter op into the ScanAllTuple. that way the filter
// op can be passed into the ExpandWShortest function without too // op can be passed into the ExpandWShortest function without too
// much refactor // much refactor
@ -2243,7 +2245,8 @@ class QueryPlanExpandAllShortestPaths : public testing::Test {
auto n = MakeScanAll(storage, symbol_table, "n", existing_node_input ? existing_node_input->op_ : nullptr); auto n = MakeScanAll(storage, symbol_table, "n", existing_node_input ? existing_node_input->op_ : nullptr);
auto last_op = n.op_; auto last_op = n.op_;
if (node_id) { if (node_id) {
last_op = std::make_shared<Filter>(last_op, EQ(PROPERTY_LOOKUP(n.node_->identifier_, prop), LITERAL(*node_id))); last_op = std::make_shared<Filter>(last_op, std::vector<std::shared_ptr<LogicalOperator>>{},
EQ(PROPERTY_LOOKUP(n.node_->identifier_, prop), LITERAL(*node_id)));
} }
auto ident_e = IDENT("e"); auto ident_e = IDENT("e");
@ -2739,7 +2742,7 @@ TEST(QueryPlan, OptionalMatchThenExpandToMissingNode) {
n.node_->labels_.emplace_back(storage.GetLabelIx(label_missing)); n.node_->labels_.emplace_back(storage.GetLabelIx(label_missing));
auto *filter_expr = storage.Create<LabelsTest>(n.node_->identifier_, n.node_->labels_); auto *filter_expr = storage.Create<LabelsTest>(n.node_->identifier_, n.node_->labels_);
auto node_filter = std::make_shared<Filter>(n.op_, filter_expr); auto node_filter = std::make_shared<Filter>(n.op_, std::vector<std::shared_ptr<LogicalOperator>>{}, filter_expr);
auto optional = std::make_shared<plan::Optional>(nullptr, node_filter, std::vector<Symbol>{n.sym_}); auto optional = std::make_shared<plan::Optional>(nullptr, node_filter, std::vector<Symbol>{n.sym_});
// WITH n // WITH n
auto n_ne = NEXPR("n", IDENT("n")->MapTo(n.sym_)); auto n_ne = NEXPR("n", IDENT("n")->MapTo(n.sym_));
@ -2868,7 +2871,7 @@ TEST(QueryPlan, EdgeFilter) {
r_m.edge_->edge_types_.push_back(storage.GetEdgeTypeIx(dba.EdgeTypeToName(edge_type))); r_m.edge_->edge_types_.push_back(storage.GetEdgeTypeIx(dba.EdgeTypeToName(edge_type)));
std::get<0>(r_m.edge_->properties_)[storage.GetPropertyIx(prop.first)] = LITERAL(42); std::get<0>(r_m.edge_->properties_)[storage.GetPropertyIx(prop.first)] = LITERAL(42);
auto *filter_expr = EQ(PROPERTY_LOOKUP(r_m.edge_->identifier_, prop), LITERAL(42)); auto *filter_expr = EQ(PROPERTY_LOOKUP(r_m.edge_->identifier_, prop), LITERAL(42));
auto edge_filter = std::make_shared<Filter>(r_m.op_, filter_expr); auto edge_filter = std::make_shared<Filter>(r_m.op_, std::vector<std::shared_ptr<LogicalOperator>>{}, filter_expr);
// make a named expression and a produce // make a named expression and a produce
auto output = auto output =
@ -2936,7 +2939,7 @@ TEST(QueryPlan, Filter) {
auto n = MakeScanAll(storage, symbol_table, "n"); auto n = MakeScanAll(storage, symbol_table, "n");
auto e = PROPERTY_LOOKUP(IDENT("n")->MapTo(n.sym_), property); auto e = PROPERTY_LOOKUP(IDENT("n")->MapTo(n.sym_), property);
auto f = std::make_shared<Filter>(n.op_, e); auto f = std::make_shared<Filter>(n.op_, std::vector<std::shared_ptr<LogicalOperator>>{}, e);
auto output = NEXPR("x", IDENT("n")->MapTo(n.sym_))->MapTo(symbol_table.CreateSymbol("named_expression_1", true)); auto output = NEXPR("x", IDENT("n")->MapTo(n.sym_))->MapTo(symbol_table.CreateSymbol("named_expression_1", true));
auto produce = MakeProduce(f, output); auto produce = MakeProduce(f, output);
@ -3441,7 +3444,8 @@ TEST(QueryPlan, ScanAllEqualsScanAllByLabelProperty) {
memgraph::query::DbAccessor dba(&storage_dba); memgraph::query::DbAccessor dba(&storage_dba);
auto scan_all = MakeScanAll(storage, symbol_table, "n"); auto scan_all = MakeScanAll(storage, symbol_table, "n");
auto e = PROPERTY_LOOKUP(IDENT("n")->MapTo(scan_all.sym_), std::make_pair("prop", prop)); auto e = PROPERTY_LOOKUP(IDENT("n")->MapTo(scan_all.sym_), std::make_pair("prop", prop));
auto filter = std::make_shared<Filter>(scan_all.op_, EQ(e, LITERAL(prop_value))); auto filter = std::make_shared<Filter>(scan_all.op_, std::vector<std::shared_ptr<LogicalOperator>>{},
EQ(e, LITERAL(prop_value)));
auto output = auto output =
NEXPR("n", IDENT("n")->MapTo(scan_all.sym_))->MapTo(symbol_table.CreateSymbol("named_expression_1", true)); NEXPR("n", IDENT("n")->MapTo(scan_all.sym_))->MapTo(symbol_table.CreateSymbol("named_expression_1", true));
auto produce = MakeProduce(filter, output); auto produce = MakeProduce(filter, output);
@ -3455,3 +3459,220 @@ TEST(QueryPlan, ScanAllEqualsScanAllByLabelProperty) {
count_with_index(prop_value2, vertex_count - vertex_prop_count); count_with_index(prop_value2, vertex_count - vertex_prop_count);
count_with_scan_all(prop_value2, vertex_count - vertex_prop_count); count_with_scan_all(prop_value2, vertex_count - vertex_prop_count);
} }
class ExistsFixture : public testing::Test {
protected:
memgraph::storage::Storage db;
memgraph::storage::Storage::Accessor storage_dba{db.Access()};
memgraph::query::DbAccessor dba{&storage_dba};
AstStorage storage;
SymbolTable symbol_table;
std::pair<std::string, memgraph::storage::PropertyId> prop = PROPERTY_PAIR("property");
memgraph::query::VertexAccessor v1{dba.InsertVertex()};
memgraph::query::VertexAccessor v2{dba.InsertVertex()};
memgraph::storage::EdgeTypeId edge_type{db.NameToEdgeType("Edge")};
memgraph::query::EdgeAccessor r1{*dba.InsertEdge(&v1, &v2, edge_type)};
memgraph::query::VertexAccessor v3{dba.InsertVertex()};
memgraph::query::VertexAccessor v4{dba.InsertVertex()};
memgraph::storage::EdgeTypeId edge_type_unknown{db.NameToEdgeType("Other")};
memgraph::query::EdgeAccessor r2{*dba.InsertEdge(&v3, &v4, edge_type_unknown)};
void SetUp() override {
// (:l1)-[:Edge]->(:l2), (:l3)-[:Other]->(:l4)
ASSERT_TRUE(v1.AddLabel(dba.NameToLabel("l1")).HasValue());
ASSERT_TRUE(v2.AddLabel(dba.NameToLabel("l2")).HasValue());
ASSERT_TRUE(v3.AddLabel(dba.NameToLabel("l3")).HasValue());
ASSERT_TRUE(v4.AddLabel(dba.NameToLabel("l4")).HasValue());
ASSERT_TRUE(v1.SetProperty(prop.second, memgraph::storage::PropertyValue(1)).HasValue());
ASSERT_TRUE(v2.SetProperty(prop.second, memgraph::storage::PropertyValue(2)).HasValue());
ASSERT_TRUE(v3.SetProperty(prop.second, memgraph::storage::PropertyValue(3)).HasValue());
ASSERT_TRUE(v4.SetProperty(prop.second, memgraph::storage::PropertyValue(4)).HasValue());
ASSERT_TRUE(r1.SetProperty(prop.second, memgraph::storage::PropertyValue(1)).HasValue());
memgraph::license::global_license_checker.EnableTesting();
dba.AdvanceCommand();
}
int TestExists(std::string match_label, EdgeAtom::Direction direction,
std::vector<memgraph::storage::EdgeTypeId> edge_types,
std::optional<std::string> destination_label = std::nullopt,
std::optional<int64_t> destination_prop = std::nullopt,
std::optional<int64_t> edge_prop = std::nullopt) {
std::vector<std::string> edge_type_names;
for (const auto &type : edge_types) {
edge_type_names.emplace_back(db.EdgeTypeToName(type));
}
auto *source_node = NODE("n");
auto source_sym = symbol_table.CreateSymbol("n", true);
source_node->identifier_->MapTo(source_sym);
auto *expansion_edge = EDGE("edge", direction, edge_type_names, false);
auto edge_sym = symbol_table.CreateSymbol("edge", false);
expansion_edge->identifier_->MapTo(edge_sym);
auto *destination_node = NODE("n2", destination_label, false);
auto dest_sym = symbol_table.CreateSymbol("n2", false);
destination_node->identifier_->MapTo(dest_sym);
auto *exists_expression = EXISTS(PATTERN(source_node, expansion_edge, destination_node));
exists_expression->MapTo(symbol_table.CreateAnonymousSymbol());
auto scan_all = MakeScanAll(storage, symbol_table, "n");
scan_all.node_->labels_.emplace_back(storage.GetLabelIx(match_label));
std::shared_ptr<LogicalOperator> last_op = std::make_shared<Expand>(
nullptr, scan_all.sym_, dest_sym, edge_sym, direction, edge_types, false, memgraph::storage::View::OLD);
if (destination_label.has_value() || destination_prop.has_value() || edge_prop.has_value()) {
Expression *filter_expr = nullptr;
if (destination_label.has_value()) {
auto labelIx = storage.GetLabelIx(destination_label.value());
destination_node->labels_.emplace_back(labelIx);
auto label_expr = static_cast<Expression *>(
storage.Create<LabelsTest>(destination_node->identifier_, std::vector<memgraph::query::LabelIx>{labelIx}));
filter_expr = filter_expr ? AND(filter_expr, label_expr) : label_expr;
}
if (destination_prop.has_value()) {
auto prop_expr = static_cast<Expression *>(
EQ(PROPERTY_LOOKUP(destination_node->identifier_, prop), LITERAL(destination_prop.value())));
filter_expr = filter_expr ? AND(filter_expr, prop_expr) : prop_expr;
}
if (edge_prop.has_value()) {
auto prop_expr = static_cast<Expression *>(
EQ(PROPERTY_LOOKUP(expansion_edge->identifier_, prop), LITERAL(edge_prop.value())));
filter_expr = filter_expr ? AND(filter_expr, prop_expr) : prop_expr;
}
last_op =
std::make_shared<Filter>(std::move(last_op), std::vector<std::shared_ptr<LogicalOperator>>{}, filter_expr);
}
last_op = std::make_shared<Limit>(std::move(last_op), storage.Create<PrimitiveLiteral>(1));
last_op = std::make_shared<EvaluatePatternFilter>(std::move(last_op), symbol_table.at(*exists_expression));
auto *total_expression =
AND(storage.Create<LabelsTest>(scan_all.node_->identifier_, scan_all.node_->labels_), exists_expression);
auto filter = std::make_shared<Filter>(scan_all.op_, std::vector<std::shared_ptr<LogicalOperator>>{last_op},
total_expression);
auto output =
NEXPR("n", IDENT("n")->MapTo(scan_all.sym_))->MapTo(symbol_table.CreateSymbol("named_expression_1", true));
auto produce = MakeProduce(filter, output);
auto context = MakeContext(storage, symbol_table, &dba);
return PullAll(*produce, &context);
}
int TestDoubleExists(std::string match_label, EdgeAtom::Direction direction,
std::vector<memgraph::storage::EdgeTypeId> first_edge_type,
std::vector<memgraph::storage::EdgeTypeId> second_edge_type, bool or_flag = false) {
std::vector<std::string> first_edge_type_names;
for (const auto &type : first_edge_type) {
first_edge_type_names.emplace_back(db.EdgeTypeToName(type));
}
std::vector<std::string> second_edge_type_names;
for (const auto &type : second_edge_type) {
second_edge_type_names.emplace_back(db.EdgeTypeToName(type));
}
auto *source_node = NODE("n");
auto source_sym = symbol_table.CreateSymbol("n", true);
source_node->identifier_->MapTo(source_sym);
auto *expansion_edge = EDGE("edge", direction, first_edge_type_names, false);
auto edge_sym = symbol_table.CreateSymbol("edge", false);
expansion_edge->identifier_->MapTo(edge_sym);
auto *destination_node = NODE("n2", std::nullopt, false);
auto dest_sym = symbol_table.CreateSymbol("n2", false);
destination_node->identifier_->MapTo(dest_sym);
auto *exists_expression = EXISTS(PATTERN(source_node, expansion_edge, destination_node));
exists_expression->MapTo(symbol_table.CreateAnonymousSymbol());
auto *expansion_edge2 = EDGE("edge2", direction, second_edge_type_names, false);
auto edge_sym2 = symbol_table.CreateSymbol("edge2", false);
expansion_edge2->identifier_->MapTo(edge_sym2);
auto *destination_node2 = NODE("n22", std::nullopt, false);
auto dest_sym2 = symbol_table.CreateSymbol("n22", false);
destination_node2->identifier_->MapTo(dest_sym2);
auto *exists_expression2 = EXISTS(PATTERN(source_node, expansion_edge2, destination_node2));
exists_expression2->MapTo(symbol_table.CreateAnonymousSymbol());
auto scan_all = MakeScanAll(storage, symbol_table, "n");
scan_all.node_->labels_.emplace_back(storage.GetLabelIx(match_label));
std::shared_ptr<LogicalOperator> last_op = std::make_shared<Expand>(
nullptr, scan_all.sym_, dest_sym, edge_sym, direction, first_edge_type, false, memgraph::storage::View::OLD);
last_op = std::make_shared<Limit>(std::move(last_op), storage.Create<PrimitiveLiteral>(1));
last_op = std::make_shared<EvaluatePatternFilter>(std::move(last_op), symbol_table.at(*exists_expression));
std::shared_ptr<LogicalOperator> last_op2 = std::make_shared<Expand>(
nullptr, scan_all.sym_, dest_sym2, edge_sym2, direction, second_edge_type, false, memgraph::storage::View::OLD);
last_op2 = std::make_shared<Limit>(std::move(last_op2), storage.Create<PrimitiveLiteral>(1));
last_op2 = std::make_shared<EvaluatePatternFilter>(std::move(last_op2), symbol_table.at(*exists_expression2));
Expression *total_expression = storage.Create<LabelsTest>(scan_all.node_->identifier_, scan_all.node_->labels_);
if (or_flag) {
total_expression = AND(total_expression, OR(exists_expression, exists_expression2));
} else {
total_expression = AND(total_expression, AND(exists_expression, exists_expression2));
}
auto filter = std::make_shared<Filter>(
scan_all.op_, std::vector<std::shared_ptr<LogicalOperator>>{last_op, last_op2}, total_expression);
auto output =
NEXPR("n", IDENT("n")->MapTo(scan_all.sym_))->MapTo(symbol_table.CreateSymbol("named_expression_1", true));
auto produce = MakeProduce(filter, output);
auto context = MakeContext(storage, symbol_table, &dba);
return PullAll(*produce, &context);
}
};
TEST_F(ExistsFixture, BasicExists) {
std::vector<memgraph::storage::EdgeTypeId> known_edge_types;
known_edge_types.push_back(edge_type);
std::vector<memgraph::storage::EdgeTypeId> unknown_edge_types;
unknown_edge_types.push_back(edge_type_unknown);
EXPECT_EQ(1, TestExists("l1", EdgeAtom::Direction::OUT, {}));
EXPECT_EQ(1, TestExists("l1", EdgeAtom::Direction::BOTH, {}));
EXPECT_EQ(0, TestExists("l1", EdgeAtom::Direction::IN, {}));
EXPECT_EQ(1, TestExists("l1", EdgeAtom::Direction::OUT, known_edge_types));
EXPECT_EQ(0, TestExists("l1", EdgeAtom::Direction::OUT, unknown_edge_types));
}
TEST_F(ExistsFixture, ExistsWithFiltering) {
EXPECT_EQ(1, TestExists("l1", EdgeAtom::Direction::BOTH, {}, "l2"));
EXPECT_EQ(0, TestExists("l1", EdgeAtom::Direction::BOTH, {}, "l3"));
EXPECT_EQ(1, TestExists("l1", EdgeAtom::Direction::BOTH, {}, "l2", 2));
EXPECT_EQ(0, TestExists("l1", EdgeAtom::Direction::BOTH, {}, "l2", 1));
EXPECT_EQ(1, TestExists("l1", EdgeAtom::Direction::BOTH, {}, "l2", std::nullopt, 1));
EXPECT_EQ(0, TestExists("l1", EdgeAtom::Direction::BOTH, {}, "l2", std::nullopt, 2));
EXPECT_EQ(1, TestExists("l1", EdgeAtom::Direction::BOTH, {}, "l2", 2, 1));
EXPECT_EQ(0, TestExists("l1", EdgeAtom::Direction::BOTH, {}, "l2", 1, 1));
}
TEST_F(ExistsFixture, DoubleFilters) {
EXPECT_EQ(1, TestDoubleExists("l1", EdgeAtom::Direction::BOTH, {}, {}, true));
EXPECT_EQ(1, TestDoubleExists("l1", EdgeAtom::Direction::BOTH, {}, {}, false));
}

View File

@ -1,4 +1,4 @@
// Copyright 2022 Memgraph Ltd. // Copyright 2023 Memgraph Ltd.
// //
// Use of this software is governed by the Business Source License // 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 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@ -57,7 +57,8 @@ TEST_F(ReadWriteTypeCheckTest, CreateNode) {
TEST_F(ReadWriteTypeCheckTest, Filter) { TEST_F(ReadWriteTypeCheckTest, Filter) {
std::shared_ptr<LogicalOperator> scan_all = std::make_shared<ScanAll>(nullptr, GetSymbol("node1")); std::shared_ptr<LogicalOperator> scan_all = std::make_shared<ScanAll>(nullptr, GetSymbol("node1"));
std::shared_ptr<LogicalOperator> filter = std::shared_ptr<LogicalOperator> filter =
std::make_shared<Filter>(scan_all, EQ(PROPERTY_LOOKUP("node1", dba.NameToProperty("prop")), LITERAL(0))); std::make_shared<Filter>(scan_all, std::vector<std::shared_ptr<LogicalOperator>>{},
EQ(PROPERTY_LOOKUP("node1", dba.NameToProperty("prop")), LITERAL(0)));
CheckPlanType(filter.get(), RWType::R); CheckPlanType(filter.get(), RWType::R);
} }
@ -87,7 +88,8 @@ TEST_F(ReadWriteTypeCheckTest, OrderByAndLimit) {
std::shared_ptr<LogicalOperator> last_op = std::make_shared<Once>(); std::shared_ptr<LogicalOperator> last_op = std::make_shared<Once>();
last_op = std::make_shared<ScanAllByLabel>(last_op, node_sym, label); last_op = std::make_shared<ScanAllByLabel>(last_op, node_sym, label);
last_op = std::make_shared<Filter>(last_op, EQ(PROPERTY_LOOKUP("node", prop), LITERAL(5))); last_op = std::make_shared<Filter>(last_op, std::vector<std::shared_ptr<LogicalOperator>>{},
EQ(PROPERTY_LOOKUP("node", prop), LITERAL(5)));
last_op = std::make_shared<Produce>(last_op, std::vector<NamedExpression *>{NEXPR("n", IDENT("n"))}); last_op = std::make_shared<Produce>(last_op, std::vector<NamedExpression *>{NEXPR("n", IDENT("n"))});
last_op = std::make_shared<OrderBy>(last_op, std::vector<SortItem>{{Ordering::DESC, PROPERTY_LOOKUP("node", prop)}}, last_op = std::make_shared<OrderBy>(last_op, std::vector<SortItem>{{Ordering::DESC, PROPERTY_LOOKUP("node", prop)}},
std::vector<Symbol>{node_sym}); std::vector<Symbol>{node_sym});

View File

@ -1,4 +1,4 @@
// Copyright 2022 Memgraph Ltd. // Copyright 2023 Memgraph Ltd.
// //
// Use of this software is governed by the Business Source License // 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 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@ -163,5 +163,4 @@ TEST_F(ExpressionPrettyPrinterTest, NamedExpression) {
// n AS 1 // n AS 1
EXPECT_EQ(ToString(NEXPR("n", LITERAL(1))), "(NamedExpression \"n\" 1)"); EXPECT_EQ(ToString(NEXPR("n", LITERAL(1))), "(NamedExpression \"n\" 1)");
} }
} // namespace } // namespace

View File

@ -19,6 +19,7 @@
#include "query/frontend/ast/ast.hpp" #include "query/frontend/ast/ast.hpp"
#include "query/frontend/semantic/symbol_generator.hpp" #include "query/frontend/semantic/symbol_generator.hpp"
#include "query/frontend/semantic/symbol_table.hpp" #include "query/frontend/semantic/symbol_table.hpp"
#include "query/plan/preprocess.hpp"
#include "query_common.hpp" #include "query_common.hpp"
@ -1179,3 +1180,37 @@ TEST_F(TestSymbolGenerator, Foreach) {
query = QUERY(SINGLE_QUERY(FOREACH(i, {CREATE(PATTERN(NODE("n")))}), RETURN("i"))); query = QUERY(SINGLE_QUERY(FOREACH(i, {CREATE(PATTERN(NODE("n")))}), RETURN("i")));
EXPECT_THROW(memgraph::query::MakeSymbolTable(query), UnboundVariableError); EXPECT_THROW(memgraph::query::MakeSymbolTable(query), UnboundVariableError);
} }
TEST_F(TestSymbolGenerator, Exists) {
auto query = QUERY(SINGLE_QUERY(
MATCH(PATTERN(NODE("n"))),
WHERE(EXISTS(PATTERN(NODE("n"), EDGE("", EdgeAtom::Direction::BOTH, {}, false), NODE("m")))), RETURN("n")));
EXPECT_THROW(MakeSymbolTable(query), SemanticException);
query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))),
WHERE(EXISTS(PATTERN(NODE("n"), EDGE("r"), NODE("", std::nullopt, false)))), RETURN("n")));
EXPECT_THROW(MakeSymbolTable(query), SemanticException);
query = QUERY(
SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), WHERE(EXISTS(PATTERN(NODE("n"), EDGE("r"), NODE("m")))), RETURN("n")));
EXPECT_THROW(MakeSymbolTable(query), SemanticException);
// Symbols for match pattern, node symbol, exists pattern, exists edge, exists second node, named expression in return
query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))),
WHERE(EXISTS(PATTERN(NODE("n"), EDGE("edge", EdgeAtom::Direction::BOTH, {}, false),
NODE("node", std::nullopt, false)))),
RETURN("n")));
auto symbol_table = MakeSymbolTable(query);
ASSERT_EQ(symbol_table.max_position(), 7);
memgraph::query::plan::UsedSymbolsCollector collector(symbol_table);
auto *match = dynamic_cast<Match *>(query->single_query_->clauses_[0]);
auto *expression = dynamic_cast<Expression *>(match->where_->expression_);
expression->Accept(collector);
ASSERT_EQ(collector.symbols_.size(), 1);
auto symbol = *collector.symbols_.begin();
ASSERT_EQ(symbol.name_, "n");
}

View File

@ -1,4 +1,4 @@
// Copyright 2022 Memgraph Ltd. // Copyright 2023 Memgraph Ltd.
// //
// Use of this software is governed by the Business Source License // 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 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@ -649,3 +649,26 @@ TEST(PropertyStore, IsPropertyEqualTemporalData) {
ASSERT_FALSE(props.IsPropertyEqual(prop, memgraph::storage::PropertyValue(memgraph::storage::TemporalData{ ASSERT_FALSE(props.IsPropertyEqual(prop, memgraph::storage::PropertyValue(memgraph::storage::TemporalData{
memgraph::storage::TemporalType::Date, 30}))); memgraph::storage::TemporalType::Date, 30})));
} }
TEST(PropertyStore, SetMultipleProperties) {
memgraph::storage::PropertyStore store;
std::vector<memgraph::storage::PropertyValue> vec{memgraph::storage::PropertyValue(true),
memgraph::storage::PropertyValue(123),
memgraph::storage::PropertyValue()};
std::map<std::string, memgraph::storage::PropertyValue> map{{"nandare", memgraph::storage::PropertyValue(false)}};
const memgraph::storage::TemporalData temporal{memgraph::storage::TemporalType::LocalDateTime, 23};
std::map<memgraph::storage::PropertyId, memgraph::storage::PropertyValue> data{
{memgraph::storage::PropertyId::FromInt(1), memgraph::storage::PropertyValue(true)},
{memgraph::storage::PropertyId::FromInt(2), memgraph::storage::PropertyValue(123)},
{memgraph::storage::PropertyId::FromInt(3), memgraph::storage::PropertyValue(123.5)},
{memgraph::storage::PropertyId::FromInt(4), memgraph::storage::PropertyValue("nandare")},
{memgraph::storage::PropertyId::FromInt(5), memgraph::storage::PropertyValue(vec)},
{memgraph::storage::PropertyId::FromInt(6), memgraph::storage::PropertyValue(map)},
{memgraph::storage::PropertyId::FromInt(7), memgraph::storage::PropertyValue(temporal)}};
store.InitProperties(data);
for (auto &[key, value] : data) {
ASSERT_TRUE(store.IsPropertyEqual(key, value));
}
}