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
|
||||
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:
|
||||
runs-on: [self-hosted, DockerMgBuild, ARM64, strange]
|
||||
timeout-minutes: 60
|
||||
@ -177,8 +194,8 @@ jobs:
|
||||
name: debian-11-arm
|
||||
path: build/output/debian-11-arm/memgraph*.deb
|
||||
|
||||
fedora-36:
|
||||
runs-on: [self-hosted, DockerMgBuild, X64]
|
||||
ubuntu-2204-arm:
|
||||
runs-on: [self-hosted, DockerMgBuild, ARM64, strange]
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- name: "Set up repository"
|
||||
@ -187,9 +204,9 @@ jobs:
|
||||
fetch-depth: 0 # Required because of release/get_version.py
|
||||
- name: "Build package"
|
||||
run: |
|
||||
./release/package/run.sh package fedora-36
|
||||
./release/package/run.sh package ubuntu-22.04-arm
|
||||
- name: "Upload package"
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: fedora-36
|
||||
path: build/output/fedora-36/memgraph*.rpm
|
||||
name: ubuntu-22.04-arm
|
||||
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">
|
||||
<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"/>
|
||||
@ -89,6 +85,7 @@ your browser.
|
||||
### 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/lima-AACF41?style=for-the-badge&logo=macos&logoColor=F0F0F0)](https://memgraph.com/docs/memgraph/install-memgraph-on-ubuntu)
|
||||
|
||||
### 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
|
||||
SWIG_VERSION=4.0.2 # used only for LLVM compilation
|
||||
|
||||
# Check for the dependencies.
|
||||
echo "ALL BUILD PACKAGES: $($DIR/../os/$DISTRO.sh list TOOLCHAIN_BUILD_DEPS)"
|
||||
$DIR/../os/$DISTRO.sh check TOOLCHAIN_BUILD_DEPS
|
||||
echo "ALL RUN PACKAGES: $($DIR/../os/$DISTRO.sh list TOOLCHAIN_RUN_DEPS)"
|
||||
$DIR/../os/$DISTRO.sh check TOOLCHAIN_RUN_DEPS
|
||||
# Set the right env script
|
||||
ENV_SCRIPT="$DIR/../os/$DISTRO.sh"
|
||||
if [[ "$for_arm" = true ]]; then
|
||||
ENV_SCRIPT="$DIR/../os/$DISTRO-arm.sh"
|
||||
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
|
||||
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:
|
||||
|
||||
\`\`\`
|
||||
$($DIR/../os/$DISTRO.sh list TOOLCHAIN_RUN_DEPS)
|
||||
$($DIR/../os/$ENV_SCRIPT.sh list TOOLCHAIN_RUN_DEPS)
|
||||
\`\`\`
|
||||
|
||||
## Usage
|
||||
|
@ -19,13 +19,16 @@ function architecture() {
|
||||
}
|
||||
|
||||
check_architecture() {
|
||||
local ARCH=$(architecture)
|
||||
for arch in "$@"; do
|
||||
if [ "$(architecture)" = "$arch" ]; then
|
||||
if [ "${ARCH}" = "$arch" ]; then
|
||||
echo "The right architecture!"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
echo "Not the right architecture!"
|
||||
echo "Expected: $@"
|
||||
echo "Actual: ${ARCH}"
|
||||
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
|
||||
|
||||
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/../.."
|
||||
TOOLCHAIN_VERSION="toolchain-v4"
|
||||
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 && $ACTIVATE_TOOLCHAIN && ./init"
|
||||
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 .."
|
||||
else
|
||||
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
|
||||
// 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();
|
||||
} // 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
|
||||
// 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);
|
||||
}
|
||||
|
||||
storage::Result<bool> InitProperties(const std::map<storage::PropertyId, storage::PropertyValue> &properties) {
|
||||
return impl_.InitProperties(properties);
|
||||
}
|
||||
|
||||
storage::Result<storage::PropertyValue> RemoveProperty(storage::PropertyId key) {
|
||||
return SetProperty(key, storage::PropertyValue());
|
||||
}
|
||||
@ -125,6 +129,10 @@ class VertexAccessor final {
|
||||
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) {
|
||||
return SetProperty(key, storage::PropertyValue());
|
||||
}
|
||||
|
@ -2711,5 +2711,41 @@ cpp<#
|
||||
(:serialize (:slk))
|
||||
(:clone))
|
||||
|
||||
(lcp:define-class exists (expression)
|
||||
((pattern "Pattern *" :initval "nullptr" :scope :public
|
||||
:slk-save #'slk-save-ast-pointer
|
||||
:slk-load (slk-load-ast-pointer "Pattern"))
|
||||
(symbol-pos :int32_t :initval -1 :scope :public
|
||||
:documentation "Symbol table position of the symbol this Aggregation is mapped to."))
|
||||
|
||||
(:public
|
||||
#>cpp
|
||||
Exists() = default;
|
||||
|
||||
DEFVISITABLE(ExpressionVisitor<TypedValue>);
|
||||
DEFVISITABLE(ExpressionVisitor<TypedValue*>);
|
||||
DEFVISITABLE(ExpressionVisitor<void>);
|
||||
bool Accept(HierarchicalTreeVisitor &visitor) override {
|
||||
if (visitor.PreVisit(*this)) {
|
||||
pattern_->Accept(visitor);
|
||||
}
|
||||
return visitor.PostVisit(*this);
|
||||
}
|
||||
Exists *MapTo(const Symbol &symbol) {
|
||||
symbol_pos_ = symbol.position();
|
||||
return this;
|
||||
}
|
||||
cpp<#)
|
||||
(:protected
|
||||
#>cpp
|
||||
Exists(Pattern * pattern) : pattern_(pattern) {}
|
||||
cpp<#)
|
||||
(:private
|
||||
#>cpp
|
||||
friend class AstStorage;
|
||||
cpp<#)
|
||||
(:serialize (:slk))
|
||||
(:clone))
|
||||
|
||||
(lcp:pop-namespace) ;; namespace query
|
||||
(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
|
||||
// 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 Foreach;
|
||||
class ShowConfigQuery;
|
||||
class Exists;
|
||||
|
||||
using TreeCompositeVisitor = utils::CompositeVisitor<
|
||||
SingleQuery, CypherUnion, NamedExpression, OrOperator, XorOperator, AndOperator, NotOperator, AdditionOperator,
|
||||
@ -103,7 +104,7 @@ using TreeCompositeVisitor = utils::CompositeVisitor<
|
||||
ListSlicingOperator, IfOperator, UnaryPlusOperator, UnaryMinusOperator, IsNullOperator, ListLiteral, MapLiteral,
|
||||
PropertyLookup, LabelsTest, Aggregation, Function, Reduce, Coalesce, Extract, All, Single, Any, None, CallProcedure,
|
||||
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>;
|
||||
|
||||
@ -123,7 +124,7 @@ class ExpressionVisitor
|
||||
LessOperator, GreaterOperator, LessEqualOperator, GreaterEqualOperator, InListOperator, SubscriptOperator,
|
||||
ListSlicingOperator, IfOperator, UnaryPlusOperator, UnaryMinusOperator, IsNullOperator, ListLiteral,
|
||||
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>
|
||||
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
|
||||
// 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 *expr = std::any_cast<Expression *>(ctx->extractExpression()->expression()->accept(this));
|
||||
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
|
||||
// the moment.
|
||||
throw utils::NotYetImplemented("atom expression '{}'", ctx->getText());
|
||||
@ -2204,6 +2207,17 @@ antlrcpp::Any CypherMainVisitor::visitLiteral(MemgraphCypher::LiteralContext *ct
|
||||
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) {
|
||||
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
|
||||
// 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;
|
||||
|
||||
/**
|
||||
* @return Exists* (Expression)
|
||||
*/
|
||||
antlrcpp::Any visitExistsExpression(MemgraphCypher::ExistsExpressionContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @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
|
||||
// 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(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;
|
||||
@ -264,6 +265,8 @@ void ExpressionPrettyPrinter::Visit(Extract &op) {
|
||||
PrintOperator(out_, "Extract", op.identifier_, op.list_, op.expression_);
|
||||
}
|
||||
|
||||
void ExpressionPrettyPrinter::Visit(Exists & /*op*/) { PrintOperator(out_, "Exists", "expression"); }
|
||||
|
||||
void ExpressionPrettyPrinter::Visit(All &op) {
|
||||
PrintOperator(out_, "All", op.identifier_, op.list_expression_, op.where_->expression_);
|
||||
}
|
||||
|
@ -236,6 +236,7 @@ atom : literal
|
||||
| ( ANY '(' filterExpression ')' )
|
||||
| ( NONE '(' filterExpression ')' )
|
||||
| ( SINGLE '(' filterExpression ')' )
|
||||
| ( EXISTS '(' existsExpression ')' )
|
||||
| relationshipsPattern
|
||||
| parenthesizedExpression
|
||||
| functionInvocation
|
||||
@ -275,6 +276,8 @@ reduceExpression : accumulator=variable '=' initial=expression ',' idInColl '|'
|
||||
|
||||
extractExpression : idInColl '|' expression ;
|
||||
|
||||
existsExpression : patternPart ;
|
||||
|
||||
idInColl : variable IN expression ;
|
||||
|
||||
functionInvocation : functionName '(' ( DISTINCT )? ( expression ( ',' expression )* )? ')' ;
|
||||
|
@ -64,6 +64,11 @@ auto SymbolGenerator::CreateSymbol(const std::string &name, bool user_declared,
|
||||
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) {
|
||||
// NOLINTNEXTLINE
|
||||
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) {
|
||||
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;
|
||||
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
|
||||
@ -328,7 +341,8 @@ SymbolGenerator::ReturnType SymbolGenerator::Visit(Identifier &ident) {
|
||||
}
|
||||
symbol = GetOrCreateSymbol(ident.name_, ident.user_declared_, type);
|
||||
} 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
|
||||
// by the variable path itself.
|
||||
throw UnboundVariableError(ident.name_);
|
||||
@ -430,6 +444,46 @@ bool SymbolGenerator::PreVisit(Extract &extract) {
|
||||
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.
|
||||
|
||||
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");
|
||||
scope.in_create_node = true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -64,6 +64,8 @@ class SymbolGenerator : public HierarchicalTreeVisitor {
|
||||
bool PostVisit(Match &) override;
|
||||
bool PreVisit(Foreach &) override;
|
||||
bool PostVisit(Foreach &) override;
|
||||
bool PreVisit(SetProperty & /*set_property*/) override;
|
||||
bool PostVisit(SetProperty & /*set_property*/) override;
|
||||
|
||||
// Expressions
|
||||
ReturnType Visit(Identifier &) override;
|
||||
@ -79,6 +81,8 @@ class SymbolGenerator : public HierarchicalTreeVisitor {
|
||||
bool PreVisit(None &) override;
|
||||
bool PreVisit(Reduce &) override;
|
||||
bool PreVisit(Extract &) override;
|
||||
bool PreVisit(Exists & /*exists*/) override;
|
||||
bool PostVisit(Exists & /*exists*/) override;
|
||||
|
||||
// Pattern and its subparts.
|
||||
bool PreVisit(Pattern &) override;
|
||||
@ -113,6 +117,8 @@ class SymbolGenerator : public HierarchicalTreeVisitor {
|
||||
bool in_where{false};
|
||||
bool in_match{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
|
||||
// reused or created in the pattern itself.
|
||||
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,
|
||||
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);
|
||||
// Returns the symbol by name. If the mapping already exists, checks if the
|
||||
// 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
|
||||
// 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 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 Exists &exists) const { return table_.at(exists.symbol_pos_); }
|
||||
|
||||
// TODO: Remove these since members are public
|
||||
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(ParameterLookup);
|
||||
UNSUCCESSFUL_VISIT(RegexMatch);
|
||||
UNSUCCESSFUL_VISIT(Exists);
|
||||
|
||||
private:
|
||||
Frame *frame_;
|
||||
@ -619,6 +620,8 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
|
||||
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 {
|
||||
auto list_value = all.list_expression_->Accept(*this);
|
||||
if (list_value.IsNull()) {
|
||||
|
@ -25,6 +25,7 @@
|
||||
|
||||
#include <cppitertools/chain.hpp>
|
||||
#include <cppitertools/imap.hpp>
|
||||
#include "query/common.hpp"
|
||||
#include "spdlog/spdlog.h"
|
||||
|
||||
#include "license/license.hpp"
|
||||
@ -115,6 +116,7 @@ extern const Event CartesianOperator;
|
||||
extern const Event CallProcedureOperator;
|
||||
extern const Event ForeachOperator;
|
||||
extern const Event EmptyResultOperator;
|
||||
extern const Event EvaluatePatternFilterOperator;
|
||||
} // namespace EventCounter
|
||||
|
||||
namespace memgraph::query::plan {
|
||||
@ -208,17 +210,18 @@ VertexAccessor &CreateLocalVertex(const NodeCreationInfo &node_info, Frame *fram
|
||||
storage::View::NEW);
|
||||
// TODO: PropsSetChecked allocates a PropertyValue, make it use context.memory
|
||||
// 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)) {
|
||||
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 {
|
||||
auto property_map = evaluator.Visit(*std::get<ParameterLookup *>(node_info.properties));
|
||||
for (const auto &[key, value] : property_map.ValueMap()) {
|
||||
auto property_id = dba.NameToProperty(key);
|
||||
PropsSetChecked(&new_node, property_id, value);
|
||||
properties.emplace(dba.NameToProperty(key), value);
|
||||
}
|
||||
}
|
||||
MultiPropsInitChecked(&new_node, properties);
|
||||
|
||||
(*frame)[node_info.symbol] = new_node;
|
||||
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);
|
||||
if (maybe_edge.HasValue()) {
|
||||
auto &edge = *maybe_edge;
|
||||
if (const auto *properties = std::get_if<PropertiesMapList>(&edge_info.properties)) {
|
||||
for (const auto &[key, value_expression] : *properties) {
|
||||
PropsSetChecked(&edge, key, value_expression->Accept(*evaluator));
|
||||
std::map<storage::PropertyId, storage::PropertyValue> properties;
|
||||
if (const auto *edge_info_properties = std::get_if<PropertiesMapList>(&edge_info.properties)) {
|
||||
for (const auto &[key, value_expression] : *edge_info_properties) {
|
||||
properties.emplace(key, value_expression->Accept(*evaluator));
|
||||
}
|
||||
} else {
|
||||
auto property_map = evaluator->Visit(*std::get<ParameterLookup *>(edge_info.properties));
|
||||
for (const auto &[key, value] : property_map.ValueMap()) {
|
||||
auto property_id = dba->NameToProperty(key);
|
||||
PropsSetChecked(&edge, property_id, value);
|
||||
properties.emplace(dba->NameToProperty(key), value);
|
||||
}
|
||||
}
|
||||
if (!properties.empty()) MultiPropsInitChecked(&edge, properties);
|
||||
|
||||
(*frame)[edge_info.symbol] = edge;
|
||||
} else {
|
||||
@ -2254,10 +2258,19 @@ std::vector<Symbol> ConstructNamedPath::ModifiedSymbols(const SymbolTable &table
|
||||
return symbols;
|
||||
}
|
||||
|
||||
Filter::Filter(const std::shared_ptr<LogicalOperator> &input, Expression *expression)
|
||||
: input_(input ? input : std::make_shared<Once>()), expression_(expression) {}
|
||||
Filter::Filter(const std::shared_ptr<LogicalOperator> &input,
|
||||
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 {
|
||||
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); }
|
||||
|
||||
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)
|
||||
: 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) {
|
||||
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,
|
||||
storage::View::OLD);
|
||||
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;
|
||||
}
|
||||
return false;
|
||||
@ -2287,6 +2320,39 @@ void Filter::FilterCursor::Shutdown() { input_cursor_->Shutdown(); }
|
||||
|
||||
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)
|
||||
: input_(input ? input : std::make_shared<Once>()), named_expressions_(named_expressions) {}
|
||||
|
||||
|
@ -133,6 +133,7 @@ class CallProcedure;
|
||||
class LoadCsv;
|
||||
class Foreach;
|
||||
class EmptyResult;
|
||||
class EvaluatePatternFilter;
|
||||
|
||||
using LogicalOperatorCompositeVisitor = utils::CompositeVisitor<
|
||||
Once, CreateNode, CreateExpand, ScanAll, ScanAllByLabel,
|
||||
@ -141,7 +142,7 @@ using LogicalOperatorCompositeVisitor = utils::CompositeVisitor<
|
||||
Expand, ExpandVariable, ConstructNamedPath, Filter, Produce, Delete,
|
||||
SetProperty, SetProperties, SetLabels, RemoveProperty, RemoveLabels,
|
||||
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>;
|
||||
|
||||
@ -1122,6 +1123,9 @@ pulled.")
|
||||
((input "std::shared_ptr<LogicalOperator>" :scope :public
|
||||
:slk-save #'slk-save-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
|
||||
:slk-save #'slk-save-ast-pointer
|
||||
:slk-load (slk-load-ast-pointer "Expression")))
|
||||
@ -1136,6 +1140,7 @@ a boolean value.")
|
||||
Filter() {}
|
||||
|
||||
Filter(const std::shared_ptr<LogicalOperator> &input_,
|
||||
const std::vector<std::shared_ptr<LogicalOperator>> &pattern_filters_,
|
||||
Expression *expression_);
|
||||
bool Accept(HierarchicalLogicalOperatorVisitor &visitor) override;
|
||||
UniqueCursorPtr MakeCursor(utils::MemoryResource *) const override;
|
||||
@ -1159,6 +1164,7 @@ a boolean value.")
|
||||
private:
|
||||
const Filter &self_;
|
||||
const UniqueCursorPtr input_cursor_;
|
||||
const std::vector<UniqueCursorPtr> pattern_filter_cursors_;
|
||||
};
|
||||
cpp<#)
|
||||
(:serialize (:slk))
|
||||
@ -1777,6 +1783,45 @@ operator's implementation does not expect this.")
|
||||
(:serialize (:slk))
|
||||
(: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)
|
||||
((input "std::shared_ptr<LogicalOperator>" :scope :public
|
||||
: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
|
||||
// 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;
|
||||
}
|
||||
|
||||
// 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) {
|
||||
// TODO: Think about converting all filtering expression into CNF to improve
|
||||
// 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)) {
|
||||
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 {
|
||||
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`.
|
||||
}
|
||||
|
||||
// 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,
|
||||
SymbolTable &symbol_table) {
|
||||
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
|
||||
// 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 {
|
||||
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;
|
||||
}
|
||||
|
||||
@ -79,6 +98,9 @@ class UsedSymbolsCollector : public HierarchicalTreeVisitor {
|
||||
|
||||
std::unordered_set<Symbol> symbols_;
|
||||
const SymbolTable &symbol_table_;
|
||||
|
||||
private:
|
||||
bool in_exists{false};
|
||||
};
|
||||
|
||||
/// Normalized representation of a pattern that needs to be matched.
|
||||
@ -99,6 +121,93 @@ struct Expansion {
|
||||
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.
|
||||
class PropertyFilter {
|
||||
public:
|
||||
@ -153,7 +262,7 @@ struct FilterInfo {
|
||||
/// applied for labels or a property. Non generic types contain extra
|
||||
/// information which can be used to produce indexed scans of graph
|
||||
/// elements.
|
||||
enum class Type { Generic, Label, Property, Id };
|
||||
enum class Type { Generic, Label, Property, Id, Pattern };
|
||||
|
||||
Type type;
|
||||
/// The original filter expression which must be satisfied.
|
||||
@ -166,6 +275,8 @@ struct FilterInfo {
|
||||
std::optional<PropertyFilter> property_filter;
|
||||
/// Information for Type::Id filtering.
|
||||
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.
|
||||
@ -287,6 +398,13 @@ struct Matching {
|
||||
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
|
||||
/// `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
|
||||
// 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/frontend/ast/pretty_print.hpp"
|
||||
#include "query/plan/operator.hpp"
|
||||
#include "utils/string.hpp"
|
||||
|
||||
namespace memgraph::query::plan {
|
||||
@ -148,7 +149,6 @@ bool PlanPrinter::PreVisit(query::plan::Produce &op) {
|
||||
}
|
||||
|
||||
PRE_VISIT(ConstructNamedPath);
|
||||
PRE_VISIT(Filter);
|
||||
PRE_VISIT(SetProperty);
|
||||
PRE_VISIT(SetProperties);
|
||||
PRE_VISIT(SetLabels);
|
||||
@ -157,6 +157,7 @@ PRE_VISIT(RemoveLabels);
|
||||
PRE_VISIT(EdgeUniquenessFilter);
|
||||
PRE_VISIT(Accumulate);
|
||||
PRE_VISIT(EmptyResult);
|
||||
PRE_VISIT(EvaluatePatternFilter);
|
||||
|
||||
bool PlanPrinter::PreVisit(query::plan::Aggregate &op) {
|
||||
WithPrintLn([&](auto &out) {
|
||||
@ -251,6 +252,15 @@ bool PlanPrinter::PreVisit(query::plan::Foreach &op) {
|
||||
op.input_->Accept(*this);
|
||||
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
|
||||
|
||||
bool PlanPrinter::DefaultPreVisit() {
|
||||
@ -589,6 +599,13 @@ bool PlanToJsonVisitor::PreVisit(Filter &op) {
|
||||
op.input_->Accept(*this);
|
||||
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);
|
||||
return false;
|
||||
}
|
||||
@ -908,6 +925,7 @@ bool PlanToJsonVisitor::PreVisit(Cartesian &op) {
|
||||
output_ = std::move(self);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PlanToJsonVisitor::PreVisit(Foreach &op) {
|
||||
json self;
|
||||
self["name"] = "Foreach";
|
||||
@ -924,6 +942,18 @@ bool PlanToJsonVisitor::PreVisit(Foreach &op) {
|
||||
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 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
|
||||
// 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(Filter &) override;
|
||||
bool PreVisit(EvaluatePatternFilter & /*unused*/) override;
|
||||
bool PreVisit(EdgeUniquenessFilter &) override;
|
||||
|
||||
bool PreVisit(Merge &) override;
|
||||
@ -186,6 +187,7 @@ class PlanToJsonVisitor : public virtual HierarchicalLogicalOperatorVisitor {
|
||||
bool PreVisit(Optional &) override;
|
||||
|
||||
bool PreVisit(Filter &) override;
|
||||
bool PreVisit(EvaluatePatternFilter & /*op*/) override;
|
||||
bool PreVisit(EdgeUniquenessFilter &) override;
|
||||
bool PreVisit(Cartesian &) override;
|
||||
|
||||
|
@ -454,6 +454,16 @@ class IndexLookupRewriter final : public HierarchicalLogicalOperatorVisitor {
|
||||
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_;
|
||||
|
||||
private:
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
@ -25,15 +25,6 @@ namespace memgraph::query::plan {
|
||||
|
||||
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.
|
||||
// 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
|
||||
// general, comes after any OrderBy, Skip or Limit.
|
||||
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;
|
||||
}
|
||||
@ -504,6 +496,12 @@ std::unique_ptr<LogicalOperator> GenReturnBody(std::unique_ptr<LogicalOperator>
|
||||
|
||||
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 *filter_expr = nullptr;
|
||||
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;
|
||||
}
|
||||
|
||||
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::unordered_set<Symbol> &bound_symbols,
|
||||
std::unordered_map<Symbol, std::vector<Symbol>> &named_paths) {
|
||||
|
@ -81,8 +81,8 @@ namespace impl {
|
||||
// removed from `Filters`.
|
||||
Expression *ExtractFilters(const std::unordered_set<Symbol> &, Filters &, AstStorage &);
|
||||
|
||||
std::unique_ptr<LogicalOperator> GenFilters(std::unique_ptr<LogicalOperator>, const std::unordered_set<Symbol> &,
|
||||
Filters &, AstStorage &);
|
||||
/// Checks if the filters has all the bound symbols to be included in the current part of the query
|
||||
bool HasBoundFilterSymbols(const std::unordered_set<Symbol> &bound_symbols, const FilterInfo &filter);
|
||||
|
||||
/// 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
|
||||
// optimizes the optional match which filters only on symbols bound in
|
||||
// regular match.
|
||||
auto last_op = impl::GenFilters(std::move(input_op), bound_symbols, filters, storage);
|
||||
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;
|
||||
auto last_op = GenFilters(std::move(input_op), bound_symbols, filters, storage, symbol_table);
|
||||
|
||||
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});
|
||||
last_op = HandleExpansion(std::move(last_op), matching, symbol_table, storage, bound_symbols,
|
||||
match_context.new_symbols, named_paths, filters, match_context.view);
|
||||
|
||||
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");
|
||||
// We bound all named path symbols, so just add them to new_symbols.
|
||||
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));
|
||||
}
|
||||
|
||||
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> input_op,
|
||||
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_,
|
||||
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
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
@ -155,6 +155,18 @@ auto ExpansionNodes(const std::vector<Expansion> &expansions, const SymbolTable
|
||||
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
|
||||
|
||||
VaryMatchingStart::VaryMatchingStart(Matching matching, const SymbolTable &symbol_table)
|
||||
@ -209,11 +221,31 @@ CartesianProduct<VaryMatchingStart> VaryMultiMatchingStarts(const std::vector<Ma
|
||||
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)
|
||||
: query_part_(std::move(query_part)),
|
||||
matchings_(VaryMatchingStart(query_part_.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,
|
||||
VaryMatchingStart::iterator matchings_begin,
|
||||
@ -221,7 +253,9 @@ VaryQueryPartMatching::iterator::iterator(const SingleQueryPart &query_part,
|
||||
CartesianProduct<VaryMatchingStart>::iterator optional_begin,
|
||||
CartesianProduct<VaryMatchingStart>::iterator optional_end,
|
||||
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),
|
||||
matchings_it_(matchings_begin),
|
||||
matchings_end_(matchings_end),
|
||||
@ -230,7 +264,10 @@ VaryQueryPartMatching::iterator::iterator(const SingleQueryPart &query_part,
|
||||
optional_end_(optional_end),
|
||||
merge_it_(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_) {
|
||||
// Fill the query part with the first variation of matchings
|
||||
SetCurrentQueryPart();
|
||||
@ -242,26 +279,37 @@ VaryQueryPartMatching::iterator &VaryQueryPartMatching::iterator::operator++() {
|
||||
// * matchings (m1) and (m2)
|
||||
// * optional matchings (o1) and (o2)
|
||||
// * merge matching (g1)
|
||||
// * filter matching (f1) and (f2)
|
||||
// We want to produce parts for:
|
||||
// * (m1), (o1), (g1)
|
||||
// * (m1), (o2), (g1)
|
||||
// * (m2), (o1), (g1)
|
||||
// * (m2), (o2), (g1)
|
||||
// Create variations by changing the merge part first.
|
||||
if (merge_it_ != merge_end_) ++merge_it_;
|
||||
// If all merge variations are done, start them from beginning and move to the
|
||||
// next optional matching variation.
|
||||
if (merge_it_ == merge_end_) {
|
||||
// * (m1), (o1), (g1), (f1)
|
||||
// * (m1), (o1), (g1), (f2)
|
||||
// * (m1), (o2), (g1), (f1)
|
||||
// * (m1), (o2), (g1), (f2)
|
||||
// * (m2), (o1), (g1), (f1)
|
||||
// * (m2), (o1), (g1), (f2)
|
||||
// * (m2), (o2), (g1), (f1)
|
||||
// * (m2), (o2), (g1), (f2)
|
||||
|
||||
// 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_;
|
||||
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
|
||||
// matching variation.
|
||||
if (optional_it_ == optional_end_ && merge_it_ == merge_begin_) {
|
||||
|
||||
if (optional_it_ == optional_end_ && merge_it_ == merge_begin_ && filter_it_ == filter_begin_) {
|
||||
optional_it_ = optional_begin_;
|
||||
if (matchings_it_ != matchings_end_) ++matchings_it_;
|
||||
}
|
||||
|
||||
// We have reached the end, so return;
|
||||
if (matchings_it_ == matchings_end_) return *this;
|
||||
// Fill the query part with the new variation of matchings.
|
||||
@ -283,6 +331,28 @@ void VaryQueryPartMatching::iterator::SetCurrentQueryPart() {
|
||||
if (merge_it_ != merge_end_) {
|
||||
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 {
|
||||
@ -291,7 +361,8 @@ bool VaryQueryPartMatching::iterator::operator==(const iterator &other) const {
|
||||
// iterators can be at any position.
|
||||
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
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
@ -230,6 +230,8 @@ class VaryMatchingStart {
|
||||
// Cartesian product of all of them is returned.
|
||||
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
|
||||
// graph matching is done.
|
||||
class VaryQueryPartMatching {
|
||||
@ -245,6 +247,7 @@ class VaryQueryPartMatching {
|
||||
typedef const SingleQueryPart *pointer;
|
||||
|
||||
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);
|
||||
|
||||
@ -266,15 +269,20 @@ class VaryQueryPartMatching {
|
||||
CartesianProduct<VaryMatchingStart>::iterator merge_it_;
|
||||
CartesianProduct<VaryMatchingStart>::iterator merge_begin_;
|
||||
CartesianProduct<VaryMatchingStart>::iterator merge_end_;
|
||||
CartesianProduct<VaryMatchingStart>::iterator filter_it_;
|
||||
CartesianProduct<VaryMatchingStart>::iterator filter_begin_;
|
||||
CartesianProduct<VaryMatchingStart>::iterator filter_end_;
|
||||
};
|
||||
|
||||
auto 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() {
|
||||
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:
|
||||
@ -286,6 +294,7 @@ class VaryQueryPartMatching {
|
||||
CartesianProduct<VaryMatchingStart> optional_matchings_;
|
||||
// Like optional matching, but for merge matchings.
|
||||
CartesianProduct<VaryMatchingStart> merge_matchings_;
|
||||
CartesianProduct<VaryMatchingStart> filter_matchings_;
|
||||
};
|
||||
|
||||
} // 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
|
||||
// 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
|
||||
: 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) {}
|
||||
|
||||
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)) {}
|
||||
|
||||
mgp_date(const int64_t microseconds, memgraph::utils::MemoryResource *memory) noexcept
|
||||
@ -194,10 +194,10 @@ struct mgp_local_time {
|
||||
// have everything noexcept here.
|
||||
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) {}
|
||||
|
||||
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)) {}
|
||||
|
||||
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
|
||||
: memory(memory), local_date_time(CreateLocalDateTimeFromString(string)) {}
|
||||
|
||||
mgp_local_date_time(const mgp_local_date_time_parameters *parameters,
|
||||
memgraph::utils::MemoryResource *memory) noexcept
|
||||
mgp_local_date_time(const mgp_local_date_time_parameters *parameters, memgraph::utils::MemoryResource *memory)
|
||||
: memory(memory),
|
||||
local_date_time(MapDateParameters(parameters->date_parameters),
|
||||
MapLocalTimeParameters(parameters->local_time_parameters)) {}
|
||||
@ -301,7 +300,7 @@ struct mgp_duration {
|
||||
// have everything noexcept here.
|
||||
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)) {}
|
||||
|
||||
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
|
||||
// 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);
|
||||
}
|
||||
|
||||
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() {
|
||||
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
|
||||
// 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
|
||||
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.
|
||||
/// @throw std::bad_alloc
|
||||
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
|
||||
// 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;
|
||||
} else {
|
||||
// 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;
|
||||
|
||||
SetSizeData(buffer_, alloc_size, alloc_data);
|
||||
@ -1144,6 +1144,64 @@ bool PropertyStore::SetProperty(PropertyId property, const PropertyValue &value)
|
||||
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 in_local_buffer = false;
|
||||
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
|
||||
// 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
|
||||
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.
|
||||
/// `false` is returned if there were no properties to remove. The time
|
||||
/// complexity of this function is O(1).
|
||||
|
@ -109,9 +109,11 @@ void Storage::ReplicationClient::InitializeClient() {
|
||||
}
|
||||
if (branching_point) {
|
||||
spdlog::error(
|
||||
"Replica {} cannot be used with this instance. Please start a clean "
|
||||
"instance of Memgraph server on the specified endpoint.",
|
||||
name_);
|
||||
"You cannot register Replica {} to this Main because at one point "
|
||||
"Replica {} acted as the Main instance. Both the Main and Replica {} "
|
||||
"now hold unique data. Please resolve data conflicts and start the "
|
||||
"replication on a clean instance.",
|
||||
name_, name_, name_);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
@ -230,6 +230,23 @@ Result<PropertyValue> VertexAccessor::SetProperty(PropertyId property, const Pro
|
||||
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() {
|
||||
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
|
||||
// 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
|
||||
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.
|
||||
/// @throw std::bad_alloc
|
||||
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
|
||||
// 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(CallProcedureOperator, "Number of times CallProcedure 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(LabelIndexCreated, "Number of times a label index was created.") \
|
||||
|
@ -585,12 +585,12 @@ Feature: Functions
|
||||
"""
|
||||
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:
|
||||
| p |
|
||||
| {b: true} |
|
||||
| {} |
|
||||
| {b: true} |
|
||||
| {c: 123} |
|
||||
|
||||
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_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.Month(), 10);
|
||||
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
|
||||
// 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()));
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
// 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) {
|
||||
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(
|
||||
{
|
||||
@ -995,3 +996,55 @@ TEST_F(PrintToJsonTest, Foreach) {
|
||||
}
|
||||
})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
|
||||
// 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.
|
||||
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;
|
||||
types.reserve(edge_types.size());
|
||||
for (const auto &type : edge_types) {
|
||||
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
|
||||
@ -205,8 +205,9 @@ auto GetEdgeVariable(AstStorage &storage, const std::string &name, EdgeAtom::Typ
|
||||
/// Create a NodeAtom with given name and label.
|
||||
///
|
||||
/// 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 node = storage.Create<NodeAtom>(storage.Create<Identifier>(name));
|
||||
auto GetNode(AstStorage &storage, const std::string &name, std::optional<std::string> label = std::nullopt,
|
||||
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));
|
||||
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 EXTRACT(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) \
|
||||
storage.Create<memgraph::query::AuthQuery>((action), (user), (role), (user_or_role), password, (privileges), \
|
||||
(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
|
||||
// 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);
|
||||
|
||||
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) {
|
||||
|
@ -1735,4 +1735,119 @@ TYPED_TEST(TestPlanner, Foreach) {
|
||||
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
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
@ -64,7 +64,6 @@ class PlanChecker : public virtual HierarchicalLogicalOperatorVisitor {
|
||||
PRE_VISIT(ScanAllById);
|
||||
PRE_VISIT(Expand);
|
||||
PRE_VISIT(ExpandVariable);
|
||||
PRE_VISIT(Filter);
|
||||
PRE_VISIT(ConstructNamedPath);
|
||||
PRE_VISIT(EmptyResult);
|
||||
PRE_VISIT(Produce);
|
||||
@ -79,6 +78,7 @@ class PlanChecker : public virtual HierarchicalLogicalOperatorVisitor {
|
||||
PRE_VISIT(Skip);
|
||||
PRE_VISIT(Limit);
|
||||
PRE_VISIT(OrderBy);
|
||||
PRE_VISIT(EvaluatePatternFilter);
|
||||
bool PreVisit(Merge &op) override {
|
||||
CheckOp(op);
|
||||
op.input()->Accept(*this);
|
||||
@ -97,6 +97,12 @@ class PlanChecker : public virtual HierarchicalLogicalOperatorVisitor {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PreVisit(Filter &op) override {
|
||||
CheckOp(op);
|
||||
op.input()->Accept(*this);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Visit(Once &) override {
|
||||
// Ignore checking Once, it is implicitly at the end.
|
||||
return true;
|
||||
@ -141,7 +147,6 @@ using ExpectScanAll = OpChecker<ScanAll>;
|
||||
using ExpectScanAllByLabel = OpChecker<ScanAllByLabel>;
|
||||
using ExpectScanAllById = OpChecker<ScanAllById>;
|
||||
using ExpectExpand = OpChecker<Expand>;
|
||||
using ExpectFilter = OpChecker<Filter>;
|
||||
using ExpectConstructNamedPath = OpChecker<ConstructNamedPath>;
|
||||
using ExpectProduce = OpChecker<Produce>;
|
||||
using ExpectEmptyResult = OpChecker<EmptyResult>;
|
||||
@ -156,6 +161,23 @@ using ExpectLimit = OpChecker<Limit>;
|
||||
using ExpectOrderBy = OpChecker<OrderBy>;
|
||||
using ExpectUnwind = OpChecker<Unwind>;
|
||||
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> {
|
||||
public:
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
@ -1532,7 +1532,7 @@ TEST(QueryPlan, NodeFilterSet) {
|
||||
false, memgraph::storage::View::OLD);
|
||||
auto *filter_expr =
|
||||
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
|
||||
auto set_prop = PROPERTY_LOOKUP(IDENT("n")->MapTo(scan_all.sym_), prop);
|
||||
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",
|
||||
false, memgraph::storage::View::OLD);
|
||||
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
|
||||
auto rem_prop = PROPERTY_LOOKUP(IDENT("n")->MapTo(scan_all.sym_), 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
|
||||
// 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
|
||||
auto *filter_expr = AND(storage.Create<LabelsTest>(n.node_->identifier_, n.node_->labels_),
|
||||
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
|
||||
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
|
||||
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
|
||||
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) {
|
||||
auto n_from = MakeScanAll(storage, symbol_table, node_from, input_op);
|
||||
auto filter_op = std::make_shared<Filter>(
|
||||
n_from.op_,
|
||||
n_from.op_, std::vector<std::shared_ptr<LogicalOperator>>{},
|
||||
storage.Create<memgraph::query::LabelsTest>(
|
||||
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 filter_op = std::make_shared<Filter>(
|
||||
n_from.op_,
|
||||
n_from.op_, std::vector<std::shared_ptr<LogicalOperator>>{},
|
||||
storage.Create<memgraph::query::LabelsTest>(
|
||||
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 filter_op = std::make_shared<Filter>(
|
||||
n_from.op_,
|
||||
n_from.op_, std::vector<std::shared_ptr<LogicalOperator>>{},
|
||||
storage.Create<memgraph::query::LabelsTest>(
|
||||
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 last_op = n.op_;
|
||||
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");
|
||||
@ -1967,8 +1968,9 @@ TEST_F(QueryPlanExpandWeightedShortestPath, ExistingNode) {
|
||||
// scan the nodes optionally filtering on property value
|
||||
auto n0 = MakeScanAll(storage, symbol_table, "n0");
|
||||
if (preceeding_node_id) {
|
||||
auto filter = std::make_shared<Filter>(
|
||||
n0.op_, EQ(PROPERTY_LOOKUP(n0.node_->identifier_, prop), LITERAL(*preceeding_node_id)));
|
||||
auto filter =
|
||||
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
|
||||
// op can be passed into the ExpandWShortest function without too
|
||||
// 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 last_op = n.op_;
|
||||
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");
|
||||
@ -2739,7 +2742,7 @@ TEST(QueryPlan, OptionalMatchThenExpandToMissingNode) {
|
||||
n.node_->labels_.emplace_back(storage.GetLabelIx(label_missing));
|
||||
|
||||
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_});
|
||||
// WITH n
|
||||
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)));
|
||||
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 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
|
||||
auto output =
|
||||
@ -2936,7 +2939,7 @@ TEST(QueryPlan, Filter) {
|
||||
|
||||
auto n = MakeScanAll(storage, symbol_table, "n");
|
||||
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 produce = MakeProduce(f, output);
|
||||
@ -3441,7 +3444,8 @@ TEST(QueryPlan, ScanAllEqualsScanAllByLabelProperty) {
|
||||
memgraph::query::DbAccessor dba(&storage_dba);
|
||||
auto scan_all = MakeScanAll(storage, symbol_table, "n");
|
||||
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 =
|
||||
NEXPR("n", IDENT("n")->MapTo(scan_all.sym_))->MapTo(symbol_table.CreateSymbol("named_expression_1", true));
|
||||
auto produce = MakeProduce(filter, output);
|
||||
@ -3455,3 +3459,220 @@ TEST(QueryPlan, ScanAllEqualsScanAllByLabelProperty) {
|
||||
count_with_index(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
|
||||
// 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) {
|
||||
std::shared_ptr<LogicalOperator> scan_all = std::make_shared<ScanAll>(nullptr, GetSymbol("node1"));
|
||||
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);
|
||||
}
|
||||
@ -87,7 +88,8 @@ TEST_F(ReadWriteTypeCheckTest, OrderByAndLimit) {
|
||||
|
||||
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<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<OrderBy>(last_op, std::vector<SortItem>{{Ordering::DESC, PROPERTY_LOOKUP("node", prop)}},
|
||||
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
|
||||
// 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
|
||||
EXPECT_EQ(ToString(NEXPR("n", LITERAL(1))), "(NamedExpression \"n\" 1)");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
@ -19,6 +19,7 @@
|
||||
#include "query/frontend/ast/ast.hpp"
|
||||
#include "query/frontend/semantic/symbol_generator.hpp"
|
||||
#include "query/frontend/semantic/symbol_table.hpp"
|
||||
#include "query/plan/preprocess.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")));
|
||||
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
|
||||
// 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{
|
||||
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