Merge branch 'master' into T595-notification-when-sync-replica-down
This commit is contained in:
commit
d4194ff393
27
.github/workflows/package_all.yaml
vendored
27
.github/workflows/package_all.yaml
vendored
@ -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
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
96
environment/os/ubuntu-22.04-arm.sh
Executable file
96
environment/os/ubuntu-22.04-arm.sh
Executable 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}"
|
@ -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
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
11
release/package/arm-builders.yml
Normal file
11
release/package/arm-builders.yml
Normal 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"
|
@ -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 .."
|
||||||
|
17
release/package/ubuntu-22.04-arm/Dockerfile
Normal file
17
release/package/ubuntu-22.04-arm/Dockerfile
Normal 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"]
|
@ -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
|
||||||
|
@ -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());
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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,
|
||||||
|
@ -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));
|
||||||
}
|
}
|
||||||
|
@ -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*
|
||||||
*/
|
*/
|
||||||
|
@ -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_);
|
||||||
}
|
}
|
||||||
|
@ -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 )* )? ')' ;
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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.
|
||||||
|
@ -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()); }
|
||||||
|
@ -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()) {
|
||||||
|
@ -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) {}
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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_) {
|
||||||
|
@ -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.
|
||||||
///
|
///
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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:
|
||||||
|
@ -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) {
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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();
|
||||||
|
@ -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;
|
||||||
|
@ -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).
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
@ -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();
|
||||||
|
@ -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.") \
|
||||||
|
@ -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:
|
||||||
|
@ -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
|
@ -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);
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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");
|
||||||
|
}
|
||||||
|
@ -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))
|
||||||
|
@ -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) {
|
||||||
|
@ -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
|
||||||
|
@ -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:
|
||||||
|
@ -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);
|
||||||
|
@ -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));
|
||||||
|
}
|
||||||
|
@ -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});
|
||||||
|
@ -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
|
||||||
|
@ -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");
|
||||||
|
}
|
||||||
|
@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user